NOCHANGELOG Refactor of moderation detail pages
This commit is contained in:
parent
cd18b88799
commit
36030c68ee
|
@ -5,6 +5,8 @@ import { ref } from 'vue'
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'action-done', data: any): void
|
(e: 'action-done', data: any): void
|
||||||
(e: 'action-error', error: BackendError): void
|
(e: 'action-error', error: BackendError): void
|
||||||
|
@ -34,10 +36,13 @@ const ajaxCall = async () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<Button
|
||||||
:class="['ui', {loading: isLoading}, 'button']"
|
secondary
|
||||||
|
low-height
|
||||||
|
:class="{loading: isLoading}"
|
||||||
|
icon="bi-arrow-clockwise"
|
||||||
@click="ajaxCall"
|
@click="ajaxCall"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'update:modelValue', value: boolean): void
|
(e: 'update:modelValue', value: boolean): void
|
||||||
}
|
}
|
||||||
|
@ -18,8 +20,11 @@ const value = useVModel(props, 'modelValue', emit)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a
|
<Button
|
||||||
role="button"
|
secondary
|
||||||
|
low-height
|
||||||
|
tiny
|
||||||
|
:icon="value ? 'bi-chevron-expand' : 'bi-chevron-contract'"
|
||||||
class="collapse link"
|
class="collapse link"
|
||||||
@click.prevent="value = !value"
|
@click.prevent="value = !value"
|
||||||
>
|
>
|
||||||
|
@ -30,5 +35,5 @@ const value = useVModel(props, 'modelValue', emit)
|
||||||
{{ t('components.common.CollapseLink.button.collapse') }}
|
{{ t('components.common.CollapseLink.button.collapse') }}
|
||||||
</span>
|
</span>
|
||||||
<i :class="[{ down: !value, right: value }, 'angle', 'icon']" />
|
<i :class="[{ down: !value, right: value }, 'angle', 'icon']" />
|
||||||
</a>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import useMarkdown from '~/composables/useMarkdown'
|
import useMarkdown from '~/composables/useMarkdown'
|
||||||
|
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'update'): void
|
(e: 'update'): void
|
||||||
}
|
}
|
||||||
|
@ -24,21 +26,21 @@ const summary = useMarkdown(() => props.object.summary)
|
||||||
<div>
|
<div>
|
||||||
<slot />
|
<slot />
|
||||||
<p>
|
<p>
|
||||||
<i class="clock outline icon" /><human-date :date="object.creation_date" />
|
<i class="bi bi-clock" /><human-date :date="object.creation_date" />
|
||||||
<i class="user icon" />{{ object.actor }}
|
<i class="bi bi-person" />{{ object.actor }}
|
||||||
<template v-if="object.is_active">
|
<template v-if="object.is_active">
|
||||||
<i class="play icon" />
|
<i class="bi bi-play" />
|
||||||
{{ t('components.manage.moderation.InstancePolicyCard.status.enabled') }}
|
{{ t('components.manage.moderation.InstancePolicyCard.status.enabled') }}
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!object.is_active">
|
<template v-if="!object.is_active">
|
||||||
<i class="pause icon" />
|
<i class="bi bi-pause" />
|
||||||
{{ t('components.manage.moderation.InstancePolicyCard.status.paused') }}
|
{{ t('components.manage.moderation.InstancePolicyCard.status.paused') }}
|
||||||
</template>
|
</template>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<p><strong>{{ t('components.manage.moderation.InstancePolicyCard.header.rule') }}</strong></p>
|
<p><strong>{{ t('components.manage.moderation.InstancePolicyCard.header.rule') }}</strong></p>
|
||||||
<p v-if="object.block_all">
|
<p v-if="object.block_all">
|
||||||
<i class="ban icon" />
|
<i class="bi bi-ban" />
|
||||||
{{ t('components.manage.moderation.InstancePolicyCard.label.blockAll') }}
|
{{ t('components.manage.moderation.InstancePolicyCard.label.blockAll') }}
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
|
@ -49,7 +51,7 @@ const summary = useMarkdown(() => props.object.summary)
|
||||||
v-if="object.silence_activity"
|
v-if="object.silence_activity"
|
||||||
class="ui item"
|
class="ui item"
|
||||||
>
|
>
|
||||||
<i class="feed icon" />
|
<i class="bi bi-rss-fill" />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ t('components.manage.moderation.InstancePolicyCard.label.muteActivity') }}
|
{{ t('components.manage.moderation.InstancePolicyCard.label.muteActivity') }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,7 +60,7 @@ const summary = useMarkdown(() => props.object.summary)
|
||||||
v-if="object.silence_notifications"
|
v-if="object.silence_notifications"
|
||||||
class="ui item"
|
class="ui item"
|
||||||
>
|
>
|
||||||
<i class="bell icon" />
|
<i class="bi bi-bell-fill" />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ t('components.manage.moderation.InstancePolicyCard.label.muteNotifications') }}
|
{{ t('components.manage.moderation.InstancePolicyCard.label.muteNotifications') }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,7 +69,7 @@ const summary = useMarkdown(() => props.object.summary)
|
||||||
v-if="object.reject_media"
|
v-if="object.reject_media"
|
||||||
class="ui item"
|
class="ui item"
|
||||||
>
|
>
|
||||||
<i class="file icon" />
|
<i class="bi bi-file-earmark-fill" />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ t('components.manage.moderation.InstancePolicyCard.label.rejectMedia') }}
|
{{ t('components.manage.moderation.InstancePolicyCard.label.rejectMedia') }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,12 +82,12 @@ const summary = useMarkdown(() => props.object.summary)
|
||||||
<sanitized-html :html="summary" />
|
<sanitized-html :html="summary" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ui hidden divider" />
|
<div class="ui hidden divider" />
|
||||||
<button
|
<Button
|
||||||
class="ui right floated labeled icon button"
|
destructive
|
||||||
|
icon="bi-pencil"
|
||||||
@click="emit('update')"
|
@click="emit('update')"
|
||||||
>
|
>
|
||||||
<i class="edit icon" />
|
|
||||||
{{ t('components.manage.moderation.InstancePolicyCard.button.edit') }}
|
{{ t('components.manage.moderation.InstancePolicyCard.button.edit') }}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -9,6 +9,9 @@ import axios from 'axios'
|
||||||
|
|
||||||
import DangerousButton from '~/components/common/DangerousButton.vue'
|
import DangerousButton from '~/components/common/DangerousButton.vue'
|
||||||
|
|
||||||
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'save', data: InstancePolicy): void
|
(e: 'save', data: InstancePolicy): void
|
||||||
(e: 'delete'): void
|
(e: 'delete'): void
|
||||||
|
@ -119,8 +122,8 @@ const remove = async () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form
|
<Layout
|
||||||
class="ui form"
|
form
|
||||||
@submit.prevent="createOrUpdate"
|
@submit.prevent="createOrUpdate"
|
||||||
>
|
>
|
||||||
<h3 class="ui header">
|
<h3 class="ui header">
|
||||||
|
@ -135,10 +138,9 @@ const remove = async () => {
|
||||||
{{ t('components.manage.moderation.InstancePolicyForm.header.addRule') }}
|
{{ t('components.manage.moderation.InstancePolicyForm.header.addRule') }}
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
<Alert
|
||||||
v-if="errors && errors.length > 0"
|
v-if="errors && errors.length > 0"
|
||||||
role="alert"
|
red
|
||||||
class="ui negative message"
|
|
||||||
>
|
>
|
||||||
<h4 class="header">
|
<h4 class="header">
|
||||||
{{ t('components.manage.moderation.InstancePolicyForm.header.failure') }}
|
{{ t('components.manage.moderation.InstancePolicyForm.header.failure') }}
|
||||||
|
@ -151,7 +153,7 @@ const remove = async () => {
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</Alert>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="object"
|
v-if="object"
|
||||||
|
@ -220,37 +222,39 @@ const remove = async () => {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui hidden divider" />
|
<Layout flex>
|
||||||
<button
|
<Button
|
||||||
class="ui basic left floated button"
|
primary
|
||||||
@click.prevent="emit('cancel')"
|
@click.prevent="emit('cancel')"
|
||||||
>
|
>
|
||||||
{{ t('components.manage.moderation.InstancePolicyForm.button.cancel') }}
|
{{ t('components.manage.moderation.InstancePolicyForm.button.cancel') }}
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
:class="['ui', 'right', 'floated', 'success', {'disabled loading': isLoading}, 'button']"
|
primary
|
||||||
:disabled="isLoading"
|
:class="{'disabled loading': isLoading}"
|
||||||
>
|
:disabled="isLoading"
|
||||||
<span v-if="object">
|
>
|
||||||
{{ t('components.manage.moderation.InstancePolicyForm.button.update') }}
|
<span v-if="object">
|
||||||
</span>
|
{{ t('components.manage.moderation.InstancePolicyForm.button.update') }}
|
||||||
<span v-else>
|
</span>
|
||||||
{{ t('components.manage.moderation.InstancePolicyForm.button.create') }}
|
<span v-else>
|
||||||
</span>
|
{{ t('components.manage.moderation.InstancePolicyForm.button.create') }}
|
||||||
</button>
|
</span>
|
||||||
<dangerous-button
|
</Button>
|
||||||
v-if="object"
|
<dangerous-button
|
||||||
style="float: right;"
|
v-if="object"
|
||||||
:title="t('components.manage.moderation.InstancePolicyForm.modal.delete.header')"
|
style="float: right;"
|
||||||
@confirm="remove"
|
:title="t('components.manage.moderation.InstancePolicyForm.modal.delete.header')"
|
||||||
>
|
@confirm="remove"
|
||||||
{{ t('components.manage.moderation.InstancePolicyForm.button.delete') }}
|
>
|
||||||
<template #modal-content>
|
{{ t('components.manage.moderation.InstancePolicyForm.button.delete') }}
|
||||||
{{ t('components.manage.moderation.InstancePolicyForm.modal.delete.content.warning') }}
|
<template #modal-content>
|
||||||
</template>
|
{{ t('components.manage.moderation.InstancePolicyForm.modal.delete.content.warning') }}
|
||||||
<template #modal-confirm>
|
</template>
|
||||||
{{ t('components.manage.moderation.InstancePolicyForm.button.confirm') }}
|
<template #modal-confirm>
|
||||||
</template>
|
{{ t('components.manage.moderation.InstancePolicyForm.button.confirm') }}
|
||||||
</dangerous-button>
|
</template>
|
||||||
</form>
|
</dangerous-button>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -5,6 +5,8 @@ import axios from 'axios'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'created', note: Note): void
|
(e: 'created', note: Note): void
|
||||||
}
|
}
|
||||||
|
@ -76,12 +78,13 @@ const submit = async () => {
|
||||||
:placeholder="labels.summaryPlaceholder"
|
:placeholder="labels.summaryPlaceholder"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<Button
|
||||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
|
primary
|
||||||
|
:class="[{'loading': isLoading}, 'right', 'floated']"
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
>
|
>
|
||||||
{{ t('components.manage.moderation.NoteForm.button.add') }}
|
{{ t('components.manage.moderation.NoteForm.button.add') }}
|
||||||
</button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -11,6 +11,10 @@ import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
import DangerousButton from '~/components/common/DangerousButton.vue'
|
import DangerousButton from '~/components/common/DangerousButton.vue'
|
||||||
|
|
||||||
|
import Alert from '~/components/ui/Alert.vue'
|
||||||
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'deleted', uuid: string): void
|
(e: 'deleted', uuid: string): void
|
||||||
}
|
}
|
||||||
|
@ -40,48 +44,53 @@ const remove = async (note: Note) => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ui feed">
|
<div class="ui feed">
|
||||||
<div
|
<Alert
|
||||||
v-for="note in notes"
|
v-for="note in notes"
|
||||||
:key="note.uuid"
|
:key="note.uuid"
|
||||||
|
blue
|
||||||
class="event"
|
class="event"
|
||||||
>
|
>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<i class="comment outline icon" />
|
<i
|
||||||
|
class="bi bi-chat-dots-fill"
|
||||||
|
style="font-size: 22px;"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<Spacer :size="16" />
|
||||||
<div class="summary">
|
<Layout
|
||||||
<actor-link
|
flex
|
||||||
:admin="true"
|
gap-16
|
||||||
:actor="note.author"
|
class="summary"
|
||||||
/>
|
>
|
||||||
<div class="date">
|
<actor-link
|
||||||
<human-date :date="note.creation_date" />
|
:admin="true"
|
||||||
</div>
|
:actor="note.author"
|
||||||
|
/>
|
||||||
|
<div class="date">
|
||||||
|
<human-date :date="note.creation_date" />
|
||||||
</div>
|
</div>
|
||||||
<div class="extra text">
|
</Layout>
|
||||||
<expandable-div :content="note.summary">
|
<expandable-div :content="note.summary">
|
||||||
<sanitized-html :html="useMarkdownRaw(note.summary ?? '')" />
|
<sanitized-html :html="useMarkdownRaw(note.summary ?? '')" />
|
||||||
</expandable-div>
|
</expandable-div>
|
||||||
</div>
|
<template #actions>
|
||||||
<div class="meta">
|
<dangerous-button
|
||||||
<dangerous-button
|
:is-loading="isLoading"
|
||||||
:is-loading="isLoading"
|
low-height
|
||||||
low-height
|
icon="bi-trash"
|
||||||
icon="bi-trash"
|
:title="t('components.manage.moderation.NotesThread.modal.delete.header')"
|
||||||
:title="t('components.manage.moderation.NotesThread.modal.delete.header')"
|
@confirm="remove(note)"
|
||||||
@confirm="remove(note)"
|
>
|
||||||
>
|
{{ t('components.manage.moderation.NotesThread.button.delete') }}
|
||||||
{{ t('components.manage.moderation.NotesThread.button.delete') }}
|
|
||||||
|
|
||||||
<template #modal-content>
|
<template #modal-content>
|
||||||
{{ t('components.manage.moderation.NotesThread.modal.delete.content.warning') }}
|
{{ t('components.manage.moderation.NotesThread.modal.delete.content.warning') }}
|
||||||
</template>
|
</template>
|
||||||
<template #modal-confirm>
|
<template #modal-confirm>
|
||||||
{{ t('components.manage.moderation.NotesThread.button.delete') }}
|
{{ t('components.manage.moderation.NotesThread.button.delete') }}
|
||||||
</template>
|
</template>
|
||||||
</dangerous-button>
|
</dangerous-button>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</Alert>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -18,6 +18,11 @@ import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
import useMarkdown from '~/composables/useMarkdown'
|
import useMarkdown from '~/composables/useMarkdown'
|
||||||
import useLogger from '~/composables/useLogger'
|
import useLogger from '~/composables/useLogger'
|
||||||
|
|
||||||
|
import Card from '~/components/ui/Card.vue'
|
||||||
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
import Link from '~/components/ui/Link.vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'updated', updating: { type: string }): void
|
(e: 'updated', updating: { type: string }): void
|
||||||
(e: 'handled', isHandled: boolean): void
|
(e: 'handled', isHandled: boolean): void
|
||||||
|
@ -110,7 +115,7 @@ const update = async (type: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const isCollapsed = ref(false)
|
const isCollapsed = ref(true)
|
||||||
const resolveReport = async (isHandled: boolean) => {
|
const resolveReport = async (isHandled: boolean) => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
|
@ -140,136 +145,124 @@ const handleRemovedNote = (uuid: string) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ui fluid report card">
|
<Card
|
||||||
<div class="content">
|
:title="t('components.manage.moderation.ReportCard.link.report', {id: obj.uuid.substring(0, 8)})"
|
||||||
<h4 class="header">
|
:width="isCollapsed ? '350px' : ''"
|
||||||
<router-link :to="{name: 'manage.moderation.reports.detail', params: {id: obj.uuid}}">
|
:full="!isCollapsed"
|
||||||
{{ t('components.manage.moderation.ReportCard.link.report', {id: obj.uuid.substring(0, 8)}) }}
|
solid
|
||||||
</router-link>
|
:red="!obj.is_handled && isCollapsed"
|
||||||
<collapse-link
|
:green="obj.is_handled && isCollapsed"
|
||||||
v-model="isCollapsed"
|
>
|
||||||
class="right floated"
|
<template #topright>
|
||||||
/>
|
<collapse-link
|
||||||
</h4>
|
v-model="isCollapsed"
|
||||||
<div class="content">
|
class="right floated"
|
||||||
<div class="ui hidden divider" />
|
/>
|
||||||
<div class="ui stackable two column grid">
|
</template>
|
||||||
<div class="column">
|
<table class="ui very basic unstackable table">
|
||||||
<table class="ui very basic unstackable table">
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr>
|
<td>
|
||||||
<td>
|
{{ t('components.manage.moderation.ReportCard.table.report.submittedBy') }}
|
||||||
{{ t('components.manage.moderation.ReportCard.table.report.submittedBy') }}
|
</td>
|
||||||
</td>
|
<td>
|
||||||
<td>
|
<div v-if="obj.submitter">
|
||||||
<div v-if="obj.submitter">
|
<actor-link
|
||||||
<actor-link
|
:admin="true"
|
||||||
:admin="true"
|
:actor="obj.submitter"
|
||||||
:actor="obj.submitter"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div v-else-if="obj.submitter_email">
|
||||||
<div v-else-if="obj.submitter_email">
|
{{ obj.submitter_email }}
|
||||||
{{ obj.submitter_email }}
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>
|
||||||
<td>
|
{{ t('components.manage.moderation.ReportCard.table.report.category') }}
|
||||||
{{ t('components.manage.moderation.ReportCard.table.report.category') }}
|
</td>
|
||||||
</td>
|
<td>
|
||||||
<td>
|
<report-category-dropdown
|
||||||
<report-category-dropdown
|
v-model="obj.type"
|
||||||
v-model="obj.type"
|
@update:model-value="update($event)"
|
||||||
@update:model-value="update($event)"
|
>
|
||||||
>
|
 
|
||||||
 
|
<action-feedback :is-loading="updating.type" />
|
||||||
<action-feedback :is-loading="updating.type" />
|
</report-category-dropdown>
|
||||||
</report-category-dropdown>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>
|
||||||
<td>
|
{{ t('components.manage.moderation.ReportCard.table.report.creationDate') }}
|
||||||
{{ t('components.manage.moderation.ReportCard.table.report.creationDate') }}
|
</td>
|
||||||
</td>
|
<td>
|
||||||
<td>
|
<human-date
|
||||||
<human-date
|
:date="obj.creation_date"
|
||||||
:date="obj.creation_date"
|
:icon="true"
|
||||||
:icon="true"
|
/>
|
||||||
/>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
</tbody>
|
<td>
|
||||||
</table>
|
{{ t('components.manage.moderation.ReportCard.table.status.status') }}
|
||||||
</div>
|
</td>
|
||||||
<div class="column">
|
<td v-if="obj.is_handled">
|
||||||
<table class="ui very basic unstackable table">
|
<span v-if="obj.is_handled">
|
||||||
<tbody>
|
<i class="success check icon" />
|
||||||
<tr>
|
{{ t('components.manage.moderation.ReportCard.table.status.resolved') }}
|
||||||
<td>
|
</span>
|
||||||
{{ t('components.manage.moderation.ReportCard.table.status.status') }}
|
</td>
|
||||||
</td>
|
<td v-else>
|
||||||
<td v-if="obj.is_handled">
|
<i class="danger x icon" />
|
||||||
<span v-if="obj.is_handled">
|
{{ t('components.manage.moderation.ReportCard.table.status.unresolved') }}
|
||||||
<i class="success check icon" />
|
</td>
|
||||||
{{ t('components.manage.moderation.ReportCard.table.status.resolved') }}
|
</tr>
|
||||||
</span>
|
<tr>
|
||||||
</td>
|
<td>
|
||||||
<td v-else>
|
{{ t('components.manage.moderation.ReportCard.table.status.assignedTo') }}
|
||||||
<i class="danger x icon" />
|
</td>
|
||||||
{{ t('components.manage.moderation.ReportCard.table.status.unresolved') }}
|
<td>
|
||||||
</td>
|
<div v-if="obj.assigned_to">
|
||||||
</tr>
|
<actor-link
|
||||||
<tr>
|
:admin="true"
|
||||||
<td>
|
:actor="obj.assigned_to"
|
||||||
{{ t('components.manage.moderation.ReportCard.table.status.assignedTo') }}
|
/>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<span v-else>
|
||||||
<div v-if="obj.assigned_to">
|
{{ t('components.manage.moderation.ReportCard.notApplicable') }}
|
||||||
<actor-link
|
</span>
|
||||||
:admin="true"
|
</td>
|
||||||
:actor="obj.assigned_to"
|
</tr>
|
||||||
/>
|
<tr>
|
||||||
</div>
|
<td>
|
||||||
<span v-else>
|
{{ t('components.manage.moderation.ReportCard.table.status.resolutionDate') }}
|
||||||
{{ t('components.manage.moderation.ReportCard.notApplicable') }}
|
</td>
|
||||||
</span>
|
<td>
|
||||||
</td>
|
<human-date
|
||||||
</tr>
|
v-if="obj.handled_date"
|
||||||
<tr>
|
:date="obj.handled_date"
|
||||||
<td>
|
:icon="true"
|
||||||
{{ t('components.manage.moderation.ReportCard.table.status.resolutionDate') }}
|
/>
|
||||||
</td>
|
<span v-else>
|
||||||
<td>
|
{{ t('components.manage.moderation.ReportCard.notApplicable') }}
|
||||||
<human-date
|
</span>
|
||||||
v-if="obj.handled_date"
|
</td>
|
||||||
:date="obj.handled_date"
|
</tr>
|
||||||
:icon="true"
|
<tr>
|
||||||
/>
|
<td>
|
||||||
<span v-else>
|
{{ t('components.manage.moderation.ReportCard.table.status.internalNotes') }}
|
||||||
{{ t('components.manage.moderation.ReportCard.notApplicable') }}
|
</td>
|
||||||
</span>
|
<td>
|
||||||
</td>
|
<i class="comment icon" />
|
||||||
</tr>
|
{{ obj.notes.length }}
|
||||||
<tr>
|
</td>
|
||||||
<td>
|
</tr>
|
||||||
{{ t('components.manage.moderation.ReportCard.table.status.internalNotes') }}
|
</tbody>
|
||||||
</td>
|
</table>
|
||||||
<td>
|
<template
|
||||||
<i class="comment icon" />
|
|
||||||
{{ obj.notes.length }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="!isCollapsed"
|
v-if="!isCollapsed"
|
||||||
class="main content"
|
|
||||||
>
|
>
|
||||||
<div class="ui stackable two column grid">
|
<Layout flex>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h3>
|
<h3>
|
||||||
{{ t('components.manage.moderation.ReportCard.header.message') }}
|
{{ t('components.manage.moderation.ReportCard.header.message') }}
|
||||||
|
@ -286,29 +279,34 @@ const handleRemovedNote = (uuid: string) => {
|
||||||
<h3>
|
<h3>
|
||||||
{{ t('components.manage.moderation.ReportCard.header.reportedObject') }}
|
{{ t('components.manage.moderation.ReportCard.header.reportedObject') }}
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
<Alert
|
||||||
v-if="!obj.target"
|
v-if="!obj.target"
|
||||||
role="alert"
|
red
|
||||||
class="ui warning message"
|
|
||||||
>
|
>
|
||||||
{{ t('components.manage.moderation.ReportCard.warning.objectDeleted') }}
|
{{ t('components.manage.moderation.ReportCard.warning.objectDeleted') }}
|
||||||
</div>
|
</Alert>
|
||||||
<router-link
|
<Layout flex>
|
||||||
v-if="target && configs[target.type].urls.getDetail"
|
<Link
|
||||||
class="ui basic button"
|
v-if="target && configs[target.type].urls.getDetail"
|
||||||
:to="configs[target.type].urls.getDetail?.(obj.target_state) ?? '/'"
|
solid
|
||||||
>
|
secondary
|
||||||
<i class="eye icon" />
|
icon="bi-eye"
|
||||||
{{ t('components.manage.moderation.ReportCard.link.publicPage') }}
|
low-height
|
||||||
</router-link>
|
:to="configs[target.type].urls.getDetail?.(obj.target_state) ?? '/'"
|
||||||
<router-link
|
>
|
||||||
v-if="target && configs[target.type].urls.getAdminDetail"
|
{{ t('components.manage.moderation.ReportCard.link.publicPage') }}
|
||||||
class="ui basic button"
|
</Link>
|
||||||
:to="configs[target.type].urls.getAdminDetail?.(obj.target_state) ?? '/'"
|
<Link
|
||||||
>
|
v-if="target && configs[target.type].urls.getAdminDetail"
|
||||||
<i class="wrench icon" />
|
solid
|
||||||
{{ t('components.manage.moderation.ReportCard.link.moderation') }}
|
secondary
|
||||||
</router-link>
|
icon="bi-wrench"
|
||||||
|
low-height
|
||||||
|
:to="configs[target.type].urls.getAdminDetail?.(obj.target_state) ?? '/'"
|
||||||
|
>
|
||||||
|
{{ t('components.manage.moderation.ReportCard.link.moderation') }}
|
||||||
|
</Link>
|
||||||
|
</Layout>
|
||||||
<table class="ui very basic unstackable table">
|
<table class="ui very basic unstackable table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-if="target">
|
<tr v-if="target">
|
||||||
|
@ -369,9 +367,9 @@ const handleRemovedNote = (uuid: string) => {
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-else-if="obj.target_state.domain">
|
<tr v-else-if="obj.target_state.domain">
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="{name: 'manage.moderation.domains.detail', params: { id: obj.target_state.domain }}">
|
<Link :to="{name: 'manage.moderation.domains.detail', params: { id: obj.target_state.domain }}">
|
||||||
{{ t('components.manage.moderation.ReportCard.table.object.domain') }}
|
{{ t('components.manage.moderation.ReportCard.table.object.domain') }}
|
||||||
</router-link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ obj.target_state.domain }}
|
{{ obj.target_state.domain }}
|
||||||
|
@ -405,65 +403,61 @@ const handleRemovedNote = (uuid: string) => {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</Layout>
|
||||||
<div class="ui stackable two column grid">
|
<h3>
|
||||||
<div class="column">
|
{{ t('components.manage.moderation.ReportCard.header.notes') }}
|
||||||
<h3>
|
</h3>
|
||||||
{{ t('components.manage.moderation.ReportCard.header.notes') }}
|
<notes-thread
|
||||||
</h3>
|
:notes="obj.notes"
|
||||||
<notes-thread
|
@deleted="handleRemovedNote($event)"
|
||||||
:notes="obj.notes"
|
/>
|
||||||
@deleted="handleRemovedNote($event)"
|
<note-form
|
||||||
/>
|
:target="{type: 'report', uuid: obj.uuid}"
|
||||||
<note-form
|
@created="obj.notes.push($event)"
|
||||||
:target="{type: 'report', uuid: obj.uuid}"
|
/>
|
||||||
@created="obj.notes.push($event)"
|
<h3>
|
||||||
/>
|
{{ t('components.manage.moderation.ReportCard.header.actions') }}
|
||||||
</div>
|
</h3>
|
||||||
<div class="column">
|
<Layout flex>
|
||||||
<h3>
|
<Button
|
||||||
{{ t('components.manage.moderation.ReportCard.header.actions') }}
|
v-if="obj.is_handled === false"
|
||||||
</h3>
|
:class="{loading: isLoading}"
|
||||||
<div class="ui labelled icon basic buttons">
|
primary
|
||||||
<button
|
icon="bi-check"
|
||||||
v-if="obj.is_handled === false"
|
@click="resolveReport(true)"
|
||||||
:class="['ui', {loading: isLoading}, 'button']"
|
>
|
||||||
@click="resolveReport(true)"
|
{{ t('components.manage.moderation.ReportCard.button.resolve') }}
|
||||||
>
|
</Button>
|
||||||
<i class="success check icon" />
|
<Button
|
||||||
{{ t('components.manage.moderation.ReportCard.button.resolve') }}
|
v-if="obj.is_handled === true"
|
||||||
</button>
|
:class="{loading: isLoading}"
|
||||||
<button
|
secondary
|
||||||
v-if="obj.is_handled === true"
|
icon="bi-arrow-counterclockwise"
|
||||||
:class="['ui', {loading: isLoading}, 'button']"
|
@click="resolveReport(false)"
|
||||||
@click="resolveReport(false)"
|
>
|
||||||
>
|
{{ t('components.manage.moderation.ReportCard.button.unresolve') }}
|
||||||
<i class="warning redo icon" />
|
</Button>
|
||||||
{{ t('components.manage.moderation.ReportCard.button.unresolve') }}
|
<template
|
||||||
</button>
|
v-for="action in actions"
|
||||||
<template
|
:key="action.label"
|
||||||
v-for="action in actions"
|
>
|
||||||
:key="action.label"
|
<dangerous-button
|
||||||
>
|
v-if="action.dangerous && action.show(obj)"
|
||||||
<dangerous-button
|
:is-loading="isLoading"
|
||||||
v-if="action.dangerous && action.show(obj)"
|
:action="action.handler"
|
||||||
:is-loading="isLoading"
|
:title="action.modalHeader"
|
||||||
:action="action.handler"
|
:icon="`${action.iconColor} ${action.icon}`"
|
||||||
:title="action.modalHeader"
|
>
|
||||||
:icon="`${action.iconColor} ${action.icon}`"
|
{{ action.label }}
|
||||||
>
|
<template #modal-content>
|
||||||
{{ action.label }}
|
{{ action.modalContent }}
|
||||||
<template #modal-content>
|
|
||||||
{{ action.modalContent }}
|
|
||||||
</template>
|
|
||||||
<template #modal-confirm>
|
|
||||||
{{ action.modalConfirmLabel }}
|
|
||||||
</template>
|
|
||||||
</dangerous-button>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
<template #modal-confirm>
|
||||||
</div>
|
{{ action.modalConfirmLabel }}
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</dangerous-button>
|
||||||
</div>
|
</template>
|
||||||
|
</Layout>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import useLogger from '~/composables/useLogger'
|
||||||
|
|
||||||
import Modal from '~/components/ui/Modal.vue'
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
import Alert from '~/components/ui/Alert.vue'
|
import Alert from '~/components/ui/Alert.vue'
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
|
||||||
const logger = useLogger()
|
const logger = useLogger()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
@ -62,6 +63,7 @@ const hide = async () => {
|
||||||
<template>
|
<template>
|
||||||
<Modal
|
<Modal
|
||||||
v-model="show"
|
v-model="show"
|
||||||
|
destructive
|
||||||
:title="type==='artist' ? t('components.moderation.FilterModal.header.modal', {name: target?.name}) : errors.length > 0 ? t('components.moderation.FilterModal.header.failure') : ''"
|
:title="type==='artist' ? t('components.moderation.FilterModal.header.modal', {name: target?.name}) : errors.length > 0 ? t('components.moderation.FilterModal.header.failure') : ''"
|
||||||
:cancel="t('components.moderation.FilterModal.button.cancel')"
|
:cancel="t('components.moderation.FilterModal.button.cancel')"
|
||||||
>
|
>
|
||||||
|
@ -104,13 +106,14 @@ const hide = async () => {
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<template #actions>
|
||||||
<button
|
<Button
|
||||||
:class="['ui', 'success', {loading: isLoading}, 'button']"
|
destructive
|
||||||
|
:class="[{loading: isLoading}]"
|
||||||
@click="hide"
|
@click="hide"
|
||||||
>
|
>
|
||||||
{{ t('components.moderation.FilterModal.button.hide') }}
|
{{ t('components.moderation.FilterModal.button.hide') }}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -58,11 +58,11 @@ const allCategories = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="ui form">
|
||||||
<label v-if="label">{{ t('components.moderation.ReportCategoryDropdown.label.category') }}</label>
|
<label v-if="label">{{ t('components.moderation.ReportCategoryDropdown.label.category') }}</label>
|
||||||
<select
|
<select
|
||||||
v-model="value"
|
v-model="value"
|
||||||
class="ui dropdown"
|
class="dropdown"
|
||||||
:required="required || undefined"
|
:required="required || undefined"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
|
|
|
@ -133,10 +133,17 @@ const moderationNotifications = computed(() =>
|
||||||
icon="bi-megaphone-fill"
|
icon="bi-megaphone-fill"
|
||||||
>
|
>
|
||||||
{{ t('components.Sidebar.link.moderation') }}
|
{{ t('components.Sidebar.link.moderation') }}
|
||||||
|
<Spacer grow />
|
||||||
<div
|
<div
|
||||||
v-if="store.state.ui.notifications.pendingReviewReports + store.state.ui.notifications.pendingReviewRequests > 0"
|
v-if="store.state.ui.notifications.pendingReviewReports + store.state.ui.notifications.pendingReviewRequests > 0"
|
||||||
:title="t('components.Sidebar.label.reports')"
|
:title="t('components.Sidebar.label.reports')"
|
||||||
:class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
|
style="
|
||||||
|
background: var(--fw-gray-400);
|
||||||
|
color: var(--fw-gray-800);
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ store.state.ui.notifications.pendingReviewReports + store.state.ui.notifications.pendingReviewRequests }}
|
{{ store.state.ui.notifications.pendingReviewReports + store.state.ui.notifications.pendingReviewRequests }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -97,6 +97,7 @@ const labels = computed(() => ({
|
||||||
color: var(--fw-gray-800);
|
color: var(--fw-gray-800);
|
||||||
padding: 2px 7px;
|
padding: 2px 7px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
font-size: 12px;
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ store.state.ui.notifications.inbox }}
|
{{ store.state.ui.notifications.inbox }}
|
||||||
|
|
|
@ -36,31 +36,29 @@ const labels = computed(() => ({
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const object = ref()
|
const object = ref()
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`manage/library/libraries/${props.id}/`)
|
const response = await axios.get(`manage/library/libraries/${props.id}/`)
|
||||||
object.value = response.data
|
object.value = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
useErrorHandler(error as Error)
|
useErrorHandler(error as Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoadingStats = ref(false)
|
const isLoadingStats = ref(false)
|
||||||
const stats = ref()
|
const stats = ref()
|
||||||
|
|
||||||
const fetchStats = async () => {
|
const fetchStats = async () => {
|
||||||
isLoadingStats.value = true
|
isLoadingStats.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`manage/library/libraries/${props.id}/stats/`)
|
const response = await axios.get(`manage/library/libraries/${props.id}/stats/`)
|
||||||
stats.value = response.data
|
stats.value = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
useErrorHandler(error as Error)
|
useErrorHandler(error as Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoadingStats.value = false
|
isLoadingStats.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,14 +67,12 @@ fetchData()
|
||||||
|
|
||||||
const remove = async () => {
|
const remove = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.delete(`manage/library/libraries/${props.id}/`)
|
await axios.delete(`manage/library/libraries/${props.id}/`)
|
||||||
router.push({ name: 'manage.library.libraries' })
|
router.push({ name: 'manage.library.libraries' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
useErrorHandler(error as Error)
|
useErrorHandler(error as Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +82,6 @@ const updateObj = async (attr: string) => {
|
||||||
const params = {
|
const params = {
|
||||||
[attr]: object.value[attr]
|
[attr]: object.value[attr]
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.patch(`manage/library/libraries/${props.id}/`, params)
|
await axios.patch(`manage/library/libraries/${props.id}/`, params)
|
||||||
logger.info(`${attr} was updated successfully to ${params[attr]}`)
|
logger.info(`${attr} was updated successfully to ${params[attr]}`)
|
||||||
|
|
|
@ -337,7 +337,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
</router-link>
|
</router-link>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout
|
<Layout
|
||||||
v-if="!track.is_local"
|
v-if="!track?.is_local"
|
||||||
flex
|
flex
|
||||||
class="details"
|
class="details"
|
||||||
>
|
>
|
||||||
|
@ -345,7 +345,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
class="label"
|
class="label"
|
||||||
:to="{
|
:to="{
|
||||||
name: 'manage.moderation.domains.detail',
|
name: 'manage.moderation.domains.detail',
|
||||||
params: { id: track.domain }
|
params: { id: track?.domain }
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ t('views.admin.library.TrackDetail.link.domain') }}
|
{{ t('views.admin.library.TrackDetail.link.domain') }}
|
||||||
|
@ -354,7 +354,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
h
|
h
|
||||||
grow
|
grow
|
||||||
/>
|
/>
|
||||||
<span class="value">{{ track.domain }}</span>
|
<span class="value">{{ track?.domain }}</span>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout
|
<Layout
|
||||||
v-if="track?.description"
|
v-if="track?.description"
|
||||||
|
@ -428,7 +428,10 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
<span class="value">{{ stats?.track_favorites }}</span>
|
<span class="value">{{ stats?.track_favorites }}</span>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout stack>
|
<Layout
|
||||||
|
stack
|
||||||
|
style="flex: 1; gap: 0;"
|
||||||
|
>
|
||||||
<Heading
|
<Heading
|
||||||
:h3="t('views.admin.library.TrackDetail.header.trackData')"
|
:h3="t('views.admin.library.TrackDetail.header.trackData')"
|
||||||
class="category"
|
class="category"
|
||||||
|
@ -444,7 +447,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
h
|
h
|
||||||
grow
|
grow
|
||||||
/>
|
/>
|
||||||
<span class="value">{{ humanSize(stats.media_downloaded_size) }}</span>
|
<span class="value">{{ humanSize(stats?.media_downloaded_size) }}</span>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout
|
<Layout
|
||||||
flex
|
flex
|
||||||
|
@ -457,7 +460,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
h
|
h
|
||||||
grow
|
grow
|
||||||
/>
|
/>
|
||||||
<span class="value">{{ humanSize(stats.media_total_size) }}</span>
|
<span class="value">{{ humanSize(stats?.media_total_size) }}</span>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout
|
<Layout
|
||||||
flex
|
flex
|
||||||
|
@ -465,7 +468,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
class="label"
|
class="label"
|
||||||
:to="{ name: 'manage.library.libraries', query: { q: getQuery('track_id', track.id) } }"
|
:to="{ name: 'manage.library.libraries', query: { q: getQuery('track_id', track?.id) } }"
|
||||||
>
|
>
|
||||||
{{ t('views.admin.library.TrackDetail.link.libraries') }}
|
{{ t('views.admin.library.TrackDetail.link.libraries') }}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -473,7 +476,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
h
|
h
|
||||||
grow
|
grow
|
||||||
/>
|
/>
|
||||||
<span class="value">{{ stats.libraries }}</span>
|
<span class="value">{{ stats?.libraries }}</span>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout
|
<Layout
|
||||||
flex
|
flex
|
||||||
|
@ -481,7 +484,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
class="label"
|
class="label"
|
||||||
:to="{ name: 'manage.library.uploads', query: { q: getQuery('track_id', track.id) } }"
|
:to="{ name: 'manage.library.uploads', query: { q: getQuery('track_id', track?.id) } }"
|
||||||
>
|
>
|
||||||
{{ t('views.admin.library.TrackDetail.link.uploads') }}
|
{{ t('views.admin.library.TrackDetail.link.uploads') }}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -489,7 +492,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
h
|
h
|
||||||
grow
|
grow
|
||||||
/>
|
/>
|
||||||
<span class="value">{{ stats.uploads }}</span>
|
<span class="value">{{ stats?.uploads }}</span>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -2,17 +2,21 @@
|
||||||
import type { InstancePolicy } from '~/types'
|
import type { InstancePolicy } from '~/types'
|
||||||
|
|
||||||
import { computed, ref, reactive } from 'vue'
|
import { computed, ref, reactive } from 'vue'
|
||||||
// import { useCurrentElement } from '@vueuse/core'
|
|
||||||
import { humanSize } from '~/utils/filters'
|
import { humanSize } from '~/utils/filters'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import Header from '~/components/ui/Header.vue'
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import Loader from '~/components/ui/Loader.vue'
|
|
||||||
import Button from '~/components/ui/Button.vue'
|
|
||||||
import Spacer from '~/components/ui/Spacer.vue'
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
|
import HumanDate from '~/components/common/HumanDate.vue'
|
||||||
|
import Link from '~/components/ui/Link.vue'
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
import Heading from '~/components/ui/Heading.vue'
|
||||||
|
import Loader from '~/components/ui/Loader.vue'
|
||||||
|
import Alert from '~/components/ui/Alert.vue'
|
||||||
import Input from '~/components/ui/Input.vue'
|
import Input from '~/components/ui/Input.vue'
|
||||||
|
|
||||||
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
||||||
|
@ -104,13 +108,6 @@ const fetchStats = async () => {
|
||||||
fetchStats()
|
fetchStats()
|
||||||
fetchData()
|
fetchData()
|
||||||
|
|
||||||
// TODO: Find out if the following removal causes any regression #2440
|
|
||||||
// const el = useCurrentElement()
|
|
||||||
// watch(object, async () => {
|
|
||||||
// await nextTick()
|
|
||||||
// $(el.value).find('select.dropdown').dropdown()
|
|
||||||
// })
|
|
||||||
|
|
||||||
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
||||||
|
|
||||||
const updating = reactive(new Set<string>())
|
const updating = reactive(new Set<string>())
|
||||||
|
@ -152,466 +149,427 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Layout
|
<Loader v-if="isLoading" />
|
||||||
main
|
<Header
|
||||||
stack
|
v-if="object"
|
||||||
class="page-admin-account-detail"
|
v-title="object?.full_username"
|
||||||
|
:h1="object?.full_username"
|
||||||
|
page-heading
|
||||||
>
|
>
|
||||||
<Loader
|
<template #image>
|
||||||
v-if="isLoading"
|
<i class="channel-image bi-person-circle" />
|
||||||
/>
|
|
||||||
<template v-if="object">
|
|
||||||
<section
|
|
||||||
v-title="object.full_username"
|
|
||||||
>
|
|
||||||
<Layout flex>
|
|
||||||
<h2 class="ui header">
|
|
||||||
<i class="bi-person-circle" />
|
|
||||||
{{ object.full_username }}
|
|
||||||
<div class="sub header">
|
|
||||||
<template v-if="object.user">
|
|
||||||
<span class="ui tiny accent label">
|
|
||||||
<i class="bi-house-fill" />
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.header.localAccount') }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<a
|
|
||||||
:href="object.url || object.fid"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.openProfile') }}
|
|
||||||
<i class="bi bi-box-arrow-up-right" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</h2>
|
|
||||||
<Spacer grow />
|
|
||||||
<Layout stack>
|
|
||||||
<a
|
|
||||||
v-if="object.user && store.state.auth.profile && store.state.auth.profile.is_superuser"
|
|
||||||
class="ui labeled icon button"
|
|
||||||
:href="store.getters['instance/absoluteUrl'](`/api/admin/users/user/${object.user.id}`)"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<i class="bi bi-wrench" />
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.django') }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
v-else-if="store.state.auth.profile && store.state.auth.profile.is_superuser"
|
|
||||||
:href="store.getters['instance/absoluteUrl'](`/api/admin/federation/actor/${object.id}`)"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<i class="bi bi-wrench" />
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.django') }}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
:href="object.url || object.fid"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<i class="bi bi-box-arrow-up-right" />
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.remoteProfile') }}
|
|
||||||
</a>
|
|
||||||
</Layout>
|
|
||||||
<div class="ui column">
|
|
||||||
<div
|
|
||||||
v-if="!object.user"
|
|
||||||
class="ui compact clearing placeholder segment component-placeholder"
|
|
||||||
>
|
|
||||||
<template v-if="isLoadingPolicy">
|
|
||||||
<div class="paragraph">
|
|
||||||
<div class="line" />
|
|
||||||
<div class="line" />
|
|
||||||
<div class="line" />
|
|
||||||
<div class="line" />
|
|
||||||
<div class="line" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="!policy && !showPolicyForm">
|
|
||||||
<header class="ui header">
|
|
||||||
<h3>
|
|
||||||
<i class="bi bi-shield-fill" />
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.header.noPolicy') }}
|
|
||||||
</h3>
|
|
||||||
</header>
|
|
||||||
<p>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.description.policy') }}
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
primary
|
|
||||||
@click="showPolicyForm = true"
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.button.addPolicy') }}
|
|
||||||
</Button>
|
|
||||||
</template>
|
|
||||||
<instance-policy-card
|
|
||||||
v-else-if="policy && !showPolicyForm"
|
|
||||||
:object="policy"
|
|
||||||
@update="showPolicyForm = true"
|
|
||||||
>
|
|
||||||
<header class="ui header">
|
|
||||||
<h3>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.header.activePolicy') }}
|
|
||||||
</h3>
|
|
||||||
</header>
|
|
||||||
</instance-policy-card>
|
|
||||||
<instance-policy-form
|
|
||||||
v-else-if="showPolicyForm"
|
|
||||||
:object="policy"
|
|
||||||
type="actor"
|
|
||||||
:target="object.full_username"
|
|
||||||
@cancel="showPolicyForm = false"
|
|
||||||
@save="updatePolicy"
|
|
||||||
@delete="policy = null; showPolicyForm = false"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</section>
|
|
||||||
<Layout flex>
|
|
||||||
<div class="column">
|
|
||||||
<section>
|
|
||||||
<h3 class="ui header">
|
|
||||||
<i class="bi bi-info-circle-fill" />
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.header.accountData') }}
|
|
||||||
</h3>
|
|
||||||
<table class="ui very basic table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.username') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ object.preferred_username }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="!object.user">
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}">
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.domain') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ object.domain }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.displayName') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ object.name }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="object.user">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.email') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ object.user.email }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="object.user">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.loginStatus.label') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div
|
|
||||||
v-if="object.user.username != store.state.auth.profile?.username"
|
|
||||||
class="ui toggle checkbox"
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
id="is-active"
|
|
||||||
v-model="object.user.is_active"
|
|
||||||
type="checkbox"
|
|
||||||
@change="updateUser('is_active')"
|
|
||||||
/>
|
|
||||||
<label for="is-active">
|
|
||||||
<span
|
|
||||||
v-if="object.user.is_active"
|
|
||||||
>{{ t('views.admin.moderation.AccountsDetail.table.accountData.loginStatus.enabled') }}</span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
>{{ t('views.admin.moderation.AccountsDetail.table.accountData.loginStatus.disabled') }}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
v-else-if="object.user.is_active"
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.loginStatus.enabled') }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.loginStatus.disabled') }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="object.user">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.permissions') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select
|
|
||||||
v-model="permissions"
|
|
||||||
multiple
|
|
||||||
class="ui search selection dropdown"
|
|
||||||
@change="updateUser('permissions')"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="(p, key) in allPermissions"
|
|
||||||
:key="key"
|
|
||||||
:value="p.code"
|
|
||||||
>
|
|
||||||
{{ p.label }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<action-feedback :is-loading="updating.has('permissions')" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.userType') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ object.type }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="!object.user">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.lastChecked') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<human-date
|
|
||||||
v-if="object.last_fetch_date"
|
|
||||||
:date="object.last_fetch_date"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.notApplicable') }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="object.user">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.signupDate') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<human-date :date="object.user.date_joined" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="object.user">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.accountData.lastActivity') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<human-date :date="object.user.last_activity" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<section>
|
|
||||||
<h3 class="ui header">
|
|
||||||
<i class="bi bi-rss-fill" />
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.header.activity') }}
|
|
||||||
<span :data-tooltip="labels.statsWarning"><i class=" bi bi-question-circle-fill" /></span>
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
v-if="isLoadingStats"
|
|
||||||
class="ui placeholder"
|
|
||||||
>
|
|
||||||
<div class="full line" />
|
|
||||||
<div class="short line" />
|
|
||||||
<div class="medium line" />
|
|
||||||
<div class="long line" />
|
|
||||||
</div>
|
|
||||||
<table
|
|
||||||
v-else
|
|
||||||
class="ui very basic table"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr v-if="!object.user">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.activity.firstSeen') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<human-date :date="object.creation_date" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.activity.emittedMessages') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.outbox_activities }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.activity.receivedFollows') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.received_library_follows }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.activity.emittedFollows') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.emitted_library_follows }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `account:${object.full_username}`) }}">
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.linkedReports') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.reports }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.moderation.requests.list', query: {q: getQuery('submitter', `${object.full_username}`) }}">
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.requests') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.requests }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<section>
|
|
||||||
<h3 class="ui header">
|
|
||||||
<i class="bi bi-music-note-beamed" />
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.header.audioContent') }}
|
|
||||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
v-if="isLoadingStats"
|
|
||||||
class="ui placeholder"
|
|
||||||
>
|
|
||||||
<div class="full line" />
|
|
||||||
<div class="short line" />
|
|
||||||
<div class="medium line" />
|
|
||||||
<div class="long line" />
|
|
||||||
</div>
|
|
||||||
<table
|
|
||||||
v-else
|
|
||||||
class="ui very basic table"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr v-if="!object.user">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.audioContent.cachedSize') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ humanSize(stats.media_downloaded_size) }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="object.user">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.audioContent.uploadQuota') }}
|
|
||||||
<span :data-tooltip="labels.uploadQuota"><i class="question circle icon" /></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Input
|
|
||||||
v-model.number="object.user.upload_quota"
|
|
||||||
step="100"
|
|
||||||
name="quota"
|
|
||||||
type="number"
|
|
||||||
@change="updateUser('upload_quota', true)"
|
|
||||||
/>
|
|
||||||
<div class="ui basic label">
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.audioContent.megabyte') }}
|
|
||||||
</div>
|
|
||||||
<action-feedback
|
|
||||||
class="ui basic label"
|
|
||||||
size="tiny"
|
|
||||||
:is-loading="updating.has('upload_quota')"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.table.audioContent.totalSize') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ humanSize(stats.media_total_size) }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.channels', query: {q: getQuery('account', object.full_username) }}">
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.channels') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.channels }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('account', object.full_username) }}">
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.libraries') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.libraries }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('account', object.full_username) }}">
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.uploads') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.uploads }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.artists') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.artists }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.albums') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.albums }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.AccountsDetail.link.tracks') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.tracks }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</template>
|
</template>
|
||||||
|
<Spacer />
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="header-buttons"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
solid
|
||||||
|
primary
|
||||||
|
low-height
|
||||||
|
icon="bi-box-arrow-up-right"
|
||||||
|
:to="object?.url || object?.fid"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.link.openProfile') }}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
v-if="object?.user && store.state.auth.profile?.is_superuser"
|
||||||
|
solid
|
||||||
|
primary
|
||||||
|
low-height
|
||||||
|
icon="bi-wrench"
|
||||||
|
:to="store.getters['instance/absoluteUrl'](`/api/admin/users/user/${object?.user?.id}`)"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.link.django') }}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
v-if="!object?.user"
|
||||||
|
solid
|
||||||
|
primary
|
||||||
|
low-height
|
||||||
|
:to="store.getters['instance/absoluteUrl'](`/api/admin/federation/actor/${object?.id}`)"
|
||||||
|
icon="bi-wrench"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.link.django') }}
|
||||||
|
</Link>
|
||||||
|
</Layout>
|
||||||
|
</Header>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
v-if="!object?.user"
|
||||||
|
:blue="!policy"
|
||||||
|
red="policy"
|
||||||
|
>
|
||||||
|
<template v-if="isLoadingPolicy">
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="line" />
|
||||||
|
<div class="line" />
|
||||||
|
<div class="line" />
|
||||||
|
<div class="line" />
|
||||||
|
<div class="line" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="!policy && !showPolicyForm">
|
||||||
|
<Heading
|
||||||
|
:h3="t('views.admin.moderation.AccountsDetail.header.noPolicy')"
|
||||||
|
icon="bi-shield-fill"
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.description.policy') }}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
@click="showPolicyForm = true"
|
||||||
|
>
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.button.addPolicy') }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
<instance-policy-card
|
||||||
|
v-else-if="policy && !showPolicyForm"
|
||||||
|
:object="policy"
|
||||||
|
@update="showPolicyForm = true"
|
||||||
|
>
|
||||||
|
<header class="ui header">
|
||||||
|
<h3>
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.header.activePolicy') }}
|
||||||
|
</h3>
|
||||||
|
</header>
|
||||||
|
</instance-policy-card>
|
||||||
|
<instance-policy-form
|
||||||
|
v-else-if="showPolicyForm"
|
||||||
|
:object="policy"
|
||||||
|
type="actor"
|
||||||
|
:target="object?.full_username"
|
||||||
|
@cancel="showPolicyForm = false"
|
||||||
|
@save="updatePolicy"
|
||||||
|
@delete="policy = null; showPolicyForm = false"
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<Spacer />
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
gap-64
|
||||||
|
>
|
||||||
|
<Layout
|
||||||
|
stack
|
||||||
|
style="flex: 1; gap: 0;"
|
||||||
|
>
|
||||||
|
<Heading
|
||||||
|
:h3="t('views.admin.moderation.AccountsDetail.header.accountData')"
|
||||||
|
class="category"
|
||||||
|
/>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.accountData.username') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ object?.preferred_username }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="!object?.user"
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
class="label"
|
||||||
|
:to="{ name: 'manage.moderation.domains.detail', params: { id: object?.domain } }"
|
||||||
|
>
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.link.domain') }}
|
||||||
|
</router-link>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ object?.domain }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.accountData.displayName') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ object?.name }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="object?.user"
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.accountData.email') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ object?.user?.email }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="object?.user"
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.accountData.signupDate') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<HumanDate :date="object?.user?.date_joined" />
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<Layout
|
||||||
|
stack
|
||||||
|
style="flex: 1; gap: 0;"
|
||||||
|
>
|
||||||
|
<Heading
|
||||||
|
:h3="t('views.admin.moderation.AccountsDetail.header.activity')"
|
||||||
|
class="category"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:data-tooltip="labels.statsWarning"
|
||||||
|
style="margin-left: 8px"
|
||||||
|
>
|
||||||
|
<i class=" bi bi-question-circle-fill" /></span>
|
||||||
|
</Heading>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.activity.firstSeen') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<HumanDate :date="object?.creation_date" />
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.activity.emittedMessages') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.outbox_activities }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="object?.user"
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.activity.receivedFollows') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.received_library_follows }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="object?.user"
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.activity.emittedFollows') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.emitted_library_follows }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="object?.user"
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `account:${object?.full_username}`) }}">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.link.linkedReports') }}
|
||||||
|
</router-link>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.reports }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="object?.user"
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<router-link :to="{name: 'manage.moderation.requests.list', query: {q: getQuery('submitter', `${object?.full_username}`) }}">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.link.requests') }}
|
||||||
|
</router-link>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.requests }}</span>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<Layout
|
||||||
|
stack
|
||||||
|
style="flex: 1; gap: 0;"
|
||||||
|
>
|
||||||
|
<Heading
|
||||||
|
:h3="t('views.admin.moderation.AccountsDetail.header.audioContent')"
|
||||||
|
class="category"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:data-tooltip="labels.statsWarning"
|
||||||
|
style="margin-left: 8px"
|
||||||
|
>
|
||||||
|
<i class=" bi bi-question-circle-fill" /></span>
|
||||||
|
</Heading>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.audioContent.cachedSize') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ humanSize(stats?.media_downloaded_size) }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="object?.user"
|
||||||
|
flex
|
||||||
|
no-gap
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.audioContent.uploadQuota') }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
:data-tooltip="labels.uploadQuota"
|
||||||
|
style="margin-left: 8px"
|
||||||
|
>
|
||||||
|
<i class="bi bi-question-circle" />
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model.number="object.user.upload_quota"
|
||||||
|
step="100"
|
||||||
|
name="quota"
|
||||||
|
type="number"
|
||||||
|
style="width: 100px"
|
||||||
|
@change="updateUser('upload_quota', true)"
|
||||||
|
/>
|
||||||
|
<span class="ui basic label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.audioContent.megabyte') }}
|
||||||
|
</span>
|
||||||
|
<action-feedback
|
||||||
|
class="ui basic label"
|
||||||
|
size="tiny"
|
||||||
|
:is-loading="updating.has('upload_quota')"
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.AccountsDetail.table.audioContent.totalSize') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ humanSize(stats?.media_total_size) }}</span>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
.channel-image {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
font-size: 160px;
|
||||||
|
border: none;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
align-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
@include light-theme {
|
||||||
|
background-color: var(--fw-gray-200);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
background-color: var(--fw-gray-800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3.category {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 72px;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1px solid;
|
||||||
|
min-width: 280px;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
border-color: var(--fw-gray-300);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
border-color: var(--fw-gray-800);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: 800;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
color: var(--fw-gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include dark-theme {
|
||||||
|
color: var(--fw-gray-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.label,
|
||||||
|
a.value {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -14,6 +14,19 @@ import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCar
|
||||||
|
|
||||||
import useErrorHandler from '~/composables/useErrorHandler'
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
import Header from '~/components/ui/Header.vue'
|
||||||
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
|
import HumanDate from '~/components/common/HumanDate.vue'
|
||||||
|
import Link from '~/components/ui/Link.vue'
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
import Heading from '~/components/ui/Heading.vue'
|
||||||
|
import Loader from '~/components/ui/Loader.vue'
|
||||||
|
import Alert from '~/components/ui/Alert.vue'
|
||||||
|
import OptionsButton from '~/components/ui/button/Options.vue'
|
||||||
|
import Popover from '~/components/ui/Popover.vue'
|
||||||
|
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: number
|
id: number
|
||||||
allowListEnabled: boolean
|
allowListEnabled: boolean
|
||||||
|
@ -109,398 +122,442 @@ const setAllowList = async (value: boolean) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="page-admin-domain-detail">
|
<Loader v-if="isLoading" />
|
||||||
<div
|
<Header
|
||||||
v-if="isLoading"
|
v-if="object"
|
||||||
class="ui vertical segment"
|
v-title="object?.name"
|
||||||
|
:h1="object?.name"
|
||||||
|
page-heading
|
||||||
|
>
|
||||||
|
<template #image>
|
||||||
|
<i class="channel-image bi bi-cloud-fill" />
|
||||||
|
</template>
|
||||||
|
<Spacer />
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="header-buttons"
|
||||||
>
|
>
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
|
<Link
|
||||||
</div>
|
solid
|
||||||
<template v-if="object">
|
primary
|
||||||
<section
|
low-height
|
||||||
v-title="object.name"
|
icon="bi-box-arrow-up-right"
|
||||||
:class="['ui', 'head', 'vertical', 'stripe', 'segment']"
|
:to="externalUrl"
|
||||||
|
target="_blank"
|
||||||
>
|
>
|
||||||
<div class="ui stackable two column grid">
|
{{ t('views.admin.moderation.DomainsDetail.link.website') }}
|
||||||
<div class="ui column">
|
</Link>
|
||||||
<div class="segment-content">
|
<Link
|
||||||
<h2 class="ui header">
|
v-if="store.state.auth.profile?.is_superuser"
|
||||||
<i class="circular inverted cloud icon" />
|
solid
|
||||||
<div class="content">
|
primary
|
||||||
{{ object.name }}
|
low-height
|
||||||
<div class="sub header">
|
icon="bi-wrench"
|
||||||
<a
|
:to="store.getters['instance/absoluteUrl'](`/api/admin/federation/domain/${object.name}`)"
|
||||||
:href="externalUrl"
|
target="_blank"
|
||||||
target="_blank"
|
>
|
||||||
rel="noopener noreferrer"
|
{{ t('views.admin.moderation.DomainsDetail.link.django') }}
|
||||||
class="logo-wrapper"
|
</Link>
|
||||||
>
|
<Spacer grow />
|
||||||
{{ t('views.admin.moderation.DomainsDetail.link.website') }}
|
<Popover v-if="allowListEnabled">
|
||||||
<i class="external icon" />
|
<template #default="{ toggleOpen }">
|
||||||
</a>
|
<OptionsButton
|
||||||
</div>
|
is-square-small
|
||||||
</div>
|
@click="toggleOpen()"
|
||||||
</h2>
|
/>
|
||||||
<div class="header-buttons">
|
</template>
|
||||||
<div class="ui icon buttons">
|
<template #items>
|
||||||
<a
|
<PopoverItem
|
||||||
v-if="store.state.auth.profile?.is_superuser"
|
icon="bi-list-check"
|
||||||
class="ui labeled icon button"
|
@click="setAllowList(!object.allowed)"
|
||||||
:href="store.getters['instance/absoluteUrl'](`/api/admin/federation/domain/${object.name}`)"
|
>
|
||||||
target="_blank"
|
<span v-if="object.allowed">
|
||||||
rel="noopener noreferrer"
|
{{ t('views.admin.moderation.DomainsDetail.button.removeFromAllowList') }}
|
||||||
>
|
</span>
|
||||||
<i class="wrench icon" />
|
<span v-else>
|
||||||
{{ t('views.admin.moderation.DomainsDetail.link.django') }}
|
{{ t('views.admin.moderation.DomainsDetail.button.addToAllowList') }}
|
||||||
</a>
|
</span>
|
||||||
</div>
|
</PopoverItem>
|
||||||
<div
|
</template>
|
||||||
v-if="allowListEnabled"
|
</Popover>
|
||||||
class="ui icon buttons"
|
</Layout>
|
||||||
>
|
</Header>
|
||||||
<button
|
|
||||||
v-if="object.allowed"
|
|
||||||
:class="['ui', 'labeled', {loading: isLoadingAllowList}, 'icon', 'button']"
|
|
||||||
@click.prevent="setAllowList(false)"
|
|
||||||
>
|
|
||||||
<i class="x icon" />
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.button.removeFromAllowList') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
:class="['ui', 'labeled', {loading: isLoadingAllowList}, 'icon', 'button']"
|
|
||||||
@click.prevent="setAllowList(true)"
|
|
||||||
>
|
|
||||||
<i class="check icon" />
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.button.addToAllowList') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui column">
|
|
||||||
<div class="ui compact clearing placeholder segment component-placeholder">
|
|
||||||
<template v-if="isLoadingPolicy">
|
|
||||||
<div class="paragraph">
|
|
||||||
<div class="line" />
|
|
||||||
<div class="line" />
|
|
||||||
<div class="line" />
|
|
||||||
<div class="line" />
|
|
||||||
<div class="line" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="!policy && !showPolicyForm">
|
|
||||||
<header class="ui header">
|
|
||||||
<h3>
|
|
||||||
<i class="shield icon" />
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.header.noPolicy') }}
|
|
||||||
</h3>
|
|
||||||
</header>
|
|
||||||
<p>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.description.policy') }}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
class="ui primary button"
|
|
||||||
@click="showPolicyForm = true"
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.button.addPolicy') }}
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<instance-policy-card
|
|
||||||
v-else-if="policy && !showPolicyForm"
|
|
||||||
:object="policy"
|
|
||||||
@update="showPolicyForm = true"
|
|
||||||
>
|
|
||||||
<header class="ui header">
|
|
||||||
<h3>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.header.activePolicy') }}
|
|
||||||
</h3>
|
|
||||||
</header>
|
|
||||||
</instance-policy-card>
|
|
||||||
<instance-policy-form
|
|
||||||
v-else-if="showPolicyForm"
|
|
||||||
:object="policy"
|
|
||||||
type="domain"
|
|
||||||
:target="object.name"
|
|
||||||
@cancel="showPolicyForm = false"
|
|
||||||
@save="updatePolicy"
|
|
||||||
@delete="policy = null; showPolicyForm = false"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<div class="ui vertical stripe segment">
|
|
||||||
<div class="ui stackable three column grid">
|
|
||||||
<div class="column">
|
|
||||||
<section>
|
|
||||||
<h3 class="ui header">
|
|
||||||
<i class="info icon" />
|
|
||||||
<div class="content">
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.header.instanceData') }}
|
|
||||||
</div>
|
|
||||||
</h3>
|
|
||||||
<table class="ui very basic table">
|
|
||||||
<tbody>
|
|
||||||
<tr v-if="allowListEnabled">
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.inAllowList.label') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span
|
|
||||||
v-if="object.allowed"
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.inAllowList.true') }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.inAllowList.false') }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.lastChecked') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<human-date
|
|
||||||
v-if="object.nodeinfo_fetch_date"
|
|
||||||
:date="object.nodeinfo_fetch_date"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.notApplicable') }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<template v-if="object.nodeinfo && object.nodeinfo.status === 'ok'">
|
<Alert
|
||||||
<tr>
|
blue
|
||||||
<td>
|
>
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.software.label') }}
|
<template v-if="isLoadingPolicy">
|
||||||
</td>
|
<div class="paragraph">
|
||||||
<td>
|
<div class="line" />
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.software.value', {name: get(object, 'nodeinfo.payload.software.name', t('views.admin.moderation.DomainsDetail.notApplicable')), version: get(object, 'nodeinfo.payload.software.version', t('views.admin.moderation.DomainsDetail.notApplicable'))}) }}
|
<div class="line" />
|
||||||
</td>
|
<div class="line" />
|
||||||
</tr>
|
<div class="line" />
|
||||||
<tr>
|
<div class="line" />
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.domainName') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ get(object, 'nodeinfo.payload.metadata.nodeName', t('views.admin.moderation.DomainsDetail.notApplicable')) }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.totalUsers') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ get(object, 'nodeinfo.payload.usage.users.total', t('views.admin.moderation.DomainsDetail.notApplicable')) }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
<template v-if="object.nodeinfo && object.nodeinfo.status === 'error'">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.nodeInfoStatus.label') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.nodeInfoStatus.value') }}
|
|
||||||
|
|
||||||
<span :data-tooltip="object.nodeinfo.error"><i class="question circle icon" /></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<ajax-button
|
|
||||||
method="get"
|
|
||||||
:url="'manage/federation/domains/' + object.name + '/nodeinfo/'"
|
|
||||||
@action-done="refreshNodeInfo"
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.button.refreshNodeInfo') }}
|
|
||||||
</ajax-button>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<section>
|
|
||||||
<h3 class="ui header">
|
|
||||||
<i class="feed icon" />
|
|
||||||
<div class="content">
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.header.activity') }}
|
|
||||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
|
||||||
</div>
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
v-if="isLoadingStats"
|
|
||||||
class="ui placeholder"
|
|
||||||
>
|
|
||||||
<div class="full line" />
|
|
||||||
<div class="short line" />
|
|
||||||
<div class="medium line" />
|
|
||||||
<div class="long line" />
|
|
||||||
</div>
|
|
||||||
<table
|
|
||||||
v-else
|
|
||||||
class="ui very basic table"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.activity.firstSeen') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<human-date :date="object.creation_date" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link
|
|
||||||
:to="{name: 'manage.moderation.accounts.list', query: {q: 'domain:' + object.name }}"
|
|
||||||
>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.link.knownAccounts') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.actors }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.activity.emittedMessages') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.outbox_activities }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.activity.receivedFollows') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.received_library_follows }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.activity.emittedFollows') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.emitted_library_follows }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<section>
|
|
||||||
<h3 class="ui header">
|
|
||||||
<i class="music icon" />
|
|
||||||
<div class="content">
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.header.audioContent') }}
|
|
||||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
|
||||||
</div>
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
v-if="isLoadingStats"
|
|
||||||
class="ui placeholder"
|
|
||||||
>
|
|
||||||
<div class="full line" />
|
|
||||||
<div class="short line" />
|
|
||||||
<div class="medium line" />
|
|
||||||
<div class="long line" />
|
|
||||||
</div>
|
|
||||||
<table
|
|
||||||
v-else
|
|
||||||
class="ui very basic table"
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.audioContent.cachedSize') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ humanSize(stats.media_downloaded_size) }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.table.audioContent.totalSize') }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ humanSize(stats.media_total_size) }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.channels', query: {q: getQuery('domain', object.name) }}">
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.link.channels') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.channels }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('domain', object.name) }}">
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.link.libraries') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.libraries }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('domain', object.name) }}">
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.link.uploads') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.uploads }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.library.artists', query: {q: getQuery('domain', object.name) }}">
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.link.artists') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.artists }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.library.albums', query: {q: getQuery('domain', object.name) }}">
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.link.albums') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.albums }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('domain', object.name) }}">
|
|
||||||
{{ t('views.admin.moderation.DomainsDetail.link.tracks') }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ stats.tracks }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</main>
|
<template v-else-if="!policy && !showPolicyForm">
|
||||||
|
<Heading
|
||||||
|
:h3="t('views.admin.moderation.DomainsDetail.header.noPolicy')"
|
||||||
|
icon="bi-shield-lock"
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.description.policy') }}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
@click="showPolicyForm = true"
|
||||||
|
>
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.button.addPolicy') }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
<instance-policy-card
|
||||||
|
v-else-if="policy && !showPolicyForm"
|
||||||
|
:object="policy"
|
||||||
|
@update="showPolicyForm = true"
|
||||||
|
>
|
||||||
|
<header class="ui header">
|
||||||
|
<h3>
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.header.activePolicy') }}
|
||||||
|
</h3>
|
||||||
|
</header>
|
||||||
|
</instance-policy-card>
|
||||||
|
<instance-policy-form
|
||||||
|
v-else-if="showPolicyForm"
|
||||||
|
:object="policy"
|
||||||
|
type="domain"
|
||||||
|
:target="object.name"
|
||||||
|
@cancel="showPolicyForm = false"
|
||||||
|
@save="updatePolicy"
|
||||||
|
@delete="policy = null; showPolicyForm = false"
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
<Spacer />
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
gap-64
|
||||||
|
>
|
||||||
|
<Layout
|
||||||
|
stack
|
||||||
|
style="flex: 1; gap: 0;"
|
||||||
|
>
|
||||||
|
<Heading
|
||||||
|
:h3="t('views.admin.moderation.DomainsDetail.header.instanceData')"
|
||||||
|
class="category"
|
||||||
|
/>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.inAllowList.label') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">
|
||||||
|
<span v-if="object?.allowed">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.inAllowList.true') }}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.inAllowList.false') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.lastChecked') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<HumanDate
|
||||||
|
v-if="object?.nodeinfo_fetch_date"
|
||||||
|
:date="object?.nodeinfo_fetch_date"
|
||||||
|
/>
|
||||||
|
<span v-else>
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.notApplicable') }}
|
||||||
|
</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="object?.nodeinfo && object?.nodeinfo.status === 'ok'"
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.software.label') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.software.value', {name: get(object, 'nodeinfo.payload.software.name', t('views.admin.moderation.DomainsDetail.notApplicable')), version: get(object, 'nodeinfo.payload.software.version', t('views.admin.moderation.DomainsDetail.notApplicable'))}) }}
|
||||||
|
</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
v-if="object?.nodeinfo && object?.nodeinfo.status === 'error'"
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.nodeInfoStatus.label') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.nodeInfoStatus.value', {name: get(object, 'nodeinfo.payload.software.name', t('views.admin.moderation.DomainsDetail.notApplicable')), version: get(object, 'nodeinfo.payload.software.version', t('views.admin.moderation.DomainsDetail.notApplicable'))}) }}
|
||||||
|
</span>
|
||||||
|
<span :data-tooltip="object.nodeinfo.error"><i class="bi bi-question-circle" /></span>
|
||||||
|
</Layout>
|
||||||
|
<ajax-button
|
||||||
|
method="get"
|
||||||
|
:url="'manage/federation/domains/' + object?.name + '/nodeinfo/'"
|
||||||
|
@action-done="refreshNodeInfo"
|
||||||
|
>
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.button.refreshNodeInfo') }}
|
||||||
|
</ajax-button>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<Layout
|
||||||
|
stack
|
||||||
|
style="flex: 1; gap: 0;"
|
||||||
|
>
|
||||||
|
<Heading
|
||||||
|
:h3="t('views.admin.moderation.DomainsDetail.header.activity')"
|
||||||
|
class="category"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:data-tooltip="labels.statsWarning"
|
||||||
|
style="margin-left: 8px;"
|
||||||
|
>
|
||||||
|
<i class="bi bi-question-circle" /></span>
|
||||||
|
</Heading>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.activity.firstSeen') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<HumanDate :date="object?.creation_date" />
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
class="label"
|
||||||
|
:to="{name: 'manage.moderation.accounts.list', query: {q: 'domain:' + object?.name }}"
|
||||||
|
>
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.link.knownAccounts') }}
|
||||||
|
</Link>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.actors }}</span>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<Layout
|
||||||
|
stack
|
||||||
|
style="flex: 1; gap: 0;"
|
||||||
|
>
|
||||||
|
<Heading
|
||||||
|
:h3="t('views.admin.moderation.DomainsDetail.header.audioContent')"
|
||||||
|
class="category"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:data-tooltip="labels.statsWarning"
|
||||||
|
style="margin-left: 8px;"
|
||||||
|
>
|
||||||
|
<i class="bi bi-question-circle" /></span>
|
||||||
|
</Heading>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.audioContent.cachedSize') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ humanSize(stats?.media_downloaded_size) }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.table.audioContent.totalSize') }}
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ humanSize(stats?.media_total_size) }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
<router-link :to="{name: 'manage.channels', query: {q: getQuery('domain', object.name) }}">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.link.channels') }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.channels }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
<router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('domain', object.name) }}">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.link.libraries') }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.libraries }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
<router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('domain', object.name) }}">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.link.uploads') }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.uploads }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
<router-link :to="{name: 'manage.library.artists', query: {q: getQuery('domain', object.name) }}">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.link.artists') }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.artists }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
<router-link :to="{name: 'manage.library.albums', query: {q: getQuery('domain', object.name) }}">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.link.albums') }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.albums }}</span>
|
||||||
|
</Layout>
|
||||||
|
<Layout
|
||||||
|
flex
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
<span class="label">
|
||||||
|
<router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('domain', object.name) }}">
|
||||||
|
{{ t('views.admin.moderation.DomainsDetail.link.tracks') }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
<Spacer
|
||||||
|
h
|
||||||
|
grow
|
||||||
|
/>
|
||||||
|
<span class="value">{{ stats?.tracks }}</span>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.channel-image {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
font-size: 160px;
|
||||||
|
border: none;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
align-content: center;
|
||||||
|
@include light-theme {
|
||||||
|
background-color: var(--fw-gray-200);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
background-color: var(--fw-gray-800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3.category {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 72px;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1px solid;
|
||||||
|
min-width: 280px;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
border-color: var(--fw-gray-300);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
border-color: var(--fw-gray-800);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: 800;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
color: var(--fw-gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include dark-theme {
|
||||||
|
color: var(--fw-gray-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.label,
|
||||||
|
a.value {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -26,6 +26,7 @@ import useOrdering from '~/composables/navigation/useOrdering'
|
||||||
import useErrorHandler from '~/composables/useErrorHandler'
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
import usePage from '~/composables/navigation/usePage'
|
import usePage from '~/composables/navigation/usePage'
|
||||||
import useLogger from '~/composables/useLogger'
|
import useLogger from '~/composables/useLogger'
|
||||||
|
import Loader from '~/components/ui/Loader.vue'
|
||||||
|
|
||||||
interface Props extends SmartSearchProps, OrderingProps {
|
interface Props extends SmartSearchProps, OrderingProps {
|
||||||
mode?: 'card'
|
mode?: 'card'
|
||||||
|
@ -194,12 +195,9 @@ const labels = computed(() => ({
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<Loader
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
class="ui active inverted dimmer"
|
/>
|
||||||
>
|
|
||||||
<div class="ui loader" />
|
|
||||||
</div>
|
|
||||||
<div v-else-if="!result || result.count === 0">
|
<div v-else-if="!result || result.count === 0">
|
||||||
<empty-state
|
<empty-state
|
||||||
:refresh="true"
|
:refresh="true"
|
||||||
|
|
Loading…
Reference in New Issue