add: Require Approval for Signup
This commit is contained in:
parent
5c7f517895
commit
2f2d88dcfc
24 changed files with 330 additions and 29 deletions
|
|
@ -15,6 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span class="name"><MkUserName class="name" :user="user"/></span>
|
||||
<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
||||
<span class="state">
|
||||
<span v-if="!approved" class="silenced">Not 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>
|
||||
|
|
@ -176,6 +177,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkObjectView tall :value="user">
|
||||
</MkObjectView>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'approval'" class="_gaps_m">
|
||||
<MkKeyValue oneline>
|
||||
<template #key>Approval Status</template>
|
||||
<template #value><span class="_monospace">{{ approved ? 'Approved' : 'Not Approved' }}</span></template>
|
||||
</MkKeyValue>
|
||||
|
||||
<MkTextarea v-model="signupReason" readonly>
|
||||
<template #label>Reason</template>
|
||||
</MkTextarea>
|
||||
|
||||
<MkButton v-if="$i.isAdmin" inline success @click="approveAccount">Approve</MkButton>
|
||||
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">Deny & Delete</MkButton>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
|
|
@ -222,8 +237,11 @@ let ips = $ref(null);
|
|||
let ap = $ref(null);
|
||||
let moderator = $ref(false);
|
||||
let silenced = $ref(false);
|
||||
let approved = $ref(false);
|
||||
let suspended = $ref(false);
|
||||
let moderationNote = $ref('');
|
||||
let signupReason = $ref('');
|
||||
|
||||
const filesPagination = {
|
||||
endpoint: 'admin/drive/files' as const,
|
||||
limit: 10,
|
||||
|
|
@ -253,8 +271,10 @@ function createFetcher() {
|
|||
ips = _ips;
|
||||
moderator = info.isModerator;
|
||||
silenced = info.isSilenced;
|
||||
approved = info.approved;
|
||||
suspended = info.isSuspended;
|
||||
moderationNote = info.moderationNote;
|
||||
signupReason = info.signupReason;
|
||||
|
||||
watch($$(moderationNote), async () => {
|
||||
await os.api('admin/update-user-note', { userId: user.id, text: moderationNote });
|
||||
|
|
@ -346,6 +366,16 @@ async function deleteAccount() {
|
|||
}
|
||||
}
|
||||
|
||||
async function approveAccount() {
|
||||
const confirm = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.suspendConfirm,
|
||||
});
|
||||
if (confirm.canceled) return;
|
||||
await os.api('admin/approve-user', { userId: user.id });
|
||||
await refreshUser();
|
||||
}
|
||||
|
||||
async function assignRole() {
|
||||
const roles = await os.api('admin/roles/list');
|
||||
|
||||
|
|
@ -432,31 +462,60 @@ watch($$(user), () => {
|
|||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => [{
|
||||
key: 'overview',
|
||||
title: i18n.ts.overview,
|
||||
icon: 'ph-info ph-bold ph-lg',
|
||||
}, {
|
||||
key: 'roles',
|
||||
title: i18n.ts.roles,
|
||||
icon: 'ph-seal-check ph-bold pg-lg',
|
||||
}, {
|
||||
key: 'announcements',
|
||||
title: i18n.ts.announcements,
|
||||
icon: 'ph-megaphone ph-bold ph-lg',
|
||||
}, {
|
||||
key: 'drive',
|
||||
title: i18n.ts.drive,
|
||||
icon: 'ph-cloud ph-bold ph-lg',
|
||||
}, {
|
||||
key: 'chart',
|
||||
title: i18n.ts.charts,
|
||||
icon: 'ph-chart-line ph-bold pg-lg',
|
||||
}, {
|
||||
key: 'raw',
|
||||
title: 'Raw',
|
||||
icon: 'ph-code ph-bold pg-lg',
|
||||
}]);
|
||||
const headerTabs = $computed(() => iAmAdmin && !approved ?
|
||||
[{
|
||||
key: 'overview',
|
||||
title: i18n.ts.overview,
|
||||
icon: 'ph-info ph-bold ph-lg',
|
||||
}, {
|
||||
key: 'roles',
|
||||
title: i18n.ts.roles,
|
||||
icon: 'ph-seal-check ph-bold pg-lg',
|
||||
}, {
|
||||
key: 'announcements',
|
||||
title: i18n.ts.announcements,
|
||||
icon: 'ph-megaphone ph-bold ph-lg',
|
||||
}, {
|
||||
key: 'drive',
|
||||
title: i18n.ts.drive,
|
||||
icon: 'ph-cloud ph-bold ph-lg',
|
||||
}, {
|
||||
key: 'chart',
|
||||
title: i18n.ts.charts,
|
||||
icon: 'ph-chart-line ph-bold pg-lg',
|
||||
}, {
|
||||
key: 'raw',
|
||||
title: 'Raw',
|
||||
icon: 'ph-code ph-bold pg-lg',
|
||||
}, {
|
||||
key: 'approval',
|
||||
title: 'Approval',
|
||||
icon: 'ph-eye ph-bold pg-lg',
|
||||
}] : [{
|
||||
key: 'overview',
|
||||
title: i18n.ts.overview,
|
||||
icon: 'ph-info ph-bold ph-lg',
|
||||
}, {
|
||||
key: 'roles',
|
||||
title: i18n.ts.roles,
|
||||
icon: 'ph-seal-check ph-bold pg-lg',
|
||||
}, {
|
||||
key: 'announcements',
|
||||
title: i18n.ts.announcements,
|
||||
icon: 'ph-megaphone ph-bold ph-lg',
|
||||
}, {
|
||||
key: 'drive',
|
||||
title: i18n.ts.drive,
|
||||
icon: 'ph-cloud ph-bold ph-lg',
|
||||
}, {
|
||||
key: 'chart',
|
||||
title: i18n.ts.charts,
|
||||
icon: 'ph-chart-line ph-bold pg-lg',
|
||||
}, {
|
||||
key: 'raw',
|
||||
title: 'Raw',
|
||||
icon: 'ph-code ph-bold pg-lg',
|
||||
}]);
|
||||
|
||||
definePageMetadata(computed(() => ({
|
||||
title: user ? acct(user) : i18n.ts.userInfo,
|
||||
|
|
@ -547,6 +606,18 @@ definePageMetadata(computed(() => ({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.casdwq {
|
||||
.silenced {
|
||||
color: var(--warn);
|
||||
border-color: var(--warn);
|
||||
}
|
||||
|
||||
.moderator {
|
||||
color: var(--success);
|
||||
border-color: var(--success);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="approvalRequiredForSignup">
|
||||
<template #label>Require approval for new sign-ups</template>
|
||||
</MkSwitch>
|
||||
|
||||
<FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
|
||||
|
||||
<MkInput v-model="tosUrl">
|
||||
|
|
@ -71,6 +75,7 @@ import FormLink from '@/components/form/link.vue';
|
|||
|
||||
let enableRegistration: boolean = $ref(false);
|
||||
let emailRequiredForSignup: boolean = $ref(false);
|
||||
let approvalRequiredForSignup: boolean = $ref(false);
|
||||
let sensitiveWords: string = $ref('');
|
||||
let preservedUsernames: string = $ref('');
|
||||
let tosUrl: string | null = $ref(null);
|
||||
|
|
@ -80,6 +85,7 @@ async function init() {
|
|||
const meta = await os.api('admin/meta');
|
||||
enableRegistration = !meta.disableRegistration;
|
||||
emailRequiredForSignup = meta.emailRequiredForSignup;
|
||||
approvalRequiredForSignup = meta.approvalRequiredForSignup;
|
||||
sensitiveWords = meta.sensitiveWords.join('\n');
|
||||
preservedUsernames = meta.preservedUsernames.join('\n');
|
||||
tosUrl = meta.tosUrl;
|
||||
|
|
@ -90,6 +96,7 @@ function save() {
|
|||
os.apiWithDialog('admin/update-meta', {
|
||||
disableRegistration: !enableRegistration,
|
||||
emailRequiredForSignup,
|
||||
approvalRequiredForSignup,
|
||||
tosUrl,
|
||||
privacyPolicyUrl,
|
||||
sensitiveWords: sensitiveWords.split('\n'),
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:class="{
|
||||
[$style.logGreen]: ['createRole', 'addCustomEmoji', 'createGlobalAnnouncement', 'createUserAnnouncement', 'createAd', 'createInvitation'].includes(log.type),
|
||||
[$style.logYellow]: ['markSensitiveDriveFile', 'resetPassword'].includes(log.type),
|
||||
[$style.logRed]: ['suspend', 'deleteRole', 'suspendRemoteInstance', 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'deleteCustomEmoji', 'deleteNote', 'deleteDriveFile', 'deleteAd'].includes(log.type)
|
||||
[$style.logRed]: ['suspend', 'approve', 'deleteRole', 'suspendRemoteInstance', 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'deleteCustomEmoji', 'deleteNote', 'deleteDriveFile', 'deleteAd'].includes(log.type)
|
||||
}"
|
||||
>{{ i18n.ts._moderationLogTypes[log.type] }}</b>
|
||||
<span v-if="log.type === 'updateUserNote'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'suspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'approve'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'unsuspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'resetPassword'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'assignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} <i class="ti ti-arrow-right"></i> {{ log.info.roleName }}</span>
|
||||
|
|
@ -65,6 +66,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template v-else-if="log.type === 'suspend'">
|
||||
<div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
|
||||
</template>
|
||||
<template v-else-if="log.type === 'approve'">
|
||||
<div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
|
||||
</template>
|
||||
<template v-else-if="log.type === 'unsuspend'">
|
||||
<div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue