summaryrefslogtreecommitdiff
path: root/src/client/pages/instance
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-10-22 17:37:51 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2021-10-22 17:37:51 +0900
commit81a0ee4b2d089b9266d79595707a28f655f5cb27 (patch)
tree769ee1d2fe4ab72abe74008eafe49297ee152a5b /src/client/pages/instance
parenttweak ui (diff)
downloadsharkey-81a0ee4b2d089b9266d79595707a28f655f5cb27.tar.gz
sharkey-81a0ee4b2d089b9266d79595707a28f655f5cb27.tar.bz2
sharkey-81a0ee4b2d089b9266d79595707a28f655f5cb27.zip
client: change url /instance -> /admin
Diffstat (limited to 'src/client/pages/instance')
-rw-r--r--src/client/pages/instance/abuses.vue170
-rw-r--r--src/client/pages/instance/ads.vue146
-rw-r--r--src/client/pages/instance/announcements.vue134
-rw-r--r--src/client/pages/instance/bot-protection.vue138
-rw-r--r--src/client/pages/instance/database.vue61
-rw-r--r--src/client/pages/instance/email-settings.vue128
-rw-r--r--src/client/pages/instance/emoji-edit-dialog.vue120
-rw-r--r--src/client/pages/instance/emojis.vue262
-rw-r--r--src/client/pages/instance/file-dialog.vue129
-rw-r--r--src/client/pages/instance/files-settings.vue93
-rw-r--r--src/client/pages/instance/files.vue209
-rw-r--r--src/client/pages/instance/index.vue373
-rw-r--r--src/client/pages/instance/instance-block.vue72
-rw-r--r--src/client/pages/instance/instance.vue321
-rw-r--r--src/client/pages/instance/integrations-discord.vue85
-rw-r--r--src/client/pages/instance/integrations-github.vue85
-rw-r--r--src/client/pages/instance/integrations-twitter.vue85
-rw-r--r--src/client/pages/instance/integrations.vue74
-rw-r--r--src/client/pages/instance/logs.vue97
-rw-r--r--src/client/pages/instance/metrics.vue472
-rw-r--r--src/client/pages/instance/object-storage.vue155
-rw-r--r--src/client/pages/instance/other-settings.vue83
-rw-r--r--src/client/pages/instance/overview.vue188
-rw-r--r--src/client/pages/instance/proxy-account.vue87
-rw-r--r--src/client/pages/instance/queue.chart.vue218
-rw-r--r--src/client/pages/instance/queue.vue73
-rw-r--r--src/client/pages/instance/relays.vue99
-rw-r--r--src/client/pages/instance/security.vue83
-rw-r--r--src/client/pages/instance/service-worker.vue85
-rw-r--r--src/client/pages/instance/settings.vue151
-rw-r--r--src/client/pages/instance/users.vue261
31 files changed, 0 insertions, 4737 deletions
diff --git a/src/client/pages/instance/abuses.vue b/src/client/pages/instance/abuses.vue
deleted file mode 100644
index 29da8cc2c5..0000000000
--- a/src/client/pages/instance/abuses.vue
+++ /dev/null
@@ -1,170 +0,0 @@
-<template>
-<div class="lcixvhis">
- <div class="_section reports">
- <div class="_content">
- <div class="inputs" style="display: flex;">
- <MkSelect v-model="state" style="margin: 0; flex: 1;">
- <template #label>{{ $ts.state }}</template>
- <option value="all">{{ $ts.all }}</option>
- <option value="unresolved">{{ $ts.unresolved }}</option>
- <option value="resolved">{{ $ts.resolved }}</option>
- </MkSelect>
- <MkSelect v-model="targetUserOrigin" style="margin: 0; flex: 1;">
- <template #label>{{ $ts.targetUserOrigin }}</template>
- <option value="combined">{{ $ts.all }}</option>
- <option value="local">{{ $ts.local }}</option>
- <option value="remote">{{ $ts.remote }}</option>
- </MkSelect>
- <MkSelect v-model="reporterOrigin" style="margin: 0; flex: 1;">
- <template #label>{{ $ts.reporterOrigin }}</template>
- <option value="combined">{{ $ts.all }}</option>
- <option value="local">{{ $ts.local }}</option>
- <option value="remote">{{ $ts.remote }}</option>
- </MkSelect>
- </div>
- <!-- TODO
- <div class="inputs" style="display: flex; padding-top: 1.2em;">
- <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()">
- <span>{{ $ts.username }}</span>
- </MkInput>
- <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'">
- <span>{{ $ts.host }}</span>
- </MkInput>
- </div>
- -->
-
- <MkPagination :pagination="pagination" #default="{items}" ref="reports" style="margin-top: var(--margin);">
- <div class="bcekxzvu _card _gap" v-for="report in items" :key="report.id">
- <div class="_content target">
- <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/>
- <div class="info">
- <MkUserName class="name" :user="report.targetUser"/>
- <div class="acct">@{{ acct(report.targetUser) }}</div>
- </div>
- </div>
- <div class="_content">
- <div>
- <Mfm :text="report.comment"/>
- </div>
- <hr>
- <div>Reporter: <MkAcct :user="report.reporter"/></div>
- <div><MkTime :time="report.createdAt"/></div>
- </div>
- <div class="_footer">
- <div v-if="report.assignee">Assignee: <MkAcct :user="report.assignee"/></div>
- <MkButton @click="resolve(report)" primary v-if="!report.resolved">{{ $ts.abuseMarkAsResolved }}</MkButton>
- </div>
- </div>
- </MkPagination>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { parseAcct } from '@/misc/acct';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import { acct } from '@client/filters/user';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSelect,
- MkPagination,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.abuseReports,
- icon: 'fas fa-exclamation-circle',
- bg: 'var(--bg)',
- },
- searchUsername: '',
- searchHost: '',
- state: 'unresolved',
- reporterOrigin: 'combined',
- targetUserOrigin: 'combined',
- pagination: {
- endpoint: 'admin/abuse-user-reports',
- limit: 10,
- params: () => ({
- state: this.state,
- reporterOrigin: this.reporterOrigin,
- targetUserOrigin: this.targetUserOrigin,
- }),
- },
- }
- },
-
- watch: {
- state() {
- this.$refs.reports.reload();
- },
-
- reporterOrigin() {
- this.$refs.reports.reload();
- },
-
- targetUserOrigin() {
- this.$refs.reports.reload();
- },
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- acct,
-
- resolve(report) {
- os.apiWithDialog('admin/resolve-abuse-user-report', {
- reportId: report.id,
- }).then(() => {
- this.$refs.reports.removeItem(item => item.id === report.id);
- });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.lcixvhis {
- margin: var(--margin);
-}
-
-.bcekxzvu {
- > .target {
- display: flex;
- width: 100%;
- box-sizing: border-box;
- text-align: left;
- align-items: center;
-
- > .avatar {
- width: 42px;
- height: 42px;
- }
-
- > .info {
- margin-left: 0.3em;
- padding: 0 8px;
- flex: 1;
-
- > .name {
- font-weight: bold;
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/instance/ads.vue b/src/client/pages/instance/ads.vue
deleted file mode 100644
index e776f99a4c..0000000000
--- a/src/client/pages/instance/ads.vue
+++ /dev/null
@@ -1,146 +0,0 @@
-<template>
-<div>
- <MkHeader :info="header"/>
- <div class="uqshojas">
- <section class="_card _gap ads" v-for="ad in ads">
- <div class="_content ad">
- <MkAd v-if="ad.url" :specify="ad"/>
- <MkInput v-model="ad.url" type="url">
- <template #label>URL</template>
- </MkInput>
- <MkInput v-model="ad.imageUrl">
- <template #label>{{ $ts.imageUrl }}</template>
- </MkInput>
- <div style="margin: 32px 0;">
- <MkRadio v-model="ad.place" value="square">square</MkRadio>
- <MkRadio v-model="ad.place" value="horizontal">horizontal</MkRadio>
- <MkRadio v-model="ad.place" value="horizontal-big">horizontal-big</MkRadio>
- </div>
- <!--
- <div style="margin: 32px 0;">
- {{ $ts.priority }}
- <MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio>
- <MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio>
- <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio>
- </div>
- -->
- <MkInput v-model="ad.ratio" type="number">
- <template #label>{{ $ts.ratio }}</template>
- </MkInput>
- <MkInput v-model="ad.expiresAt" type="date">
- <template #label>{{ $ts.expiration }}</template>
- </MkInput>
- <MkTextarea v-model="ad.memo">
- <template #label>{{ $ts.memo }}</template>
- </MkTextarea>
- <div class="buttons">
- <MkButton class="button" inline @click="save(ad)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
- <MkButton class="button" inline @click="remove(ad)" danger><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
- </div>
- </div>
- </section>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import MkRadio from '@client/components/form/radio.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkTextarea,
- MkRadio,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.ads,
- icon: 'fas fa-audio-description',
- bg: 'var(--bg)',
- },
- header: {
- title: this.$ts.ads,
- icon: 'fas fa-audio-description',
- bg: 'var(--bg)',
- actions: [{
- asFullButton: true,
- icon: 'fas fa-plus',
- text: this.$ts.add,
- handler: this.add,
- }],
- },
- ads: [],
- }
- },
-
- created() {
- os.api('admin/ad/list').then(ads => {
- this.ads = ads;
- });
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- add() {
- this.ads.unshift({
- id: null,
- memo: '',
- place: 'square',
- priority: 'middle',
- ratio: 1,
- url: '',
- imageUrl: null,
- expiresAt: null,
- });
- },
-
- remove(ad) {
- os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: ad.url }),
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
- this.ads = this.ads.filter(x => x != ad);
- os.apiWithDialog('admin/ad/delete', {
- id: ad.id
- });
- });
- },
-
- save(ad) {
- if (ad.id == null) {
- os.apiWithDialog('admin/ad/create', {
- ...ad,
- expiresAt: new Date(ad.expiresAt).getTime()
- });
- } else {
- os.apiWithDialog('admin/ad/update', {
- ...ad,
- expiresAt: new Date(ad.expiresAt).getTime()
- });
- }
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.uqshojas {
- margin: var(--margin);
-}
-</style>
diff --git a/src/client/pages/instance/announcements.vue b/src/client/pages/instance/announcements.vue
deleted file mode 100644
index 78637c095a..0000000000
--- a/src/client/pages/instance/announcements.vue
+++ /dev/null
@@ -1,134 +0,0 @@
-<template>
-<div>
- <MkHeader :info="header"/>
-
- <div class="ztgjmzrw">
- <section class="_card _gap announcements" v-for="announcement in announcements">
- <div class="_content announcement">
- <MkInput v-model="announcement.title">
- <template #label>{{ $ts.title }}</template>
- </MkInput>
- <MkTextarea v-model="announcement.text">
- <template #label>{{ $ts.text }}</template>
- </MkTextarea>
- <MkInput v-model="announcement.imageUrl">
- <template #label>{{ $ts.imageUrl }}</template>
- </MkInput>
- <p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p>
- <div class="buttons">
- <MkButton class="button" inline @click="save(announcement)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
- <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
- </div>
- </div>
- </section>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkTextarea,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.announcements,
- icon: 'fas fa-broadcast-tower',
- bg: 'var(--bg)',
- },
- header: {
- title: this.$ts.announcements,
- icon: 'fas fa-broadcast-tower',
- bg: 'var(--bg)',
- actions: [{
- asFullButton: true,
- icon: 'fas fa-plus',
- text: this.$ts.add,
- handler: this.add,
- }],
- },
- announcements: [],
- }
- },
-
- created() {
- os.api('admin/announcements/list').then(announcements => {
- this.announcements = announcements;
- });
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- add() {
- this.announcements.unshift({
- id: null,
- title: '',
- text: '',
- imageUrl: null
- });
- },
-
- remove(announcement) {
- os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: announcement.title }),
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
- this.announcements = this.announcements.filter(x => x != announcement);
- os.api('admin/announcements/delete', announcement);
- });
- },
-
- save(announcement) {
- if (announcement.id == null) {
- os.api('admin/announcements/create', announcement).then(() => {
- os.dialog({
- type: 'success',
- text: this.$ts.saved
- });
- }).catch(e => {
- os.dialog({
- type: 'error',
- text: e
- });
- });
- } else {
- os.api('admin/announcements/update', announcement).then(() => {
- os.dialog({
- type: 'success',
- text: this.$ts.saved
- });
- }).catch(e => {
- os.dialog({
- type: 'error',
- text: e
- });
- });
- }
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.ztgjmzrw {
- margin: var(--margin);
-}
-</style>
diff --git a/src/client/pages/instance/bot-protection.vue b/src/client/pages/instance/bot-protection.vue
deleted file mode 100644
index 731f114cc2..0000000000
--- a/src/client/pages/instance/bot-protection.vue
+++ /dev/null
@@ -1,138 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormRadios v-model="provider">
- <template #desc><i class="fas fa-shield-alt"></i> {{ $ts.botProtection }}</template>
- <option :value="null">{{ $ts.none }} ({{ $ts.notRecommended }})</option>
- <option value="hcaptcha">hCaptcha</option>
- <option value="recaptcha">reCAPTCHA</option>
- </FormRadios>
-
- <template v-if="provider === 'hcaptcha'">
- <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <div class="_debobigegoLabel">hCaptcha</div>
- <div class="main">
- <FormInput v-model="hcaptchaSiteKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>{{ $ts.hcaptchaSiteKey }}</span>
- </FormInput>
- <FormInput v-model="hcaptchaSecretKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>{{ $ts.hcaptchaSecretKey }}</span>
- </FormInput>
- </div>
- </div>
- <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <div class="_debobigegoLabel">{{ $ts.preview }}</div>
- <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);">
- <MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
- </div>
- </div>
- </template>
- <template v-else-if="provider === 'recaptcha'">
- <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <div class="_debobigegoLabel">reCAPTCHA</div>
- <div class="main">
- <FormInput v-model="recaptchaSiteKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>{{ $ts.recaptchaSiteKey }}</span>
- </FormInput>
- <FormInput v-model="recaptchaSecretKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>{{ $ts.recaptchaSecretKey }}</span>
- </FormInput>
- </div>
- </div>
- <div v-if="recaptchaSiteKey" class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <div class="_debobigegoLabel">{{ $ts.preview }}</div>
- <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);">
- <MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/>
- </div>
- </div>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormRadios from '@client/components/debobigego/radios.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormRadios,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormInfo,
- FormSuspense,
- MkCaptcha: defineAsyncComponent(() => import('@client/components/captcha.vue')),
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.botProtection,
- icon: 'fas fa-shield-alt'
- },
- provider: null,
- enableHcaptcha: false,
- hcaptchaSiteKey: null,
- hcaptchaSecretKey: null,
- enableRecaptcha: false,
- recaptchaSiteKey: null,
- recaptchaSecretKey: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableHcaptcha = meta.enableHcaptcha;
- this.hcaptchaSiteKey = meta.hcaptchaSiteKey;
- this.hcaptchaSecretKey = meta.hcaptchaSecretKey;
- this.enableRecaptcha = meta.enableRecaptcha;
- this.recaptchaSiteKey = meta.recaptchaSiteKey;
- this.recaptchaSecretKey = meta.recaptchaSecretKey;
-
- this.provider = this.enableHcaptcha ? 'hcaptcha' : this.enableRecaptcha ? 'recaptcha' : null;
-
- this.$watch(() => this.provider, () => {
- this.enableHcaptcha = this.provider === 'hcaptcha';
- this.enableRecaptcha = this.provider === 'recaptcha';
- });
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableHcaptcha: this.enableHcaptcha,
- hcaptchaSiteKey: this.hcaptchaSiteKey,
- hcaptchaSecretKey: this.hcaptchaSecretKey,
- enableRecaptcha: this.enableRecaptcha,
- recaptchaSiteKey: this.recaptchaSiteKey,
- recaptchaSecretKey: this.recaptchaSecretKey,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/database.vue b/src/client/pages/instance/database.vue
deleted file mode 100644
index ffbeed8b30..0000000000
--- a/src/client/pages/instance/database.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="databasePromiseFactory" v-slot="{ result: database }">
- <FormGroup v-for="table in database" :key="table[0]">
- <template #label>{{ table[0] }}</template>
- <FormKeyValueView>
- <template #key>Size</template>
- <template #value>{{ bytes(table[1].size) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>Records</template>
- <template #value>{{ number(table[1].count) }}</template>
- </FormKeyValueView>
- </FormGroup>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import bytes from '@client/filters/bytes';
-import number from '@client/filters/number';
-
-export default defineComponent({
- components: {
- FormSuspense,
- FormKeyValueView,
- FormBase,
- FormGroup,
- FormLink,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.database,
- icon: 'fas fa-database',
- bg: 'var(--bg)',
- },
- databasePromiseFactory: () => os.api('admin/get-table-stats', {}).then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)),
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- bytes, number,
- }
-});
-</script>
diff --git a/src/client/pages/instance/email-settings.vue b/src/client/pages/instance/email-settings.vue
deleted file mode 100644
index ebf724fcdd..0000000000
--- a/src/client/pages/instance/email-settings.vue
+++ /dev/null
@@ -1,128 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableEmail">{{ $ts.enableEmail }}<template #desc>{{ $ts.emailConfigInfo }}</template></FormSwitch>
-
- <template v-if="enableEmail">
- <FormInput v-model="email" type="email">
- <span>{{ $ts.emailAddress }}</span>
- </FormInput>
-
- <div class="_debobigegoItem _debobigegoNoConcat" v-sticky-container>
- <div class="_debobigegoLabel">{{ $ts.smtpConfig }}</div>
- <div class="main">
- <FormInput v-model="smtpHost">
- <span>{{ $ts.smtpHost }}</span>
- </FormInput>
- <FormInput v-model="smtpPort" type="number">
- <span>{{ $ts.smtpPort }}</span>
- </FormInput>
- <FormInput v-model="smtpUser">
- <span>{{ $ts.smtpUser }}</span>
- </FormInput>
- <FormInput v-model="smtpPass" type="password">
- <span>{{ $ts.smtpPass }}</span>
- </FormInput>
- <FormInfo>{{ $ts.emptyToDisableSmtpAuth }}</FormInfo>
- <FormSwitch v-model="smtpSecure">{{ $ts.smtpSecure }}<template #desc>{{ $ts.smtpSecureInfo }}</template></FormSwitch>
- </div>
- </div>
-
- <FormButton @click="testEmail">{{ $ts.testEmail }}</FormButton>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.emailServer,
- icon: 'fas fa-envelope',
- bg: 'var(--bg)',
- },
- enableEmail: false,
- email: null,
- smtpSecure: false,
- smtpHost: '',
- smtpPort: 0,
- smtpUser: '',
- smtpPass: '',
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableEmail = meta.enableEmail;
- this.email = meta.email;
- this.smtpSecure = meta.smtpSecure;
- this.smtpHost = meta.smtpHost;
- this.smtpPort = meta.smtpPort;
- this.smtpUser = meta.smtpUser;
- this.smtpPass = meta.smtpPass;
- },
-
- async testEmail() {
- const { canceled, result: destination } = await os.dialog({
- title: this.$ts.destination,
- input: {
- placeholder: this.$instance.maintainerEmail
- }
- });
- if (canceled) return;
- os.apiWithDialog('admin/send-email', {
- to: destination,
- subject: 'Test email',
- text: 'Yo'
- });
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableEmail: this.enableEmail,
- email: this.email,
- smtpSecure: this.smtpSecure,
- smtpHost: this.smtpHost,
- smtpPort: this.smtpPort,
- smtpUser: this.smtpUser,
- smtpPass: this.smtpPass,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/emoji-edit-dialog.vue b/src/client/pages/instance/emoji-edit-dialog.vue
deleted file mode 100644
index 4854c69884..0000000000
--- a/src/client/pages/instance/emoji-edit-dialog.vue
+++ /dev/null
@@ -1,120 +0,0 @@
-<template>
-<XModalWindow ref="dialog"
- :width="370"
- :with-ok-button="true"
- @close="$refs.dialog.close()"
- @closed="$emit('closed')"
- @ok="ok()"
->
- <template #header>:{{ emoji.name }}:</template>
-
- <div class="_monolithic_">
- <div class="yigymqpb _section">
- <img :src="emoji.url" class="img"/>
- <MkInput class="_formBlock" v-model="name">
- <template #label>{{ $ts.name }}</template>
- </MkInput>
- <MkInput class="_formBlock" v-model="category" :datalist="categories">
- <template #label>{{ $ts.category }}</template>
- </MkInput>
- <MkInput class="_formBlock" v-model="aliases">
- <template #label>{{ $ts.tags }}</template>
- <template #caption>{{ $ts.setMultipleBySeparatingWithSpace }}</template>
- </MkInput>
- <MkButton danger @click="del()"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton>
- </div>
- </div>
-</XModalWindow>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XModalWindow from '@client/components/ui/modal-window.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import * as os from '@client/os';
-import { unique } from '../../../prelude/array';
-
-export default defineComponent({
- components: {
- XModalWindow,
- MkButton,
- MkInput,
- },
-
- props: {
- emoji: {
- required: true,
- }
- },
-
- emits: ['done', 'closed'],
-
- data() {
- return {
- name: this.emoji.name,
- category: this.emoji.category,
- aliases: this.emoji.aliases?.join(' '),
- categories: [],
- }
- },
-
- created() {
- os.api('meta', { detail: false }).then(({ emojis }) => {
- this.categories = unique(emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
- });
- },
-
- methods: {
- ok() {
- this.update();
- },
-
- async update() {
- await os.apiWithDialog('admin/emoji/update', {
- id: this.emoji.id,
- name: this.name,
- category: this.category,
- aliases: this.aliases.split(' '),
- });
-
- this.$emit('done', {
- updated: {
- name: this.name,
- category: this.category,
- aliases: this.aliases.split(' '),
- }
- });
- this.$refs.dialog.close();
- },
-
- async del() {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: this.emoji.name }),
- showCancelButton: true
- });
- if (canceled) return;
-
- os.api('admin/emoji/remove', {
- id: this.emoji.id
- }).then(() => {
- this.$emit('done', {
- deleted: true
- });
- this.$refs.dialog.close();
- });
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.yigymqpb {
- > .img {
- display: block;
- height: 64px;
- margin: 0 auto;
- }
-}
-</style>
diff --git a/src/client/pages/instance/emojis.vue b/src/client/pages/instance/emojis.vue
deleted file mode 100644
index 4cd34b046d..0000000000
--- a/src/client/pages/instance/emojis.vue
+++ /dev/null
@@ -1,262 +0,0 @@
-<template>
-<div class="ogwlenmc">
- <MkHeader :info="header"/>
-
- <div class="local" v-if="tab === 'local'">
- <MkInput v-model="query" :debounce="true" type="search" style="margin: var(--margin);">
- <template #prefix><i class="fas fa-search"></i></template>
- <template #label>{{ $ts.search }}</template>
- </MkInput>
- <MkPagination :pagination="pagination" ref="emojis">
- <template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
- <template #default="{items}">
- <div class="ldhfsamy">
- <button class="emoji _panel _button" v-for="emoji in items" :key="emoji.id" @click="edit(emoji)">
- <img :src="emoji.url" class="img" :alt="emoji.name"/>
- <div class="body">
- <div class="name _monospace">{{ emoji.name }}</div>
- <div class="info">{{ emoji.category }}</div>
- </div>
- </button>
- </div>
- </template>
- </MkPagination>
- </div>
-
- <div class="remote" v-else-if="tab === 'remote'">
- <MkInput v-model="queryRemote" :debounce="true" type="search" style="margin: var(--margin);">
- <template #prefix><i class="fas fa-search"></i></template>
- <template #label>{{ $ts.search }}</template>
- </MkInput>
- <MkInput v-model="host" :debounce="true" style="margin: var(--margin);">
- <template #label>{{ $ts.host }}</template>
- </MkInput>
- <MkPagination :pagination="remotePagination" ref="remoteEmojis">
- <template #empty><span>{{ $ts.noCustomEmojis }}</span></template>
- <template #default="{items}">
- <div class="ldhfsamy">
- <div class="emoji _panel _button" v-for="emoji in items" :key="emoji.id" @click="remoteMenu(emoji, $event)">
- <img :src="emoji.url" class="img" :alt="emoji.name"/>
- <div class="body">
- <div class="name _monospace">{{ emoji.name }}</div>
- <div class="info">{{ emoji.host }}</div>
- </div>
- </div>
- </div>
- </template>
- </MkPagination>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkTab from '@client/components/tab.vue';
-import { selectFile } from '@client/scripts/select-file';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkTab,
- MkButton,
- MkInput,
- MkPagination,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.customEmojis,
- icon: 'fas fa-laugh',
- bg: 'var(--bg)',
- },
- header: computed(() => ({
- title: this.$ts.customEmojis,
- icon: 'fas fa-laugh',
- bg: 'var(--bg)',
- actions: [{
- asFullButton: true,
- icon: 'fas fa-plus',
- text: this.$ts.addEmoji,
- handler: this.add,
- }],
- tabs: [{
- active: this.tab === 'local',
- title: this.$ts.local,
- onClick: () => { this.tab = 'local'; },
- }, {
- active: this.tab === 'remote',
- title: this.$ts.remote,
- onClick: () => { this.tab = 'remote'; },
- },]
- })),
- tab: 'local',
- query: null,
- queryRemote: null,
- host: '',
- pagination: {
- endpoint: 'admin/emoji/list',
- limit: 30,
- params: computed(() => ({
- query: (this.query && this.query !== '') ? this.query : null
- }))
- },
- remotePagination: {
- endpoint: 'admin/emoji/list-remote',
- limit: 30,
- params: computed(() => ({
- query: (this.queryRemote && this.queryRemote !== '') ? this.queryRemote : null,
- host: (this.host && this.host !== '') ? this.host : null
- }))
- },
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async add(e) {
- const files = await selectFile(e.currentTarget || e.target, null, true);
-
- const promise = Promise.all(files.map(file => os.api('admin/emoji/add', {
- fileId: file.id,
- })));
- promise.then(() => {
- this.$refs.emojis.reload();
- });
- os.promiseDialog(promise);
- },
-
- edit(emoji) {
- os.popup(import('./emoji-edit-dialog.vue'), {
- emoji: emoji
- }, {
- done: result => {
- if (result.updated) {
- this.$refs.emojis.replaceItem(item => item.id === emoji.id, {
- ...emoji,
- ...result.updated
- });
- } else if (result.deleted) {
- this.$refs.emojis.removeItem(item => item.id === emoji.id);
- }
- },
- }, 'closed');
- },
-
- im(emoji) {
- os.apiWithDialog('admin/emoji/copy', {
- emojiId: emoji.id,
- });
- },
-
- remoteMenu(emoji, ev) {
- os.popupMenu([{
- type: 'label',
- text: ':' + emoji.name + ':',
- }, {
- text: this.$ts.import,
- icon: 'fas fa-plus',
- action: () => { this.im(emoji) }
- }], ev.currentTarget || ev.target);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.ogwlenmc {
- > .local {
- .ldhfsamy {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
- grid-gap: 12px;
- margin: var(--margin);
-
- > .emoji {
- display: flex;
- align-items: center;
- padding: 12px;
- text-align: left;
-
- &:hover {
- color: var(--accent);
- }
-
- > .img {
- width: 42px;
- height: 42px;
- }
-
- > .body {
- padding: 0 0 0 8px;
- white-space: nowrap;
- overflow: hidden;
-
- > .name {
- text-overflow: ellipsis;
- overflow: hidden;
- }
-
- > .info {
- opacity: 0.5;
- text-overflow: ellipsis;
- overflow: hidden;
- }
- }
- }
- }
- }
-
- > .remote {
- .ldhfsamy {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
- grid-gap: 12px;
- margin: var(--margin);
-
- > .emoji {
- display: flex;
- align-items: center;
- padding: 12px;
- text-align: left;
-
- &:hover {
- color: var(--accent);
- }
-
- > .img {
- width: 32px;
- height: 32px;
- }
-
- > .body {
- padding: 0 0 0 8px;
- white-space: nowrap;
- overflow: hidden;
-
- > .name {
- text-overflow: ellipsis;
- overflow: hidden;
- }
-
- > .info {
- opacity: 0.5;
- font-size: 90%;
- text-overflow: ellipsis;
- overflow: hidden;
- }
- }
- }
- }
- }
-}
-</style>
diff --git a/src/client/pages/instance/file-dialog.vue b/src/client/pages/instance/file-dialog.vue
deleted file mode 100644
index 02d83e5022..0000000000
--- a/src/client/pages/instance/file-dialog.vue
+++ /dev/null
@@ -1,129 +0,0 @@
-<template>
-<XModalWindow ref="dialog"
- :width="370"
- @close="$refs.dialog.close()"
- @closed="$emit('closed')"
->
- <template #header v-if="file">{{ file.name }}</template>
- <div class="cxqhhsmd" v-if="file">
- <div class="_section">
- <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
- <div class="info">
- <span style="margin-right: 1em;">{{ file.type }}</span>
- <span>{{ bytes(file.size) }}</span>
- <MkTime :time="file.createdAt" mode="detail" style="display: block;"/>
- </div>
- </div>
- <div class="_section">
- <div class="_content">
- <MkSwitch @update:modelValue="toggleIsSensitive" v-model="isSensitive">NSFW</MkSwitch>
- </div>
- </div>
- <div class="_section">
- <div class="_content">
- <MkButton full @click="showUser"><i class="fas fa-external-link-square-alt"></i> {{ $ts.user }}</MkButton>
- <MkButton full danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton>
- </div>
- </div>
- <div class="_section" v-if="info">
- <details class="_content rawdata">
- <pre><code>{{ JSON.stringify(info, null, 2) }}</code></pre>
- </details>
- </div>
- </div>
-</XModalWindow>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import XModalWindow from '@client/components/ui/modal-window.vue';
-import MkDriveFileThumbnail from '@client/components/drive-file-thumbnail.vue';
-import Progress from '@client/scripts/loading';
-import bytes from '@client/filters/bytes';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- MkButton,
- MkSwitch,
- XModalWindow,
- MkDriveFileThumbnail,
- },
-
- props: {
- fileId: {
- required: true,
- }
- },
-
- emits: ['closed'],
-
- data() {
- return {
- file: null,
- info: null,
- isSensitive: false,
- };
- },
-
- created() {
- this.fetch();
- },
-
- methods: {
- async fetch() {
- Progress.start();
- this.file = await os.api('drive/files/show', { fileId: this.fileId });
- this.info = await os.api('admin/drive/show-file', { fileId: this.fileId });
- this.isSensitive = this.file.isSensitive;
- Progress.done();
- },
-
- showUser() {
- os.pageWindow(`/user-info/${this.file.userId}`);
- },
-
- async del() {
- const { canceled } = await os.dialog({
- type: 'warning',
- text: this.$t('removeAreYouSure', { x: this.file.name }),
- showCancelButton: true
- });
- if (canceled) return;
-
- os.apiWithDialog('drive/files/delete', {
- fileId: this.file.id
- });
- },
-
- async toggleIsSensitive(v) {
- await os.api('drive/files/update', { fileId: this.fileId, isSensitive: v });
- this.isSensitive = v;
- },
-
- bytes
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.cxqhhsmd {
- > ._section {
- > .thumbnail {
- height: 150px;
- max-width: 100%;
- }
-
- > .info {
- text-align: center;
- margin-top: 8px;
- }
-
- > .rawdata {
- overflow: auto;
- }
- }
-}
-</style>
diff --git a/src/client/pages/instance/files-settings.vue b/src/client/pages/instance/files-settings.vue
deleted file mode 100644
index 8aefa9e90d..0000000000
--- a/src/client/pages/instance/files-settings.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="cacheRemoteFiles">
- {{ $ts.cacheRemoteFiles }}
- <template #desc>{{ $ts.cacheRemoteFilesDescription }}</template>
- </FormSwitch>
-
- <FormSwitch v-model="proxyRemoteFiles">
- {{ $ts.proxyRemoteFiles }}
- <template #desc>{{ $ts.proxyRemoteFilesDescription }}</template>
- </FormSwitch>
-
- <FormInput v-model="localDriveCapacityMb" type="number">
- <span>{{ $ts.driveCapacityPerLocalAccount }}</span>
- <template #suffix>MB</template>
- <template #desc>{{ $ts.inMb }}</template>
- </FormInput>
-
- <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles">
- <span>{{ $ts.driveCapacityPerRemoteAccount }}</span>
- <template #suffix>MB</template>
- <template #desc>{{ $ts.inMb }}</template>
- </FormInput>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.files,
- icon: 'fas fa-cloud',
- bg: 'var(--bg)',
- },
- cacheRemoteFiles: false,
- proxyRemoteFiles: false,
- localDriveCapacityMb: 0,
- remoteDriveCapacityMb: 0,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.cacheRemoteFiles = meta.cacheRemoteFiles;
- this.proxyRemoteFiles = meta.proxyRemoteFiles;
- this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
- this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- cacheRemoteFiles: this.cacheRemoteFiles,
- proxyRemoteFiles: this.proxyRemoteFiles,
- localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
- remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/files.vue b/src/client/pages/instance/files.vue
deleted file mode 100644
index 55189cfd84..0000000000
--- a/src/client/pages/instance/files.vue
+++ /dev/null
@@ -1,209 +0,0 @@
-<template>
-<div class="xrmjdkdw">
- <MkContainer :foldable="true" class="lookup">
- <template #header><i class="fas fa-search"></i> {{ $ts.lookup }}</template>
- <div class="xrmjdkdw-lookup">
- <MkInput class="item" v-model="q" type="text" @enter="find()">
- <template #label>{{ $ts.fileIdOrUrl }}</template>
- </MkInput>
- <MkButton @click="find()" primary><i class="fas fa-search"></i> {{ $ts.lookup }}</MkButton>
- </div>
- </MkContainer>
-
- <div class="_section">
- <div class="_content">
- <div class="inputs" style="display: flex;">
- <MkSelect v-model="origin" style="margin: 0; flex: 1;">
- <template #label>{{ $ts.instance }}</template>
- <option value="combined">{{ $ts.all }}</option>
- <option value="local">{{ $ts.local }}</option>
- <option value="remote">{{ $ts.remote }}</option>
- </MkSelect>
- <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params().origin === 'local'">
- <template #label>{{ $ts.host }}</template>
- </MkInput>
- </div>
- <div class="inputs" style="display: flex; padding-top: 1.2em;">
- <MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;">
- <template #label>MIME type</template>
- </MkInput>
- </div>
- <MkPagination :pagination="pagination" #default="{items}" class="urempief" ref="files">
- <button class="file _panel _button _gap" v-for="file in items" :key="file.id" @click="show(file, $event)">
- <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
- <div class="body">
- <div>
- <small style="opacity: 0.7;">{{ file.name }}</small>
- </div>
- <div>
- <MkAcct v-if="file.user" :user="file.user"/>
- <div v-else>{{ $ts.system }}</div>
- </div>
- <div>
- <span style="margin-right: 1em;">{{ file.type }}</span>
- <span>{{ bytes(file.size) }}</span>
- </div>
- <div>
- <span>{{ $ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span>
- </div>
- </div>
- </button>
- </MkPagination>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkDriveFileThumbnail from '@client/components/drive-file-thumbnail.vue';
-import bytes from '@client/filters/bytes';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSelect,
- MkPagination,
- MkContainer,
- MkDriveFileThumbnail,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.files,
- icon: 'fas fa-cloud',
- bg: 'var(--bg)',
- actions: [{
- text: this.$ts.clearCachedFiles,
- icon: 'fas fa-trash-alt',
- handler: this.clear
- }]
- },
- q: null,
- origin: 'local',
- type: null,
- searchHost: '',
- pagination: {
- endpoint: 'admin/drive/files',
- limit: 10,
- params: () => ({
- type: (this.type && this.type !== '') ? this.type : null,
- origin: this.origin,
- hostname: (this.hostname && this.hostname !== '') ? this.hostname : null,
- }),
- },
- }
- },
-
- watch: {
- type() {
- this.$refs.files.reload();
- },
- origin() {
- this.$refs.files.reload();
- },
- searchHost() {
- this.$refs.files.reload();
- },
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- clear() {
- os.dialog({
- type: 'warning',
- text: this.$ts.clearCachedFilesConfirm,
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
-
- os.apiWithDialog('admin/drive/clean-remote-files', {});
- });
- },
-
- show(file, ev) {
- os.popup(import('./file-dialog.vue'), {
- fileId: file.id
- }, {}, 'closed');
- },
-
- find() {
- os.api('admin/drive/show-file', this.q.startsWith('http://') || this.q.startsWith('https://') ? { url: this.q.trim() } : { fileId: this.q.trim() }).then(file => {
- this.show(file);
- }).catch(e => {
- if (e.code === 'NO_SUCH_FILE') {
- os.dialog({
- type: 'error',
- text: this.$ts.notFound
- });
- }
- });
- },
-
- bytes
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.xrmjdkdw {
- margin: var(--margin);
-
- > .lookup {
- margin-bottom: 16px;
- }
-
- .urempief {
- margin-top: var(--margin);
-
- > .file {
- display: flex;
- width: 100%;
- box-sizing: border-box;
- text-align: left;
- align-items: center;
-
- &:hover {
- color: var(--accent);
- }
-
- > .thumbnail {
- width: 128px;
- height: 128px;
- }
-
- > .body {
- margin-left: 0.3em;
- padding: 8px;
- flex: 1;
-
- @media (max-width: 500px) {
- font-size: 14px;
- }
- }
- }
- }
-}
-
-.xrmjdkdw-lookup {
- padding: 16px;
-
- > .item {
- margin-bottom: 16px;
- }
-}
-</style>
diff --git a/src/client/pages/instance/index.vue b/src/client/pages/instance/index.vue
deleted file mode 100644
index 7b07bf2dde..0000000000
--- a/src/client/pages/instance/index.vue
+++ /dev/null
@@ -1,373 +0,0 @@
-<template>
-<div class="hiyeyicy" :class="{ wide: !narrow }" ref="el">
- <div class="nav" v-if="!narrow || page == null">
- <MkHeader :info="header"></MkHeader>
-
- <div class="lxpfedzu">
- <img :src="$instance.iconUrl || '/favicon.ico'" alt="" class="icon"/>
- </div>
-
- <MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/instance/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo>
- <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/instance/bot-protection" class="_link">{{ $ts.configure }}</MkA></MkInfo>
-
- <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu>
- </div>
- <div class="main">
- <component :is="component" :key="page" @info="onInfo" v-bind="pageProps"/>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineAsyncComponent, defineComponent, nextTick, onMounted, reactive, ref, watch } from 'vue';
-import { i18n } from '@client/i18n';
-import MkSuperMenu from '@client/components/ui/super-menu.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import MkInfo from '@client/components/ui/info.vue';
-import { scroll } from '@client/scripts/scroll';
-import { instance } from '@client/instance';
-import * as symbols from '@client/symbols';
-import * as os from '@client/os';
-import { lookupUser } from '@client/scripts/lookup-user';
-
-export default defineComponent({
- components: {
- FormBase,
- MkSuperMenu,
- FormGroup,
- FormButton,
- MkInfo,
- },
-
- props: {
- initialPage: {
- type: String,
- required: false
- }
- },
-
- setup(props, context) {
- const indexInfo = {
- title: i18n.locale.instance,
- icon: 'fas fa-cog',
- bg: 'var(--bg)',
- };
- const INFO = ref(indexInfo);
- const page = ref(props.initialPage);
- const narrow = ref(false);
- const view = ref(null);
- const el = ref(null);
- const onInfo = (viewInfo) => {
- INFO.value = viewInfo;
- };
- const pageProps = ref({});
-
- const isEmpty = (x: any) => x == null || x == '';
-
- const noMaintainerInformation = ref(false);
- const noBotProtection = ref(false);
-
- os.api('meta', { detail: true }).then(meta => {
- // TODO: 設定が完了しても残ったままになるので、ストリーミングでmeta更新イベントを受け取ってよしなに更新する
- noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail);
- noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha;
- });
-
- const menuDef = computed(() => [{
- title: i18n.locale.quickAction,
- items: [{
- type: 'button',
- icon: 'fas fa-search',
- text: i18n.locale.lookup,
- action: lookup,
- }, ...(instance.disableRegistration ? [{
- type: 'button',
- icon: 'fas fa-user',
- text: i18n.locale.invite,
- action: invite,
- }] : [])],
- }, {
- title: i18n.locale.administration,
- items: [{
- icon: 'fas fa-tachometer-alt',
- text: i18n.locale.dashboard,
- to: '/instance/overview',
- active: page.value === 'overview',
- }, {
- icon: 'fas fa-users',
- text: i18n.locale.users,
- to: '/instance/users',
- active: page.value === 'users',
- }, {
- icon: 'fas fa-laugh',
- text: i18n.locale.customEmojis,
- to: '/instance/emojis',
- active: page.value === 'emojis',
- }, {
- icon: 'fas fa-globe',
- text: i18n.locale.federation,
- to: '/instance/federation',
- active: page.value === 'federation',
- }, {
- icon: 'fas fa-clipboard-list',
- text: i18n.locale.jobQueue,
- to: '/instance/queue',
- active: page.value === 'queue',
- }, {
- icon: 'fas fa-cloud',
- text: i18n.locale.files,
- to: '/instance/files',
- active: page.value === 'files',
- }, {
- icon: 'fas fa-broadcast-tower',
- text: i18n.locale.announcements,
- to: '/instance/announcements',
- active: page.value === 'announcements',
- }, {
- icon: 'fas fa-audio-description',
- text: i18n.locale.ads,
- to: '/instance/ads',
- active: page.value === 'ads',
- }, {
- icon: 'fas fa-exclamation-circle',
- text: i18n.locale.abuseReports,
- to: '/instance/abuses',
- active: page.value === 'abuses',
- }],
- }, {
- title: i18n.locale.settings,
- items: [{
- icon: 'fas fa-cog',
- text: i18n.locale.general,
- to: '/instance/settings',
- active: page.value === 'settings',
- }, {
- icon: 'fas fa-cloud',
- text: i18n.locale.files,
- to: '/instance/files-settings',
- active: page.value === 'files-settings',
- }, {
- icon: 'fas fa-envelope',
- text: i18n.locale.emailServer,
- to: '/instance/email-settings',
- active: page.value === 'email-settings',
- }, {
- icon: 'fas fa-cloud',
- text: i18n.locale.objectStorage,
- to: '/instance/object-storage',
- active: page.value === 'object-storage',
- }, {
- icon: 'fas fa-lock',
- text: i18n.locale.security,
- to: '/instance/security',
- active: page.value === 'security',
- }, {
- icon: 'fas fa-bolt',
- text: 'ServiceWorker',
- to: '/instance/service-worker',
- active: page.value === 'service-worker',
- }, {
- icon: 'fas fa-globe',
- text: i18n.locale.relays,
- to: '/instance/relays',
- active: page.value === 'relays',
- }, {
- icon: 'fas fa-share-alt',
- text: i18n.locale.integration,
- to: '/instance/integrations',
- active: page.value === 'integrations',
- }, {
- icon: 'fas fa-ban',
- text: i18n.locale.instanceBlocking,
- to: '/instance/instance-block',
- active: page.value === 'instance-block',
- }, {
- icon: 'fas fa-ghost',
- text: i18n.locale.proxyAccount,
- to: '/instance/proxy-account',
- active: page.value === 'proxy-account',
- }, {
- icon: 'fas fa-cogs',
- text: i18n.locale.other,
- to: '/instance/other-settings',
- active: page.value === 'other-settings',
- }],
- }, {
- title: i18n.locale.info,
- items: [{
- icon: 'fas fa-database',
- text: i18n.locale.database,
- to: '/instance/database',
- active: page.value === 'database',
- }, {
- icon: 'fas fa-stream',
- text: i18n.locale.logs,
- to: '/instance/logs',
- active: page.value === 'logs',
- }],
- }]);
- const component = computed(() => {
- if (page.value == null) return null;
- switch (page.value) {
- case 'overview': return defineAsyncComponent(() => import('./overview.vue'));
- case 'users': return defineAsyncComponent(() => import('./users.vue'));
- case 'emojis': return defineAsyncComponent(() => import('./emojis.vue'));
- case 'federation': return defineAsyncComponent(() => import('../federation.vue'));
- case 'queue': return defineAsyncComponent(() => import('./queue.vue'));
- case 'files': return defineAsyncComponent(() => import('./files.vue'));
- case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
- case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
- case 'database': return defineAsyncComponent(() => import('./database.vue'));
- case 'logs': return defineAsyncComponent(() => import('./logs.vue'));
- case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
- case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
- case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue'));
- case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue'));
- case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue'));
- case 'security': return defineAsyncComponent(() => import('./security.vue'));
- case 'bot-protection': return defineAsyncComponent(() => import('./bot-protection.vue'));
- case 'service-worker': return defineAsyncComponent(() => import('./service-worker.vue'));
- case 'relays': return defineAsyncComponent(() => import('./relays.vue'));
- case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
- case 'integrations/twitter': return defineAsyncComponent(() => import('./integrations-twitter.vue'));
- case 'integrations/github': return defineAsyncComponent(() => import('./integrations-github.vue'));
- case 'integrations/discord': return defineAsyncComponent(() => import('./integrations-discord.vue'));
- case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
- case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
- case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue'));
- }
- });
-
- watch(component, () => {
- pageProps.value = {};
-
- nextTick(() => {
- scroll(el.value, { top: 0 });
- });
- }, { immediate: true });
-
- watch(() => props.initialPage, () => {
- if (props.initialPage == null && !narrow.value) {
- page.value = 'overview';
- } else {
- page.value = props.initialPage;
- if (props.initialPage == null) {
- INFO.value = indexInfo;
- }
- }
- });
-
- onMounted(() => {
- narrow.value = el.value.offsetWidth < 800;
- if (!narrow.value) {
- page.value = 'overview';
- }
- });
-
- const invite = () => {
- os.api('admin/invite').then(x => {
- os.dialog({
- type: 'info',
- text: x.code
- });
- }).catch(e => {
- os.dialog({
- type: 'error',
- text: e
- });
- });
- };
-
- const lookup = (ev) => {
- os.popupMenu([{
- text: i18n.locale.user,
- icon: 'fas fa-user',
- action: () => {
- lookupUser();
- }
- }, {
- text: i18n.locale.note,
- icon: 'fas fa-pencil-alt',
- action: () => {
- alert('TODO');
- }
- }, {
- text: i18n.locale.file,
- icon: 'fas fa-cloud',
- action: () => {
- alert('TODO');
- }
- }, {
- text: i18n.locale.instance,
- icon: 'fas fa-globe',
- action: () => {
- alert('TODO');
- }
- }], ev.currentTarget || ev.target);
- };
-
- return {
- [symbols.PAGE_INFO]: INFO,
- menuDef,
- header: {
- title: i18n.locale.controllPanel,
- },
- noMaintainerInformation,
- noBotProtection,
- page,
- narrow,
- view,
- el,
- onInfo,
- pageProps,
- component,
- invite,
- lookup,
- };
- },
-});
-</script>
-
-<style lang="scss" scoped>
-.hiyeyicy {
- &.wide {
- display: flex;
- margin: 0 auto;
- height: 100%;
-
- > .nav {
- width: 32%;
- max-width: 280px;
- box-sizing: border-box;
- border-right: solid 0.5px var(--divider);
- overflow: auto;
- height: 100%;
- }
-
- > .main {
- flex: 1;
- min-width: 0;
- --baseContentWidth: 100%;
- }
- }
-
- > .nav {
- > .info {
- margin: 16px;
- }
- }
-}
-
-.lxpfedzu {
- margin: 16px;
-
- > .icon {
- display: block;
- margin: auto;
- height: 42px;
- border-radius: 8px;
- }
-}
-</style>
diff --git a/src/client/pages/instance/instance-block.vue b/src/client/pages/instance/instance-block.vue
deleted file mode 100644
index 105cdb4941..0000000000
--- a/src/client/pages/instance/instance-block.vue
+++ /dev/null
@@ -1,72 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormTextarea v-model="blockedHosts">
- <span>{{ $ts.blockedInstances }}</span>
- <template #desc>{{ $ts.blockedInstancesDescription }}</template>
- </FormTextarea>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormTextarea,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.instanceBlocking,
- icon: 'fas fa-ban',
- bg: 'var(--bg)',
- },
- blockedHosts: '',
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.blockedHosts = meta.blockedHosts.join('\n');
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- blockedHosts: this.blockedHosts.split('\n') || [],
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/instance.vue b/src/client/pages/instance/instance.vue
deleted file mode 100644
index 5572fbbf75..0000000000
--- a/src/client/pages/instance/instance.vue
+++ /dev/null
@@ -1,321 +0,0 @@
-<template>
-<XModalWindow ref="dialog"
- :width="520"
- :height="500"
- @close="$refs.dialog.close()"
- @closed="$emit('closed')"
->
- <template #header>{{ instance.host }}</template>
- <div class="mk-instance-info">
- <div class="_table section">
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.software }}</div>
- <div class="_data">{{ instance.softwareName || '?' }}</div>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.version }}</div>
- <div class="_data">{{ instance.softwareVersion || '?' }}</div>
- </div>
- </div>
- </div>
- <div class="_table data section">
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.registeredAt }}</div>
- <div class="_data">{{ new Date(instance.caughtAt).toLocaleString() }} (<MkTime :time="instance.caughtAt"/>)</div>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.following }}</div>
- <button class="_data _textButton" @click="showFollowing()">{{ number(instance.followingCount) }}</button>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.followers }}</div>
- <button class="_data _textButton" @click="showFollowers()">{{ number(instance.followersCount) }}</button>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.users }}</div>
- <button class="_data _textButton" @click="showUsers()">{{ number(instance.usersCount) }}</button>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.notes }}</div>
- <div class="_data">{{ number(instance.notesCount) }}</div>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.files }}</div>
- <div class="_data">{{ number(instance.driveFiles) }}</div>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.storageUsage }}</div>
- <div class="_data">{{ bytes(instance.driveUsage) }}</div>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.latestRequestSentAt }}</div>
- <div class="_data"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div>
- </div>
- <div class="_cell">
- <div class="_label">{{ $ts.latestStatus }}</div>
- <div class="_data">{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</div>
- </div>
- </div>
- <div class="_row">
- <div class="_cell">
- <div class="_label">{{ $ts.latestRequestReceivedAt }}</div>
- <div class="_data"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div>
- </div>
- </div>
- </div>
- <div class="chart">
- <div class="header">
- <span class="label">{{ $ts.charts }}</span>
- <div class="selects">
- <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
- <option value="instance-requests">{{ $ts._instanceCharts.requests }}</option>
- <option value="instance-users">{{ $ts._instanceCharts.users }}</option>
- <option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option>
- <option value="instance-notes">{{ $ts._instanceCharts.notes }}</option>
- <option value="instance-notes-total">{{ $ts._instanceCharts.notesTotal }}</option>
- <option value="instance-ff">{{ $ts._instanceCharts.ff }}</option>
- <option value="instance-ff-total">{{ $ts._instanceCharts.ffTotal }}</option>
- <option value="instance-drive-usage">{{ $ts._instanceCharts.cacheSize }}</option>
- <option value="instance-drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option>
- <option value="instance-drive-files">{{ $ts._instanceCharts.files }}</option>
- <option value="instance-drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option>
- </MkSelect>
- <MkSelect v-model="chartSpan" style="margin: 0;">
- <option value="hour">{{ $ts.perHour }}</option>
- <option value="day">{{ $ts.perDay }}</option>
- </MkSelect>
- </div>
- </div>
- <div class="chart">
- <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart>
- </div>
- </div>
- <div class="operations section">
- <span class="label">{{ $ts.operations }}</span>
- <MkSwitch v-model="isSuspended" class="switch">{{ $ts.stopActivityDelivery }}</MkSwitch>
- <MkSwitch :model-value="isBlocked" class="switch" @update:modelValue="changeBlock">{{ $ts.blockThisInstance }}</MkSwitch>
- <details>
- <summary>{{ $ts.deleteAllFiles }}</summary>
- <MkButton @click="deleteAllFiles()" style="margin: 0.5em 0 0.5em 0;"><i class="fas fa-trash-alt"></i> {{ $ts.deleteAllFiles }}</MkButton>
- </details>
- <details>
- <summary>{{ $ts.removeAllFollowing }}</summary>
- <MkButton @click="removeAllFollowing()" style="margin: 0.5em 0 0.5em 0;"><i class="fas fa-minus-circle"></i> {{ $ts.removeAllFollowing }}</MkButton>
- <MkInfo warn>{{ $t('removeAllFollowingDescription', { host: instance.host }) }}</MkInfo>
- </details>
- </div>
- <details class="metadata section">
- <summary class="label">{{ $ts.metadata }}</summary>
- <pre><code>{{ JSON.stringify(instance, null, 2) }}</code></pre>
- </details>
- </div>
-</XModalWindow>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import XModalWindow from '@client/components/ui/modal-window.vue';
-import MkUsersDialog from '@client/components/users-dialog.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkSwitch from '@client/components/form/switch.vue';
-import MkInfo from '@client/components/ui/info.vue';
-import MkChart from '@client/components/chart.vue';
-import bytes from '@client/filters/bytes';
-import number from '@client/filters/number';
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- XModalWindow,
- MkSelect,
- MkButton,
- MkSwitch,
- MkInfo,
- MkChart,
- },
-
- props: {
- instance: {
- type: Object,
- required: true
- }
- },
-
- emits: ['closed'],
-
- data() {
- return {
- isSuspended: this.instance.isSuspended,
- chartSrc: 'requests',
- chartSpan: 'hour',
- };
- },
-
- computed: {
- meta() {
- return this.$instance;
- },
-
- isBlocked() {
- return this.meta && this.meta.blockedHosts && this.meta.blockedHosts.includes(this.instance.host);
- }
- },
-
- watch: {
- isSuspended() {
- os.api('admin/federation/update-instance', {
- host: this.instance.host,
- isSuspended: this.isSuspended
- });
- },
- },
-
- methods: {
- changeBlock(e) {
- os.api('admin/update-meta', {
- blockedHosts: this.isBlocked ? this.meta.blockedHosts.concat([this.instance.host]) : this.meta.blockedHosts.filter(x => x !== this.instance.host)
- });
- },
-
- removeAllFollowing() {
- os.apiWithDialog('admin/federation/remove-all-following', {
- host: this.instance.host
- });
- },
-
- deleteAllFiles() {
- os.apiWithDialog('admin/federation/delete-all-files', {
- host: this.instance.host
- });
- },
-
- showFollowing() {
- os.modal(MkUsersDialog, {
- title: this.$ts.instanceFollowing,
- pagination: {
- endpoint: 'federation/following',
- limit: 10,
- params: {
- host: this.instance.host
- }
- },
- extract: item => item.follower
- });
- },
-
- showFollowers() {
- os.modal(MkUsersDialog, {
- title: this.$ts.instanceFollowers,
- pagination: {
- endpoint: 'federation/followers',
- limit: 10,
- params: {
- host: this.instance.host
- }
- },
- extract: item => item.followee
- });
- },
-
- showUsers() {
- os.modal(MkUsersDialog, {
- title: this.$ts.instanceUsers,
- pagination: {
- endpoint: 'federation/users',
- limit: 10,
- params: {
- host: this.instance.host
- }
- }
- });
- },
-
- bytes,
-
- number
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-instance-info {
- overflow: auto;
-
- > .section {
- padding: 16px 32px;
-
- @media (max-width: 500px) {
- padding: 8px 16px;
- }
-
- &:not(:first-child) {
- border-top: solid 0.5px var(--divider);
- }
- }
-
- > .chart {
- border-top: solid 0.5px var(--divider);
- padding: 16px 0 12px 0;
-
- > .header {
- padding: 0 32px;
-
- @media (max-width: 500px) {
- padding: 0 16px;
- }
-
- > .label {
- font-size: 80%;
- opacity: 0.7;
- }
-
- > .selects {
- display: flex;
- }
- }
-
- > .chart {
- padding: 0 16px;
-
- @media (max-width: 500px) {
- padding: 0;
- }
- }
- }
-
- > .operations {
- > .label {
- font-size: 80%;
- opacity: 0.7;
- }
-
- > .switch {
- margin: 16px 0;
- }
- }
-
- > .metadata {
- > .label {
- font-size: 80%;
- opacity: 0.7;
- }
-
- > pre > code {
- display: block;
- max-height: 200px;
- overflow: auto;
- }
- }
-}
-</style>
diff --git a/src/client/pages/instance/integrations-discord.vue b/src/client/pages/instance/integrations-discord.vue
deleted file mode 100644
index c33b24f17f..0000000000
--- a/src/client/pages/instance/integrations-discord.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableDiscordIntegration">
- {{ $ts.enable }}
- </FormSwitch>
-
- <template v-if="enableDiscordIntegration">
- <FormInfo>Callback URL: {{ `${url}/api/dc/cb` }}</FormInfo>
-
- <FormInput v-model="discordClientId">
- <template #prefix><i class="fas fa-key"></i></template>
- Client ID
- </FormInput>
-
- <FormInput v-model="discordClientSecret">
- <template #prefix><i class="fas fa-key"></i></template>
- Client Secret
- </FormInput>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormInfo,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'Discord',
- icon: 'fab fa-discord'
- },
- enableDiscordIntegration: false,
- discordClientId: null,
- discordClientSecret: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableDiscordIntegration = meta.enableDiscordIntegration;
- this.discordClientId = meta.discordClientId;
- this.discordClientSecret = meta.discordClientSecret;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableDiscordIntegration: this.enableDiscordIntegration,
- discordClientId: this.discordClientId,
- discordClientSecret: this.discordClientSecret,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/integrations-github.vue b/src/client/pages/instance/integrations-github.vue
deleted file mode 100644
index cdf85868ff..0000000000
--- a/src/client/pages/instance/integrations-github.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableGithubIntegration">
- {{ $ts.enable }}
- </FormSwitch>
-
- <template v-if="enableGithubIntegration">
- <FormInfo>Callback URL: {{ `${url}/api/gh/cb` }}</FormInfo>
-
- <FormInput v-model="githubClientId">
- <template #prefix><i class="fas fa-key"></i></template>
- Client ID
- </FormInput>
-
- <FormInput v-model="githubClientSecret">
- <template #prefix><i class="fas fa-key"></i></template>
- Client Secret
- </FormInput>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormInfo,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'GitHub',
- icon: 'fab fa-github'
- },
- enableGithubIntegration: false,
- githubClientId: null,
- githubClientSecret: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableGithubIntegration = meta.enableGithubIntegration;
- this.githubClientId = meta.githubClientId;
- this.githubClientSecret = meta.githubClientSecret;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableGithubIntegration: this.enableGithubIntegration,
- githubClientId: this.githubClientId,
- githubClientSecret: this.githubClientSecret,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/integrations-twitter.vue b/src/client/pages/instance/integrations-twitter.vue
deleted file mode 100644
index ed7d097d0a..0000000000
--- a/src/client/pages/instance/integrations-twitter.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableTwitterIntegration">
- {{ $ts.enable }}
- </FormSwitch>
-
- <template v-if="enableTwitterIntegration">
- <FormInfo>Callback URL: {{ `${url}/api/tw/cb` }}</FormInfo>
-
- <FormInput v-model="twitterConsumerKey">
- <template #prefix><i class="fas fa-key"></i></template>
- Consumer Key
- </FormInput>
-
- <FormInput v-model="twitterConsumerSecret">
- <template #prefix><i class="fas fa-key"></i></template>
- Consumer Secret
- </FormInput>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormInfo,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'Twitter',
- icon: 'fab fa-twitter'
- },
- enableTwitterIntegration: false,
- twitterConsumerKey: null,
- twitterConsumerSecret: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableTwitterIntegration = meta.enableTwitterIntegration;
- this.twitterConsumerKey = meta.twitterConsumerKey;
- this.twitterConsumerSecret = meta.twitterConsumerSecret;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableTwitterIntegration: this.enableTwitterIntegration,
- twitterConsumerKey: this.twitterConsumerKey,
- twitterConsumerSecret: this.twitterConsumerSecret,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/integrations.vue b/src/client/pages/instance/integrations.vue
deleted file mode 100644
index 6964ae5704..0000000000
--- a/src/client/pages/instance/integrations.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormLink to="/instance/integrations/twitter">
- <i class="fab fa-twitter"></i> Twitter
- <template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template>
- </FormLink>
- <FormLink to="/instance/integrations/github">
- <i class="fab fa-github"></i> GitHub
- <template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template>
- </FormLink>
- <FormLink to="/instance/integrations/discord">
- <i class="fab fa-discord"></i> Discord
- <template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template>
- </FormLink>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormLink,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormTextarea,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.integration,
- icon: 'fas fa-share-alt',
- bg: 'var(--bg)',
- },
- enableTwitterIntegration: false,
- enableGithubIntegration: false,
- enableDiscordIntegration: false,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableTwitterIntegration = meta.enableTwitterIntegration;
- this.enableGithubIntegration = meta.enableGithubIntegration;
- this.enableDiscordIntegration = meta.enableDiscordIntegration;
- },
- }
-});
-</script>
diff --git a/src/client/pages/instance/logs.vue b/src/client/pages/instance/logs.vue
deleted file mode 100644
index 74aea0fc45..0000000000
--- a/src/client/pages/instance/logs.vue
+++ /dev/null
@@ -1,97 +0,0 @@
-<template>
-<div class="_section">
- <div class="_inputs">
- <MkInput v-model="logDomain" :debounce="true">
- <template #label>{{ $ts.domain }}</template>
- </MkInput>
- <MkSelect v-model="logLevel">
- <template #label>Level</template>
- <option value="all">All</option>
- <option value="info">Info</option>
- <option value="success">Success</option>
- <option value="warning">Warning</option>
- <option value="error">Error</option>
- <option value="debug">Debug</option>
- </MkSelect>
- </div>
-
- <div class="logs">
- <code v-for="log in logs" :key="log.id" :class="log.level">
- <details>
- <summary><MkTime :time="log.createdAt"/> [{{ log.domain.join('.') }}] {{ log.message }}</summary>
- <!--<vue-json-pretty v-if="log.data" :data="log.data"></vue-json-pretty>-->
- </details>
- </code>
- </div>
-
- <MkButton @click="deleteAllLogs()" primary><i class="fas fa-trash-alt"></i> {{ $ts.deleteAll }}</MkButton>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkTextarea from '@client/components/form/textarea.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSelect,
- MkTextarea,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.serverLogs,
- icon: 'fas fa-stream'
- },
- logs: [],
- logLevel: 'all',
- logDomain: '',
- }
- },
-
- watch: {
- logLevel() {
- this.logs = [];
- this.fetchLogs();
- },
- logDomain() {
- this.logs = [];
- this.fetchLogs();
- }
- },
-
- created() {
- this.fetchLogs();
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- fetchLogs() {
- os.api('admin/logs', {
- level: this.logLevel === 'all' ? null : this.logLevel,
- domain: this.logDomain === '' ? null : this.logDomain,
- limit: 30
- }).then(logs => {
- this.logs = logs.reverse();
- });
- },
-
- deleteAllLogs() {
- os.apiWithDialog('admin/delete-logs');
- },
- }
-});
-</script>
diff --git a/src/client/pages/instance/metrics.vue b/src/client/pages/instance/metrics.vue
deleted file mode 100644
index da36f6c688..0000000000
--- a/src/client/pages/instance/metrics.vue
+++ /dev/null
@@ -1,472 +0,0 @@
-<template>
-<div class="_debobigegoItem">
- <div class="_debobigegoLabel"><i class="fas fa-microchip"></i> {{ $ts.cpuAndMemory }}</div>
- <div class="_debobigegoPanel xhexznfu">
- <div>
- <canvas :ref="cpumem"></canvas>
- </div>
- <div v-if="serverInfo">
- <div class="_table">
- <div class="_row">
- <div class="_cell"><div class="_label">MEM total</div>{{ bytes(serverInfo.mem.total) }}</div>
- <div class="_cell"><div class="_label">MEM used</div>{{ bytes(memUsage) }} ({{ (memUsage / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
- <div class="_cell"><div class="_label">MEM free</div>{{ bytes(serverInfo.mem.total - memUsage) }} ({{ ((serverInfo.mem.total - memUsage) / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
- </div>
- </div>
- </div>
- </div>
-</div>
-<div class="_debobigegoItem">
- <div class="_debobigegoLabel"><i class="fas fa-hdd"></i> {{ $ts.disk }}</div>
- <div class="_debobigegoPanel xhexznfu">
- <div>
- <canvas :ref="disk"></canvas>
- </div>
- <div v-if="serverInfo">
- <div class="_table">
- <div class="_row">
- <div class="_cell"><div class="_label">Disk total</div>{{ bytes(serverInfo.fs.total) }}</div>
- <div class="_cell"><div class="_label">Disk used</div>{{ bytes(serverInfo.fs.used) }} ({{ (serverInfo.fs.used / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
- <div class="_cell"><div class="_label">Disk free</div>{{ bytes(serverInfo.fs.total - serverInfo.fs.used) }} ({{ ((serverInfo.fs.total - serverInfo.fs.used) / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
- </div>
- </div>
- </div>
- </div>
-</div>
-<div class="_debobigegoItem">
- <div class="_debobigegoLabel"><i class="fas fa-exchange-alt"></i> {{ $ts.network }}</div>
- <div class="_debobigegoPanel xhexznfu">
- <div>
- <canvas :ref="net"></canvas>
- </div>
- <div v-if="serverInfo">
- <div class="_table">
- <div class="_row">
- <div class="_cell"><div class="_label">Interface</div>{{ serverInfo.net.interface }}</div>
- </div>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import {
- Chart,
- ArcElement,
- LineElement,
- BarElement,
- PointElement,
- BarController,
- LineController,
- CategoryScale,
- LinearScale,
- Legend,
- Title,
- Tooltip,
- SubTitle
-} from 'chart.js';
-import MkButton from '@client/components/ui/button.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkFolder from '@client/components/ui/folder.vue';
-import MkwFederation from '../../widgets/federation.vue';
-import { version, url } from '@client/config';
-import bytes from '@client/filters/bytes';
-import number from '@client/filters/number';
-import MkInstanceInfo from './instance.vue';
-
-Chart.register(
- ArcElement,
- LineElement,
- BarElement,
- PointElement,
- BarController,
- LineController,
- CategoryScale,
- LinearScale,
- Legend,
- Title,
- Tooltip,
- SubTitle
-);
-
-const alpha = (hex, a) => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
- const r = parseInt(result[1], 16);
- const g = parseInt(result[2], 16);
- const b = parseInt(result[3], 16);
- return `rgba(${r}, ${g}, ${b}, ${a})`;
-};
-import * as os from '@client/os';
-
-export default defineComponent({
- components: {
- MkButton,
- MkSelect,
- MkInput,
- MkContainer,
- MkFolder,
- MkwFederation,
- },
-
- data() {
- return {
- version,
- url,
- stats: null,
- serverInfo: null,
- connection: null,
- queueConnection: markRaw(os.stream.useChannel('queueStats')),
- memUsage: 0,
- chartCpuMem: null,
- chartNet: null,
- jobs: [],
- logs: [],
- logLevel: 'all',
- logDomain: '',
- modLogs: [],
- dbInfo: null,
- overviewHeight: '1fr',
- queueHeight: '1fr',
- paused: false,
- }
- },
-
- computed: {
- gridColor() {
- // TODO: var(--panel)の色が暗いか明るいかで判定する
- return this.$store.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
- },
- },
-
- mounted() {
- this.fetchJobs();
-
- Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
-
- os.api('admin/server-info', {}).then(res => {
- this.serverInfo = res;
-
- this.connection = markRaw(os.stream.useChannel('serverStats'));
- this.connection.on('stats', this.onStats);
- this.connection.on('statsLog', this.onStatsLog);
- this.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 150
- });
-
- this.$nextTick(() => {
- this.queueConnection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 200
- });
- });
- });
- },
-
- beforeUnmount() {
- if (this.connection) {
- this.connection.off('stats', this.onStats);
- this.connection.off('statsLog', this.onStatsLog);
- this.connection.dispose();
- }
- this.queueConnection.dispose();
- },
-
- methods: {
- cpumem(el) {
- if (this.chartCpuMem != null) return;
- this.chartCpuMem = markRaw(new Chart(el, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'CPU',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#86b300',
- backgroundColor: alpha('#86b300', 0.1),
- data: []
- }, {
- label: 'MEM (active)',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#935dbf',
- backgroundColor: alpha('#935dbf', 0.02),
- data: []
- }, {
- label: 'MEM (used)',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#935dbf',
- borderDash: [5, 5],
- fill: false,
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- x: {
- gridLines: {
- display: false,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false,
- }
- },
- y: {
- position: 'right',
- gridLines: {
- display: true,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false,
- max: 100
- }
- }
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- }));
- },
-
- net(el) {
- if (this.chartNet != null) return;
- this.chartNet = markRaw(new Chart(el, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'In',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#94a029',
- backgroundColor: alpha('#94a029', 0.1),
- data: []
- }, {
- label: 'Out',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#ff9156',
- backgroundColor: alpha('#ff9156', 0.1),
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- x: {
- gridLines: {
- display: false,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false
- }
- },
- y: {
- position: 'right',
- gridLines: {
- display: true,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false,
- }
- }
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- }));
- },
-
- disk(el) {
- if (this.chartDisk != null) return;
- this.chartDisk = markRaw(new Chart(el, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'Read',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#94a029',
- backgroundColor: alpha('#94a029', 0.1),
- data: []
- }, {
- label: 'Write',
- pointRadius: 0,
- tension: 0,
- borderWidth: 2,
- borderColor: '#ff9156',
- backgroundColor: alpha('#ff9156', 0.1),
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- x: {
- gridLines: {
- display: false,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false
- }
- },
- y: {
- position: 'right',
- gridLines: {
- display: true,
- color: this.gridColor,
- zeroLineColor: this.gridColor,
- },
- ticks: {
- display: false,
- }
- }
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- }));
- },
-
- fetchJobs() {
- os.api('admin/queue/deliver-delayed', {}).then(jobs => {
- this.jobs = jobs;
- });
- },
-
- onStats(stats) {
- if (this.paused) return;
-
- const cpu = (stats.cpu * 100).toFixed(0);
- const memActive = (stats.mem.active / this.serverInfo.mem.total * 100).toFixed(0);
- const memUsed = (stats.mem.used / this.serverInfo.mem.total * 100).toFixed(0);
- this.memUsage = stats.mem.active;
-
- this.chartCpuMem.data.labels.push('');
- this.chartCpuMem.data.datasets[0].data.push(cpu);
- this.chartCpuMem.data.datasets[1].data.push(memActive);
- this.chartCpuMem.data.datasets[2].data.push(memUsed);
- this.chartNet.data.labels.push('');
- this.chartNet.data.datasets[0].data.push(stats.net.rx);
- this.chartNet.data.datasets[1].data.push(stats.net.tx);
- this.chartDisk.data.labels.push('');
- this.chartDisk.data.datasets[0].data.push(stats.fs.r);
- this.chartDisk.data.datasets[1].data.push(stats.fs.w);
- if (this.chartCpuMem.data.datasets[0].data.length > 150) {
- this.chartCpuMem.data.labels.shift();
- this.chartCpuMem.data.datasets[0].data.shift();
- this.chartCpuMem.data.datasets[1].data.shift();
- this.chartCpuMem.data.datasets[2].data.shift();
- this.chartNet.data.labels.shift();
- this.chartNet.data.datasets[0].data.shift();
- this.chartNet.data.datasets[1].data.shift();
- this.chartDisk.data.labels.shift();
- this.chartDisk.data.datasets[0].data.shift();
- this.chartDisk.data.datasets[1].data.shift();
- }
- this.chartCpuMem.update();
- this.chartNet.update();
- this.chartDisk.update();
- },
-
- onStatsLog(statsLog) {
- for (const stats of [...statsLog].reverse()) {
- this.onStats(stats);
- }
- },
-
- bytes,
-
- number,
-
- pause() {
- this.paused = true;
- },
-
- resume() {
- this.paused = false;
- },
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.xhexznfu {
- > div:nth-child(2) {
- padding: 16px;
- border-top: solid 0.5px var(--divider);
- }
-}
-</style>
diff --git a/src/client/pages/instance/object-storage.vue b/src/client/pages/instance/object-storage.vue
deleted file mode 100644
index 2d765270e6..0000000000
--- a/src/client/pages/instance/object-storage.vue
+++ /dev/null
@@ -1,155 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="useObjectStorage">{{ $ts.useObjectStorage }}</FormSwitch>
-
- <template v-if="useObjectStorage">
- <FormInput v-model="objectStorageBaseUrl">
- <span>{{ $ts.objectStorageBaseUrl }}</span>
- <template #desc>{{ $ts.objectStorageBaseUrlDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStorageBucket">
- <span>{{ $ts.objectStorageBucket }}</span>
- <template #desc>{{ $ts.objectStorageBucketDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStoragePrefix">
- <span>{{ $ts.objectStoragePrefix }}</span>
- <template #desc>{{ $ts.objectStoragePrefixDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStorageEndpoint">
- <span>{{ $ts.objectStorageEndpoint }}</span>
- <template #desc>{{ $ts.objectStorageEndpointDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStorageRegion">
- <span>{{ $ts.objectStorageRegion }}</span>
- <template #desc>{{ $ts.objectStorageRegionDesc }}</template>
- </FormInput>
-
- <FormInput v-model="objectStorageAccessKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>Access key</span>
- </FormInput>
-
- <FormInput v-model="objectStorageSecretKey">
- <template #prefix><i class="fas fa-key"></i></template>
- <span>Secret key</span>
- </FormInput>
-
- <FormSwitch v-model="objectStorageUseSSL">
- {{ $ts.objectStorageUseSSL }}
- <template #desc>{{ $ts.objectStorageUseSSLDesc }}</template>
- </FormSwitch>
-
- <FormSwitch v-model="objectStorageUseProxy">
- {{ $ts.objectStorageUseProxy }}
- <template #desc>{{ $ts.objectStorageUseProxyDesc }}</template>
- </FormSwitch>
-
- <FormSwitch v-model="objectStorageSetPublicRead">
- {{ $ts.objectStorageSetPublicRead }}
- </FormSwitch>
-
- <FormSwitch v-model="objectStorageS3ForcePathStyle">
- s3ForcePathStyle
- </FormSwitch>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.objectStorage,
- icon: 'fas fa-cloud',
- bg: 'var(--bg)',
- },
- useObjectStorage: false,
- objectStorageBaseUrl: null,
- objectStorageBucket: null,
- objectStoragePrefix: null,
- objectStorageEndpoint: null,
- objectStorageRegion: null,
- objectStoragePort: null,
- objectStorageAccessKey: null,
- objectStorageSecretKey: null,
- objectStorageUseSSL: false,
- objectStorageUseProxy: false,
- objectStorageSetPublicRead: false,
- objectStorageS3ForcePathStyle: true,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.useObjectStorage = meta.useObjectStorage;
- this.objectStorageBaseUrl = meta.objectStorageBaseUrl;
- this.objectStorageBucket = meta.objectStorageBucket;
- this.objectStoragePrefix = meta.objectStoragePrefix;
- this.objectStorageEndpoint = meta.objectStorageEndpoint;
- this.objectStorageRegion = meta.objectStorageRegion;
- this.objectStoragePort = meta.objectStoragePort;
- this.objectStorageAccessKey = meta.objectStorageAccessKey;
- this.objectStorageSecretKey = meta.objectStorageSecretKey;
- this.objectStorageUseSSL = meta.objectStorageUseSSL;
- this.objectStorageUseProxy = meta.objectStorageUseProxy;
- this.objectStorageSetPublicRead = meta.objectStorageSetPublicRead;
- this.objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- useObjectStorage: this.useObjectStorage,
- objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null,
- objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null,
- objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null,
- objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null,
- objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null,
- objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null,
- objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null,
- objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null,
- objectStorageUseSSL: this.objectStorageUseSSL,
- objectStorageUseProxy: this.objectStorageUseProxy,
- objectStorageSetPublicRead: this.objectStorageSetPublicRead,
- objectStorageS3ForcePathStyle: this.objectStorageS3ForcePathStyle,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/other-settings.vue b/src/client/pages/instance/other-settings.vue
deleted file mode 100644
index 4e55df41fb..0000000000
--- a/src/client/pages/instance/other-settings.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormGroup>
- <FormInput v-model="summalyProxy">
- <template #prefix><i class="fas fa-link"></i></template>
- Summaly Proxy URL
- </FormInput>
- </FormGroup>
- <FormGroup>
- <FormInput v-model="deeplAuthKey">
- <template #prefix><i class="fas fa-key"></i></template>
- DeepL Auth Key
- </FormInput>
- <FormSwitch v-model="deeplIsPro">
- Pro account
- </FormSwitch>
- </FormGroup>
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.other,
- icon: 'fas fa-cogs',
- bg: 'var(--bg)',
- },
- summalyProxy: '',
- deeplAuthKey: '',
- deeplIsPro: false,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.summalyProxy = meta.summalyProxy;
- this.deeplAuthKey = meta.deeplAuthKey;
- this.deeplIsPro = meta.deeplIsPro;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- summalyProxy: this.summalyProxy,
- deeplAuthKey: this.deeplAuthKey,
- deeplIsPro: this.deeplIsPro,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/overview.vue b/src/client/pages/instance/overview.vue
deleted file mode 100644
index 327a6492e6..0000000000
--- a/src/client/pages/instance/overview.vue
+++ /dev/null
@@ -1,188 +0,0 @@
-<template>
-<div>
- <MkHeader :info="header"/>
-
- <div class="edbbcaef">
- <div v-if="stats" class="cfcdecdf" style="margin: var(--margin)">
- <div class="number _panel">
- <div class="label">Users</div>
- <div class="value _monospace">
- {{ number(stats.originalUsersCount) }}
- <MkNumberDiff v-if="usersComparedToThePrevDay != null" class="diff" :value="usersComparedToThePrevDay" v-tooltip="$ts.dayOverDayChanges"><template #before>(</template><template #after>)</template></MkNumberDiff>
- </div>
- </div>
- <div class="number _panel">
- <div class="label">Notes</div>
- <div class="value _monospace">
- {{ number(stats.originalNotesCount) }}
- <MkNumberDiff v-if="notesComparedToThePrevDay != null" class="diff" :value="notesComparedToThePrevDay" v-tooltip="$ts.dayOverDayChanges"><template #before>(</template><template #after>)</template></MkNumberDiff>
- </div>
- </div>
- </div>
-
- <MkContainer :foldable="true" class="charts">
- <template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template>
- <div style="padding-top: 12px;">
- <MkInstanceStats :chart-limit="500" :detailed="true"/>
- </div>
- </MkContainer>
-
- <!--<XMetrics/>-->
-
- <MkFolder style="margin: var(--margin)">
- <template #header><i class="fas fa-info-circle"></i> {{ $ts.info }}</template>
- <div class="cfcdecdf">
- <div class="number _panel">
- <div class="label">Misskey</div>
- <div class="value _monospace">{{ version }}</div>
- </div>
- <div class="number _panel" v-if="serverInfo">
- <div class="label">Node.js</div>
- <div class="value _monospace">{{ serverInfo.node }}</div>
- </div>
- <div class="number _panel" v-if="serverInfo">
- <div class="label">PostgreSQL</div>
- <div class="value _monospace">{{ serverInfo.psql }}</div>
- </div>
- <div class="number _panel" v-if="serverInfo">
- <div class="label">Redis</div>
- <div class="value _monospace">{{ serverInfo.redis }}</div>
- </div>
- <div class="number _panel">
- <div class="label">Vue</div>
- <div class="value _monospace">{{ vueVersion }}</div>
- </div>
- </div>
- </MkFolder>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, version as vueVersion } from 'vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import MkInstanceStats from '@client/components/instance-stats.vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkNumberDiff from '@client/components/number-diff.vue';
-import MkContainer from '@client/components/ui/container.vue';
-import MkFolder from '@client/components/ui/folder.vue';
-import { version, url } from '@client/config';
-import bytes from '@client/filters/bytes';
-import number from '@client/filters/number';
-import MkInstanceInfo from './instance.vue';
-import XMetrics from './metrics.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- MkNumberDiff,
- FormKeyValueView,
- MkInstanceStats,
- MkContainer,
- MkFolder,
- XMetrics,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.dashboard,
- icon: 'fas fa-tachometer-alt',
- bg: 'var(--bg)',
- },
- header: {
- title: this.$ts.dashboard,
- icon: 'fas fa-tachometer-alt',
- },
- version,
- vueVersion,
- url,
- stats: null,
- meta: null,
- serverInfo: null,
- usersComparedToThePrevDay: null,
- notesComparedToThePrevDay: null,
- fetchJobs: () => os.api('admin/queue/deliver-delayed', {}),
- fetchModLogs: () => os.api('admin/show-moderation-logs', {}),
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
- os.api('meta', { detail: true }).then(meta => {
- this.meta = meta;
- });
-
- os.api('stats', {}).then(stats => {
- this.stats = stats;
-
- os.api('charts/users', { limit: 2, span: 'day' }).then(chart => {
- this.usersComparedToThePrevDay = this.stats.originalUsersCount - chart.local.total[1];
- });
-
- os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => {
- this.notesComparedToThePrevDay = this.stats.originalNotesCount - chart.local.total[1];
- });
- });
-
- os.api('admin/server-info', {}).then(serverInfo => {
- this.serverInfo = serverInfo;
- });
- },
-
- methods: {
- async showInstanceInfo(q) {
- let instance = q;
- if (typeof q === 'string') {
- instance = await os.api('federation/show-instance', {
- host: q
- });
- }
- os.popup(MkInstanceInfo, {
- instance: instance
- }, {}, 'closed');
- },
-
- bytes,
-
- number,
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.edbbcaef {
- .cfcdecdf {
- display: grid;
- grid-gap: 8px;
- grid-template-columns: repeat(auto-fill,minmax(150px,1fr));
-
- > .number {
- padding: 12px 16px;
-
- > .label {
- opacity: 0.7;
- font-size: 0.8em;
- }
-
- > .value {
- font-weight: bold;
- font-size: 1.2em;
-
- > .diff {
- font-size: 0.8em;
- }
- }
- }
- }
-
- > .charts {
- margin: var(--margin);
- }
-}
-</style>
diff --git a/src/client/pages/instance/proxy-account.vue b/src/client/pages/instance/proxy-account.vue
deleted file mode 100644
index b1ece19710..0000000000
--- a/src/client/pages/instance/proxy-account.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormGroup>
- <FormKeyValueView>
- <template #key>{{ $ts.proxyAccount }}</template>
- <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template>
- </FormKeyValueView>
- <template #caption>{{ $ts.proxyAccountDescription }}</template>
- </FormGroup>
-
- <FormButton @click="chooseProxyAccount" primary>{{ $ts.selectAccount }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormKeyValueView,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormTextarea,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.proxyAccount,
- icon: 'fas fa-ghost',
- bg: 'var(--bg)',
- },
- proxyAccount: null,
- proxyAccountId: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.proxyAccountId = meta.proxyAccountId;
- if (this.proxyAccountId) {
- this.proxyAccount = await os.api('users/show', { userId: this.proxyAccountId });
- }
- },
-
- chooseProxyAccount() {
- os.selectUser().then(user => {
- this.proxyAccount = user;
- this.proxyAccountId = user.id;
- this.save();
- });
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- proxyAccountId: this.proxyAccountId,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/queue.chart.vue b/src/client/pages/instance/queue.chart.vue
deleted file mode 100644
index 4f8fd762bb..0000000000
--- a/src/client/pages/instance/queue.chart.vue
+++ /dev/null
@@ -1,218 +0,0 @@
-<template>
-<div class="_debobigegoItem">
- <div class="_debobigegoLabel"><slot name="title"></slot></div>
- <div class="_debobigegoPanel pumxzjhg">
- <div class="_table status">
- <div class="_row">
- <div class="_cell"><div class="_label">Process</div>{{ number(activeSincePrevTick) }}</div>
- <div class="_cell"><div class="_label">Active</div>{{ number(active) }}</div>
- <div class="_cell"><div class="_label">Waiting</div>{{ number(waiting) }}</div>
- <div class="_cell"><div class="_label">Delayed</div>{{ number(delayed) }}</div>
- </div>
- </div>
- <div class="">
- <canvas ref="chart"></canvas>
- </div>
- <div class="jobs">
- <div v-if="jobs.length > 0">
- <div v-for="job in jobs" :key="job[0]">
- <span>{{ job[0] }}</span>
- <span style="margin-left: 8px; opacity: 0.7;">({{ number(job[1]) }} jobs)</span>
- </div>
- </div>
- <span v-else style="opacity: 0.5;">{{ $ts.noJobs }}</span>
- </div>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import Chart from 'chart.js';
-import number from '@client/filters/number';
-
-const alpha = (hex, a) => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
- const r = parseInt(result[1], 16);
- const g = parseInt(result[2], 16);
- const b = parseInt(result[3], 16);
- return `rgba(${r}, ${g}, ${b}, ${a})`;
-};
-import * as os from '@client/os';
-
-export default defineComponent({
- props: {
- domain: {
- required: true
- },
- connection: {
- required: true
- },
- },
-
- data() {
- return {
- chart: null,
- jobs: [],
- activeSincePrevTick: 0,
- active: 0,
- waiting: 0,
- delayed: 0,
- }
- },
-
- mounted() {
- this.fetchJobs();
-
- // TODO: var(--panel)の色が暗いか明るいかで判定する
- const gridColor = this.$store.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
-
- Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
-
- this.chart = markRaw(new Chart(this.$refs.chart, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'Process',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#00E396',
- backgroundColor: alpha('#00E396', 0.1),
- data: []
- }, {
- label: 'Active',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#00BCD4',
- backgroundColor: alpha('#00BCD4', 0.1),
- data: []
- }, {
- label: 'Waiting',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#FFB300',
- backgroundColor: alpha('#FFB300', 0.1),
- data: []
- }, {
- label: 'Delayed',
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: '#E53935',
- borderDash: [5, 5],
- fill: false,
- data: []
- }]
- },
- options: {
- aspectRatio: 3,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 12
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- xAxes: [{
- gridLines: {
- display: false,
- color: gridColor,
- zeroLineColor: gridColor,
- },
- ticks: {
- display: false
- }
- }],
- yAxes: [{
- position: 'right',
- gridLines: {
- display: true,
- color: gridColor,
- zeroLineColor: gridColor,
- },
- ticks: {
- display: false,
- }
- }]
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- }));
-
- this.connection.on('stats', this.onStats);
- this.connection.on('statsLog', this.onStatsLog);
- },
-
- beforeUnmount() {
- this.connection.off('stats', this.onStats);
- this.connection.off('statsLog', this.onStatsLog);
- },
-
- methods: {
- onStats(stats) {
- this.activeSincePrevTick = stats[this.domain].activeSincePrevTick;
- this.active = stats[this.domain].active;
- this.waiting = stats[this.domain].waiting;
- this.delayed = stats[this.domain].delayed;
- this.chart.data.labels.push('');
- this.chart.data.datasets[0].data.push(stats[this.domain].activeSincePrevTick);
- this.chart.data.datasets[1].data.push(stats[this.domain].active);
- this.chart.data.datasets[2].data.push(stats[this.domain].waiting);
- this.chart.data.datasets[3].data.push(stats[this.domain].delayed);
- if (this.chart.data.datasets[0].data.length > 200) {
- this.chart.data.labels.shift();
- this.chart.data.datasets[0].data.shift();
- this.chart.data.datasets[1].data.shift();
- this.chart.data.datasets[2].data.shift();
- this.chart.data.datasets[3].data.shift();
- }
- this.chart.update();
- },
-
- onStatsLog(statsLog) {
- for (const stats of [...statsLog].reverse()) {
- this.onStats(stats);
- }
- },
-
- fetchJobs() {
- os.api(this.domain === 'inbox' ? 'admin/queue/inbox-delayed' : this.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(jobs => {
- this.jobs = jobs;
- });
- },
-
- number
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.pumxzjhg {
- > .status {
- padding: 16px;
- border-bottom: solid 0.5px var(--divider);
- }
-
- > .jobs {
- padding: 16px;
- border-top: solid 0.5px var(--divider);
- max-height: 180px;
- overflow: auto;
- }
-}
-</style>
diff --git a/src/client/pages/instance/queue.vue b/src/client/pages/instance/queue.vue
deleted file mode 100644
index f88825eb19..0000000000
--- a/src/client/pages/instance/queue.vue
+++ /dev/null
@@ -1,73 +0,0 @@
-<template>
-<FormBase>
- <XQueue :connection="connection" domain="inbox">
- <template #title>In</template>
- </XQueue>
- <XQueue :connection="connection" domain="deliver">
- <template #title>Out</template>
- </XQueue>
- <FormButton @click="clear()" danger><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import XQueue from './queue.chart.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- MkButton,
- XQueue,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.jobQueue,
- icon: 'fas fa-clipboard-list',
- bg: 'var(--bg)',
- },
- connection: markRaw(os.stream.useChannel('queueStats')),
- }
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
-
- this.$nextTick(() => {
- this.connection.send('requestLog', {
- id: Math.random().toString().substr(2, 8),
- length: 200
- });
- });
- },
-
- beforeUnmount() {
- this.connection.dispose();
- },
-
- methods: {
- clear() {
- os.dialog({
- type: 'warning',
- title: this.$ts.clearQueueConfirmTitle,
- text: this.$ts.clearQueueConfirmText,
- showCancelButton: true
- }).then(({ canceled }) => {
- if (canceled) return;
-
- os.apiWithDialog('admin/queue/clear', {});
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/relays.vue b/src/client/pages/instance/relays.vue
deleted file mode 100644
index 7d7888eaa8..0000000000
--- a/src/client/pages/instance/relays.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-<FormBase class="relaycxt">
- <FormButton @click="addRelay" primary><i class="fas fa-plus"></i> {{ $ts.addRelay }}</FormButton>
-
- <div class="_debobigegoItem" v-for="relay in relays" :key="relay.inbox">
- <div class="_debobigegoPanel" style="padding: 16px;">
- <div>{{ relay.inbox }}</div>
- <div>{{ $t(`_relayStatus.${relay.status}`) }}</div>
- <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
- </div>
- </div>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormBase,
- FormButton,
- MkButton,
- MkInput,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.relays,
- icon: 'fas fa-globe',
- bg: 'var(--bg)',
- },
- relays: [],
- inbox: '',
- }
- },
-
- created() {
- this.refresh();
- },
-
- mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async addRelay() {
- const { canceled, result: inbox } = await os.dialog({
- title: this.$ts.addRelay,
- input: {
- placeholder: this.$ts.inboxUrl
- }
- });
- if (canceled) return;
- os.api('admin/relays/add', {
- inbox
- }).then((relay: any) => {
- this.refresh();
- }).catch((e: any) => {
- os.dialog({
- type: 'error',
- text: e.message || e
- });
- });
- },
-
- remove(inbox: string) {
- os.api('admin/relays/remove', {
- inbox
- }).then(() => {
- this.refresh();
- }).catch((e: any) => {
- os.dialog({
- type: 'error',
- text: e.message || e
- });
- });
- },
-
- refresh() {
- os.api('admin/relays/list').then((relays: any) => {
- this.relays = relays;
- });
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/client/pages/instance/security.vue b/src/client/pages/instance/security.vue
deleted file mode 100644
index a854b6dbd0..0000000000
--- a/src/client/pages/instance/security.vue
+++ /dev/null
@@ -1,83 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormLink to="/instance/bot-protection">
- <i class="fas fa-shield-alt"></i> {{ $ts.botProtection }}
- <template #suffix v-if="enableHcaptcha">hCaptcha</template>
- <template #suffix v-else-if="enableRecaptcha">reCAPTCHA</template>
- <template #suffix v-else>{{ $ts.none }} ({{ $ts.notRecommended }})</template>
- </FormLink>
-
- <FormSwitch v-model="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch>
-
- <FormSwitch v-model="emailRequiredForSignup">{{ $ts.emailRequiredForSignup }}</FormSwitch>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import FormLink from '@client/components/debobigego/link.vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormLink,
- FormSwitch,
- FormBase,
- FormGroup,
- FormButton,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.security,
- icon: 'fas fa-lock',
- bg: 'var(--bg)',
- },
- enableHcaptcha: false,
- enableRecaptcha: false,
- enableRegistration: false,
- emailRequiredForSignup: false,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableHcaptcha = meta.enableHcaptcha;
- this.enableRecaptcha = meta.enableRecaptcha;
- this.enableRegistration = !meta.disableRegistration;
- this.emailRequiredForSignup = meta.emailRequiredForSignup;
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- disableRegistration: !this.enableRegistration,
- emailRequiredForSignup: this.emailRequiredForSignup,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/service-worker.vue b/src/client/pages/instance/service-worker.vue
deleted file mode 100644
index 430e02ad2e..0000000000
--- a/src/client/pages/instance/service-worker.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSwitch v-model="enableServiceWorker">
- {{ $ts.enableServiceworker }}
- <template #desc>{{ $ts.serviceworkerInfo }}</template>
- </FormSwitch>
-
- <template v-if="enableServiceWorker">
- <FormInput v-model="swPublicKey">
- <template #prefix><i class="fas fa-key"></i></template>
- Public key
- </FormInput>
-
- <FormInput v-model="swPrivateKey">
- <template #prefix><i class="fas fa-key"></i></template>
- Private key
- </FormInput>
- </template>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: 'ServiceWorker',
- icon: 'fas fa-bolt',
- bg: 'var(--bg)',
- },
- enableServiceWorker: false,
- swPublicKey: null,
- swPrivateKey: null,
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.enableServiceWorker = meta.enableServiceWorker;
- this.swPublicKey = meta.swPublickey;
- this.swPrivateKey = meta.swPrivateKey;
- },
- save() {
- os.apiWithDialog('admin/update-meta', {
- enableServiceWorker: this.enableServiceWorker,
- swPublicKey: this.swPublicKey,
- swPrivateKey: this.swPrivateKey,
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/settings.vue b/src/client/pages/instance/settings.vue
deleted file mode 100644
index 7bd363e5f3..0000000000
--- a/src/client/pages/instance/settings.vue
+++ /dev/null
@@ -1,151 +0,0 @@
-<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormInput v-model="name">
- <span>{{ $ts.instanceName }}</span>
- </FormInput>
-
- <FormTextarea v-model="description">
- <span>{{ $ts.instanceDescription }}</span>
- </FormTextarea>
-
- <FormInput v-model="iconUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.iconUrl }}</span>
- </FormInput>
-
- <FormInput v-model="bannerUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.bannerUrl }}</span>
- </FormInput>
-
- <FormInput v-model="backgroundImageUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.backgroundImageUrl }}</span>
- </FormInput>
-
- <FormInput v-model="tosUrl">
- <template #prefix><i class="fas fa-link"></i></template>
- <span>{{ $ts.tosUrl }}</span>
- </FormInput>
-
- <FormInput v-model="maintainerName">
- <span>{{ $ts.maintainerName }}</span>
- </FormInput>
-
- <FormInput v-model="maintainerEmail" type="email">
- <template #prefix><i class="fas fa-envelope"></i></template>
- <span>{{ $ts.maintainerEmail }}</span>
- </FormInput>
-
- <FormTextarea v-model="pinnedUsers">
- <span>{{ $ts.pinnedUsers }}</span>
- <template #desc>{{ $ts.pinnedUsersDescription }}</template>
- </FormTextarea>
-
- <FormInput v-model="maxNoteTextLength" type="number">
- <template #prefix><i class="fas fa-pencil-alt"></i></template>
- <span>{{ $ts.maxNoteTextLength }}</span>
- </FormInput>
-
- <FormSwitch v-model="enableLocalTimeline">{{ $ts.enableLocalTimeline }}</FormSwitch>
- <FormSwitch v-model="enableGlobalTimeline">{{ $ts.enableGlobalTimeline }}</FormSwitch>
- <FormInfo>{{ $ts.disablingTimelinesInfo }}</FormInfo>
-
- <FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
- </FormSuspense>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@client/components/debobigego/switch.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { fetchInstance } from '@client/instance';
-
-export default defineComponent({
- components: {
- FormSwitch,
- FormInput,
- FormBase,
- FormGroup,
- FormButton,
- FormTextarea,
- FormInfo,
- FormSuspense,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.general,
- icon: 'fas fa-cog',
- bg: 'var(--bg)',
- },
- name: null,
- description: null,
- tosUrl: null as string | null,
- maintainerName: null,
- maintainerEmail: null,
- iconUrl: null,
- bannerUrl: null,
- backgroundImageUrl: null,
- maxNoteTextLength: 0,
- enableLocalTimeline: false,
- enableGlobalTimeline: false,
- pinnedUsers: '',
- }
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- async init() {
- const meta = await os.api('meta', { detail: true });
- this.name = meta.name;
- this.description = meta.description;
- this.tosUrl = meta.tosUrl;
- this.iconUrl = meta.iconUrl;
- this.bannerUrl = meta.bannerUrl;
- this.backgroundImageUrl = meta.backgroundImageUrl;
- this.maintainerName = meta.maintainerName;
- this.maintainerEmail = meta.maintainerEmail;
- this.maxNoteTextLength = meta.maxNoteTextLength;
- this.enableLocalTimeline = !meta.disableLocalTimeline;
- this.enableGlobalTimeline = !meta.disableGlobalTimeline;
- this.pinnedUsers = meta.pinnedUsers.join('\n');
- },
-
- save() {
- os.apiWithDialog('admin/update-meta', {
- name: this.name,
- description: this.description,
- tosUrl: this.tosUrl,
- iconUrl: this.iconUrl,
- bannerUrl: this.bannerUrl,
- backgroundImageUrl: this.backgroundImageUrl,
- maintainerName: this.maintainerName,
- maintainerEmail: this.maintainerEmail,
- maxNoteTextLength: this.maxNoteTextLength,
- disableLocalTimeline: !this.enableLocalTimeline,
- disableGlobalTimeline: !this.enableGlobalTimeline,
- pinnedUsers: this.pinnedUsers.split('\n'),
- }).then(() => {
- fetchInstance();
- });
- }
- }
-});
-</script>
diff --git a/src/client/pages/instance/users.vue b/src/client/pages/instance/users.vue
deleted file mode 100644
index f7f9306b70..0000000000
--- a/src/client/pages/instance/users.vue
+++ /dev/null
@@ -1,261 +0,0 @@
-<template>
-<div class="lknzcolw">
- <MkHeader :info="header"/>
-
- <div class="users">
- <div class="inputs">
- <MkSelect v-model="sort" style="flex: 1;">
- <template #label>{{ $ts.sort }}</template>
- <option value="-createdAt">{{ $ts.registeredDate }} ({{ $ts.ascendingOrder }})</option>
- <option value="+createdAt">{{ $ts.registeredDate }} ({{ $ts.descendingOrder }})</option>
- <option value="-updatedAt">{{ $ts.lastUsed }} ({{ $ts.ascendingOrder }})</option>
- <option value="+updatedAt">{{ $ts.lastUsed }} ({{ $ts.descendingOrder }})</option>
- </MkSelect>
- <MkSelect v-model="state" style="flex: 1;">
- <template #label>{{ $ts.state }}</template>
- <option value="all">{{ $ts.all }}</option>
- <option value="available">{{ $ts.normal }}</option>
- <option value="admin">{{ $ts.administrator }}</option>
- <option value="moderator">{{ $ts.moderator }}</option>
- <option value="silenced">{{ $ts.silence }}</option>
- <option value="suspended">{{ $ts.suspend }}</option>
- </MkSelect>
- <MkSelect v-model="origin" style="flex: 1;">
- <template #label>{{ $ts.instance }}</template>
- <option value="combined">{{ $ts.all }}</option>
- <option value="local">{{ $ts.local }}</option>
- <option value="remote">{{ $ts.remote }}</option>
- </MkSelect>
- </div>
- <div class="inputs">
- <MkInput v-model="searchUsername" style="flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.users.reload()">
- <template #prefix>@</template>
- <template #label>{{ $ts.username }}</template>
- </MkInput>
- <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.users.reload()" :disabled="pagination.params().origin === 'local'">
- <template #prefix>@</template>
- <template #label>{{ $ts.host }}</template>
- </MkInput>
- </div>
-
- <MkPagination :pagination="pagination" #default="{items}" class="users" ref="users">
- <button class="user _panel _button _gap" v-for="user in items" :key="user.id" @click="show(user)">
- <MkAvatar class="avatar" :user="user" :disable-link="true" :show-indicator="true"/>
- <div class="body">
- <header>
- <MkUserName class="name" :user="user"/>
- <span class="acct">@{{ acct(user) }}</span>
- <span class="staff" v-if="user.isAdmin"><i class="fas fa-bookmark"></i></span>
- <span class="staff" v-if="user.isModerator"><i class="far fa-bookmark"></i></span>
- <span class="punished" v-if="user.isSilenced"><i class="fas fa-microphone-slash"></i></span>
- <span class="punished" v-if="user.isSuspended"><i class="fas fa-snowflake"></i></span>
- </header>
- <div>
- <span>{{ $ts.lastUsed }}: <MkTime v-if="user.updatedAt" :time="user.updatedAt" mode="detail"/></span>
- </div>
- <div>
- <span>{{ $ts.registeredDate }}: <MkTime :time="user.createdAt" mode="detail"/></span>
- </div>
- </div>
- </button>
- </MkPagination>
- </div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@client/components/ui/button.vue';
-import MkInput from '@client/components/form/input.vue';
-import MkSelect from '@client/components/form/select.vue';
-import MkPagination from '@client/components/ui/pagination.vue';
-import { acct } from '@client/filters/user';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-import { lookupUser } from '@client/scripts/lookup-user';
-
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkSelect,
- MkPagination,
- },
-
- emits: ['info'],
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.users,
- icon: 'fas fa-users',
- bg: 'var(--bg)',
- },
- header: {
- title: this.$ts.users,
- icon: 'fas fa-users',
- bg: 'var(--bg)',
- actions: [{
- icon: 'fas fa-search',
- text: this.$ts.search,
- handler: this.searchUser
- }, {
- asFullButton: true,
- icon: 'fas fa-plus',
- text: this.$ts.addUser,
- handler: this.addUser
- }, {
- asFullButton: true,
- icon: 'fas fa-search',
- text: this.$ts.lookup,
- handler: this.lookupUser
- }]
- },
- sort: '+createdAt',
- state: 'all',
- origin: 'local',
- searchUsername: '',
- searchHost: '',
- pagination: {
- endpoint: 'admin/show-users',
- limit: 10,
- params: () => ({
- sort: this.sort,
- state: this.state,
- origin: this.origin,
- username: this.searchUsername,
- hostname: this.searchHost,
- }),
- offsetMode: true
- },
- }
- },
-
- watch: {
- sort() {
- this.$refs.users.reload();
- },
- state() {
- this.$refs.users.reload();
- },
- origin() {
- this.$refs.users.reload();
- },
- },
-
- async mounted() {
- this.$emit('info', this[symbols.PAGE_INFO]);
- },
-
- methods: {
- lookupUser,
-
- searchUser() {
- os.selectUser().then(user => {
- this.show(user);
- });
- },
-
- async addUser() {
- const { canceled: canceled1, result: username } = await os.dialog({
- title: this.$ts.username,
- input: true
- });
- if (canceled1) return;
-
- const { canceled: canceled2, result: password } = await os.dialog({
- title: this.$ts.password,
- input: { type: 'password' }
- });
- if (canceled2) return;
-
- os.apiWithDialog('admin/accounts/create', {
- username: username,
- password: password,
- }).then(res => {
- this.$refs.users.reload();
- });
- },
-
- show(user) {
- os.pageWindow(`/user-info/${user.id}`);
- },
-
- acct
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.lknzcolw {
- > .users {
- margin: var(--margin);
-
- > .inputs {
- display: flex;
- margin-bottom: 16px;
-
- > * {
- margin-right: 16px;
-
- &:last-child {
- margin-right: 0;
- }
- }
- }
-
- > .users {
- margin-top: var(--margin);
-
- > .user {
- display: flex;
- width: 100%;
- box-sizing: border-box;
- text-align: left;
- align-items: center;
- padding: 16px;
-
- &:hover {
- color: var(--accent);
- }
-
- > .avatar {
- width: 60px;
- height: 60px;
- }
-
- > .body {
- margin-left: 0.3em;
- padding: 0 8px;
- flex: 1;
-
- @media (max-width: 500px) {
- font-size: 14px;
- }
-
- > header {
- > .name {
- font-weight: bold;
- }
-
- > .acct {
- margin-left: 8px;
- opacity: 0.7;
- }
-
- > .staff {
- margin-left: 0.5em;
- color: var(--badge);
- }
-
- > .punished {
- margin-left: 0.5em;
- color: #4dabf7;
- }
- }
- }
- }
- }
- }
-}
-</style>