diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-22 17:37:51 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-22 17:37:51 +0900 |
| commit | 81a0ee4b2d089b9266d79595707a28f655f5cb27 (patch) | |
| tree | 769ee1d2fe4ab72abe74008eafe49297ee152a5b /src/client/pages/instance | |
| parent | tweak ui (diff) | |
| download | sharkey-81a0ee4b2d089b9266d79595707a28f655f5cb27.tar.gz sharkey-81a0ee4b2d089b9266d79595707a28f655f5cb27.tar.bz2 sharkey-81a0ee4b2d089b9266d79595707a28f655f5cb27.zip | |
client: change url /instance -> /admin
Diffstat (limited to 'src/client/pages/instance')
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> |