list users, fetch updates, make them clickable

This commit is contained in:
luce 2025-07-15 14:30:46 +02:00
commit ce43c81ce8
11 changed files with 244 additions and 1 deletions

View file

@ -7,6 +7,10 @@ const SelectableList = {
Checkbox
},
props: {
boxOnly: {
type: Boolean,
default: false
},
items: {
type: Array,
default: () => []

View file

@ -27,6 +27,7 @@
>
<template #item="{item}">
<div
v-if="!boxOnly"
class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
@click.stop="toggle(!isSelected(item), item)"
@ -43,6 +44,26 @@
:item="item"
/>
</div>
<div
v-if="boxOnly"
class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
>
<div
class="selectable-list-checkbox-wrapper"
@click.stop="toggle(!isSelected(item), item)"
>
<Checkbox
:model-value="isSelected(item)"
@update:model-value="checked => toggle(checked, item)"
@click.stop
/>
</div>
<slot
name="item"
:item="item"
/>
</div>
</template>
<template #empty>
<slot name="empty" />

View file

@ -0,0 +1,25 @@
import BasicUserCard from '../../basic_user_card/basic_user_card.vue'
const AdminCard = {
props: ['userId'],
data () {
return {
progress: false
}
},
computed: {
user () {
return this.$store.getters.findUser(this.userId)
},
relationship () {
return this.$store.getters.relationship(this.userId)
},
},
components: {
BasicUserCard
},
methods: {
}
}
export default AdminCard

View file

@ -0,0 +1,45 @@
<template>
<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
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>
</template>
<script src="./admin_card.js"></script>
<style lang="scss">
.admin-card-content-container {
margin-top: 0.5em;
text-align: right;
button {
width: 10em;
}
}
</style>

View file

@ -0,0 +1,100 @@
//import get from 'lodash/get'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Select from 'src/components/select/select.vue'
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
import withLoadMore from 'src/components/../hocs/with_load_more/with_load_more'
import SelectableList from 'src/components/selectable_list/selectable_list.vue'
import ProgressButton from 'src/components/progress_button/progress_button.vue'
import AdminCard from 'src/components/settings_modal/admin_tabs/admin_card.vue'
//import { ref } from 'vue'
const UserList = withLoadMore({
fetch: (props, $store) => {
console.log('fetch', props)
return $store.dispatch('fetchAdminUsers')
},
select: (props, $store) => {
console.log('select', props)
const filterMethod = typeof props.filterMethod === 'function' ? props.filterMethod : () => true
const users = $store.state.users.users.filter(user => user.id !== $store.state.users.currentUser.id && filterMethod(user))
console.log('users', users)
return users
},
destroy: () => {},
childPropName: 'items'
})(SelectableList)
const UserSortStrategy = Object.freeze({
NONE: 0,
NICKNAME: 1,
DISPLAYNAME: 2,
JOINED: 3
})
const UsersTab = {
provide () {
return {
defaultDraftMode: true,
defaultSource: 'admin'
}
},
data() {
return {
filterTerms: [],
users: [],
sortStrategy: UserSortStrategy.NONE,
sortAscending: true,
expandedUser: null,
filterActive: 'active_only',
filterLocal: 'local_only',
loading: false
}
},
components: {
Checkbox,
Select,
BasicUserCard,
UserList,
ProgressButton,
AdminCard,
},
computed: {
knownDomains () {
return this.$store.state.instance.knownDomains
},
user () {
return this.$store.state.users.currentUser
}
},
methods: {
},
mounted() {
console.log("mounted")
/*const store = this.$store;
const moduleTree = buildModuleTree(store._modules.root);
console.log(JSON.stringify(moduleTree, null, 2));*/
}
}
/*function buildModuleTree(module, path = []) {
const fullPath = path.join('/') || 'root';
const moduleInfo = {
path: fullPath,
namespaced: module.namespaced,
state: Object.keys(module.state),
actions: module._rawModule.actions ? Object.keys(module._rawModule.actions) : [],
mutations: module._rawModule.mutations ? Object.keys(module._rawModule.mutations) : [],
getters: module._rawModule.getters ? Object.keys(module._rawModule.getters) : [],
modules: []
};
if (module._children) {
for (const key in module._children) {
const child = module._children[key];
moduleInfo.modules.push(buildModuleTree(child, path.concat(key)));
}
}
return moduleInfo;
}*/
export default UsersTab

View file

@ -0,0 +1,14 @@
<template>
<div :label="$t('admin_dash.tabs.instance')">
<UserList
:refresh="true"
:get-key="i => i"
:boxOnly="true"
>
<template #item="{item}">
<AdminCard :userId="item.id" />
</template>
</UserList>
</div>
</template>
<script src="./users_tab.js"></script>

View file

@ -1,6 +1,7 @@
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import InstanceTab from './admin_tabs/instance_tab.vue'
import UsersTab from './admin_tabs/users_tab.vue'
import LimitsTab from './admin_tabs/limits_tab.vue'
import FrontendsTab from './admin_tabs/frontends_tab.vue'
import EmojiTab from './admin_tabs/emoji_tab.vue'
@ -34,6 +35,7 @@ const SettingsModalAdminContent = {
TabSwitcher,
InstanceTab,
UsersTab,
LimitsTab,
FrontendsTab,
EmojiTab

View file

@ -48,6 +48,14 @@
>
<InstanceTab />
</div>
<div
v-if="adminDbLoaded"
:label="$t('admin_dash.tabs.users')"
icon="wrench"
data-tab-name="users"
>
<UsersTab />
</div>
<div
v-if="adminDbLoaded"
:label="$t('admin_dash.tabs.limits')"

View file

@ -1099,6 +1099,7 @@
"tabs": {
"nodb": "No DB Config",
"instance": "Instance",
"users": "Users",
"limits": "Limits",
"frontends": "Front-ends",
"emoji": "Emoji"
@ -1127,6 +1128,18 @@
"activities": "Statuses/activities access"
}
},
"users": {
"users": "User Management",
"search_users": "Search for users...",
"filters": {
"show_all": "show all",
"active_only": "active only",
"inactive_only": "inactive only",
"local_only": "local only",
"external_only": "external only"
},
"loading": "Loading..."
},
"limits": {
"arbitrary_limits": "Arbitrary limits",
"posts": "Post limits",

View file

@ -60,6 +60,9 @@ const adminSettingsStorage = {
}
},
actions: {
fetchAdminUsers (store) {
store.rootState.api.backendInteractor.adminListUsers().then((users) => users.forEach(user => store.dispatch('fetchUserIfMissing', user.id)))
},
loadFrontendsStuff ({ rootState, commit }) {
rootState.api.backendInteractor.fetchAvailableFrontends()
.then(frontends => commit('setAvailableFrontends', { frontends }))

View file

@ -115,6 +115,7 @@ const PLEROMA_ADMIN_CONFIG_URL = '/api/pleroma/admin/config'
const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/pleroma/admin/config/descriptions'
const PLEROMA_ADMIN_FRONTENDS_URL = '/api/pleroma/admin/frontends'
const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/pleroma/admin/frontends/install'
const PLEROMA_ADMIN_USERS_URL = '/api/v1/pleroma/admin/users'
const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
@ -1440,6 +1441,12 @@ const dismissAnnouncement = ({ id, credentials }) => {
})
}
const adminListUsers = ({ credentials }) => {
// the reported list is hardly useful because standards are for dating i guess,
// so make sure to fetchIfMissing right afterward using this call
return promisedRequest({ url: PLEROMA_ADMIN_USERS_URL, credentials }).then((data) => data.users.map(parseUser))
}
const announcementToPayload = ({ content, startsAt, endsAt, allDay }) => {
const payload = { content }
@ -2111,7 +2118,8 @@ const apiService = {
fetchBookmarkFolders,
createBookmarkFolder,
updateBookmarkFolder,
deleteBookmarkFolder
deleteBookmarkFolder,
adminListUsers,
}
export default apiService