@@ -7,38 +7,49 @@ SPDX-License-Identifier: AGPL-3.0-only
< PageWithHeader v -model :tab = "tab" :actions = "headerActions" :tabs = "headerTabs" >
< div class = "_spacer" style = "--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;" >
< FormSuspense :p = "init" >
< div v-if = "tab === 'overview'" class="_gaps_m " >
< div class = "aeakzknw" >
< div v-if = "tab === 'overview'" class="_gaps" >
< div v-if = "user" class=" aeakzknw" >
< MkAvatar class = "avatar" :user = "user" indicator link preview / >
< div class = "body" >
< span class = "name" > < MkUserName class = "name" :user = "user" / > < / span >
< span class = "sub" > < span class = "acct _monospace" > @ { { acct ( user ) } } < / span > < / span >
< span class = "sub" >
< span class = "acct _monospace" > @ { { acct ( user ) } } < / span >
< button v-tooltip = "i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copyToClipboard('@' + acct(user))"><i class="ti ti-copy" > < / i > < / button >
< / span >
< span class = "sub" >
< span class = "_monospace" > { { user . id } } < / span >
< button v-tooltip = "i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copyToClipboard(user.id)"><i class="ti ti-copy" > < / i > < / button >
< / span >
< span class = "state" >
< span v-if = "!approved" class="silenced" > {{ i18n.ts.notApproved }} < / span >
< span v-if = "approved && !user.host" class="moderator" > {{ i18n.ts.approved }} < / span >
< span v-if = "suspended" class="suspended" > Suspended < / span >
< span v-if = "silenced" class="silenced" > Silenced < / span >
< span v-if = "moderator" class="moderator" > Moderator < / span >
< span v-if = "suspended" class="suspended" > {{ i18n.ts.suspended }} < / span >
< span v-if = "silenced" class="silenced" > {{ i18n.ts.silenced }} < / span >
< span v-if = "moderator" class="moderator" > {{ i18n.ts.moderator }} < / span >
< / span >
< / div >
< / div >
< MkInfo v-if = "isSystem" > {{ i18n.ts.isSystemAccount }} < / MkInfo >
< FormLink v-if = "user.host" :to="`/instance-info/${user.host}`" > {{ i18n.ts.instanceInfo }} < / FormLink >
< div style = "display: flex; flex-direction: column; gap: 1em;" >
< MkKeyValue :copy = "user.id" oneline >
< template # key > ID < / template >
< template # value > < span class = "_monospace" > { { user . id } } < / span > < / template >
< / MkKeyValue >
<!-- 要る ?
< MkKeyValue v-if = "ips.length > 0" :copy=" user.id " oneline >
< template # key > IP ( recent ) < / template >
< template # value > < span class = "_monospace" > { { ips [ 0 ] . ip } } < / span > < / template >
< / MkKeyValue >
-- >
< template v-if = "!isSystem" >
< MkFolder v-if = "!isSystem" >
< template # icon > < i class = "ph-list-bullets ph-bold ph-lg" > < / i > < / template >
< template # label > { { i18n . ts . details } } < / template >
< div style = "display: flex; flex-direction: column; gap: 1em;" >
< MkKeyValue v-if = "user" :copy="user.id" oneline >
< template # key > { { i18n . ts . id } } < / template >
< template # value > < span class = "_monospace" > { { user . id } } < / span > < / template >
< / MkKeyValue >
< MkKeyValue v-if = "user" :copy="'@' + acct( user) " oneline >
< template # key > { { i18n . ts . username } } < / template >
< template # value > < span class = "_monospace" > @ { { acct ( user ) } } < / span > < / template >
< / MkKeyValue >
<!-- 要る ?
< MkKeyValue v-if = "ips.length > 0" :copy="user.id" oneline >
< template # key > IP ( recent ) < / template >
< template # value > < span class = "_monospace" > { { ips [ 0 ] . ip } } < / span > < / template >
< / MkKeyValue >
-- >
< MkKeyValue oneline >
< template # key > { { i18n . ts . createdAt } } < / template >
< template # value > < span class = "_monospace" > < MkTime :time = "user.createdAt" :mode = "'detail'" / > < / span > < / template >
@@ -51,16 +62,64 @@ SPDX-License-Identifier: AGPL-3.0-only
< template # key > { { i18n . ts . email } } < / template >
< template # value > < span class = "_monospace" > { { info . email } } < / span > < / template >
< / MkKeyValue >
< / template >
< / div >
< MkKeyValue v-if = "info" oneline >
< template # key > { { i18n . ts . totalFollowers } } < / template >
< template # value > < span class = "_monospace" > < MkNumber :value = "info.followStats.totalFollowers" > < / MkNumber > < / span > < / template >
< / MkKeyValue >
< MkKeyValue v-if = "info" oneline >
< template # key > { { i18n . ts . totalFollowing } } < / template >
< template # value > < span class = "_monospace" > < MkNumber :value = "info.followStats.totalFollowing" > < / MkNumber > < / span > < / template >
< / MkKeyValue >
< MkKeyValue v-if = "info" oneline >
< template # key > { { i18n . ts . remoteFollowers } } < / template >
< template # value > < span class = "_monospace" > < MkNumber :value = "info.followStats.remoteFollowers" > < / MkNumber > < / span > < / template >
< / MkKeyValue >
< MkKeyValue v-if = "info" oneline >
< template # key > { { i18n . ts . remoteFollowing } } < / template >
< template # value > < span class = "_monospace" > < MkNumber :value = "info.followStats.remoteFollowing" > < / MkNumber > < / span > < / template >
< / MkKeyValue >
< MkKeyValue v-if = "info" oneline >
< template # key > { { i18n . ts . localFollowers } } < / template >
< template # value > < span class = "_monospace" > < MkNumber :value = "info.followStats.localFollowers" > < / MkNumber > < / span > < / template >
< / MkKeyValue >
< MkKeyValue v-if = "info" oneline >
< template # key > { { i18n . ts . localFollowing } } < / template >
< template # value > < span class = "_monospace" > < MkNumber :value = "info.followStats.localFollowing" > < / MkNumber > < / span > < / template >
< / MkKeyValue >
< / div >
< / MkFolder >
< MkTextarea v-model = "moderationNote" manualSave @update:modelValue="onModerationNoteChanged " >
< MkFolder v-if = "info " >
< template # icon > < i class = "ph-scroll ph-bold ph-lg" > < / i > < / template >
< template # label > { { i18n . ts . _role . policies } } < / template >
< div class = "_gaps" >
< div v-for = "policy in Object.keys(info.policies)" :key="policy" >
{{ policy }} ... {{ info.policies [ policy ] }}
< / div >
< / div >
< / MkFolder >
< MkFolder v-if = "iAmAdmin && ips && ips.length > 0" >
< template # icon > < i class = "ph-network ph-bold ph-lg" > < / i > < / template >
< template # label > { { i18n . ts . ip } } < / template >
< MkInfo > { { i18n . ts . ipTip } } < / MkInfo >
< div v-for = "record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;" >
< span class = "date" > { { record . createdAt } } < / span >
< span class = "ip" > { { record . ip } } < / span >
< / div >
< / MkFolder >
< MkFolder v-if = "iAmModerator" :defaultOpen="moderationNote.length > 0" >
< template # icon > < i class = "ph-stamp ph-bold ph-lg" > < / i > < / template >
< template # label > { { i18n . ts . moderationNote } } < / template >
< template # caption > { { i18n . ts . moderationNoteDescription } } < / template >
< / MkTextarea >
< MkTextarea v-model = "moderationNote" manualSave @update:modelValue="onModerationNoteChanged" >
< template # label > { { i18n . ts . moderationNote } } < / template >
< template # caption > { { i18n . ts . moderationNoteDescription } } < / template >
< / MkTextarea >
< / MkFolder >
< FormSection v-if = "user.host" >
< template # label > ActivityPub < / template >
< FormSection v-if = "user? .host" >
< template # label > { { i18n . ts . activityPub } } < / template >
< div class = "_gaps_m" >
< div style = "display: flex; flex-direction: column; gap: 1em;" >
@@ -73,12 +132,10 @@ SPDX-License-Identifier: AGPL-3.0-only
< template # value > < MkTime mode = "detail" :time = "user.lastFetchedAt" / > < / template >
< / MkKeyValue >
< / div >
< MkButton @click ="updateRemoteUser" > < i class = "ti ti-refresh" > < / i > { { i18n . ts . updateRemoteUser } } < / MkButton >
< / div >
< / FormSection >
< FormSection v-if = "!isSystem" >
< FormSection v-if = "!isSystem && user && iAmModerator " >
< div class = "_gaps" >
< MkSwitch v-model = "silenced" @update:modelValue="toggleSilence" > {{ i18n.ts.silence }} < / MkSwitch >
< MkSwitch v-if = "!isSystem" v-model="suspended" @update:modelValue="toggleSuspend" > {{ i18n.ts.suspend }} < / MkSwitch >
@@ -90,58 +147,40 @@ SPDX-License-Identifier: AGPL-3.0-only
< template # caption > { { i18n . ts . mandatoryCWDescription } } < / template >
< / MkInput >
< div >
< MkButton v-if = "user.host = = null && !isSystem" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key " > < / i > {{ i18n.ts.resetPassword }} < / MkButton >
< div :class = "$style.buttonStrip" >
< MkButton v-if = "user.host ! = null" inline @click="updateRemoteUser"><i class="ph-cloud-arrow-down ph-bold ph-lg " > < / i > {{ i18n.ts.updateRemoteUser }} < / MkButton >
< MkButton v-if = "user.host == null" inline accent @click="resetPassword"><i class="ph-password ph-bold ph-lg" > < / i > {{ i18n.ts.resetPassword }} < / MkButton >
< MkButton inline accent @click ="unsetUserAvatar" > < i class = "ph-camera-slash ph-bold ph-lg" > < / i > { { i18n . ts . unsetUserAvatar } } < / MkButton >
< MkButton inline accent @click ="unsetUserBanner" > < i class = "ph-image-broken ph-bold ph-lg" > < / i > { { i18n . ts . unsetUserBanner } } < / MkButton >
< MkButton inline danger @click ="deleteAllFiles" > < i class = "ph-trash ph-bold ph-lg" > < / i > { { i18n . ts . deleteAllFiles } } < / MkButton >
< MkButton v-if = "iAmAdmin" inline danger @click="deleteAccount"><i class="ph-skull ph-bold ph-lg" > < / i > {{ i18n.ts.deleteAccount }} < / MkButton >
< / div >
< MkFolder >
< template # icon > < i class = "ph-scroll ph-bold ph-lg" > < / i > < / template >
< template # label > { { i18n . ts . _role . policies } } < / template >
< div class = "_gaps" >
< div v-for = "policy in Object.keys(info.policies)" :key="policy" >
{{ policy }} ... {{ info.policies [ policy ] }}
< / div >
< / div >
< / MkFolder >
< MkFolder >
< template # icon > < i class = "ti ti-password" > < / i > < / template >
< template # label > IP < / template >
< MkInfo v-if = "!iAmAdmin" warn > { { i18n . ts . requireAdminForView } } < / MkInfo >
<!-- TODO translate -- >
< MkInfo v-else > The date is the IP address was first acknowledged. < / MkInfo >
< template v-if = "iAmAdmin && ips" >
< div v-for = "record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;" >
< span class = "date" > { { record . createdAt } } < / span >
< span class = "ip" > { { record . ip } } < / span >
< / div >
< / template >
< / MkFolder >
< div >
< MkButton v-if = "iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserAvatar"><i class="ti ti-user-circle" > < / i > {{ i18n.ts.unsetUserAvatar }} < / MkButton >
< MkButton v-if = "iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserBanner"><i class="ti ti-photo" > < / i > {{ i18n.ts.unsetUserBanner }} < / MkButton >
< MkButton v-if = "iAmModerator" inline danger @click="deleteAllFiles"><i class="ph-cloud ph-bold ph-lg" > < / i > {{ i18n.ts.deleteAllFiles }} < / MkButton >
< / div >
< MkButton v-if = "$i.isAdmin && !isSystem" inline danger @click="deleteAccount" > {{ i18n.ts.deleteAccount }} < / MkButton >
< / div >
< / FormSection >
< / div >
< div v-else-if = "tab === 'roles'" class="_gaps" >
< MkButton v-if = "user.host == null" primary rounded @click="assignRole"><i class= "ti ti-plus"> < / i > {{ i18n.ts.assign }} < / MkButton >
< MkButton primary rounded @click ="assignRole" > < i class = "ti ti-plus" > < / i > { { i18n . ts . assign } } < / MkButton >
< div v-for = "role in info.roles" :key="role.id" >
< div :class = "$style.roleItemMain" >
< MkRolePreview :class = "$style.role" :role = "role" :forModeration = "true" / >
< button class = "_button" @click ="toggleRoleItem(role)" > < i class = "ti ti-chevron-down" > < / i > < / button >
< button v-if = "role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x " > < / i > < / button >
< button class = "_button" @click ="toggleRoleItem(role)" >
< i v-if = "!expandedRoles.includes(role.id)" class="ti ti-chevron-down " > < / i >
< i v-if = "expandedRoles.includes(role.id)" class="ti ti-chevron-left" > < / i >
< / button >
< button v-if = "role.target === 'manual' || info.roleAssigns.some(a => a.roleId === role.id)" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x" > < / i > < / button >
< button v-else class = "_button" :class = "$style.roleUnassign" disabled > < i class = "ti ti-ban" > < / i > < / button >
< / div >
< div v-if = "expandedRoles.includes(role.id)" :class="$style.roleItemSub" >
< div > Assigned : < MkTime : time = "info.roleAssigns.find (a => a.roleId === role.id).createdAt" mode = "detail" / > < / div >
< div v-if = "info.roleAssigns.find(a => a.roleId === role.id).expiresAt" > Period : {{ new Date ( info.roleAssigns.find ( a = > a . roleId === role . id ) . expiresAt ) . toLocaleString ( ) } } < / div >
< div v-else > Period : {{ i18n.ts.indefinitely }} < / div >
< template v-if = "info.roleAssigns.some (a => a.roleId === role.id)" >
< div > { { i18n . ts . roleAssigned } } : < MkTime : time = "info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode = "detail" / > < / div >
< div v-if = "info.roleAssigns.find(a => a.roleId === role.id).expiresAt" > {{ i18n.ts.rolePeriod }} : {{ new Date ( info.roleAssigns.find ( a = > a . roleId === role . id ) . expiresAt ) . toLocaleString ( ) } } < / div >
< div v-else > {{ i18n.ts.rolePeriod }} : {{ i18n.ts.indefinitely }} < / div >
< / template >
< template v-else >
< div > { { i18n . ts . roleAssigned } } : { { i18n . ts . roleAutomatic } } < / div >
< / template >
< / div >
< / div >
< / div >
@@ -231,6 +270,8 @@ import { iAmAdmin, $i, iAmModerator } from '@/i.js';
import MkRolePreview from '@/components/MkRolePreview.vue' ;
import MkPagination from '@/components/MkPagination.vue' ;
import MkInput from '@/components/MkInput.vue' ;
import MkNumber from '@/components/MkNumber.vue' ;
import { copyToClipboard } from '@/utility/copy-to-clipboard' ;
const props = withDefaults ( defineProps < {
userId : string ;
@@ -740,4 +781,12 @@ definePage(() => ({
border - radius : var ( -- MI - radius - sm ) ;
cursor : pointer ;
}
. buttonStrip {
margin : calc ( var ( -- MI - margin ) / 2 * - 1 ) ;
> * {
margin : calc ( var ( -- MI - margin ) / 2 ) ;
}
}
< / style >