summaryrefslogtreecommitdiff
path: root/packages/frontend/src/pages/admin
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/pages/admin')
-rw-r--r--packages/frontend/src/pages/admin/abuses.vue2
-rw-r--r--packages/frontend/src/pages/admin/ads.vue19
-rw-r--r--packages/frontend/src/pages/admin/index.vue13
-rw-r--r--packages/frontend/src/pages/admin/invites.vue126
-rw-r--r--packages/frontend/src/pages/admin/moderation.vue2
-rw-r--r--packages/frontend/src/pages/admin/other-settings.vue40
-rw-r--r--packages/frontend/src/pages/admin/overview.ap-requests.vue2
-rw-r--r--packages/frontend/src/pages/admin/overview.federation.vue2
-rw-r--r--packages/frontend/src/pages/admin/overview.queue.vue4
-rw-r--r--packages/frontend/src/pages/admin/overview.stats.vue2
-rw-r--r--packages/frontend/src/pages/admin/overview.vue4
-rw-r--r--packages/frontend/src/pages/admin/queue.chart.vue2
-rw-r--r--packages/frontend/src/pages/admin/roles.editor.vue65
-rw-r--r--packages/frontend/src/pages/admin/roles.role.vue2
-rw-r--r--packages/frontend/src/pages/admin/roles.vue23
-rw-r--r--packages/frontend/src/pages/admin/settings.vue15
16 files changed, 297 insertions, 26 deletions
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index 3bc5ee9723..9cf96d3d04 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -75,7 +75,7 @@ const pagination = {
};
function resolved(reportId) {
- reports.removeItem(item => item.id === reportId);
+ reports.removeItem(reportId);
}
const headerActions = $computed(() => []);
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index 2c9e18b0bf..9a5bd88b2e 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -36,6 +36,16 @@
<template #label>{{ i18n.ts.expiration }}</template>
</MkInput>
</FormSplit>
+ <MkFolder>
+ <template #label>{{ i18n.ts.advancedSettings }}</template>
+ <span>
+ {{ i18n.ts._ad.timezoneinfo }}
+ <div v-for="(day, index) in daysOfWeek" :key="index">
+ <input :id="`ad${ad.id}-${index}`" type="checkbox" :checked="(ad.dayOfWeek & (1 << index)) !== 0" @change="toggleDayOfWeek(ad, index)">
+ <label :for="`ad${ad.id}-${index}`">{{ day }}</label>
+ </div>
+ </span>
+ </MkFolder>
<MkTextarea v-model="ad.memo">
<template #label>{{ i18n.ts.memo }}</template>
</MkTextarea>
@@ -59,6 +69,7 @@ import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkRadios from '@/components/MkRadios.vue';
+import MkFolder from '@/components/MkFolder.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
@@ -69,6 +80,7 @@ let ads: any[] = $ref([]);
// ISO形式はTZがUTCになってしまうので、TZ分ずらして時間を初期化
const localTime = new Date();
const localTimeDiff = localTime.getTimezoneOffset() * 60 * 1000;
+const daysOfWeek: string[] = [i18n.ts._weekday.sunday, i18n.ts._weekday.monday, i18n.ts._weekday.tuesday, i18n.ts._weekday.wednesday, i18n.ts._weekday.thursday, i18n.ts._weekday.friday, i18n.ts._weekday.saturday];
os.api('admin/ad/list').then(adsResponse => {
ads = adsResponse.map(r => {
@@ -84,6 +96,11 @@ os.api('admin/ad/list').then(adsResponse => {
});
});
+// 選択された曜日(index)のビットフラグを操作する
+function toggleDayOfWeek(ad, index) {
+ ad.dayOfWeek ^= 1 << index;
+}
+
function add() {
ads.unshift({
id: null,
@@ -95,6 +112,7 @@ function add() {
imageUrl: null,
expiresAt: null,
startsAt: null,
+ dayOfWeek: 0,
});
}
@@ -105,6 +123,7 @@ function remove(ad) {
}).then(({ canceled }) => {
if (canceled) return;
ads = ads.filter(x => x !== ad);
+ if (ad.id == null) return;
os.apiWithDialog('admin/ad/delete', {
id: ad.id,
});
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 8b083bc896..e91f65b5d5 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -1,6 +1,6 @@
<template>
<div ref="el" class="hiyeyicy" :class="{ wide: !narrow }">
- <div v-if="!narrow || currentPage?.route.name == null" class="nav">
+ <div v-if="!narrow || currentPage?.route.name == null" class="nav">
<MkSpacer :contentMax="700" :marginMin="16">
<div class="lxpfedzu">
<div class="banner">
@@ -80,7 +80,7 @@ const menuDef = $computed(() => [{
}, ...(instance.disableRegistration ? [{
type: 'button',
icon: 'ti ti-user-plus',
- text: i18n.ts.invite,
+ text: i18n.ts.createInviteCode,
action: invite,
}] : [])],
}, {
@@ -96,6 +96,11 @@ const menuDef = $computed(() => [{
to: '/admin/users',
active: currentPage?.route.name === 'users',
}, {
+ icon: 'ti ti-user-plus',
+ text: i18n.ts.invite,
+ to: '/admin/invites',
+ active: currentPage?.route.name === 'invites',
+ }, {
icon: 'ti ti-badges',
text: i18n.ts.roles,
to: '/admin/roles',
@@ -240,10 +245,10 @@ provideMetadataReceiver((info) => {
});
const invite = () => {
- os.api('invite').then(x => {
+ os.api('admin/invite/create').then(x => {
os.alert({
type: 'info',
- text: x.code,
+ text: x?.[0].code,
});
}).catch(err => {
os.alert({
diff --git a/packages/frontend/src/pages/admin/invites.vue b/packages/frontend/src/pages/admin/invites.vue
new file mode 100644
index 0000000000..70a9c93713
--- /dev/null
+++ b/packages/frontend/src/pages/admin/invites.vue
@@ -0,0 +1,126 @@
+<template>
+<MkStickyContainer>
+ <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
+ <MkSpacer :contentMax="800">
+ <div class="_gaps_m">
+ <MkFolder :expanded="false">
+ <template #icon><i class="ti ti-plus"></i></template>
+ <template #label>{{ i18n.ts.createInviteCode }}</template>
+
+ <div class="_gaps_m">
+ <MkSwitch v-model="noExpirationDate">
+ <template #label>{{ i18n.ts.noExpirationDate }}</template>
+ </MkSwitch>
+ <MkInput v-if="!noExpirationDate" v-model="expiresAt" type="datetime-local">
+ <template #label>{{ i18n.ts.expirationDate }}</template>
+ </MkInput>
+ <MkInput v-model="createCount" type="number">
+ <template #label>{{ i18n.ts.createCount }}</template>
+ </MkInput>
+ <MkButton primary rounded @click="createWithOptions">{{ i18n.ts.create }}</MkButton>
+ </div>
+ </MkFolder>
+
+ <div :class="$style.inputs">
+ <MkSelect v-model="type" :class="$style.input">
+ <template #label>{{ i18n.ts.state }}</template>
+ <option value="all">{{ i18n.ts.all }}</option>
+ <option value="unused">{{ i18n.ts.unused }}</option>
+ <option value="used">{{ i18n.ts.used }}</option>
+ <option value="expired">{{ i18n.ts.expired }}</option>
+ </MkSelect>
+ <MkSelect v-model="sort" :class="$style.input">
+ <template #label>{{ i18n.ts.sort }}</template>
+ <option value="+createdAt">{{ i18n.ts.createdAt }} ({{ i18n.ts.ascendingOrder }})</option>
+ <option value="-createdAt">{{ i18n.ts.createdAt }} ({{ i18n.ts.descendingOrder }})</option>
+ <option value="+usedAt">{{ i18n.ts.usedAt }} ({{ i18n.ts.ascendingOrder }})</option>
+ <option value="-usedAt">{{ i18n.ts.usedAt }} ({{ i18n.ts.descendingOrder }})</option>
+ </MkSelect>
+ </div>
+ <MkPagination ref="pagingComponent" :pagination="pagination">
+ <template #default="{ items }">
+ <div class="_gaps_s">
+ <MkInviteCode v-for="item in items" :key="item.id" :invite="(item as any)" :onDeleted="deleted" moderator/>
+ </div>
+ </template>
+ </MkPagination>
+ </div>
+ </MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, shallowRef } from 'vue';
+import XHeader from './_header_.vue';
+import { i18n } from '@/i18n';
+import * as os from '@/os';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkSelect from '@/components/MkSelect.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkInviteCode from '@/components/MkInviteCode.vue';
+import { definePageMetadata } from '@/scripts/page-metadata';
+
+const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+
+let type = ref('all');
+let sort = ref('+createdAt');
+
+const pagination: Paging = {
+ endpoint: 'admin/invite/list' as const,
+ limit: 10,
+ params: computed(() => ({
+ type: type.value,
+ sort: sort.value,
+ })),
+ offsetMode: true,
+};
+
+const expiresAt = ref('');
+const noExpirationDate = ref(true);
+const createCount = ref(1);
+
+async function createWithOptions() {
+ const options = {
+ expiresAt: noExpirationDate.value ? null : expiresAt.value,
+ count: createCount.value,
+ };
+
+ const tickets = await os.api('admin/invite/create', options);
+ os.alert({
+ type: 'success',
+ title: i18n.ts.inviteCodeCreated,
+ text: tickets?.map(x => x.code).join('\n'),
+ });
+
+ tickets?.forEach(ticket => pagingComponent.value?.prepend(ticket));
+}
+
+function deleted(id: string) {
+ if (pagingComponent.value) {
+ pagingComponent.value.items.delete(id);
+ }
+}
+
+const headerActions = $computed(() => []);
+const headerTabs = $computed(() => []);
+
+definePageMetadata({
+ title: i18n.ts.invite,
+ icon: 'ti ti-user-plus',
+});
+</script>
+
+<style lang="scss" module>
+.inputs {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.input {
+ flex: 1;
+}
+</style>
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index e36c9ac91d..13789820a0 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -24,7 +24,7 @@
<template #label>{{ i18n.ts.preservedUsernames }}</template>
<template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
</MkTextarea>
-
+
<MkTextarea v-model="sensitiveWords">
<template #label>{{ i18n.ts.sensitiveWords }}</template>
<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
diff --git a/packages/frontend/src/pages/admin/other-settings.vue b/packages/frontend/src/pages/admin/other-settings.vue
index 15d720a070..13e3588740 100644
--- a/packages/frontend/src/pages/admin/other-settings.vue
+++ b/packages/frontend/src/pages/admin/other-settings.vue
@@ -3,14 +3,34 @@
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
<FormSuspense :p="init">
- <div class="_gaps_s">
- <MkSwitch v-model="enableChartsForRemoteUser">
- <template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
- </MkSwitch>
+ <div class="_gaps">
+ <div class="_panel" style="padding: 16px;">
+ <MkSwitch v-model="enableServerMachineStats">
+ <template #label>{{ i18n.ts.enableServerMachineStats }}</template>
+ <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+ </MkSwitch>
+ </div>
- <MkSwitch v-model="enableChartsForFederatedInstances">
- <template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
- </MkSwitch>
+ <div class="_panel" style="padding: 16px;">
+ <MkSwitch v-model="enableIdenticonGeneration">
+ <template #label>{{ i18n.ts.enableIdenticonGeneration }}</template>
+ <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+ </MkSwitch>
+ </div>
+
+ <div class="_panel" style="padding: 16px;">
+ <MkSwitch v-model="enableChartsForRemoteUser">
+ <template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
+ <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+ </MkSwitch>
+ </div>
+
+ <div class="_panel" style="padding: 16px;">
+ <MkSwitch v-model="enableChartsForFederatedInstances">
+ <template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
+ <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+ </MkSwitch>
+ </div>
</div>
</FormSuspense>
</MkSpacer>
@@ -27,17 +47,23 @@ import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import MkSwitch from '@/components/MkSwitch.vue';
+let enableServerMachineStats: boolean = $ref(false);
+let enableIdenticonGeneration: boolean = $ref(false);
let enableChartsForRemoteUser: boolean = $ref(false);
let enableChartsForFederatedInstances: boolean = $ref(false);
async function init() {
const meta = await os.api('admin/meta');
+ enableServerMachineStats = meta.enableServerMachineStats;
+ enableIdenticonGeneration = meta.enableIdenticonGeneration;
enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
}
function save() {
os.apiWithDialog('admin/update-meta', {
+ enableServerMachineStats,
+ enableIdenticonGeneration,
enableChartsForRemoteUser,
enableChartsForFederatedInstances,
}).then(() => {
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue
index ad8e623415..bde5580366 100644
--- a/packages/frontend/src/pages/admin/overview.ap-requests.vue
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue
@@ -259,7 +259,7 @@ onMounted(async () => {
},
plugins: [chartVLine(vLineColor)],
});
-
+
fetching = false;
});
</script>
diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue
index ab78c4c393..469d2e6927 100644
--- a/packages/frontend/src/pages/admin/overview.federation.vue
+++ b/packages/frontend/src/pages/admin/overview.federation.vue
@@ -58,7 +58,7 @@ let federationSubActiveDiff = $ref<number | null>(null);
let fetching = $ref(true);
const { handler: externalTooltipHandler } = useChartTooltip();
-
+
onMounted(async () => {
const chart = await os.apiGet('charts/federation', { limit: 2, span: 'day' });
federationPubActive = chart.pubActive[0];
diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue
index 69ca89e226..7d8d468512 100644
--- a/packages/frontend/src/pages/admin/overview.queue.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.vue
@@ -85,7 +85,7 @@ onMounted(() => {
connection.on('stats', onStats);
connection.on('statsLog', onStatsLog);
connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
+ id: Math.random().toString().substring(2, 10),
length: 100,
});
});
@@ -122,4 +122,4 @@ onUnmounted(() => {
}
}
}
-</style>
+</style>
diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue
index 142e70c698..f746ad14b9 100644
--- a/packages/frontend/src/pages/admin/overview.stats.vue
+++ b/packages/frontend/src/pages/admin/overview.stats.vue
@@ -73,7 +73,7 @@ let fetching = $ref(true);
onMounted(async () => {
const [_stats, _onlineUsersCount] = await Promise.all([
os.api('stats', {}),
- os.api('get-online-users-count').then(res => res.count),
+ os.apiGet('get-online-users-count').then(res => res.count),
]);
stats = _stats;
onlineUsersCount = _onlineUsersCount;
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index e8295c81b5..41a6d4f5b7 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -30,7 +30,7 @@
<template #header>Federation</template>
<XFederation/>
</MkFoldableSection>
-
+
<MkFoldableSection class="item">
<template #header>Instances</template>
<XInstances/>
@@ -156,7 +156,7 @@ onMounted(async () => {
nextTick(() => {
queueStatsConnection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
+ id: Math.random().toString().substring(2, 10),
length: 100,
});
});
diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue
index 8e6856fddd..83ca9639e7 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.vue
@@ -106,7 +106,7 @@ onMounted(() => {
connection.on('stats', onStats);
connection.on('statsLog', onStatsLog);
connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
+ id: Math.random().toString().substring(2, 10),
length: 200,
});
});
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index a1fa9d2932..1ba502ff86 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -1,5 +1,9 @@
<template>
<div class="_gaps">
+ <MkInput v-if="readonly" :modelValue="role.id" :readonly="true">
+ <template #label>ID</template>
+ </MkInput>
+
<MkInput v-model="role.name" :readonly="readonly">
<template #label>{{ i18n.ts._role.name }}</template>
</MkInput>
@@ -171,6 +175,65 @@
</div>
</MkFolder>
+ <MkFolder v-if="matchQuery([i18n.ts._role._options.inviteLimit, 'inviteLimit'])">
+ <template #label>{{ i18n.ts._role._options.inviteLimit }}</template>
+ <template #suffix>
+ <span v-if="role.policies.inviteLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+ <span v-else>{{ role.policies.inviteLimit.value }}</span>
+ <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.inviteLimit)"></i></span>
+ </template>
+ <div class="_gaps">
+ <MkSwitch v-model="role.policies.inviteLimit.useDefault" :readonly="readonly">
+ <template #label>{{ i18n.ts._role.useBaseValue }}</template>
+ </MkSwitch>
+ <MkInput v-model="role.policies.inviteLimit.value" :disabled="role.policies.inviteLimit.useDefault" type="number" :readonly="readonly">
+ </MkInput>
+ <MkRange v-model="role.policies.inviteLimit.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.inviteLimitCycle, 'inviteLimitCycle'])">
+ <template #label>{{ i18n.ts._role._options.inviteLimitCycle }}</template>
+ <template #suffix>
+ <span v-if="role.policies.inviteLimitCycle.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+ <span v-else>{{ role.policies.inviteLimitCycle.value + i18n.ts._time.minute }}</span>
+ <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.inviteLimitCycle)"></i></span>
+ </template>
+ <div class="_gaps">
+ <MkSwitch v-model="role.policies.inviteLimitCycle.useDefault" :readonly="readonly">
+ <template #label>{{ i18n.ts._role.useBaseValue }}</template>
+ </MkSwitch>
+ <MkInput v-model="role.policies.inviteLimitCycle.value" :disabled="role.policies.inviteLimitCycle.useDefault" type="number" :readonly="readonly">
+ <template #suffix>{{ i18n.ts._time.minute }}</template>
+ </MkInput>
+ <MkRange v-model="role.policies.inviteLimitCycle.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.inviteExpirationTime, 'inviteExpirationTime'])">
+ <template #label>{{ i18n.ts._role._options.inviteExpirationTime }}</template>
+ <template #suffix>
+ <span v-if="role.policies.inviteExpirationTime.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+ <span v-else>{{ role.policies.inviteExpirationTime.value + i18n.ts._time.minute }}</span>
+ <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.inviteExpirationTime)"></i></span>
+ </template>
+ <div class="_gaps">
+ <MkSwitch v-model="role.policies.inviteExpirationTime.useDefault" :readonly="readonly">
+ <template #label>{{ i18n.ts._role.useBaseValue }}</template>
+ </MkSwitch>
+ <MkInput v-model="role.policies.inviteExpirationTime.value" :disabled="role.policies.inviteExpirationTime.useDefault" type="number" :readonly="readonly">
+ <template #suffix>{{ i18n.ts._time.minute }}</template>
+ </MkInput>
+ <MkRange v-model="role.policies.inviteExpirationTime.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.canManageCustomEmojis, 'canManageCustomEmojis'])">
<template #label>{{ i18n.ts._role._options.canManageCustomEmojis }}</template>
<template #suffix>
@@ -210,7 +273,7 @@
</MkRange>
</div>
</MkFolder>
-
+
<MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])">
<template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
<template #suffix>
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index 6cbe7ae658..789c9da277 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -40,7 +40,7 @@
</div>
<div v-if="expandedItems.includes(item.id)" :class="$style.userItemSub">
<div>Assigned: <MkTime :time="item.createdAt" mode="detail"/></div>
- <div v-if="item.expiresAt">Period: {{ item.expiresAt.toLocaleString() }}</div>
+ <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
</div>
</div>
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 6634d9cba9..cdb6e90505 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -51,6 +51,29 @@
</MkSwitch>
</MkFolder>
+ <MkFolder v-if="matchQuery([i18n.ts._role._options.inviteLimit, 'inviteLimit'])">
+ <template #label>{{ i18n.ts._role._options.inviteLimit }}</template>
+ <template #suffix>{{ policies.inviteLimit }}</template>
+ <MkInput v-model="policies.inviteLimit" type="number">
+ </MkInput>
+ </MkFolder>
+
+ <MkFolder v-if="matchQuery([i18n.ts._role._options.inviteLimitCycle, 'inviteLimitCycle'])">
+ <template #label>{{ i18n.ts._role._options.inviteLimitCycle }}</template>
+ <template #suffix>{{ policies.inviteLimitCycle + i18n.ts._time.minute }}</template>
+ <MkInput v-model="policies.inviteLimitCycle" type="number">
+ <template #suffix>{{ i18n.ts._time.minute }}</template>
+ </MkInput>
+ </MkFolder>
+
+ <MkFolder v-if="matchQuery([i18n.ts._role._options.inviteExpirationTime, 'inviteExpirationTime'])">
+ <template #label>{{ i18n.ts._role._options.inviteExpirationTime }}</template>
+ <template #suffix>{{ policies.inviteExpirationTime + i18n.ts._time.minute }}</template>
+ <MkInput v-model="policies.inviteExpirationTime" type="number">
+ <template #suffix>{{ i18n.ts._time.minute }}</template>
+ </MkInput>
+ </MkFolder>
+
<MkFolder v-if="matchQuery([i18n.ts._role._options.canManageCustomEmojis, 'canManageCustomEmojis'])">
<template #label>{{ i18n.ts._role._options.canManageCustomEmojis }}</template>
<template #suffix>{{ policies.canManageCustomEmojis ? i18n.ts.yes : i18n.ts.no }}</template>
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 4c2fe46f28..bd57c06181 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -37,6 +37,13 @@
<template #label>{{ i18n.ts.cacheRemoteFiles }}</template>
<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}</template>
</MkSwitch>
+
+ <template v-if="cacheRemoteFiles">
+ <MkSwitch v-model="cacheRemoteSensitiveFiles">
+ <template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}</template>
+ <template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template>
+ </MkSwitch>
+ </template>
</div>
</FormSection>
@@ -104,7 +111,6 @@ import { fetchInstance } from '@/instance';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import MkButton from '@/components/MkButton.vue';
-import MkColorInput from '@/components/MkColorInput.vue';
let name: string | null = $ref(null);
let description: string | null = $ref(null);
@@ -112,13 +118,14 @@ let maintainerName: string | null = $ref(null);
let maintainerEmail: string | null = $ref(null);
let pinnedUsers: string = $ref('');
let cacheRemoteFiles: boolean = $ref(false);
+let cacheRemoteSensitiveFiles: boolean = $ref(false);
let enableServiceWorker: boolean = $ref(false);
let swPublicKey: any = $ref(null);
let swPrivateKey: any = $ref(null);
let deeplAuthKey: string = $ref('');
let deeplIsPro: boolean = $ref(false);
-async function init() {
+async function init(): Promise<void> {
const meta = await os.api('admin/meta');
name = meta.name;
description = meta.description;
@@ -126,6 +133,7 @@ async function init() {
maintainerEmail = meta.maintainerEmail;
pinnedUsers = meta.pinnedUsers.join('\n');
cacheRemoteFiles = meta.cacheRemoteFiles;
+ cacheRemoteSensitiveFiles = meta.cacheRemoteSensitiveFiles;
enableServiceWorker = meta.enableServiceWorker;
swPublicKey = meta.swPublickey;
swPrivateKey = meta.swPrivateKey;
@@ -133,7 +141,7 @@ async function init() {
deeplIsPro = meta.deeplIsPro;
}
-function save() {
+function save(): void {
os.apiWithDialog('admin/update-meta', {
name,
description,
@@ -141,6 +149,7 @@ function save() {
maintainerEmail,
pinnedUsers: pinnedUsers.split('\n'),
cacheRemoteFiles,
+ cacheRemoteSensitiveFiles,
enableServiceWorker,
swPublicKey,
swPrivateKey,