chore(front): admin moderation pages

This commit is contained in:
ArneBo 2025-03-12 12:59:41 +01:00
parent 1409bc6761
commit 58f39630e2
9 changed files with 908 additions and 928 deletions

View File

@ -208,6 +208,7 @@ const launchAction = async () => {
:disabled="checked.length === 0 || undefined" :disabled="checked.length === 0 || undefined"
:is-loading="isLoading" :is-loading="isLoading"
:confirm-color="currentAction?.confirmColor ?? 'success'" :confirm-color="currentAction?.confirmColor ?? 'success'"
style="margin-top: 7px;"
:aria-label="labels.performAction" :aria-label="labels.performAction"
:title="t('components.common.ActionTable.modal.performAction.header', { action: currentActionName }, affectedObjectsCount)" :title="t('components.common.ActionTable.modal.performAction.header', { action: currentActionName }, affectedObjectsCount)"
@confirm="launchAction" @confirm="launchAction"

View File

@ -11,7 +11,11 @@ import axios from 'axios'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import ActionTable from '~/components/common/ActionTable.vue' import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue'
import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Input from '~/components/ui/Input.vue'
import Pagination from '~/components/ui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch' import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useOrdering from '~/composables/navigation/useOrdering' import useOrdering from '~/composables/navigation/useOrdering'
@ -98,22 +102,21 @@ const labels = computed(() => ({
</script> </script>
<template> <template>
<div> <Spacer />
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui six wide field">
<label for="accounts-search">{{ t('components.manage.moderation.AccountsTable.label.search') }}</label>
<form @submit.prevent="query = search.value"> <form @submit.prevent="query = search.value">
<input <Input
id="accounts-search" id="accounts-search"
ref="search" v-model="query"
name="search" search
type="text" :label="t('components.manage.moderation.AccountsTable.label.search')"
:value="query"
:placeholder="labels.searchPlaceholder" :placeholder="labels.searchPlaceholder"
> />
</form> </form>
</div> <Spacer :size="16" />
<Layout flex>
<Spacer grow />
<div class="field"> <div class="field">
<label for="accounts-ordering">{{ t('components.manage.moderation.AccountsTable.ordering.label') }}</label> <label for="accounts-ordering">{{ t('components.manage.moderation.AccountsTable.ordering.label') }}</label>
<select <select
@ -145,15 +148,11 @@ const labels = computed(() => ({
</option> </option>
</select> </select>
</div> </div>
</Layout>
</div> </div>
</div> </div>
<div class="dimmable"> <div class="dimmable">
<div <Loader v-if="isLoading" />
v-if="isLoading"
class="ui active inverted dimmer"
>
<div class="ui loader" />
</div>
<action-table <action-table
v-if="result" v-if="result"
:objects-data="result" :objects-data="result"
@ -231,17 +230,14 @@ const labels = computed(() => ({
</action-table> </action-table>
</div> </div>
<div> <div>
<pagination <Pagination
v-if="result && result.count > paginateBy" v-if="result && result.count > paginateBy"
v-model:current="page" v-model:current="page"
:compact="true"
:paginate-by="paginateBy" :paginate-by="paginateBy"
:total="result.count"
/> />
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
{{ t('components.manage.moderation.AccountsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }} {{ t('components.manage.moderation.AccountsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
</span> </span>
</div> </div>
</div>
</template> </template>

View File

@ -10,7 +10,12 @@ import { useI18n } from 'vue-i18n'
import axios from 'axios' import axios from 'axios'
import ActionTable from '~/components/common/ActionTable.vue' import ActionTable from '~/components/common/ActionTable.vue'
import Pagination from '~/components/vui/Pagination.vue'
import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Loader from '~/components/ui/Loader.vue'
import Input from '~/components/ui/Input.vue'
import Pagination from '~/components/ui/Pagination.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useOrdering from '~/composables/navigation/useOrdering' import useOrdering from '~/composables/navigation/useOrdering'
@ -120,19 +125,21 @@ const labels = computed(() => ({
</script> </script>
<template> <template>
<div>
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label for="domains-search">{{ t('components.manage.moderation.DomainsTable.label.search') }}</label> <Input
<input
id="domains-search" id="domains-search"
v-model="query" v-model="query"
name="search" name="search"
type="text" search
:label="t('components.manage.moderation.DomainsTable.label.search')"
:placeholder="labels.searchPlaceholder" :placeholder="labels.searchPlaceholder"
> />
</div> </div>
<Spacer :size="16" />
<Layout flex>
<Spacer grow />
<div <div
v-if="allowListEnabled" v-if="allowListEnabled"
class="field" class="field"
@ -185,15 +192,11 @@ const labels = computed(() => ({
</option> </option>
</select> </select>
</div> </div>
</Layout>
</div> </div>
</div> </div>
<div class="dimmable"> <div class="dimmable">
<div <Loader v-if="isLoading" />
v-if="isLoading"
class="ui active inverted dimmer"
>
<div class="ui loader" />
</div>
<action-table <action-table
v-if="result && result.results.length > 0" v-if="result && result.results.length > 0"
:objects-data="result" :objects-data="result"
@ -270,5 +273,4 @@ const labels = computed(() => ({
{{ t('components.manage.moderation.DomainsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }} {{ t('components.manage.moderation.DomainsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
</span> </span>
</div> </div>
</div>
</template> </template>

View File

@ -11,6 +11,8 @@ import TracksTable from '~/components/manage/library/TracksTable.vue'
import UploadsTable from '~/components/manage/library/UploadsTable.vue' import UploadsTable from '~/components/manage/library/UploadsTable.vue'
import UsersTable from '~/components/manage/users/UsersTable.vue' import UsersTable from '~/components/manage/users/UsersTable.vue'
import Header from '~/components/ui/Header.vue'
import { computed } from 'vue' import { computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@ -43,13 +45,8 @@ const title = computed(() => labels.value[props.type])
</script> </script>
<template> <template>
<main v-title="title"> <Header :h1="title" />
<section class="ui vertical stripe segment">
<h2 class="ui header">
{{ title }}
</h2>
<invitation-form v-if="type === 'invitations'" /> <invitation-form v-if="type === 'invitations'" />
<div class="ui hidden divider" />
<accounts-table <accounts-table
v-if="type === 'accounts'" v-if="type === 'accounts'"
:update-url="true" :update-url="true"
@ -92,6 +89,4 @@ const title = computed(() => labels.value[props.type])
:default-query="defaultQuery" :default-query="defaultQuery"
/> />
<users-table v-else-if="type === 'users'" /> <users-table v-else-if="type === 'users'" />
</section>
</main>
</template> </template>

View File

@ -10,6 +10,12 @@ import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import $ from 'jquery' import $ from 'jquery'
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 Input from '~/components/ui/Input.vue'
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue' import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue' import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
@ -146,29 +152,26 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
</script> </script>
<template> <template>
<main class="page-admin-account-detail"> <Layout
<div main
v-if="isLoading" stack
class="ui vertical segment" class="page-admin-account-detail"
> >
<div :class="['ui', 'centered', 'active', 'inline', 'loader']" /> <Loader
</div> v-if="isLoading"
/>
<template v-if="object"> <template v-if="object">
<section <section
v-title="object.full_username" v-title="object.full_username"
:class="['ui', 'head', 'vertical', 'stripe', 'segment']"
> >
<div class="ui stackable two column grid"> <Layout flex>
<div class="ui column">
<div class="segment-content">
<h2 class="ui header"> <h2 class="ui header">
<i class="circular inverted user icon" /> <i class="bi-person-circle" />
<div class="content">
{{ object.full_username }} {{ object.full_username }}
<div class="sub header"> <div class="sub header">
<template v-if="object.user"> <template v-if="object.user">
<span class="ui tiny accent label"> <span class="ui tiny accent label">
<i class="home icon" /> <i class="bi-house-fill" />
{{ t('views.admin.moderation.AccountsDetail.header.localAccount') }} {{ t('views.admin.moderation.AccountsDetail.header.localAccount') }}
</span> </span>
&nbsp; &nbsp;
@ -179,13 +182,12 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{{ t('views.admin.moderation.AccountsDetail.link.openProfile') }}&nbsp; {{ t('views.admin.moderation.AccountsDetail.link.openProfile') }}&nbsp;
<i class="external icon" /> <i class="bi bi-box-arrow-up-right" />
</a> </a>
</div> </div>
</div>
</h2> </h2>
<div class="header-buttons"> <Spacer grow />
<div class="ui icon buttons"> <Layout stack>
<a <a
v-if="object.user && store.state.auth.profile && store.state.auth.profile.is_superuser" v-if="object.user && store.state.auth.profile && store.state.auth.profile.is_superuser"
class="ui labeled icon button" class="ui labeled icon button"
@ -193,40 +195,27 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i class="wrench icon" /> <i class="bi bi-wrench" />
{{ t('views.admin.moderation.AccountsDetail.link.django') }}&nbsp; {{ t('views.admin.moderation.AccountsDetail.link.django') }}&nbsp;
</a> </a>
<a <a
v-else-if="store.state.auth.profile && store.state.auth.profile.is_superuser" v-else-if="store.state.auth.profile && store.state.auth.profile.is_superuser"
class="ui labeled icon button"
:href="store.getters['instance/absoluteUrl'](`/api/admin/federation/actor/${object.id}`)" :href="store.getters['instance/absoluteUrl'](`/api/admin/federation/actor/${object.id}`)"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i class="wrench icon" /> <i class="bi bi-wrench" />
{{ t('views.admin.moderation.AccountsDetail.link.django') }}&nbsp; {{ t('views.admin.moderation.AccountsDetail.link.django') }}&nbsp;
</a> </a>
<button
v-dropdown
class="ui floating dropdown icon button"
>
<i class="dropdown icon" />
<div class="menu">
<a <a
class="basic item"
:href="object.url || object.fid" :href="object.url || object.fid"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i class="external icon" /> <i class="bi bi-box-arrow-up-right" />
{{ t('views.admin.moderation.AccountsDetail.link.remoteProfile') }}&nbsp; {{ t('views.admin.moderation.AccountsDetail.link.remoteProfile') }}&nbsp;
</a> </a>
</div> </Layout>
</button>
</div>
</div>
</div>
</div>
<div class="ui column"> <div class="ui column">
<div <div
v-if="!object.user" v-if="!object.user"
@ -244,19 +233,19 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
<template v-else-if="!policy && !showPolicyForm"> <template v-else-if="!policy && !showPolicyForm">
<header class="ui header"> <header class="ui header">
<h3> <h3>
<i class="shield icon" /> <i class="bi bi-shield-fill" />
{{ t('views.admin.moderation.AccountsDetail.header.noPolicy') }} {{ t('views.admin.moderation.AccountsDetail.header.noPolicy') }}
</h3> </h3>
</header> </header>
<p> <p>
{{ t('views.admin.moderation.AccountsDetail.description.policy') }} {{ t('views.admin.moderation.AccountsDetail.description.policy') }}
</p> </p>
<button <Button
class="ui primary button" primary
@click="showPolicyForm = true" @click="showPolicyForm = true"
> >
{{ t('views.admin.moderation.AccountsDetail.button.addPolicy') }} {{ t('views.admin.moderation.AccountsDetail.button.addPolicy') }}
</button> </Button>
</template> </template>
<instance-policy-card <instance-policy-card
v-else-if="policy && !showPolicyForm" v-else-if="policy && !showPolicyForm"
@ -280,17 +269,14 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
/> />
</div> </div>
</div> </div>
</div> </Layout>
</section> </section>
<div class="ui vertical stripe segment"> <Layout flex>
<div class="ui stackable three column grid">
<div class="column"> <div class="column">
<section> <section>
<h3 class="ui header"> <h3 class="ui header">
<i class="info icon" /> <i class="bi bi-info-circle-fill" />
<div class="content">
{{ t('views.admin.moderation.AccountsDetail.header.accountData') }} {{ t('views.admin.moderation.AccountsDetail.header.accountData') }}
</div>
</h3> </h3>
<table class="ui very basic table"> <table class="ui very basic table">
<tbody> <tbody>
@ -337,12 +323,12 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
v-if="object.user.username != store.state.auth.profile?.username" v-if="object.user.username != store.state.auth.profile?.username"
class="ui toggle checkbox" class="ui toggle checkbox"
> >
<input <Input
id="is-active" id="is-active"
v-model="object.user.is_active" v-model="object.user.is_active"
type="checkbox" type="checkbox"
@change="updateUser('is_active')" @change="updateUser('is_active')"
> />
<label for="is-active"> <label for="is-active">
<span <span
v-if="object.user.is_active" v-if="object.user.is_active"
@ -433,11 +419,9 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
<div class="column"> <div class="column">
<section> <section>
<h3 class="ui header"> <h3 class="ui header">
<i class="feed icon" /> <i class="bi bi-rss-fill" />
<div class="content">
{{ t('views.admin.moderation.AccountsDetail.header.activity') }}&nbsp; {{ t('views.admin.moderation.AccountsDetail.header.activity') }}&nbsp;
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span> <span :data-tooltip="labels.statsWarning"><i class=" bi bi-question-circle-fill" /></span>
</div>
</h3> </h3>
<div <div
v-if="isLoadingStats" v-if="isLoadingStats"
@ -512,11 +496,9 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
<div class="column"> <div class="column">
<section> <section>
<h3 class="ui header"> <h3 class="ui header">
<i class="music icon" /> <i class="bi bi-music-note-beamed" />
<div class="content">
{{ t('views.admin.moderation.AccountsDetail.header.audioContent') }}&nbsp; {{ t('views.admin.moderation.AccountsDetail.header.audioContent') }}&nbsp;
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span> <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
</div>
</h3> </h3>
<div <div
v-if="isLoadingStats" v-if="isLoadingStats"
@ -546,14 +528,13 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
<span :data-tooltip="labels.uploadQuota"><i class="question circle icon" /></span> <span :data-tooltip="labels.uploadQuota"><i class="question circle icon" /></span>
</td> </td>
<td> <td>
<div class="ui right labeled input"> <Input
<input
v-model.number="object.user.upload_quota" v-model.number="object.user.upload_quota"
step="100" step="100"
name="quota" name="quota"
type="number" type="number"
@change="updateUser('upload_quota', true)" @change="updateUser('upload_quota', true)"
> />
<div class="ui basic label"> <div class="ui basic label">
{{ t('views.admin.moderation.AccountsDetail.table.audioContent.megabyte') }} {{ t('views.admin.moderation.AccountsDetail.table.audioContent.megabyte') }}
</div> </div>
@ -562,7 +543,6 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
size="tiny" size="tiny"
:is-loading="updating.has('upload_quota')" :is-loading="updating.has('upload_quota')"
/> />
</div>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -631,8 +611,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
</table> </table>
</section> </section>
</div> </div>
</div> </Layout>
</div>
</template> </template>
</main> </Layout>
</template> </template>

View File

@ -7,6 +7,11 @@ import { useRoute } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import Layout from '~/components/ui/Layout.vue'
import Tabs from '~/components/ui/Tabs.vue'
import Tab from '~/components/ui/Tab.vue'
const store = useStore() const store = useStore()
const { t } = useI18n() const { t } = useI18n()
const route = useRoute() const route = useRoute()
@ -26,55 +31,50 @@ fetchNodeInfo()
</script> </script>
<template> <template>
<div <!-- TODO: Replace with Tabs component -->
<Layout
v-title="labels.moderation" v-title="labels.moderation"
class="main" main
no-gap
> >
<nav <Tabs
class="ui secondary pointing menu"
role="navigation" role="navigation"
:aria-label="labels.secondaryMenu" :aria-label="labels.secondaryMenu"
> >
<router-link <Tab
class="ui item" :title="t('views.admin.moderation.Base.link.reports')"
:to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}" :to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}"
> >
{{ t('views.admin.moderation.Base.link.reports') }}
<div <div
v-if="store.state.ui.notifications.pendingReviewReports > 0" v-if="store.state.ui.notifications.pendingReviewReports > 0"
:class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']" :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
> >
{{ store.state.ui.notifications.pendingReviewReports }} {{ store.state.ui.notifications.pendingReviewReports }}
</div> </div>
</router-link> </Tab>
<router-link <Tab
class="ui item" :title="t('views.admin.moderation.Base.link.userRequests')"
:to="{name: 'manage.moderation.requests.list', query: {q: 'status:pending'}}" :to="{name: 'manage.moderation.requests.list', query: {q: 'status:pending'}}"
> >
{{ t('views.admin.moderation.Base.link.userRequests') }}
<div <div
v-if="store.state.ui.notifications.pendingReviewRequests > 0" v-if="store.state.ui.notifications.pendingReviewRequests > 0"
:class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']" :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
> >
{{ store.state.ui.notifications.pendingReviewRequests }} {{ store.state.ui.notifications.pendingReviewRequests }}
</div> </div>
</router-link> </Tab>
<router-link <Tab
class="ui item" :title="t('views.admin.moderation.Base.link.domains')"
:to="{name: 'manage.moderation.domains.list'}" :to="{name: 'manage.moderation.domains.list'}"
> />
{{ t('views.admin.moderation.Base.link.domains') }} <Tab
</router-link> :title="t('views.admin.moderation.Base.link.accounts')"
<router-link
class="ui item"
:to="{name: 'manage.moderation.accounts.list'}" :to="{name: 'manage.moderation.accounts.list'}"
> />
{{ t('views.admin.moderation.Base.link.accounts') }} </Tabs>
</router-link>
</nav>
<router-view <router-view
:key="route.fullPath" :key="route.fullPath"
:allow-list-enabled="allowListEnabled" :allow-list-enabled="allowListEnabled"
/> />
</div> </Layout>
</template> </template>

View File

@ -9,6 +9,14 @@ import axios from 'axios'
import DomainsTable from '~/components/manage/moderation/DomainsTable.vue' import DomainsTable from '~/components/manage/moderation/DomainsTable.vue'
import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Header from '~/components/ui/Header.vue'
import Toggle from '~/components/ui/Toggle.vue'
import Button from '~/components/ui/Button.vue'
import Input from '~/components/ui/Input.vue'
import Alert from '~/components/ui/Alert.vue'
interface Props { interface Props {
allowListEnabled: boolean allowListEnabled: boolean
} }
@ -48,18 +56,14 @@ const createDomain = async () => {
<template> <template>
<main v-title="labels.domains"> <main v-title="labels.domains">
<section class="ui vertical stripe segment"> <Layout
<h2 class="ui left floated header"> form
{{ t('views.admin.moderation.DomainsList.header.domains') }}
</h2>
<form
class="ui right floated form"
@submit.prevent="createDomain" @submit.prevent="createDomain"
> >
<div <Header :h1="t('views.admin.moderation.DomainsList.header.domains')" />
<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('views.admin.moderation.DomainsList.header.failure') }} {{ t('views.admin.moderation.DomainsList.header.failure') }}
@ -72,42 +76,39 @@ const createDomain = async () => {
{{ error }} {{ error }}
</li> </li>
</ul> </ul>
</div> </Alert>
<div class="inline fields">
<div class="field"> <div class="field">
<label for="add-domain">{{ t('views.admin.moderation.DomainsList.label.addDomain') }}</label> <Input
<input
id="add-domain" id="add-domain"
v-model="domainName" v-model="domainName"
type="text" type="text"
name="domain" name="domain"
:label="t('views.admin.moderation.DomainsList.label.addDomain')"
> >
</div> <template #input-right>
<div <Button
v-if="allowListEnabled" primary
class="field" :class="[{'loading': isCreating}, 'success']"
>
<input
id="allowed"
v-model="domainAllowed"
type="checkbox"
name="allowed"
>
<label for="allowed">{{ t('views.admin.moderation.DomainsList.label.addToAllowList') }}</label>
</div>
<div class="field">
<button
:class="['ui', {'loading': isCreating}, 'success', 'button']"
type="submit" type="submit"
:disabled="isCreating" :disabled="isCreating"
> >
{{ t('views.admin.moderation.DomainsList.button.add') }} {{ t('views.admin.moderation.DomainsList.button.add') }}
</button> </Button>
</template>
</Input>
<div
v-if="allowListEnabled"
>
<Toggle
id="allowed"
v-model="domainAllowed"
name="allowed"
:label="t('views.admin.moderation.DomainsList.label.addToAllowList')"
/>
</div> </div>
</div> </div>
</form> </Layout>
<div class="ui clearing hidden divider" /> <Spacer />
<domains-table :allow-list-enabled="allowListEnabled" /> <domains-table :allow-list-enabled="allowListEnabled" />
</section>
</main> </main>
</template> </template>

View File

@ -13,7 +13,12 @@ import axios from 'axios'
import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdown.vue' import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdown.vue'
import ReportCard from '~/components/manage/moderation/ReportCard.vue' import ReportCard from '~/components/manage/moderation/ReportCard.vue'
import Pagination from '~/components/vui/Pagination.vue'
import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Header from '~/components/ui/Header.vue'
import Input from '~/components/ui/Input.vue'
import Pagination from '~/components/ui/Pagination.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch' import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
@ -99,27 +104,31 @@ const labels = computed(() => ({
</script> </script>
<template> <template>
<main v-title="labels.reports"> <Layout
<section class="ui vertical stripe segment"> v-title="labels.reports"
<h2 class="ui header"> stack
{{ t('views.admin.moderation.ReportsList.header.reports') }} >
</h2> <Header :h1="t('views.admin.moderation.ReportsList.header.reports')" />
<div class="ui hidden divider" />
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label for="reports-search">{{ t('views.admin.moderation.ReportsList.label.search') }}</label> <label for="reports-search">{{ t('views.admin.moderation.ReportsList.label.search') }}</label>
<form @submit.prevent="query = search.value"> <form @submit.prevent="query = search.value">
<input <Input
id="reports-search" id="reports-search"
ref="search" ref="search"
name="search" name="search"
type="text" search
:value="query" :value="query"
:placeholder="labels.searchPlaceholder" :placeholder="labels.searchPlaceholder"
> />
</form> </form>
</div> </div>
<Spacer :size="16" />
<Layout
flex
>
<Spacer grow />
<div class="field"> <div class="field">
<label for="reports-status">{{ t('views.admin.moderation.ReportsList.label.status') }}</label> <label for="reports-status">{{ t('views.admin.moderation.ReportsList.label.status') }}</label>
<select <select
@ -177,6 +186,7 @@ const labels = computed(() => ({
</option> </option>
</select> </select>
</div> </div>
</Layout>
</div> </div>
</div> </div>
<div <div
@ -200,13 +210,11 @@ const labels = computed(() => ({
/> />
</div> </div>
<div class="ui center aligned basic segment"> <div class="ui center aligned basic segment">
<pagination <Pagination
v-if="result && result.count > paginateBy" v-if="result && result.count > paginateBy"
v-model:current="page" v-model:current="page"
:paginate-by="paginateBy" :paginate-by="paginateBy"
:total="result.count"
/> />
</div> </div>
</section> </Layout>
</main>
</template> </template>

View File

@ -12,7 +12,13 @@ import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import UserRequestCard from '~/components/manage/moderation/UserRequestCard.vue' import UserRequestCard from '~/components/manage/moderation/UserRequestCard.vue'
import Pagination from '~/components/vui/Pagination.vue'
import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Pagination from '~/components/ui/Pagination.vue'
import Input from '~/components/ui/Input.vue'
import Loader from '~/components/ui/Loader.vue'
import Header from '~/components/ui/Header.vue'
import useSmartSearch from '~/composables/navigation/useSmartSearch' import useSmartSearch from '~/composables/navigation/useSmartSearch'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
@ -92,27 +98,26 @@ const labels = computed(() => ({
</script> </script>
<template> <template>
<main v-title="labels.reports"> <Header :h1="t('views.admin.moderation.RequestsList.header.userRequests')" />
<section class="ui vertical stripe segment"> <Spacer />
<h2 class="ui header">
{{ t('views.admin.moderation.RequestsList.header.userRequests') }}
</h2>
<div class="ui hidden divider" />
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label for="requests-search">{{ t('views.admin.moderation.RequestsList.label.search') }}</label>
<form @submit.prevent="query = search.value"> <form @submit.prevent="query = search.value">
<input <Input
id="requests-search" id="requests-search"
ref="search" ref="search"
name="search" name="search"
type="text" search
:label="t('views.admin.moderation.RequestsList.label.search')"
:value="query" :value="query"
:placeholder="labels.searchPlaceholder" :placeholder="labels.searchPlaceholder"
> />
</form> </form>
</div> </div>
<Spacer :size="16" />
<Layout flex>
<Spacer grow />
<div class="field"> <div class="field">
<label for="requests-status">{{ t('views.admin.moderation.RequestsList.label.status') }}</label> <label for="requests-status">{{ t('views.admin.moderation.RequestsList.label.status') }}</label>
<select <select
@ -166,36 +171,29 @@ const labels = computed(() => ({
</option> </option>
</select> </select>
</div> </div>
</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">
<Spacer />
<empty-state <empty-state
:refresh="true" :refresh="true"
@refresh="fetchData()" @refresh="fetchData()"
/> />
</div> </div>
<template v-else> <template v-else>
<Spacer />
<user-request-card <user-request-card
v-for="obj in result.results" v-for="obj in result.results"
:key="obj.uuid" :key="obj.uuid"
:init-obj="obj" :init-obj="obj"
@handled="fetchData" @handled="fetchData"
/> />
<div class="ui center aligned basic segment"> <Pagination
<pagination
v-if="result.count > paginateBy" v-if="result.count > paginateBy"
v-model:current="page" v-model:current="page"
:paginate-by="paginateBy" :paginate-by="paginateBy"
:total="result.count"
/> />
</div>
</template> </template>
</section>
</main>
</template> </template>