feat(frontend): user permissions

This commit is contained in:
FrenchGithubUser
2025-12-14 14:35:52 +01:00
parent 0ef99532c9
commit b8f522116f
19 changed files with 97 additions and 112 deletions

View File

@@ -11,6 +11,7 @@ import { ref } from 'vue'
import { Button } from 'primevue' import { Button } from 'primevue'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import type { UserPermission } from '@/services/api-schema'
const userStore = useUserStore() const userStore = useUserStore()
@@ -29,7 +30,9 @@ const menuItems = ref([
]) ])
onMounted(() => { onMounted(() => {
if (userStore.class === 'staff') { // if the user can do one of those actions, they can access the staff dashboard
const permissionsToSeeStaffDashboard: UserPermission[] = ['create_css_sheet', 'edit_css_sheet', 'get_user_application', 'read_staff_pm']
if (permissionsToSeeStaffDashboard.some((x: UserPermission) => userStore.permissions.includes(x))) {
menuItems.value.push({ label: 'Staff Dashboard', route: '/staff-dashboard' }) menuItems.value.push({ label: 'Staff Dashboard', route: '/staff-dashboard' })
} }
}) })

View File

@@ -4,7 +4,7 @@
<div class="actions"> <div class="actions">
<i <i
class="pi pi-pen-to-square" class="pi pi-pen-to-square"
v-if="userStore.class === 'staff' || artist.created_by_id === userStore.id" v-if="userStore.permissions.includes('edit_artist') || artist.created_by_id === userStore.id"
v-tooltip.top="t('artist.edit')" v-tooltip.top="t('artist.edit')"
@click="editArtist" @click="editArtist"
/> />

View File

@@ -3,7 +3,7 @@
<div class="actions"> <div class="actions">
<i <i
class="pi pi-pen-to-square" class="pi pi-pen-to-square"
v-if="(userStore.id === comment.created_by.id && 'locked' in comment && comment.locked === false) || userStore.class === 'staff'" v-if="(userStore.id === comment.created_by.id && 'locked' in comment && comment.locked === false) || hasEditPermission"
@click="editCommentDialogVisible = true" @click="editCommentDialogVisible = true"
/> />
<RouterLink <RouterLink
@@ -55,6 +55,7 @@ const props = defineProps<{
comment: TitleGroupCommentHierarchy | ForumPostHierarchy | ConversationMessageHierarchy comment: TitleGroupCommentHierarchy | ForumPostHierarchy | ConversationMessageHierarchy
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
editCommentMethod?: Function editCommentMethod?: Function
hasEditPermission: boolean
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -5,6 +5,7 @@
:key="message.id" :key="message.id"
:comment="message" :comment="message"
:class="`message ${userStore.id === message.created_by.id ? 'sent' : 'received'}`" :class="`message ${userStore.id === message.created_by.id ? 'sent' : 'received'}`"
:hasEditPermission="false"
/> />
</div> </div>
</template> </template>

View File

@@ -10,7 +10,7 @@
<label for="subcategory">{{ t('forum.subcategory') }}</label> <label for="subcategory">{{ t('forum.subcategory') }}</label>
</FloatLabel> </FloatLabel>
<div v-if="userStore.class === 'staff'" class="staff-options"> <div v-if="userStore.permissions.includes('edit_forum_thread')" class="staff-options">
<div class="checkbox-row"> <div class="checkbox-row">
<Checkbox v-model="editedThread.locked" binary inputId="locked" /> <Checkbox v-model="editedThread.locked" binary inputId="locked" />
<label for="locked">{{ t('general.locked') }}</label> <label for="locked">{{ t('general.locked') }}</label>

View File

@@ -3,12 +3,16 @@
<div class="top"> <div class="top">
<div class="title">{{ forumCategory.name }}</div> <div class="title">{{ forumCategory.name }}</div>
<div class="actions"> <div class="actions">
<RouterLink :to="`/forum/category/${forumCategory.id}/edit`" v-if="userStore.class === 'staff'" v-tooltip.top="t('forum.edit_category')"> <RouterLink
:to="`/forum/category/${forumCategory.id}/edit`"
v-if="userStore.permissions.includes('edit_forum_category')"
v-tooltip.top="t('forum.edit_category')"
>
<i class="pi pi-pen-to-square" /> <i class="pi pi-pen-to-square" />
</RouterLink> </RouterLink>
<RouterLink <RouterLink
:to="{ path: '/forum/sub-category/new', query: { categoryId: forumCategory.id, categoryName: forumCategory.name } }" :to="{ path: '/forum/sub-category/new', query: { categoryId: forumCategory.id, categoryName: forumCategory.name } }"
v-if="userStore.class === 'staff'" v-if="userStore.permissions.includes('create_forum_sub_category')"
v-tooltip.top="t('forum.create_sub_category')" v-tooltip.top="t('forum.create_sub_category')"
> >
<i class="pi pi-plus" /> <i class="pi pi-plus" />

View File

@@ -6,6 +6,7 @@
:comment="comment" :comment="comment"
@commentEdited="commentEdited($event, comment.id)" @commentEdited="commentEdited($event, comment.id)"
:editCommentMethod="(post: EditedTitleGroupComment) => editTitleGroupComment({ EditedTitleGroupComment: post, id: comment.id })" :editCommentMethod="(post: EditedTitleGroupComment) => editTitleGroupComment({ EditedTitleGroupComment: post, id: comment.id })"
:hasEditPermission="useUserStore().permissions.includes('edit_title_group_comment')"
/> />
</div> </div>
<Form v-slot="$form" :initialValues="new_comment" :resolver @submit="onFormSubmit" validateOnSubmit :validateOnValueUpdate="false"> <Form v-slot="$form" :initialValues="new_comment" :resolver @submit="onFormSubmit" validateOnSubmit :validateOnValueUpdate="false">

View File

@@ -49,11 +49,11 @@
<i <i
v-tooltip.top="t('general.delete')" v-tooltip.top="t('general.delete')"
class="action pi pi-trash" class="action pi pi-trash"
v-if="showActionBtns && (user.id === slotProps.data.created_by_id || user.class === 'staff')" v-if="showActionBtns && (user.id === slotProps.data.created_by_id || user.permissions.includes('delete_torrent'))"
@click="deleteTorrent(slotProps.data.id)" @click="deleteTorrent(slotProps.data.id)"
/> />
<i <i
v-if="showActionBtns && (user.id === slotProps.data.created_by_id || user.class === 'staff')" v-if="showActionBtns && (user.id === slotProps.data.created_by_id || user.permissions.includes('edit_torrent'))"
v-tooltip.top="t('general.edit')" v-tooltip.top="t('general.edit')"
@click="editTorrent(slotProps.data)" @click="editTorrent(slotProps.data)"
class="action pi pi-pen-to-square" class="action pi pi-pen-to-square"

View File

@@ -8,7 +8,7 @@
{{ t('user.last_seen') }}: {{ t('user.last_seen') }}:
<span v-tooltip.top="formatDate(user.last_seen)">{{ timeAgo(user.last_seen) }}</span> <span v-tooltip.top="formatDate(user.last_seen)">{{ timeAgo(user.last_seen) }}</span>
<br /> <br />
{{ t('user.class') }}: {{ user.class }} {{ t('user.class') }}: {{ user.class_name }}
<br /> <br />
{{ t('user.bonus_points') }}: {{ user.bonus_points }} {{ t('user.bonus_points') }}: {{ user.bonus_points }}
<br /> <br />

View File

@@ -1049,7 +1049,7 @@ export interface PublicUser {
'average_seeding_time': number; 'average_seeding_time': number;
'banned': boolean; 'banned': boolean;
'bonus_points': number; 'bonus_points': number;
'class': UserClass; 'class_name': string;
'collages_started': number; 'collages_started': number;
'created_at': string; 'created_at': string;
'description': string; 'description': string;
@@ -1076,8 +1076,6 @@ export interface PublicUser {
'username': string; 'username': string;
'warned': boolean; 'warned': boolean;
} }
export interface RefreshToken { export interface RefreshToken {
'refresh_token': string; 'refresh_token': string;
} }
@@ -1495,11 +1493,6 @@ export interface TorrentHierarchyLite {
} }
export interface TorrentMinimal {
'created_at': string;
'id': number;
'info_hash'?: string | null;
}
export interface TorrentReport { export interface TorrentReport {
'description': string; 'description': string;
'id': number; 'id': number;
@@ -1669,7 +1662,8 @@ export interface User {
'average_seeding_time': number; 'average_seeding_time': number;
'banned': boolean; 'banned': boolean;
'bonus_points': number; 'bonus_points': number;
'class': UserClass; 'class_locked': boolean;
'class_name': string;
'collages_started': number; 'collages_started': number;
'created_at': string; 'created_at': string;
'css_sheet_name': string; 'css_sheet_name': string;
@@ -1686,6 +1680,7 @@ export interface User {
'leeching': number; 'leeching': number;
'passkey': string; 'passkey': string;
'password_hash': string; 'password_hash': string;
'permissions': Array<UserPermission>;
'ratio': number; 'ratio': number;
'real_downloaded': number; 'real_downloaded': number;
'real_uploaded': number; 'real_uploaded': number;
@@ -1703,8 +1698,6 @@ export interface User {
'username': string; 'username': string;
'warned': boolean; 'warned': boolean;
} }
export interface UserApplication { export interface UserApplication {
'applied_from_ip': string; 'applied_from_ip': string;
'body': string; 'body': string;
@@ -1727,16 +1720,6 @@ export const UserApplicationStatus = {
export type UserApplicationStatus = typeof UserApplicationStatus[keyof typeof UserApplicationStatus]; export type UserApplicationStatus = typeof UserApplicationStatus[keyof typeof UserApplicationStatus];
export const UserClass = {
Newbie: 'newbie',
Staff: 'staff',
Tracker: 'tracker'
} as const;
export type UserClass = typeof UserClass[keyof typeof UserClass];
export interface UserCreatedAffiliatedArtist { export interface UserCreatedAffiliatedArtist {
'artist_id': number; 'artist_id': number;
'nickname'?: string | null; 'nickname'?: string | null;
@@ -1924,6 +1907,50 @@ export interface UserLiteAvatar {
'username': string; 'username': string;
'warned': boolean; 'warned': boolean;
} }
export const UserPermission = {
UploadTorrent: 'upload_torrent',
DownloadTorrent: 'download_torrent',
CreateTorrentRequest: 'create_torrent_request',
ImmuneActivityPruning: 'immune_activity_pruning',
EditTitleGroup: 'edit_title_group',
EditTitleGroupComment: 'edit_title_group_comment',
EditEditionGroup: 'edit_edition_group',
EditTorrent: 'edit_torrent',
EditArtist: 'edit_artist',
EditCollage: 'edit_collage',
EditSeries: 'edit_series',
EditTorrentRequest: 'edit_torrent_request',
EditForumPost: 'edit_forum_post',
EditForumThread: 'edit_forum_thread',
EditForumSubCategory: 'edit_forum_sub_category',
EditForumCategory: 'edit_forum_category',
CreateForumCategory: 'create_forum_category',
CreateForumSubCategory: 'create_forum_sub_category',
CreateForumThread: 'create_forum_thread',
CreateForumPost: 'create_forum_post',
SendPm: 'send_pm',
CreateCssSheet: 'create_css_sheet',
EditCssSheet: 'edit_css_sheet',
SetDefaultCssSheet: 'set_default_css_sheet',
ReadStaffPm: 'read_staff_pm',
ReplyStaffPm: 'reply_staff_pm',
ResolveStaffPm: 'resolve_staff_pm',
UnresolveStaffPm: 'unresolve_staff_pm',
DeleteTitleGroupTag: 'delete_title_group_tag',
EditTitleGroupTag: 'edit_title_group_tag',
DeleteTorrent: 'delete_torrent',
GetUserApplication: 'get_user_application',
UpdateUserApplication: 'update_user_application',
WarnUser: 'warn_user',
EditUser: 'edit_user',
CreateWikiArticle: 'create_wiki_article',
EditWikiArticle: 'edit_wiki_article'
} as const;
export type UserPermission = typeof UserPermission[keyof typeof UserPermission];
export interface UserSettings { export interface UserSettings {
'css_sheet_name': string; 'css_sheet_name': string;
} }
@@ -9172,39 +9199,6 @@ export const TorrentApiAxiosParamCreator = function (configuration?: Configurati
options: localVarRequestOptions, options: localVarRequestOptions,
}; };
}, },
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getRegisteredTorrents: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/torrents/registered`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication http required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/** /**
* *
* @param {string} period * @param {string} period
@@ -9370,17 +9364,6 @@ export const TorrentApiFp = function(configuration?: Configuration) {
const localVarOperationServerBasePath = operationServerMap['TorrentApi.editTorrent']?.[localVarOperationServerIndex]?.url; const localVarOperationServerBasePath = operationServerMap['TorrentApi.editTorrent']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
}, },
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getRegisteredTorrents(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<TorrentMinimal>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getRegisteredTorrents(options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['TorrentApi.getRegisteredTorrents']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/** /**
* *
* @param {string} period * @param {string} period
@@ -9479,14 +9462,6 @@ export const TorrentApiFactory = function (configuration?: Configuration, basePa
editTorrent(editedTorrent: EditedTorrent, options?: RawAxiosRequestConfig): AxiosPromise<Torrent> { editTorrent(editedTorrent: EditedTorrent, options?: RawAxiosRequestConfig): AxiosPromise<Torrent> {
return localVarFp.editTorrent(editedTorrent, options).then((request) => request(axios, basePath)); return localVarFp.editTorrent(editedTorrent, options).then((request) => request(axios, basePath));
}, },
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getRegisteredTorrents(options?: RawAxiosRequestConfig): AxiosPromise<Array<TorrentMinimal>> {
return localVarFp.getRegisteredTorrents(options).then((request) => request(axios, basePath));
},
/** /**
* *
* @param {string} period * @param {string} period
@@ -9582,15 +9557,6 @@ export class TorrentApi extends BaseAPI {
return TorrentApiFp(this.configuration).editTorrent(editedTorrent, options).then((request) => request(this.axios, this.basePath)); return TorrentApiFp(this.configuration).editTorrent(editedTorrent, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public getRegisteredTorrents(options?: RawAxiosRequestConfig) {
return TorrentApiFp(this.configuration).getRegisteredTorrents(options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @param {string} period * @param {string} period
@@ -9691,12 +9657,6 @@ export const editTorrent = async (editedTorrent: EditedTorrent, options?: RawAxi
return response.data; return response.data;
}; };
export const getRegisteredTorrents = async (options?: RawAxiosRequestConfig): Promise<Array<TorrentMinimal>> => {
const response = await torrentApi.getRegisteredTorrents(options);
return response.data;
};
export interface GetTopTorrentRequest { export interface GetTopTorrentRequest {
/** */ /** */
'period': string; 'period': string;
@@ -10365,7 +10325,7 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration)
warnUser: async (userCreatedUserWarning: UserCreatedUserWarning, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => { warnUser: async (userCreatedUserWarning: UserCreatedUserWarning, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'userCreatedUserWarning' is not null or undefined // verify required parameter 'userCreatedUserWarning' is not null or undefined
assertParamExists('warnUser', 'userCreatedUserWarning', userCreatedUserWarning) assertParamExists('warnUser', 'userCreatedUserWarning', userCreatedUserWarning)
const localVarPath = `/api/users/warnings`; const localVarPath = `/api/users/warn`;
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions; let baseOptions;

View File

@@ -6,7 +6,9 @@ const initialState: User = {
avatar: null, avatar: null,
average_seeding_time: 0, average_seeding_time: 0,
bonus_points: 0, bonus_points: 0,
class: 'newbie', class_name: 'newbie',
class_locked: false,
permissions: [],
collages_started: 0, collages_started: 0,
created_at: '', created_at: '',
description: '', description: '',

View File

@@ -64,10 +64,20 @@
</span> </span>
</template> </template>
</Column> </Column>
<Column :header="t('general.action', 2)" class="actions" v-if="userStore.class === 'staff'"> <Column :header="t('general.action', 2)" class="actions">
<template #body="slotProps"> <template #body="slotProps">
<i class="pi pi-pen-to-square cursor-pointer" v-tooltip.top="t('general.edit')" @click="editTag(slotProps.data)" /> <i
<i class="pi pi-trash cursor-pointer" v-tooltip.top="t('general.delete')" @click="deleteTag(slotProps.data)" /> class="pi pi-pen-to-square cursor-pointer"
v-if="userStore.permissions.includes('edit_title_group_tag')"
v-tooltip.top="t('general.edit')"
@click="editTag(slotProps.data)"
/>
<i
class="pi pi-trash cursor-pointer"
v-if="userStore.permissions.includes('delete_title_group_tag')"
v-tooltip.top="t('general.delete')"
@click="deleteTag(slotProps.data)"
/>
</template> </template>
</Column> </Column>
</DataTable> </DataTable>

View File

@@ -20,7 +20,7 @@
</div> </div>
<div> <div>
<i <i
v-if="titleGroupAndAssociatedData.title_group.created_by_id === userStore.id || userStore.class === 'staff'" v-if="titleGroupAndAssociatedData.title_group.created_by_id === userStore.id || userStore.permissions.includes('edit_title_group')"
v-tooltip.top="t('general.edit')" v-tooltip.top="t('general.edit')"
class="pi pi-pen-to-square" class="pi pi-pen-to-square"
@click="editTitleGroupDialogVisible = true" @click="editTitleGroupDialogVisible = true"

View File

@@ -11,7 +11,7 @@
<RouterLink :to="`/conversation/new?receiverId=${user.id}&username=${user.username}`" class="no-color" v-if="userStore.id !== user.id"> <RouterLink :to="`/conversation/new?receiverId=${user.id}&username=${user.username}`" class="no-color" v-if="userStore.id !== user.id">
<i v-tooltip.top="t('user.message_user', [user.username])" class="pi pi-envelope" /> <i v-tooltip.top="t('user.message_user', [user.username])" class="pi pi-envelope" />
</RouterLink> </RouterLink>
<template v-if="userStore.class === 'staff' && userStore.id !== user.id"> <template v-if="userStore.permissions.includes('warn_user') && userStore.id !== user.id">
<i v-tooltip.top="t('user.warn')" class="cursor-pointer pi pi-exclamation-triangle" @click="warnUserDialogVisible = true" /> <i v-tooltip.top="t('user.warn')" class="cursor-pointer pi pi-exclamation-triangle" @click="warnUserDialogVisible = true" />
</template> </template>
<template v-if="userStore.id === user.id"> <template v-if="userStore.id === user.id">

View File

@@ -13,7 +13,7 @@
<RouterLink to="/forum/search"> <RouterLink to="/forum/search">
<i class="pi pi-search" v-tooltip.top="t('forum.search')" /> <i class="pi pi-search" v-tooltip.top="t('forum.search')" />
</RouterLink> </RouterLink>
<RouterLink to="/forum/category/new" v-if="userStore.class === 'staff'"> <RouterLink to="/forum/category/new" v-if="userStore.permissions.includes('create_forum_category')">
<i class="pi pi-plus" v-tooltip.top="t('forum.create_category')" /> <i class="pi pi-plus" v-tooltip.top="t('forum.create_category')" />
</RouterLink> </RouterLink>
</div> </div>

View File

@@ -6,7 +6,7 @@
<RouterLink to="">{{ forumSubCategory.name }}</RouterLink> <RouterLink to="">{{ forumSubCategory.name }}</RouterLink>
</div> </div>
<div class="actions"> <div class="actions">
<RouterLink :to="`/forum/sub-category/${route.params.id}/edit`" v-if="forumSubCategory && userStore.class === 'staff'"> <RouterLink :to="`/forum/sub-category/${route.params.id}/edit`" v-if="forumSubCategory && userStore.permissions.includes('edit_forum_sub_category')">
<i v-tooltip.top="t('forum.edit_subcategory')" class="pi pi-pen-to-square cursor-pointer" /> <i v-tooltip.top="t('forum.edit_subcategory')" class="pi pi-pen-to-square cursor-pointer" />
</RouterLink> </RouterLink>
<RouterLink :to="`/forum/thread/new?subCategoryId=${route.params.id}`"> <RouterLink :to="`/forum/thread/new?subCategoryId=${route.params.id}`">

View File

@@ -8,7 +8,7 @@
</div> </div>
<div class="actions"> <div class="actions">
<i <i
v-if="userStore.class === 'staff' || forumThread.created_by_id === userStore.id" v-if="userStore.permissions.includes('edit_forum_thread') || forumThread.created_by_id === userStore.id"
class="pi pi-pen-to-square" class="pi pi-pen-to-square"
v-tooltip.top="t('forum.edit_thread')" v-tooltip.top="t('forum.edit_thread')"
@click="editThreadDialogVisible = true" @click="editThreadDialogVisible = true"
@@ -36,6 +36,7 @@
:comment="post" :comment="post"
:editCommentMethod="editForumPostMethod" :editCommentMethod="editForumPostMethod"
@commentEdited="postEdited($event as EditedForumPost)" @commentEdited="postEdited($event as EditedForumPost)"
:hasEditPermission="userStore.permissions.includes('edit_forum_post')"
/> />
</PaginatedResults> </PaginatedResults>
<Form v-slot="$form" :initialValues="newPost" :resolver @submit="onFormSubmit" validateOnSubmit :validateOnValueUpdate="false"> <Form v-slot="$form" :initialValues="newPost" :resolver @submit="onFormSubmit" validateOnSubmit :validateOnValueUpdate="false">

View File

@@ -7,13 +7,13 @@
<Tab value="2">{{ t('css_sheet.css_sheet', 2) }}</Tab> <Tab value="2">{{ t('css_sheet.css_sheet', 2) }}</Tab>
</TabList> </TabList>
<TabPanels> <TabPanels>
<TabPanel value="0"> <TabPanel value="0" v-if="userStore.permissions.includes('get_user_application')">
<UserApplications /> <UserApplications />
</TabPanel> </TabPanel>
<TabPanel value="1"> <TabPanel value="1" v-if="userStore.permissions.includes('read_staff_pm')">
<StaffPmsTable /> <StaffPmsTable />
</TabPanel> </TabPanel>
<TabPanel value="2"> <TabPanel value="2" v-if="userStore.permissions.includes('edit_css_sheet') || userStore.permissions.includes('create_css_sheet')">
<CssSheetList showStaffActions /> <CssSheetList showStaffActions />
</TabPanel> </TabPanel>
</TabPanels> </TabPanels>
@@ -31,8 +31,10 @@ import UserApplications from '@/components/staff/UserApplications.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import StaffPmsTable from '@/components/staff_pm/StaffPmsTable.vue' import StaffPmsTable from '@/components/staff_pm/StaffPmsTable.vue'
import CssSheetList from '@/components/CssSheetList.vue' import CssSheetList from '@/components/CssSheetList.vue'
import { useUserStore } from '@/stores/user'
const { t } = useI18n() const { t } = useI18n()
const userStore = useUserStore()
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="actions wrapper-center"> <div class="actions wrapper-center">
<RouterLink to="/wiki/create-article"> <RouterLink to="/wiki/create-article" v-if="userStore.permissions.includes('create_wiki_article')">
<i class="pi pi-plus" v-tooltip.top="t('wiki.create_article')" /> <i class="pi pi-plus" v-tooltip.top="t('wiki.create_article')" />
</RouterLink> </RouterLink>
<RouterLink to="/wiki/search"> <RouterLink to="/wiki/search">
@@ -9,7 +9,7 @@
</div> </div>
<div v-if="wikiArticle" class="wiki-article"> <div v-if="wikiArticle" class="wiki-article">
<ContentContainer :containerTitle="wikiArticle.title"> <ContentContainer :containerTitle="wikiArticle.title">
<template v-if="userStore.class === 'staff'" #top-right> <template v-if="userStore.permissions.includes('edit_wiki_article')" #top-right>
<RouterLink :to="`/wiki/article/${wikiArticle.id}/edit`" v-tooltip.top="t('wiki.edit_article')"> <RouterLink :to="`/wiki/article/${wikiArticle.id}/edit`" v-tooltip.top="t('wiki.edit_article')">
<i class="pi pi-pen-to-square" style="color: white" /> <i class="pi pi-pen-to-square" style="color: white" />
</RouterLink> </RouterLink>