enhance: コンテンツ削除を制限されていてもアカウントの閉鎖ができるように (MisskeyIO#532)
This commit is contained in:
parent
2564fc7346
commit
075ec2d7df
33 changed files with 390 additions and 403 deletions
|
|
@ -108,6 +108,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
|
|||
isMuted: false,
|
||||
isSilenced: false,
|
||||
isLimited: false,
|
||||
isDeleted: false,
|
||||
isSuspended: false,
|
||||
lang: 'en',
|
||||
location: 'Fediverse',
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ export const ROLE_POLICIES = [
|
|||
'canCreateContent',
|
||||
'canUpdateContent',
|
||||
'canDeleteContent',
|
||||
'canPurgeAccount',
|
||||
'canUpdateAvatar',
|
||||
'canUpdateBanner',
|
||||
'mentionLimit',
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ 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="suspended" class="suspended">Suspended</span>
|
||||
<span v-if="limited" class="limited">Limited</span>
|
||||
<span v-if="silenced" class="silenced">Silenced</span>
|
||||
<span v-if="admin" class="admin">Admin</span>
|
||||
<span v-if="moderator" class="moderator">Moderator</span>
|
||||
<span v-if="silenced" class="silenced">Silenced</span>
|
||||
<span v-if="limited" class="limited">Limited</span>
|
||||
<span v-if="suspended" class="suspended">Suspended</span>
|
||||
<span v-if="deleted" class="deleted">Deleted</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -32,12 +34,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<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>
|
||||
-->
|
||||
<MkKeyValue oneline>
|
||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
||||
|
|
@ -50,48 +46,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #key>{{ i18n.ts.email }}</template>
|
||||
<template #value><span class="_monospace">{{ info.email }}</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>
|
||||
</div>
|
||||
|
||||
<MkTextarea v-model="moderationNote" manualSave>
|
||||
<template #label>{{ i18n.ts.moderationNote }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<!--
|
||||
<FormSection>
|
||||
<template #label>ActivityPub</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||
<MkKeyValue v-if="user.host" oneline>
|
||||
<template #key>{{ i18n.ts.instanceInfo }}</template>
|
||||
<template #value><MkA :to="`/instance-info/${user.host}`" class="_link">{{ user.host }} <i class="ti ti-chevron-right"></i></MkA></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-else oneline>
|
||||
<template #key>{{ i18n.ts.instanceInfo }}</template>
|
||||
<template #value>(Local user)</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline>
|
||||
<template #key>{{ i18n.ts.updatedAt }}</template>
|
||||
<template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-if="ap" oneline>
|
||||
<template #key>Type</template>
|
||||
<template #value><span class="_monospace">{{ ap.type }}</span></template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
|
||||
<MkButton v-if="user.host != null" @click="updateRemoteUser"><i class="ti ti-refresh"></i> {{ i18n.ts.updateRemoteUser }}</MkButton>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>Raw</template>
|
||||
|
||||
<MkObjectView v-if="ap" tall :value="ap">
|
||||
</MkObjectView>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
-->
|
||||
|
||||
<FormSection>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
||||
|
|
@ -123,8 +87,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</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>
|
||||
<div class="_buttons">
|
||||
<MkButton v-if="iAmModerator" inline danger @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
||||
<MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
||||
</div>
|
||||
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
|
||||
|
|
@ -172,6 +136,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
|
||||
<div v-else-if="tab === 'drive'" class="_gaps">
|
||||
<MkButton v-if="iAmModerator" inline danger @click="deleteAllFiles"><i class="ti ti-trash"></i> {{ i18n.ts.deleteAllFiles }}</MkButton>
|
||||
<MkFileListForAdmin :pagination="filesPagination" viewMode="grid"/>
|
||||
</div>
|
||||
|
||||
|
|
@ -191,6 +156,36 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'activitypub'" class="_gaps_m">
|
||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||
<MkKeyValue v-if="user.host" oneline>
|
||||
<template #key>{{ i18n.ts.instanceInfo }}</template>
|
||||
<template #value><MkA :to="`/instance-info/${user.host}`" class="_link">{{ user.host }} <i class="ti ti-chevron-right"></i></MkA></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-else oneline>
|
||||
<template #key>{{ i18n.ts.instanceInfo }}</template>
|
||||
<template #value>(Local user)</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline>
|
||||
<template #key>{{ i18n.ts.updatedAt }}</template>
|
||||
<template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-if="ap" oneline>
|
||||
<template #key>Type</template>
|
||||
<template #value><span class="_monospace">{{ ap.type }}</span></template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
|
||||
<MkButton v-if="user.host != null" @click="updateRemoteUser"><i class="ti ti-refresh"></i> {{ i18n.ts.updateRemoteUser }}</MkButton>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>Raw</template>
|
||||
|
||||
<MkObjectView v-if="ap" tall :value="ap">
|
||||
</MkObjectView>
|
||||
</MkFolder>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
||||
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
|
||||
</MkObjectView>
|
||||
|
|
@ -243,10 +238,12 @@ const init = ref<ReturnType<typeof createFetcher>>();
|
|||
const info = ref<any>();
|
||||
const ips = ref<Misskey.entities.AdminGetUserIpsResponse | null>(null);
|
||||
const ap = ref<any>(null);
|
||||
const admin = ref(false);
|
||||
const moderator = ref(false);
|
||||
const silenced = ref(false);
|
||||
const limited = ref(false);
|
||||
const suspended = ref(false);
|
||||
const deleted = ref(false);
|
||||
const moderationNote = ref('');
|
||||
const filesPagination = {
|
||||
endpoint: 'admin/drive/files' as const,
|
||||
|
|
@ -277,15 +274,18 @@ function createFetcher() {
|
|||
user.value = _user;
|
||||
info.value = _info;
|
||||
ips.value = _ips;
|
||||
admin.value = info.value.isAdmin;
|
||||
moderator.value = info.value.isModerator;
|
||||
silenced.value = info.value.isSilenced;
|
||||
limited.value = info.value.isLimited;
|
||||
suspended.value = info.value.isSuspended;
|
||||
deleted.value = info.value.isDeleted;
|
||||
moderationNote.value = info.value.moderationNote;
|
||||
|
||||
watch(moderationNote, async () => {
|
||||
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
||||
await refreshUser();
|
||||
await misskeyApi('admin/update-user-note', {
|
||||
userId: user.value.id, text: moderationNote.value
|
||||
}).then(refreshUser);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -295,8 +295,9 @@ function refreshUser() {
|
|||
}
|
||||
|
||||
async function updateRemoteUser() {
|
||||
await os.apiWithDialog('federation/update-remote-user', { userId: user.value.id });
|
||||
refreshUser();
|
||||
await os.apiWithDialog('federation/update-remote-user', {
|
||||
userId: user.value.id
|
||||
}).then(refreshUser);
|
||||
}
|
||||
|
||||
async function resetPassword() {
|
||||
|
|
@ -325,8 +326,9 @@ async function toggleSuspend(v) {
|
|||
if (confirm.canceled) {
|
||||
suspended.value = !v;
|
||||
} else {
|
||||
await misskeyApi(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: user.value.id });
|
||||
await refreshUser();
|
||||
await misskeyApi(v ? 'admin/suspend-user' : 'admin/unsuspend-user', {
|
||||
userId: user.value.id
|
||||
}).then(refreshUser);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -336,17 +338,10 @@ async function unsetUserAvatar() {
|
|||
text: i18n.ts.unsetUserAvatarConfirm,
|
||||
});
|
||||
if (confirm.canceled) return;
|
||||
const process = async () => {
|
||||
await misskeyApi('admin/unset-user-avatar', { userId: user.value.id });
|
||||
os.success();
|
||||
};
|
||||
await process().catch(err => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err.toString(),
|
||||
});
|
||||
});
|
||||
refreshUser();
|
||||
|
||||
await os.apiWithDialog('admin/unset-user-avatar', {
|
||||
userId: user.value.id
|
||||
}).then(refreshUser);
|
||||
}
|
||||
|
||||
async function unsetUserBanner() {
|
||||
|
|
@ -355,17 +350,10 @@ async function unsetUserBanner() {
|
|||
text: i18n.ts.unsetUserBannerConfirm,
|
||||
});
|
||||
if (confirm.canceled) return;
|
||||
const process = async () => {
|
||||
await misskeyApi('admin/unset-user-banner', { userId: user.value.id });
|
||||
os.success();
|
||||
};
|
||||
await process().catch(err => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err.toString(),
|
||||
});
|
||||
});
|
||||
refreshUser();
|
||||
|
||||
await os.apiWithDialog('admin/unset-user-banner', {
|
||||
userId: user.value.id
|
||||
}).then(refreshUser);
|
||||
}
|
||||
|
||||
async function deleteAllFiles() {
|
||||
|
|
@ -374,17 +362,22 @@ async function deleteAllFiles() {
|
|||
text: i18n.ts.deleteAllFilesConfirm,
|
||||
});
|
||||
if (confirm.canceled) return;
|
||||
const process = async () => {
|
||||
await misskeyApi('admin/delete-all-files-of-a-user', { userId: user.value.id });
|
||||
os.success();
|
||||
};
|
||||
await process().catch(err => {
|
||||
|
||||
const typed = await os.inputText({
|
||||
text: i18n.tsx.typeToConfirm({ x: user.value?.username }),
|
||||
});
|
||||
if (typed.canceled) return;
|
||||
|
||||
if (typed.result === user.value?.username) {
|
||||
await os.apiWithDialog('admin/drive/delete-all-files-of-a-user', {
|
||||
userId: user.value.id
|
||||
}).then(refreshUser);
|
||||
} else {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err.toString(),
|
||||
text: 'input not match',
|
||||
});
|
||||
});
|
||||
await refreshUser();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAccount() {
|
||||
|
|
@ -400,9 +393,9 @@ async function deleteAccount() {
|
|||
if (typed.canceled) return;
|
||||
|
||||
if (typed.result === user.value?.username) {
|
||||
await os.apiWithDialog('admin/delete-account', {
|
||||
await os.apiWithDialog('admin/accounts/delete', {
|
||||
userId: user.value.id,
|
||||
});
|
||||
}).then(refreshUser);
|
||||
} else {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
|
|
@ -444,8 +437,9 @@ async function assignRole() {
|
|||
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
|
||||
: null;
|
||||
|
||||
await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.value.id, expiresAt });
|
||||
refreshUser();
|
||||
await os.apiWithDialog('admin/roles/assign', {
|
||||
roleId, userId: user.value.id, expiresAt
|
||||
}).then(refreshUser);
|
||||
}
|
||||
|
||||
async function unassignRole(role, ev) {
|
||||
|
|
@ -454,8 +448,9 @@ async function unassignRole(role, ev) {
|
|||
icon: 'ti ti-x',
|
||||
danger: true,
|
||||
action: async () => {
|
||||
await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.value.id });
|
||||
refreshUser();
|
||||
await os.apiWithDialog('admin/roles/unassign', {
|
||||
roleId: role.id, userId: user.value.id
|
||||
}).then(refreshUser);
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
|
@ -525,6 +520,10 @@ const headerTabs = computed(() => [{
|
|||
key: 'chart',
|
||||
title: i18n.ts.charts,
|
||||
icon: 'ti ti-chart-line',
|
||||
}, {
|
||||
key: 'activitypub',
|
||||
title: 'ActivityPub',
|
||||
icon: 'ti ti-share',
|
||||
}, {
|
||||
key: 'raw',
|
||||
title: 'Raw',
|
||||
|
|
@ -581,7 +580,12 @@ definePageMetadata(() => ({
|
|||
display: none;
|
||||
}
|
||||
|
||||
> .suspended, > .limited, > .silenced, > .moderator {
|
||||
> .admin,
|
||||
> .moderator,
|
||||
> .silenced,
|
||||
> .limited,
|
||||
> .suspended,
|
||||
> .deleted {
|
||||
display: inline-block;
|
||||
border: solid 1px;
|
||||
border-radius: 6px;
|
||||
|
|
@ -589,14 +593,14 @@ definePageMetadata(() => ({
|
|||
font-size: 85%;
|
||||
}
|
||||
|
||||
> .suspended {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
> .admin {
|
||||
color: var(--success);
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
> .limited {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
> .moderator {
|
||||
color: var(--success);
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
> .silenced {
|
||||
|
|
@ -604,9 +608,19 @@ definePageMetadata(() => ({
|
|||
border-color: var(--warn);
|
||||
}
|
||||
|
||||
> .moderator {
|
||||
color: var(--success);
|
||||
border-color: var(--success);
|
||||
> .limited {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
> .suspended {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
> .deleted {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,6 +240,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPurgeAccount, 'canPurgeAccount'])">
|
||||
<template #label>{{ i18n.ts._role._options.canPurgeAccount }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.canPurgeAccount.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.canPurgeAccount.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canPurgeAccount)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.canPurgeAccount.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="role.policies.canPurgeAccount.value" :disabled="role.policies.canPurgeAccount.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange v-model="role.policies.canPurgeAccount.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUpdateAvatar, 'canUpdateAvatar'])">
|
||||
<template #label>{{ i18n.ts._role._options.canUpdateAvatar }}</template>
|
||||
<template #suffix>
|
||||
|
|
|
|||
|
|
@ -80,6 +80,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkSwitch>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPurgeAccount, 'canPurgeAccount'])">
|
||||
<template #label>{{ i18n.ts._role._options.canPurgeAccount }}</template>
|
||||
<template #suffix>{{ policies.canPurgeAccount ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
<MkSwitch v-model="policies.canPurgeAccount">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUpdateAvatar, 'canUpdateAvatar'])">
|
||||
<template #label>{{ i18n.ts._role._options.canUpdateAvatar }}</template>
|
||||
<template #suffix>{{ policies.canUpdateAvatar ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span :class="$style.userMInfoMetaName"><MkUserName :class="$style.userMInfoMetaName" :user="user"/></span>
|
||||
<span :class="$style.userMInfoMetaSub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
||||
<span :class="$style.userMInfoMetaState">
|
||||
<span v-if="suspended" :class="$style.suspended">Suspended</span>
|
||||
<span v-if="limited" :class="$style.limited">Limited</span>
|
||||
<span v-if="silenced" :class="$style.silenced">Silenced</span>
|
||||
<span v-if="moderator" :class="$style.moderator">Moderator</span>
|
||||
<span v-if="silenced" :class="$style.silenced">Silenced</span>
|
||||
<span v-if="limited" :class="$style.limited">Limited</span>
|
||||
<span v-if="suspended" :class="$style.suspended">Suspended</span>
|
||||
<span v-if="deleted" :class="$style.deleted">Deleted</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -56,6 +57,7 @@ const moderator = computed(() => props.user.isModerator ?? false);
|
|||
const silenced = computed(() => props.user.isSilenced ?? false);
|
||||
const limited = computed(() => props.user.isLimited ?? false);
|
||||
const suspended = computed(() => props.user.isSuspended ?? false);
|
||||
const deleted = computed(() => props.user.isDeleted ?? false);
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
@ -104,10 +106,11 @@ const suspended = computed(() => props.user.isSuspended ?? false);
|
|||
display: none;
|
||||
}
|
||||
|
||||
> .suspended,
|
||||
> .limited,
|
||||
> .moderator,
|
||||
> .silenced,
|
||||
> .moderator {
|
||||
> .limited,
|
||||
> .suspended,
|
||||
> .deleted {
|
||||
display: inline-block;
|
||||
border: solid 1px;
|
||||
border-radius: 6px;
|
||||
|
|
@ -115,14 +118,9 @@ const suspended = computed(() => props.user.isSuspended ?? false);
|
|||
font-size: 85%;
|
||||
}
|
||||
|
||||
> .suspended {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
> .limited {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
> .moderator {
|
||||
color: var(--success);
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
> .silenced {
|
||||
|
|
@ -130,9 +128,19 @@ const suspended = computed(() => props.user.isSuspended ?? false);
|
|||
border-color: var(--warn);
|
||||
}
|
||||
|
||||
> .moderator {
|
||||
color: var(--success);
|
||||
border-color: var(--success);
|
||||
> .limited {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
> .suspended {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
> .deleted {
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue