diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-01-27 00:17:13 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2022-01-27 00:17:13 +0900 |
| commit | 5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff (patch) | |
| tree | 51e9e6179f6d1bda3013d1412f6e43f9f8f70e86 /packages/client/src/pages/admin | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.102.0 (diff) | |
| download | misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.tar.gz misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.tar.bz2 misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.zip | |
Merge branch 'develop'
Diffstat (limited to 'packages/client/src/pages/admin')
28 files changed, 802 insertions, 1276 deletions
diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 8df20097b3..92f93797ce 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -34,27 +34,7 @@ --> <MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);"> - <div v-for="report in items" :key="report.id" class="bcekxzvu _card _gap"> - <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 v-if="!report.resolved" primary @click="resolve(report)">{{ $ts.abuseMarkAsResolved }}</MkButton> - </div> - </div> + <XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/> </MkPagination> </div> </div> @@ -62,22 +42,21 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; import MkPagination from '@/components/ui/pagination.vue'; -import { acct } from '@/filters/user'; +import XAbuseReport from '@/components/abuse-report.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - MkButton, MkInput, MkSelect, MkPagination, + XAbuseReport, }, emits: ['info'], @@ -95,44 +74,20 @@ export default defineComponent({ reporterOrigin: 'combined', targetUserOrigin: 'combined', pagination: { - endpoint: 'admin/abuse-user-reports', + endpoint: 'admin/abuse-user-reports' as const, limit: 10, - params: () => ({ + params: computed(() => ({ 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); - }); + resolved(reportId) { + this.$refs.reports.removeItem(item => item.id === reportId); }, } }); @@ -142,29 +97,4 @@ export default defineComponent({ .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/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue index d12ed8563e..8f164caa99 100644 --- a/packages/client/src/pages/admin/ads.vue +++ b/packages/client/src/pages/admin/ads.vue @@ -23,14 +23,14 @@ <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> </div> --> - <div class="_inputSplit"> + <FormSplit> <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> - </div> + </FormSplit> <MkTextarea v-model="ad.memo" class="_formBlock"> <template #label>{{ $ts.memo }}</template> </MkTextarea> @@ -49,6 +49,7 @@ import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import FormRadios from '@/components/form/radios.vue'; +import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; @@ -58,6 +59,7 @@ export default defineComponent({ MkInput, MkTextarea, FormRadios, + FormSplit, }, emits: ['info'], @@ -85,10 +87,6 @@ export default defineComponent({ }); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { add() { this.ads.unshift({ diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue index 3614cb1441..a0d720bb29 100644 --- a/packages/client/src/pages/admin/announcements.vue +++ b/packages/client/src/pages/admin/announcements.vue @@ -61,10 +61,6 @@ export default defineComponent({ }); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { add() { this.announcements.unshift({ diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 5a97083841..82ab155317 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -1,70 +1,55 @@ <template> -<FormBase> +<div> <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> + <div class="_formRoot"> + <FormRadios v-model="provider" class="_formBlock"> + <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 v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <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 v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">{{ $ts.preview }}</div> - <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);"> + <template v-if="provider === 'hcaptcha'"> + <FormInput v-model="hcaptchaSiteKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.hcaptchaSiteKey }}</template> + </FormInput> + <FormInput v-model="hcaptchaSecretKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.hcaptchaSecretKey }}</template> + </FormInput> + <FormSlot class="_formBlock"> + <template #label>{{ $ts.preview }}</template> <MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/> - </div> - </div> - </template> - <template v-else-if="provider === 'recaptcha'"> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <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" v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">{{ $ts.preview }}</div> - <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);"> + </FormSlot> + </template> + <template v-else-if="provider === 'recaptcha'"> + <FormInput v-model="recaptchaSiteKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.recaptchaSiteKey }}</template> + </FormInput> + <FormInput v-model="recaptchaSecretKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.recaptchaSecretKey }}</template> + </FormInput> + <FormSlot v-if="recaptchaSiteKey" class="_formBlock"> + <template #label>{{ $ts.preview }}</template> <MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/> - </div> - </div> - </template> + </FormSlot> + </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> </FormSuspense> -</FormBase> +</div> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormRadios from '@/components/debobigego/radios.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormRadios from '@/components/form/radios.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSlot from '@/components/form/slot.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -73,11 +58,9 @@ export default defineComponent({ components: { FormRadios, FormInput, - FormBase, - FormGroup, FormButton, - FormInfo, FormSuspense, + FormSlot, MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')), }, @@ -99,10 +82,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue index b09f1ad867..3a835eeafa 100644 --- a/packages/client/src/pages/admin/database.vue +++ b/packages/client/src/pages/admin/database.vue @@ -1,28 +1,18 @@ <template> -<FormBase> +<MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> <FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory"> - <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> + <MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;"> + <template #key>{{ table[0] }}</template> + <template #value>{{ bytes(table[1].size) }} ({{ number(table[1].count) }} recs)</template> + </MkKeyValue> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import bytes from '@/filters/bytes'; @@ -31,10 +21,7 @@ import number from '@/filters/number'; export default defineComponent({ components: { FormSuspense, - FormKeyValueView, - FormBase, - FormGroup, - FormLink, + MkKeyValue, }, emits: ['info'], @@ -50,10 +37,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { bytes, number, } diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue index 873a853918..6491a453ab 100644 --- a/packages/client/src/pages/admin/email-settings.vue +++ b/packages/client/src/pages/admin/email-settings.vue @@ -1,50 +1,55 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormSwitch v-model="enableEmail">{{ $ts.enableEmail }}<template #desc>{{ $ts.emailConfigInfo }}</template></FormSwitch> + <div class="_formRoot"> + <FormSwitch v-model="enableEmail" class="_formBlock"> + <template #label>{{ $ts.enableEmail }}</template> + <template #caption>{{ $ts.emailConfigInfo }}</template> + </FormSwitch> - <template v-if="enableEmail"> - <FormInput v-model="email" type="email"> - <span>{{ $ts.emailAddress }}</span> - </FormInput> + <template v-if="enableEmail"> + <FormInput v-model="email" type="email" class="_formBlock"> + <template #label>{{ $ts.emailAddress }}</template> + </FormInput> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <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 primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormSection> + <template #label>{{ $ts.smtpConfig }}</template> + <FormSplit :min-width="280"> + <FormInput v-model="smtpHost" class="_formBlock"> + <template #label>{{ $ts.smtpHost }}</template> + </FormInput> + <FormInput v-model="smtpPort" type="number" class="_formBlock"> + <template #label>{{ $ts.smtpPort }}</template> + </FormInput> + </FormSplit> + <FormSplit :min-width="280"> + <FormInput v-model="smtpUser" class="_formBlock"> + <template #label>{{ $ts.smtpUser }}</template> + </FormInput> + <FormInput v-model="smtpPass" type="password" class="_formBlock"> + <template #label>{{ $ts.smtpPass }}</template> + </FormInput> + </FormSplit> + <FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo> + <FormSwitch v-model="smtpSecure" class="_formBlock"> + <template #label>{{ $ts.smtpSecure }}</template> + <template #caption>{{ $ts.smtpSecureInfo }}</template> + </FormSwitch> + </FormSection> + </template> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -53,9 +58,8 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, - FormGroup, - FormButton, + FormSplit, + FormSection, FormInfo, FormSuspense, }, @@ -68,6 +72,16 @@ export default defineComponent({ title: this.$ts.emailServer, icon: 'fas fa-envelope', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + text: this.$ts.testEmail, + handler: this.testEmail, + }, { + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, enableEmail: false, email: null, @@ -79,10 +93,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue index a45d92fa16..2e3903426e 100644 --- a/packages/client/src/pages/admin/emoji-edit-dialog.vue +++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue @@ -95,7 +95,7 @@ export default defineComponent({ }); if (canceled) return; - os.api('admin/emoji/remove', { + os.api('admin/emoji/delete', { id: this.emoji.id }).then(() => { this.$emit('done', { diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index 49277325a0..5b1dfe565a 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -6,11 +6,22 @@ <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.search }}</template> </MkInput> - <MkPagination ref="emojis" :pagination="pagination"> + <MkSwitch v-model="selectMode" style="margin: 8px 0;"> + <template #label>Select mode</template> + </MkSwitch> + <div v-if="selectMode" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <MkButton inline @click="selectAll">Select all</MkButton> + <MkButton inline @click="setCategoryBulk">Set category</MkButton> + <MkButton inline @click="addTagBulk">Add tag</MkButton> + <MkButton inline @click="removeTagBulk">Remove tag</MkButton> + <MkButton inline @click="setTagBulk">Set tag</MkButton> + <MkButton inline danger @click="delBulk">Delete</MkButton> + </div> + <MkPagination ref="emojisPaginationComponent" :pagination="pagination"> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template v-slot="{items}"> <div class="ldhfsamy"> - <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="edit(emoji)"> + <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)"> <img :src="emoji.url" class="img" :alt="emoji.name"/> <div class="body"> <div class="name _monospace">{{ emoji.name }}</div> @@ -23,7 +34,7 @@ </div> <div v-else-if="tab === 'remote'" class="remote"> - <div class="_inputSplit"> + <FormSplit> <MkInput v-model="queryRemote" :debounce="true" type="search"> <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.search }}</template> @@ -31,8 +42,8 @@ <MkInput v-model="host" :debounce="true"> <template #label>{{ $ts.host }}</template> </MkInput> - </div> - <MkPagination ref="remoteEmojis" :pagination="remotePagination"> + </FormSplit> + <MkPagination :pagination="remotePagination"> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template v-slot="{items}"> <div class="ldhfsamy"> @@ -51,146 +62,233 @@ </MkSpacer> </template> -<script lang="ts"> -import { computed, defineComponent, toRef } from 'vue'; +<script lang="ts" setup> +import { computed, defineComponent, ref, toRef } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkTab from '@/components/tab.vue'; -import { selectFiles } from '@/scripts/select-file'; +import MkSwitch from '@/components/form/switch.vue'; +import FormSplit from '@/components/form/split.vue'; +import { selectFile, selectFiles } from '@/scripts/select-file'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkTab, - MkButton, - MkInput, - MkPagination, - }, +const emojisPaginationComponent = ref<InstanceType<typeof MkPagination>>(); - emits: ['info'], +const tab = ref('local'); +const query = ref(null); +const queryRemote = ref(null); +const host = ref(null); +const selectMode = ref(false); +const selectedEmojis = ref<string[]>([]); - data() { - return { - [symbols.PAGE_INFO]: 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, - }, { - icon: 'fas fa-ellipsis-h', - handler: this.menu, - }], - 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 - })) - }, - } - }, +const pagination = { + endpoint: 'admin/emoji/list' as const, + limit: 30, + params: computed(() => ({ + query: (query.value && query.value !== '') ? query.value : null, + })), +}; - async mounted() { - this.$emit('info', toRef(this, symbols.PAGE_INFO)); - }, +const remotePagination = { + endpoint: 'admin/emoji/list-remote' as const, + limit: 30, + params: computed(() => ({ + query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null, + host: (host.value && host.value !== '') ? host.value : null, + })), +}; - methods: { - async add(e) { - const files = await selectFiles(e.currentTarget || e.target, null); +const selectAll = () => { + if (selectedEmojis.value.length > 0) { + selectedEmojis.value = []; + } else { + selectedEmojis.value = emojisPaginationComponent.value.items.map(item => item.id); + } +}; - const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { - fileId: file.id, - }))); - promise.then(() => { - this.$refs.emojis.reload(); - }); - os.promiseDialog(promise); - }, +const toggleSelect = (emoji) => { + if (selectedEmojis.value.includes(emoji.id)) { + selectedEmojis.value = selectedEmojis.value.filter(x => x !== emoji.id); + } else { + selectedEmojis.value.push(emoji.id); + } +}; - 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'); - }, +const add = async (ev: MouseEvent) => { + const files = await selectFiles(ev.currentTarget || ev.target, null); - im(emoji) { - os.apiWithDialog('admin/emoji/copy', { - emojiId: emoji.id, - }); - }, + const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { + fileId: file.id, + }))); + promise.then(() => { + emojisPaginationComponent.value.reload(); + }); + os.promiseDialog(promise); +}; - 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); +const edit = (emoji) => { + os.popup(import('./emoji-edit-dialog.vue'), { + emoji: emoji + }, { + done: result => { + if (result.updated) { + emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, { + ...emoji, + ...result.updated + }); + } else if (result.deleted) { + emojisPaginationComponent.value.removeItem(item => item.id === emoji.id); + } }, + }, 'closed'); +}; - menu(ev) { - os.popupMenu([{ - icon: 'fas fa-download', - text: this.$ts.export, - action: async () => { - os.api('export-custom-emojis', { - }) - .then(() => { - os.alert({ - type: 'info', - text: this.$ts.exportRequested, - }); - }).catch((e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }); - } - }], ev.currentTarget || ev.target); +const im = (emoji) => { + os.apiWithDialog('admin/emoji/copy', { + emojiId: emoji.id, + }); +}; + +const remoteMenu = (emoji, ev: MouseEvent) => { + os.popupMenu([{ + type: 'label', + text: ':' + emoji.name + ':', + }, { + text: i18n.locale.import, + icon: 'fas fa-plus', + action: () => { im(emoji) } + }], ev.currentTarget || ev.target); +}; + +const menu = (ev: MouseEvent) => { + os.popupMenu([{ + icon: 'fas fa-download', + text: i18n.locale.export, + action: async () => { + os.api('export-custom-emojis', { + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.exportRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); } - } + }, { + icon: 'fas fa-upload', + text: i18n.locale.import, + action: async () => { + const file = await selectFile(ev.currentTarget || ev.target); + os.api('admin/emoji/import-zip', { + fileId: file.id, + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.importRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); + } + }], ev.currentTarget || ev.target); +}; + +const setCategoryBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Category', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-category-bulk', { + ids: selectedEmojis.value, + category: result, + }); + emojisPaginationComponent.value.reload(); +}; + +const addTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/add-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const removeTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/remove-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const setTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const delBulk = async () => { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.deleteConfirm, + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/delete-bulk', { + ids: selectedEmojis.value, + }); + emojisPaginationComponent.value.reload(); +}; + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.customEmojis, + icon: 'fas fa-laugh', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.locale.addEmoji, + handler: add, + }, { + icon: 'fas fa-ellipsis-h', + handler: menu, + }], + tabs: [{ + active: tab.value === 'local', + title: i18n.locale.local, + onClick: () => { tab.value = 'local'; }, + }, { + active: tab.value === 'remote', + title: i18n.locale.remote, + onClick: () => { tab.value = 'remote'; }, + },] + })), }); </script> @@ -210,11 +308,16 @@ export default defineComponent({ > .emoji { display: flex; align-items: center; - padding: 12px; + padding: 11px; text-align: left; + border: solid 1px var(--panel); &:hover { - color: var(--accent); + border-color: var(--inputBorderHover); + } + + &.selected { + border-color: var(--accent); } > .img { diff --git a/packages/client/src/pages/admin/files-settings.vue b/packages/client/src/pages/admin/files-settings.vue deleted file mode 100644 index df25bd0fb2..0000000000 --- a/packages/client/src/pages/admin/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 primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; -import { fetchInstance } from '@/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/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index 032e394a66..87dd12f489 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -19,7 +19,7 @@ <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'"> + <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> @@ -55,7 +55,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -95,33 +95,17 @@ export default defineComponent({ type: null, searchHost: '', pagination: { - endpoint: 'admin/drive/files', + endpoint: 'admin/drive/files' as const, limit: 10, - params: () => ({ + params: computed(() => ({ type: (this.type && this.type !== '') ? this.type : null, origin: this.origin, - hostname: (this.hostname && this.hostname !== '') ? this.hostname : null, - }), + hostname: (this.searchHost && this.searchHost !== '') ? this.searchHost : 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.confirm({ diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index e363d1bd03..350e7defc6 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -3,14 +3,14 @@ <div v-if="!narrow || page == null" class="nav"> <MkHeader :info="header"></MkHeader> - <MkSpacer :content-max="700"> + <MkSpacer :content-max="700" :margin-min="16"> <div class="lxpfedzu"> <div class="banner"> <img :src="$instance.iconUrl || '/favicon.ico'" alt="" class="icon"/> </div> <MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo> - <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/bot-protection" class="_link">{{ $ts.configure }}</MkA></MkInfo> + <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo> <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> </div> @@ -19,7 +19,7 @@ <div class="main"> <MkStickyContainer> <template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template> - <component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/> + <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> </MkStickyContainer> </div> </div> @@ -29,9 +29,6 @@ import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue'; import { i18n } from '@/i18n'; import MkSuperMenu from '@/components/ui/super-menu.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; import MkInfo from '@/components/ui/info.vue'; import { scroll } from '@/scripts/scroll'; import { instance } from '@/instance'; @@ -41,10 +38,7 @@ import { lookupUser } from '@/scripts/lookup-user'; export default defineComponent({ components: { - FormBase, MkSuperMenu, - FormGroup, - FormButton, MkInfo, }, @@ -72,7 +66,9 @@ export default defineComponent({ const narrow = ref(false); const view = ref(null); const el = ref(null); - const onInfo = (viewInfo) => { + const pageChanged = (page) => { + if (page == null) return; + const viewInfo = page[symbols.PAGE_INFO]; if (isRef(viewInfo)) { watch(viewInfo, () => { childInfo.value = viewInfo.value; @@ -163,11 +159,6 @@ export default defineComponent({ to: '/admin/settings', active: page.value === 'settings', }, { - icon: 'fas fa-cloud', - text: i18n.locale.files, - to: '/admin/files-settings', - active: page.value === 'files-settings', - }, { icon: 'fas fa-envelope', text: i18n.locale.emailServer, to: '/admin/email-settings', @@ -183,11 +174,6 @@ export default defineComponent({ to: '/admin/security', active: page.value === 'security', }, { - icon: 'fas fa-bolt', - text: 'ServiceWorker', - to: '/admin/service-worker', - active: page.value === 'service-worker', - }, { icon: 'fas fa-globe', text: i18n.locale.relays, to: '/admin/relays', @@ -236,17 +222,11 @@ export default defineComponent({ case 'database': return defineAsyncComponent(() => import('./database.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')); @@ -333,7 +313,7 @@ export default defineComponent({ narrow, view, el, - onInfo, + pageChanged, childInfo, pageProps, component, diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue index 2e899de687..6cadc7df39 100644 --- a/packages/client/src/pages/admin/instance-block.vue +++ b/packages/client/src/pages/admin/instance-block.vue @@ -1,39 +1,29 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormTextarea v-model="blockedHosts"> + <FormTextarea v-model="blockedHosts" class="_formBlock"> <span>{{ $ts.blockedInstances }}</span> - <template #desc>{{ $ts.blockedInstancesDescription }}</template> + <template #caption>{{ $ts.blockedInstancesDescription }}</template> </FormTextarea> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormSwitch, - FormInput, - FormBase, - FormGroup, FormButton, FormTextarea, - FormInfo, FormSuspense, }, @@ -50,10 +40,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/instance.vue b/packages/client/src/pages/admin/instance.vue deleted file mode 100644 index 51fcb8675a..0000000000 --- a/packages/client/src/pages/admin/instance.vue +++ /dev/null @@ -1,291 +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 style="margin: 0.5em 0 0.5em 0;" @click="deleteAllFiles()"><i class="fas fa-trash-alt"></i> {{ $ts.deleteAllFiles }}</MkButton> - </details> - <details> - <summary>{{ $ts.removeAllFollowing }}</summary> - <MkButton style="margin: 0.5em 0 0.5em 0;" @click="removeAllFollowing()"><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 '@/components/ui/modal-window.vue'; -import MkSelect from '@/components/form/select.vue'; -import MkButton from '@/components/ui/button.vue'; -import MkSwitch from '@/components/form/switch.vue'; -import MkInfo from '@/components/ui/info.vue'; -import MkChart from '@/components/chart.vue'; -import bytes from '@/filters/bytes'; -import number from '@/filters/number'; -import * as os from '@/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() { - // TODO: ページ遷移 - }, - - showFollowers() { - // TODO: ページ遷移 - }, - - showUsers() { - // TODO: ページ遷移 - }, - - 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/packages/client/src/pages/admin/integrations-discord.vue b/packages/client/src/pages/admin/integrations.discord.vue index 383031f3d1..8fc340150a 100644 --- a/packages/client/src/pages/admin/integrations-discord.vue +++ b/packages/client/src/pages/admin/integrations.discord.vue @@ -1,37 +1,36 @@ <template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableDiscordIntegration"> - {{ $ts.enable }} +<FormSuspense :p="init"> + <div class="_formRoot"> + <FormSwitch v-model="enableDiscordIntegration" class="_formBlock"> + <template #label>{{ $ts.enable }}</template> </FormSwitch> <template v-if="enableDiscordIntegration"> - <FormInfo>Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo> + <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo> - <FormInput v-model="discordClientId"> + <FormInput v-model="discordClientId" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client ID + <template #label>Client ID</template> </FormInput> - <FormInput v-model="discordClientSecret"> + <FormInput v-model="discordClientSecret" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client Secret + <template #label>Client Secret</template> </FormInput> </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> +</FormSuspense> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -40,7 +39,6 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormInfo, FormButton, FormSuspense, @@ -60,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations-github.vue b/packages/client/src/pages/admin/integrations.github.vue index ecb2fd67fa..d9db9c00f1 100644 --- a/packages/client/src/pages/admin/integrations-github.vue +++ b/packages/client/src/pages/admin/integrations.github.vue @@ -1,37 +1,36 @@ <template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableGithubIntegration"> - {{ $ts.enable }} +<FormSuspense :p="init"> + <div class="_formRoot"> + <FormSwitch v-model="enableGithubIntegration" class="_formBlock"> + <template #label>{{ $ts.enable }}</template> </FormSwitch> <template v-if="enableGithubIntegration"> - <FormInfo>Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo> + <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo> - <FormInput v-model="githubClientId"> + <FormInput v-model="githubClientId" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client ID + <template #label>Client ID</template> </FormInput> - <FormInput v-model="githubClientSecret"> + <FormInput v-model="githubClientSecret" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client Secret + <template #label>Client Secret</template> </FormInput> </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> +</FormSuspense> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -40,7 +39,6 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormInfo, FormButton, FormSuspense, @@ -60,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations-twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue index 1404102c57..1f8074535a 100644 --- a/packages/client/src/pages/admin/integrations-twitter.vue +++ b/packages/client/src/pages/admin/integrations.twitter.vue @@ -1,37 +1,36 @@ <template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableTwitterIntegration"> - {{ $ts.enable }} +<FormSuspense :p="init"> + <div class="_formRoot"> + <FormSwitch v-model="enableTwitterIntegration" class="_formBlock"> + <template #label>{{ $ts.enable }}</template> </FormSwitch> <template v-if="enableTwitterIntegration"> - <FormInfo>Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo> + <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo> - <FormInput v-model="twitterConsumerKey"> + <FormInput v-model="twitterConsumerKey" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Consumer Key + <template #label>Consumer Key</template> </FormInput> - <FormInput v-model="twitterConsumerSecret"> + <FormInput v-model="twitterConsumerSecret" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Consumer Secret + <template #label>Consumer Secret</template> </FormInput> </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> +</FormSuspense> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -40,7 +39,6 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormInfo, FormButton, FormSuspense, @@ -60,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue index c21eebc1c6..91d03fef31 100644 --- a/packages/client/src/pages/admin/integrations.vue +++ b/packages/client/src/pages/admin/integrations.vue @@ -1,46 +1,48 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormLink to="/admin/integrations/twitter"> - <i class="fab fa-twitter"></i> Twitter + <FormFolder class="_formBlock"> + <template #icon><i class="fab fa-twitter"></i></template> + <template #label>Twitter</template> <template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template> - </FormLink> - <FormLink to="/admin/integrations/github"> - <i class="fab fa-github"></i> GitHub + <XTwitter/> + </FormFolder> + <FormFolder to="/admin/integrations/github" class="_formBlock"> + <template #icon><i class="fab fa-github"></i></template> + <template #label>GitHub</template> <template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template> - </FormLink> - <FormLink to="/admin/integrations/discord"> - <i class="fab fa-discord"></i> Discord + <XGithub/> + </FormFolder> + <FormFolder to="/admin/integrations/discord" class="_formBlock"> + <template #icon><i class="fab fa-discord"></i></template> + <template #label>Discord</template> <template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template> - </FormLink> + <XDiscord/> + </FormFolder> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormFolder from '@/components/form/folder.vue'; +import FormSecion from '@/components/form/section.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import XTwitter from './integrations.twitter.vue'; +import XGithub from './integrations.github.vue'; +import XDiscord from './integrations.discord.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormLink, - FormInput, - FormBase, - FormGroup, - FormButton, - FormTextarea, - FormInfo, + FormFolder, + FormSecion, FormSuspense, + XTwitter, + XGithub, + XDiscord, }, emits: ['info'], @@ -58,10 +60,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue index 05b64b235c..1de297fd93 100644 --- a/packages/client/src/pages/admin/metrics.vue +++ b/packages/client/src/pages/admin/metrics.vue @@ -76,7 +76,6 @@ import MkwFederation from '../../widgets/federation.vue'; import { version, url } from '@/config'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; -import MkInstanceInfo from './instance.vue'; Chart.register( ArcElement, @@ -101,6 +100,7 @@ const alpha = (hex, a) => { return `rgba(${r}, ${g}, ${b}, ${a})`; }; import * as os from '@/os'; +import { stream } from '@/stream'; export default defineComponent({ components: { @@ -119,7 +119,7 @@ export default defineComponent({ stats: null, serverInfo: null, connection: null, - queueConnection: markRaw(os.stream.useChannel('queueStats')), + queueConnection: markRaw(stream.useChannel('queueStats')), memUsage: 0, chartCpuMem: null, chartNet: null, @@ -150,7 +150,7 @@ export default defineComponent({ os.api('admin/server-info', {}).then(res => { this.serverInfo = res; - this.connection = markRaw(os.stream.useChannel('serverStats')); + this.connection = markRaw(stream.useChannel('serverStats')); this.connection.on('stats', this.onStats); this.connection.on('statsLog', this.onStatsLog); this.connection.send('requestLog', { diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue index 8984686b5e..6c5be220f8 100644 --- a/packages/client/src/pages/admin/object-storage.vue +++ b/packages/client/src/pages/admin/object-storage.vue @@ -1,76 +1,78 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormSwitch v-model="useObjectStorage">{{ $ts.useObjectStorage }}</FormSwitch> + <div class="_formRoot"> + <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch> - <template v-if="useObjectStorage"> - <FormInput v-model="objectStorageBaseUrl"> - <span>{{ $ts.objectStorageBaseUrl }}</span> - <template #desc>{{ $ts.objectStorageBaseUrlDesc }}</template> - </FormInput> + <template v-if="useObjectStorage"> + <FormInput v-model="objectStorageBaseUrl" class="_formBlock"> + <template #label>{{ $ts.objectStorageBaseUrl }}</template> + <template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageBucket"> - <span>{{ $ts.objectStorageBucket }}</span> - <template #desc>{{ $ts.objectStorageBucketDesc }}</template> - </FormInput> + <FormInput v-model="objectStorageBucket" class="_formBlock"> + <template #label>{{ $ts.objectStorageBucket }}</template> + <template #caption>{{ $ts.objectStorageBucketDesc }}</template> + </FormInput> - <FormInput v-model="objectStoragePrefix"> - <span>{{ $ts.objectStoragePrefix }}</span> - <template #desc>{{ $ts.objectStoragePrefixDesc }}</template> - </FormInput> + <FormInput v-model="objectStoragePrefix" class="_formBlock"> + <template #label>{{ $ts.objectStoragePrefix }}</template> + <template #caption>{{ $ts.objectStoragePrefixDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageEndpoint"> - <span>{{ $ts.objectStorageEndpoint }}</span> - <template #desc>{{ $ts.objectStorageEndpointDesc }}</template> - </FormInput> + <FormInput v-model="objectStorageEndpoint" class="_formBlock"> + <template #label>{{ $ts.objectStorageEndpoint }}</template> + <template #caption>{{ $ts.objectStorageEndpointDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageRegion"> - <span>{{ $ts.objectStorageRegion }}</span> - <template #desc>{{ $ts.objectStorageRegionDesc }}</template> - </FormInput> + <FormInput v-model="objectStorageRegion" class="_formBlock"> + <template #label>{{ $ts.objectStorageRegion }}</template> + <template #caption>{{ $ts.objectStorageRegionDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageAccessKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>Access key</span> - </FormInput> + <FormSplit :min-width="280"> + <FormInput v-model="objectStorageAccessKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Access key</template> + </FormInput> - <FormInput v-model="objectStorageSecretKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>Secret key</span> - </FormInput> + <FormInput v-model="objectStorageSecretKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Secret key</template> + </FormInput> + </FormSplit> - <FormSwitch v-model="objectStorageUseSSL"> - {{ $ts.objectStorageUseSSL }} - <template #desc>{{ $ts.objectStorageUseSSLDesc }}</template> - </FormSwitch> + <FormSwitch v-model="objectStorageUseSSL" class="_formBlock"> + <template #label>{{ $ts.objectStorageUseSSL }}</template> + <template #caption>{{ $ts.objectStorageUseSSLDesc }}</template> + </FormSwitch> - <FormSwitch v-model="objectStorageUseProxy"> - {{ $ts.objectStorageUseProxy }} - <template #desc>{{ $ts.objectStorageUseProxyDesc }}</template> - </FormSwitch> + <FormSwitch v-model="objectStorageUseProxy" class="_formBlock"> + <template #label>{{ $ts.objectStorageUseProxy }}</template> + <template #caption>{{ $ts.objectStorageUseProxyDesc }}</template> + </FormSwitch> - <FormSwitch v-model="objectStorageSetPublicRead"> - {{ $ts.objectStorageSetPublicRead }} - </FormSwitch> + <FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock"> + <template #label>{{ $ts.objectStorageSetPublicRead }}</template> + </FormSwitch> - <FormSwitch v-model="objectStorageS3ForcePathStyle"> - s3ForcePathStyle - </FormSwitch> - </template> - - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock"> + <template #label>s3ForcePathStyle</template> + </FormSwitch> + </template> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormGroup from '@/components/form/group.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -79,10 +81,10 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormGroup, - FormButton, FormSuspense, + FormSplit, + FormSection, }, emits: ['info'], @@ -93,6 +95,12 @@ export default defineComponent({ title: this.$ts.objectStorage, icon: 'fas fa-cloud', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, useObjectStorage: false, objectStorageBaseUrl: null, @@ -110,10 +118,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue index eb214a21c8..6b588e88aa 100644 --- a/packages/client/src/pages/admin/other-settings.vue +++ b/packages/client/src/pages/admin/other-settings.vue @@ -1,34 +1,17 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <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 primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + none </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -37,9 +20,7 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, - FormGroup, - FormButton, + FormSection, FormSuspense, }, @@ -51,29 +32,22 @@ export default defineComponent({ title: this.$ts.other, icon: 'fas fa-cogs', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, - 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(); }); diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue index da5fc0ba6d..b8ae8ad9e1 100644 --- a/packages/client/src/pages/admin/overview.vue +++ b/packages/client/src/pages/admin/overview.vue @@ -19,7 +19,7 @@ <MkContainer :foldable="true" class="charts"> <template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template> - <div style="padding-top: 12px;"> + <div style="padding: 12px;"> <MkInstanceStats :chart-limit="500" :detailed="true"/> </div> </MkContainer> @@ -67,7 +67,6 @@ <script lang="ts"> import { computed, defineComponent, markRaw, version as vueVersion } from 'vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; import MkInstanceStats from '@/components/instance-stats.vue'; import MkButton from '@/components/ui/button.vue'; import MkSelect from '@/components/form/select.vue'; @@ -78,15 +77,14 @@ import MkQueueChart from '@/components/queue-chart.vue'; import { version, url } from '@/config'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; -import MkInstanceInfo from './instance.vue'; import XMetrics from './metrics.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as symbols from '@/symbols'; export default defineComponent({ components: { MkNumberDiff, - FormKeyValueView, MkInstanceStats, MkContainer, MkFolder, @@ -113,13 +111,11 @@ export default defineComponent({ notesComparedToThePrevDay: null, fetchJobs: () => os.api('admin/queue/deliver-delayed', {}), fetchModLogs: () => os.api('admin/show-moderation-logs', {}), - queueStatsConnection: markRaw(os.stream.useChannel('queueStats')), + queueStatsConnection: markRaw(stream.useChannel('queueStats')), } }, async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - os.api('meta', { detail: true }).then(meta => { this.meta = meta; }); @@ -160,9 +156,7 @@ export default defineComponent({ host: q }); } - os.popup(MkInstanceInfo, { - instance: instance - }, {}, 'closed'); + // TODO }, bytes, diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue index 14ef92a747..5c4fbffa0c 100644 --- a/packages/client/src/pages/admin/proxy-account.vue +++ b/packages/client/src/pages/admin/proxy-account.vue @@ -1,42 +1,32 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <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> + <MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo> + <MkKeyValue class="_formBlock"> + <template #key>{{ $ts.proxyAccount }}</template> + <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template> + </MkKeyValue> - <FormButton primary @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton> + <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import MkKeyValue from '@/components/key-value.vue'; +import FormButton from '@/components/ui/button.vue'; +import MkInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormKeyValueView, - FormInput, - FormBase, - FormGroup, + MkKeyValue, FormButton, - FormTextarea, - FormInfo, + MkInfo, FormSuspense, }, @@ -54,10 +44,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue index 37a87089cb..522210d933 100644 --- a/packages/client/src/pages/admin/queue.vue +++ b/packages/client/src/pages/admin/queue.vue @@ -1,28 +1,25 @@ <template> -<FormBase> +<MkSpacer :content-max="800"> <XQueue :connection="connection" domain="inbox"> <template #title>In</template> </XQueue> <XQueue :connection="connection" domain="deliver"> <template #title>Out</template> </XQueue> - <FormButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</FormButton> -</FormBase> + <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton> +</MkSpacer> </template> <script lang="ts"> import { defineComponent, markRaw } from 'vue'; import MkButton from '@/components/ui/button.vue'; import XQueue from './queue.chart.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, - FormButton, MkButton, XQueue, }, @@ -36,13 +33,11 @@ export default defineComponent({ icon: 'fas fa-clipboard-list', bg: 'var(--bg)', }, - connection: markRaw(os.stream.useChannel('queueStats')), + connection: markRaw(stream.useChannel('queueStats')), } }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.$nextTick(() => { this.connection.send('requestLog', { id: Math.random().toString().substr(2, 8), diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue index 3e2f1c6f26..bb840db0a2 100644 --- a/packages/client/src/pages/admin/relays.vue +++ b/packages/client/src/pages/admin/relays.vue @@ -1,32 +1,27 @@ <template> -<FormBase class="relaycxt"> - <FormButton primary @click="addRelay"><i class="fas fa-plus"></i> {{ $ts.addRelay }}</FormButton> - - <div v-for="relay in relays" :key="relay.inbox" class="_debobigegoItem"> - <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> +<MkSpacer :content-max="800"> + <div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel _block" style="padding: 16px;"> + <div>{{ relay.inbox }}</div> + <div class="status"> + <i v-if="relay.status === 'accepted'" class="fas fa-check icon accepted"></i> + <i v-else-if="relay.status === 'rejected'" class="fas fa-ban icon rejected"></i> + <i v-else class="fas fa-clock icon requesting"></i> + <span>{{ $t(`_relayStatus.${relay.status}`) }}</span> </div> + <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> </div> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; -import MkInput from '@/components/form/input.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, - FormButton, MkButton, - MkInput, }, emits: ['info'], @@ -37,6 +32,12 @@ export default defineComponent({ title: this.$ts.relays, icon: 'fas fa-globe', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: this.$ts.addRelay, + handler: this.addRelay, + }], }, relays: [], inbox: '', @@ -47,10 +48,6 @@ export default defineComponent({ this.refresh(); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async addRelay() { const { canceled, result: inbox } = await os.inputText({ @@ -94,5 +91,22 @@ export default defineComponent({ </script> <style lang="scss" scoped> +.relaycxt { + > .status { + margin: 8px 0; + + > .icon { + width: 1em; + margin-right: 0.75em; + &.accepted { + color: var(--success); + } + + &.rejected { + color: var(--error); + } + } + } +} </style> diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index adfb2e786c..d069891647 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -1,44 +1,58 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormLink to="/admin/bot-protection"> - <i class="fas fa-shield-alt"></i> {{ $ts.botProtection }} - <template v-if="enableHcaptcha" #suffix>hCaptcha</template> - <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> - <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template> - </FormLink> + <div class="_formRoot"> + <FormFolder class="_formBlock"> + <template #icon><i class="fas fa-shield-alt"></i></template> + <template #label>{{ $ts.botProtection }}</template> + <template v-if="enableHcaptcha" #suffix>hCaptcha</template> + <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> + <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template> - <FormSwitch v-model="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch> + <XBotProtection/> + </FormFolder> - <FormSwitch v-model="emailRequiredForSignup">{{ $ts.emailRequiredForSignup }}</FormSwitch> + <FormFolder class="_formBlock"> + <template #label>Summaly Proxy</template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <div class="_formRoot"> + <FormInput v-model="summalyProxy" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>Summaly Proxy URL</template> + </FormInput> + + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> + </FormFolder> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormFolder from '@/components/form/folder.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSection from '@/components/form/section.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import XBotProtection from './bot-protection.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormLink, + FormFolder, FormSwitch, - FormBase, - FormGroup, - FormButton, FormInfo, + FormSection, FormSuspense, + FormButton, + FormInput, + XBotProtection, }, emits: ['info'], @@ -50,30 +64,23 @@ export default defineComponent({ icon: 'fas fa-lock', bg: 'var(--bg)', }, + summalyProxy: '', 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.summalyProxy = meta.summalyProxy; 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, + summalyProxy: this.summalyProxy, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/service-worker.vue b/packages/client/src/pages/admin/service-worker.vue deleted file mode 100644 index f34cb03e4e..0000000000 --- a/packages/client/src/pages/admin/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 primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; -import { fetchInstance } from '@/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/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index d88445abdb..a4bac93834 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -1,72 +1,146 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormInput v-model="name"> - <span>{{ $ts.instanceName }}</span> - </FormInput> + <div class="_formRoot"> + <FormInput v-model="name" class="_formBlock"> + <template #label>{{ $ts.instanceName }}</template> + </FormInput> - <FormTextarea v-model="description"> - <span>{{ $ts.instanceDescription }}</span> - </FormTextarea> + <FormTextarea v-model="description" class="_formBlock"> + <template #label>{{ $ts.instanceDescription }}</template> + </FormTextarea> - <FormInput v-model="iconUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.iconUrl }}</span> - </FormInput> + <FormInput v-model="iconUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.iconUrl }}</template> + </FormInput> - <FormInput v-model="bannerUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.bannerUrl }}</span> - </FormInput> + <FormInput v-model="bannerUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.bannerUrl }}</template> + </FormInput> - <FormInput v-model="backgroundImageUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.backgroundImageUrl }}</span> - </FormInput> + <FormInput v-model="backgroundImageUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.backgroundImageUrl }}</template> + </FormInput> - <FormInput v-model="tosUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.tosUrl }}</span> - </FormInput> + <FormInput v-model="tosUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.tosUrl }}</template> + </FormInput> - <FormInput v-model="maintainerName"> - <span>{{ $ts.maintainerName }}</span> - </FormInput> + <FormSplit :min-width="300"> + <FormInput v-model="maintainerName" class="_formBlock"> + <template #label>{{ $ts.maintainerName }}</template> + </FormInput> - <FormInput v-model="maintainerEmail" type="email"> - <template #prefix><i class="fas fa-envelope"></i></template> - <span>{{ $ts.maintainerEmail }}</span> - </FormInput> + <FormInput v-model="maintainerEmail" type="email" class="_formBlock"> + <template #prefix><i class="fas fa-envelope"></i></template> + <template #label>{{ $ts.maintainerEmail }}</template> + </FormInput> + </FormSplit> - <FormTextarea v-model="pinnedUsers"> - <span>{{ $ts.pinnedUsers }}</span> - <template #desc>{{ $ts.pinnedUsersDescription }}</template> - </FormTextarea> + <FormTextarea v-model="pinnedUsers" class="_formBlock"> + <template #label>{{ $ts.pinnedUsers }}</template> + <template #caption>{{ $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> + <FormInput v-model="maxNoteTextLength" type="number" class="_formBlock"> + <template #prefix><i class="fas fa-pencil-alt"></i></template> + <template #label>{{ $ts.maxNoteTextLength }}</template> + </FormInput> - <FormSwitch v-model="enableLocalTimeline">{{ $ts.enableLocalTimeline }}</FormSwitch> - <FormSwitch v-model="enableGlobalTimeline">{{ $ts.enableGlobalTimeline }}</FormSwitch> - <FormInfo>{{ $ts.disablingTimelinesInfo }}</FormInfo> + <FormSection> + <FormSwitch v-model="enableRegistration" class="_formBlock"> + <template #label>{{ $ts.enableRegistration }}</template> + </FormSwitch> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormSwitch v-model="emailRequiredForSignup" class="_formBlock"> + <template #label>{{ $ts.emailRequiredForSignup }}</template> + </FormSwitch> + </FormSection> + + <FormSection> + <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch> + <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch> + <FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo> + </FormSection> + + <FormSection> + <template #label>{{ $ts.files }}</template> + + <FormSwitch v-model="cacheRemoteFiles" class="_formBlock"> + <template #label>{{ $ts.cacheRemoteFiles }}</template> + <template #caption>{{ $ts.cacheRemoteFilesDescription }}</template> + </FormSwitch> + + <FormSwitch v-model="proxyRemoteFiles" class="_formBlock"> + <template #label>{{ $ts.proxyRemoteFiles }}</template> + <template #caption>{{ $ts.proxyRemoteFilesDescription }}</template> + </FormSwitch> + + <FormSplit :min-width="280"> + <FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock"> + <template #label>{{ $ts.driveCapacityPerLocalAccount }}</template> + <template #suffix>MB</template> + <template #caption>{{ $ts.inMb }}</template> + </FormInput> + + <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock"> + <template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template> + <template #suffix>MB</template> + <template #caption>{{ $ts.inMb }}</template> + </FormInput> + </FormSplit> + </FormSection> + + <FormSection> + <template #label>ServiceWorker</template> + + <FormSwitch v-model="enableServiceWorker" class="_formBlock"> + <template #label>{{ $ts.enableServiceworker }}</template> + <template #caption>{{ $ts.serviceworkerInfo }}</template> + </FormSwitch> + + <template v-if="enableServiceWorker"> + <FormInput v-model="swPublicKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Public key</template> + </FormInput> + + <FormInput v-model="swPrivateKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Private key</template> + </FormInput> + </template> + </FormSection> + + <FormSection> + <template #label>DeepL Translation</template> + + <FormInput v-model="deeplAuthKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>DeepL Auth Key</template> + </FormInput> + <FormSwitch v-model="deeplIsPro" class="_formBlock"> + <template #label>Pro account</template> + </FormSwitch> + </FormSection> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -75,12 +149,11 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, - FormGroup, - FormButton, + FormSuspense, FormTextarea, FormInfo, - FormSuspense, + FormSection, + FormSplit, }, emits: ['info'], @@ -91,6 +164,12 @@ export default defineComponent({ title: this.$ts.general, icon: 'fas fa-cog', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, name: null, description: null, @@ -104,13 +183,20 @@ export default defineComponent({ enableLocalTimeline: false, enableGlobalTimeline: false, pinnedUsers: '', + cacheRemoteFiles: false, + proxyRemoteFiles: false, + localDriveCapacityMb: 0, + remoteDriveCapacityMb: 0, + enableRegistration: false, + emailRequiredForSignup: false, + enableServiceWorker: false, + swPublicKey: null, + swPrivateKey: null, + deeplAuthKey: '', + deeplIsPro: false, } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); @@ -126,6 +212,17 @@ export default defineComponent({ this.enableLocalTimeline = !meta.disableLocalTimeline; this.enableGlobalTimeline = !meta.disableGlobalTimeline; this.pinnedUsers = meta.pinnedUsers.join('\n'); + this.cacheRemoteFiles = meta.cacheRemoteFiles; + this.proxyRemoteFiles = meta.proxyRemoteFiles; + this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; + this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; + this.enableRegistration = !meta.disableRegistration; + this.emailRequiredForSignup = meta.emailRequiredForSignup; + this.enableServiceWorker = meta.enableServiceWorker; + this.swPublicKey = meta.swPublickey; + this.swPrivateKey = meta.swPrivateKey; + this.deeplAuthKey = meta.deeplAuthKey; + this.deeplIsPro = meta.deeplIsPro; }, save() { @@ -142,6 +239,17 @@ export default defineComponent({ disableLocalTimeline: !this.enableLocalTimeline, disableGlobalTimeline: !this.enableGlobalTimeline, pinnedUsers: this.pinnedUsers.split('\n'), + cacheRemoteFiles: this.cacheRemoteFiles, + proxyRemoteFiles: this.proxyRemoteFiles, + localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), + remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), + disableRegistration: !this.enableRegistration, + emailRequiredForSignup: this.emailRequiredForSignup, + enableServiceWorker: this.enableServiceWorker, + swPublicKey: this.swPublicKey, + swPrivateKey: this.swPrivateKey, + deeplAuthKey: this.deeplAuthKey, + deeplIsPro: this.deeplIsPro, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue index e7a3437167..03e155ddcf 100644 --- a/packages/client/src/pages/admin/users.vue +++ b/packages/client/src/pages/admin/users.vue @@ -30,7 +30,7 @@ <template #prefix>@</template> <template #label>{{ $ts.username }}</template> </MkInput> - <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'" @update:modelValue="$refs.users.reload()"> + <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()"> <template #prefix>@</template> <template #label>{{ $ts.host }}</template> </MkInput> @@ -62,7 +62,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -110,36 +110,20 @@ export default defineComponent({ searchUsername: '', searchHost: '', pagination: { - endpoint: 'admin/show-users', + endpoint: 'admin/show-users' as const, limit: 10, - params: () => ({ + params: computed(() => ({ 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, |