adding more things for managing users

This commit is contained in:
luce 2025-07-20 18:59:05 +02:00
commit 7b4fd8e5e8
9 changed files with 476 additions and 190 deletions

View file

@ -5,6 +5,9 @@
:get-key="i => i" :get-key="i => i"
:items="items" :items="items"
> >
<template v-slot:header="slotProps">
<slot name="header" v-bind="slotProps"/>
</template>
<template v-slot:item="slotProps"> <template v-slot:item="slotProps">
<slot name="item" v-bind="slotProps"/> <slot name="item" v-bind="slotProps"/>
</template> </template>

View file

@ -1,28 +1,108 @@
import BasicUserCard from '../../basic_user_card/basic_user_card.vue' import BasicUserCard from '../../basic_user_card/basic_user_card.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
const AdminCard = { const AdminCard = {
props: ['userId'], props: ['userDetails'],
data () { data () {
return { return {
progress: false progress: false,
} top_level_expanded: false,
}, json_expanded: false,
computed: { just_approved: false,
isLoaded () { just_confirmed: false,
return typeof(this.user) !== 'undefined' just_deleted: false,
}, }
user () { },
return this.$store.getters.findUser(this.userId) mounted () {
}, },
relationship () { computed: {
return this.$store.getters.relationship(this.userId) isLoaded () {
}, return typeof(this.user) !== 'undefined'
}, },
components: { user () {
BasicUserCard return this.$store.getters.findUser(this.userDetails.id)
}, },
methods: { relationship () {
} return this.$store.getters.relationship(this.userDetails.id)
},
is_local () {
const u = this.$store.getters.findUser(this.userDetails.id)
if (typeof(u) !== 'undefined') {
return u.is_local === true
}
return false
},
is_admin () {
const u = this.$store.getters.findUser(this.userDetails.id)
if (typeof(u) !== 'undefined') {
return u.rights.admin === true
}
return false
},
is_moderator () {
const u = this.$store.getters.findUser(this.userDetails.id)
if (typeof(u) !== 'undefined') {
return u.rights.moderator === true
}
return false
},
is_activated () {
const u = this.$store.getters.findUser(this.userDetails.id)
if (typeof(u) !== 'undefined') {
return u.deactivated === false
}
return false
},
is_confirmed () {
return (this.userDetails.is_confirmed === false) || (this.just_confirmed === true)
},
is_approved () {
return (this.userDetails.is_approved === false) || (this.just_approved === true)
}
},
components: {
BasicUserCard,
Checkbox
},
methods: {
toggle_admin (v) {
const u = this.$store.getters.findUser(this.userDetails.id)
console.log('user', u)
if (v === true) {
this.$store.dispatch('adminAddUserToAdminGroup', u).then(res => console.log("res: ", res))
} else {
this.$store.dispatch('adminRemoveUserFromAdminGroup', u)
}
},
toggle_moderator (v) {
const u = this.$store.getters.findUser(this.userDetails.id)
if (v === true) {
this.$store.dispatch('adminAddUserToModeratorGroup', u)
} else {
this.$store.dispatch('adminRemoveUserFromModeratorGroup', u)
}
},
toggle_activation (v) {
const u = this.$store.getters.findUser(this.userDetails.id)
if (v === true) {
this.$store.dispatch('adminActivateUser', u)
} else {
this.$store.dispatch('adminDeactivateUser', u)
}
},
toggle_confirmation () {},
toggle_approval () {},
force_update_user () {
this.$store.dispatch('fetchUser', this.userDetails.id)
},
delete_user () {
if (!this.just_deleted) {
const u = this.$store.getters.findUser(this.userDetails.id)
this.$store.dispatch('adminDeleteUser', u)
this.just_deleted = true
}
}
}
} }
export default AdminCard export default AdminCard

View file

@ -1,42 +1,126 @@
<!-- eslint-disable -->
<template> <template>
<div v-if="!just_deleted">
<div <div
v-if="!isLoaded" v-if="!isLoaded"
> >
loading user... loading user...
</div> </div>
<div <div
v-else
>
<BasicUserCard :user="user">
<div class="admin-card-content-container">
<!--<button
v-if="muted"
class="btn button-default"
:disabled="progress"
@click="unmuteUser"
>
<template v-if="progress">
{{ $t('user_card.unmute_progress') }}
</template>
<template v-else>
{{ $t('user_card.unmute') }}
</template>
</button>
<button
v-else v-else
class="btn button-default" >
:disabled="progress" <div v-if="userDetails.id !== this.$store.state.users.currentUser.id">
@click="muteUser" <BasicUserCard :user="user">
> <div class="admin-card-content-container">
<template v-if="progress"> <!--<button
{{ $t('user_card.mute_progress') }} v-if="muted"
</template> class="btn button-default"
<template v-else> :disabled="progress"
{{ $t('user_card.mute') }} @click="unmuteUser"
</template> >
</button>--> <template v-if="progress">
{{ $t('user_card.unmute_progress') }}
</template>
<template v-else>
{{ $t('user_card.unmute') }}
</template>
</button>
<button
v-else
class="btn button-default"
:disabled="progress"
@click="muteUser"
>
<template v-if="progress">
{{ $t('user_card.mute_progress') }}
</template>
<template v-else>
{{ $t('user_card.mute') }}
</template>
</button>-->
</div>
</BasicUserCard>
<div v-if="!top_level_expanded">
<button
class="button button-default btn"
type="button"
@click="top_level_expanded = true"
>
expand user
</button>
</div>
<div v-else>
<button
class="button button-default btn"
type="button"
@click="top_level_expanded = false"
>
collapse user
</button><br>
<div v-if="is_local">
<Checkbox
:model-value="is_admin"
@update:model-value="v => toggle_admin(v)"
>
is admin
</Checkbox><br>
<Checkbox
:model-value="is_moderator"
@update:model-value="v => toggle_moderator(v)"
>
is moderator
</Checkbox><br>
<Checkbox
:model-value="is_confirmed"
@update:model-value="v => toggle_confirmation(v)"
>
is confirmed
</Checkbox><br>
<Checkbox
:model-value="is_approved"
@update:model-value="v => toggle_approval(v)"
>
is approved
</Checkbox><br>
</div>
<Checkbox
:model-value="is_activated"
@update:model-value="v => toggle_activation(v)"
>
is active
</Checkbox><br>
<button class="button button-default btn"
type="button"
@click="delete_user()"
>
delete user
</button>
</div>
<div v-if="!json_expanded"
>
<button
class="button button-default btn"
type="button"
@click="json_expanded = true"
>
expand raw info
</button>
</div>
<div v-else>
<button
class="button button-default btn"
type="button"
@click="json_expanded = false"
>
collapse raw info
</button>
<h2> database </h2>
<pre> {{ JSON.stringify(user, null, 2) }} </pre>
<h2> details </h2>
<pre> {{ JSON.stringify(this.userDetails, null, 2) }} </pre>
</div>
</div>
</div> </div>
</BasicUserCard>
</div> </div>
</template> </template>

View file

@ -5,8 +5,7 @@ import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
import ProgressButton from 'src/components/progress_button/progress_button.vue' import ProgressButton from 'src/components/progress_button/progress_button.vue'
import AdminCard from 'src/components/settings_modal/admin_tabs/admin_card.vue' import AdminCard from 'src/components/settings_modal/admin_tabs/admin_card.vue'
import PageList from 'src/components/page_list/page_list.vue' import PageList from 'src/components/page_list/page_list.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
const UsersTab = { const UsersTab = {
provide () { provide () {
@ -17,8 +16,14 @@ const UsersTab = {
}, },
data() { data() {
return { return {
/* filters must match the filter options below initially, or the ui is gonna have a computer moment
* no, i won't fix this
* */
filters_origin: "local",
filters_activity: "all",
filters_permission: "all",
filters: { filters: {
local: false, local: true,
external: false, external: false,
active: false, active: false,
need_approval: false, need_approval: false,
@ -38,35 +43,89 @@ const UsersTab = {
PageList, PageList,
ProgressButton, ProgressButton,
AdminCard, AdminCard,
TabSwitcher,
}, },
computed: { computed: {
}, },
methods: { methods: {
update_origin (v) {
switch (v) {
case 'local':
this.filters.local = true
this.filters.external = false
break;
case 'external':
this.filters.local = false
this.filters.external = true
break;
default:
case 'all':
this.filters.local = false
this.filters.external = false
break;
}
this.reset()
},
update_activity (v) {
switch (v) {
case 'active':
this.filters.active = true
this.filters.deactivated = false
break;
case 'deactivated':
this.filters.active = false
this.filters.deactivated = true
break;
default:
case 'all':
this.filters.active = false
this.filters.deactivated = false
break;
}
this.reset()
},
update_permission (v) {
switch (v) {
case 'admin':
this.filters.is_admin = true
this.filters.is_moderator = false
break;
case 'moderator':
this.filters.is_admin = false
this.filters.is_moderator = true
break;
case 'modsnadmins':
this.filters.is_admin = true
this.filters.is_moderator = true
break;
default:
case 'all':
this.filters.is_admin = false
this.filters.is_moderator = false
break;
}
this.reset()
},
delete_selection () { delete_selection () {
console.log('delete selection')
}, },
delete_user () {}, delete_user () {},
fetch_page (store, opts) { fetch_page (store, opts) {
opts.query = "" opts.query = ""
console.log('current filters:', this.filters)
opts.filters = this.filters opts.filters = this.filters
opts.name = "" opts.name = ""
opts.email = "" opts.email = ""
const users = store.dispatch('fetchAdminUsers', opts) const users = store.dispatch('fetchAdminUsers', opts)
console.log('users', users)
return users return users
}, },
reset () { reset () {
this.$refs.userList.reset() this.$refs.userList.reset()
}, },
toggleLocal () { toggleLocal () {
console.log('toggle local')
this.filters.local = !this.filters.local this.filters.local = !this.filters.local
this.reset() this.reset()
} }
}, },
mounted() { mounted() {
console.log("mounted")
} }
} }

View file

@ -0,0 +1,3 @@
.user-tab {
height: 100%;
}

View file

@ -1,101 +1,130 @@
<!-- eslint-disable -->
<template> <template>
<div :label="$t('admin_dash.tabs.users')"> <TabSwitcher
<Checkbox :scrollable-tabs="false"
:model-value="filters[local]" class="user-tab"
@update:model-value="v => {filters.local = v; reset();}" >
> <div :label="$t('admin_dash.users.management')">
only local <div class="setting-item">
</Checkbox><br> <h2> filter user search </h2>
<Checkbox todo: query, name and email input<br>
:model-value="filters[external]" <Select
@update:model-value="v => {filters.external = v; reset();}" :model-value="filters_origin"
> @update:model-value="v => update_origin(v) "
only external >
</Checkbox><br> <option
<Checkbox value="all"
:model-value="filters[active]" >
@update:model-value="v => {filters.active = v; reset();}" all
> </option>
only active <option
</Checkbox><br> value="local"
<Checkbox >
:model-value="filters[need_approval]" local only
@update:model-value="v => {filters.need_approval = v; reset();}" </option>
> <option
only unconfirmed value="external"
</Checkbox><br> >
<Checkbox external only
:model-value="filters[deactivated]" </option>
@update:model-value="v => {filters.deactivated = v; reset();}" </Select>
> <Select
only deactivated :model-value="filters_activity"
</Checkbox><br> @update:model-value="v => update_activity(v) "
<Checkbox >
:model-value="filters[is_admin]" <option
@update:model-value="v => {filters.is_admin = v; reset();}" value="all"
> >
only if admin all
</Checkbox><br> </option>
<Checkbox <option
:model-value="filters[is_moderator]" value="active"
@update:model-value="v => {filters.is_moderator = v; reset();}" >
> active only
only if moderator </option>
</Checkbox><br> <option
<button value="deactivated"
class="button button-default btn" >
type="button" deactivated only
@click="delete_selection" </option>
> </Select>
{{ $t('admin_dash.users.activate') }} <Select
</button> :model-value="filters_permission"
<button @update:model-value="v => update_permission(v) "
class="button button-default btn" >
type="button" <option
@click="activate_selection" value="all"
> >
{{ $t('admin_dash.users.deactivate') }} all
</button> </option>
<button <option
class="button button-default btn" value="admin"
type="button" >
@click="deactivate_selection" admin only
> </option>
{{ $t('admin_dash.users.delete') }} <option
</button> value="modsnadmins"
<PageList >
ref="userList" all privileged
:refresh="true" </option>
:get-key="i => i" <option
:box-only="true" value="moderator"
:page-size="50" >
:fetch-page="(store, opts) => this.fetch_page(store, opts)" moderator only
> </option>
<template #item="{item}"> </Select>
<AdminCard :user-id="item.id" /> <Checkbox
<button @update:model-value="v => {filters.need_approval = v; reset();}"
class="button button-default btn" >
type="button" only unapproved
@click="delete_user" </Checkbox>
> <Checkbox
{{ $t('admin_dash.users.activate') }} @update:model-value="v => {filters.unconfirmed = v; reset();}"
</button> >
<button only unconfirmed
class="button button-default btn" </Checkbox>
type="button" </div>
@click="activate_user" <PageList
> ref="userList"
{{ $t('admin_dash.users.deactivate') }} :refresh="true"
</button> :get-key="i => i"
<button :box-only="true"
class="button button-default btn" :page-size="50"
type="button" :fetch-page="(store, opts) => this.fetch_page(store, opts)"
@click="deactivate_user" >
> <template #header>
{{ $t('admin_dash.users.delete') }} <button
</button> class="button button-default btn"
</template> type="button"
</PageList> @click="delete_selection"
</div> >
{{ $t('admin_dash.users.activate') }}
</button>
<button
class="button button-default btn"
type="button"
@click="activate_selection"
>
{{ $t('admin_dash.users.deactivate') }}
</button>
<button
class="button button-default btn"
type="button"
@click="deactivate_selection"
>
{{ $t('admin_dash.users.delete') }}
</button>
</template>
<template #item="{item}">
<AdminCard :user-details="item" />
</template>
</PageList>
</div>
<div :label="$t('admin_dash.users.invitations')">
TBC
</div>
</TabSwitcher>
</template> </template>
<script src="./users_tab.js"></script> <script src="./users_tab.js"></script>
<style lang="scss" src="./users_tab.scss"></style>

View file

@ -1138,7 +1138,8 @@
} }
}, },
"users": { "users": {
"users": "User Management", "management": "Management",
"invitations": "Invitations",
"search_users": "Search for users...", "search_users": "Search for users...",
"filters": { "filters": {
"show_all": "show all", "show_all": "show all",

View file

@ -57,7 +57,7 @@ const adminSettingsStorage = {
}, },
resetAdminDraft (state) { resetAdminDraft (state) {
state.draft = cloneDeep(state.config) state.draft = cloneDeep(state.config)
} },
}, },
actions: { actions: {
async fetchAdminUsers (store, opts) { async fetchAdminUsers (store, opts) {
@ -67,6 +67,34 @@ const adminSettingsStorage = {
//) //)
return users return users
}, },
adminAddUserToAdminGroup (store, user) {
store.rootState.api.backendInteractor.adminAddUserToAdminGroup({ user })
.then(res => store.commit('updateRight', { user, right: 'admin', value: res.is_admin }))
},
adminRemoveUserFromAdminGroup (store, user) {
return store.rootState.api.backendInteractor.adminRemoveUserFromAdminGroup({ user })
.then(res => store.commit('updateRight', { user, right: 'admin', value: res.is_admin }))
},
adminAddUserToModeratorGroup (store, user) {
return store.rootState.api.backendInteractor.adminAddUserToModeratorGroup({ user })
.then(res => store.commit('updateRight', { user, right: 'moderator', value: res.is_moderator }))
},
adminRemoveUserFromModeratorGroup (store, user) {
return store.rootState.api.backendInteractor.adminRemoveUserFromModeratorGroup({ user })
.then(res => store.commit('updateRight', { user, right: 'moderator', value: res.is_moderator }))
},
adminActivateUser (store, user) {
return store.rootState.api.backendInteractor.activateUser({ user })
.then(res => console.log(res))
},
adminDeactivateUser (store, user) {
return store.rootState.api.backendInteractor.deactivateUser({ user })
.then(res => console.log(res))
},
adminDeleteUser (store, user) {
return store.rootState.api.backendInteractor.deleteUser({ user })
.then(res => console.log(res))
},
loadFrontendsStuff ({ rootState, commit }) { loadFrontendsStuff ({ rootState, commit }) {
rootState.api.backendInteractor.fetchAvailableFrontends() rootState.api.backendInteractor.fetchAvailableFrontends()
.then(frontends => commit('setAvailableFrontends', { frontends })) .then(frontends => commit('setAvailableFrontends', { frontends }))

View file

@ -138,7 +138,7 @@ const PLEROMA_ADMIN_USERS_URL = ({page, pageSize, filters = {}, query = '', name
+ (is_moderator ? 'is_moderator,' : '') + (is_moderator ? 'is_moderator,' : '')
return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}` return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}`
} }
const PLEROMA_ADMIN_MODIFY_GROUP_URL = (nickname, group) => `/api/v1/pleroma/admin/users/${nickname}/permission_group/${group}`
const PLEROMA_ADMIN_DELETE_USERS_URL = '/api/v1/pleroma/admin/users' const PLEROMA_ADMIN_DELETE_USERS_URL = '/api/v1/pleroma/admin/users'
const PLEROMA_ADMIN_ACTIVATE_USER_URL = '/api/v1/pleroma/admin/users/activate' const PLEROMA_ADMIN_ACTIVATE_USER_URL = '/api/v1/pleroma/admin/users/activate'
const PLEROMA_ADMIN_DEACTIVATE_USER_URL = '/api/v1/pleroma/admin/users/deactivate' const PLEROMA_ADMIN_DEACTIVATE_USER_URL = '/api/v1/pleroma/admin/users/deactivate'
@ -1492,37 +1492,35 @@ const adminListUsers = ({ opts, credentials }) => {
}).then((data) => data.users.map(parseUser)) }).then((data) => data.users.map(parseUser))
} }
const adminDeleteUser = ({ nicknames, credentials }) => { const adminAddUserToAdminGroup = ({ user, credentials }) => {
return promisedRequest({ const url = PLEROMA_ADMIN_MODIFY_GROUP_URL(user.name_html, 'admin')
url: PLEROMA_ADMIN_DELETE_USERS_URL, return promisedRequest({ url: url,
credentials, credentials,
method: 'DELETE', method: 'POST'
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({'nicknames': nicknames})
}) })
} }
const adminActivateUser = ({ nicknames, credentials }) => { const adminRemoveUserFromAdminGroup = ({ user, credentials }) => {
return promisedRequest({ url: PLEROMA_ADMIN_ACTIVATE_USER_URL, const url = PLEROMA_ADMIN_MODIFY_GROUP_URL(user.name_html, 'admin')
return promisedRequest({ url: url,
credentials, credentials,
method: 'PATCH', method: 'DELETE'
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({'nicknames': nicknames})
}) })
} }
const adminDeactivateUser = ({ nicknames, credentials }) => { const adminAddUserToModeratorGroup = ({ user, credentials }) => {
return promisedRequest({ url: PLEROMA_ADMIN_DEACTIVATE_USER_URL, const url = PLEROMA_ADMIN_MODIFY_GROUP_URL(user.name_html, 'moderator')
return promisedRequest({ url: url,
credentials, credentials,
method: 'PATCH', method: 'POST'
headers: { })
'Content-Type': 'application/json' }
},
body: JSON.stringify({'nicknames': nicknames}) const adminRemoveUserFromModeratorGroup = ({ user, credentials }) => {
const url = PLEROMA_ADMIN_MODIFY_GROUP_URL(user.name_html, 'moderator')
return promisedRequest({ url: url,
credentials,
method: 'DELETE'
}) })
} }
@ -2199,9 +2197,10 @@ const apiService = {
updateBookmarkFolder, updateBookmarkFolder,
deleteBookmarkFolder, deleteBookmarkFolder,
adminListUsers, adminListUsers,
adminDeleteUser, adminAddUserToAdminGroup,
adminActivateUser, adminRemoveUserFromAdminGroup,
adminDeactivateUser, adminAddUserToModeratorGroup,
adminRemoveUserFromModeratorGroup,
} }
export default apiService export default apiService