mirror of
https://github.com/gotson/komga.git
synced 2025-12-15 21:12:27 +01:00
feat(webui): restrict page streaming and file download per user
also add the ability to edit user roles closes #146
This commit is contained in:
parent
6291dab864
commit
381b196033
9 changed files with 237 additions and 16 deletions
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
<!-- FAB reading (center) -->
|
||||
<v-btn
|
||||
v-if="bookReady && !selected && !preselect"
|
||||
v-if="bookReady && !selected && !preselect && canReadPages"
|
||||
fab
|
||||
x-large
|
||||
color="accent"
|
||||
|
|
@ -144,8 +144,11 @@ export default Vue.extend({
|
|||
return {}
|
||||
},
|
||||
computed: {
|
||||
canReadPages (): boolean {
|
||||
return this.$store.getters.mePageStreaming
|
||||
},
|
||||
overlay (): boolean {
|
||||
return this.onEdit !== undefined || this.onSelected !== undefined || this.bookReady
|
||||
return this.onEdit !== undefined || this.onSelected !== undefined || this.bookReady || this.canReadPages
|
||||
},
|
||||
computedItem (): Item<BookDto | SeriesDto> {
|
||||
return createItem(this.item)
|
||||
|
|
|
|||
|
|
@ -52,8 +52,22 @@
|
|||
<v-col>
|
||||
<span>Roles</span>
|
||||
<v-checkbox
|
||||
v-model="form.admin"
|
||||
v-model="form.roles"
|
||||
label="Administrator"
|
||||
:value="UserRoles.ADMIN"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="form.roles"
|
||||
label="Page Streaming"
|
||||
:value="UserRoles.PAGE_STREAMING"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="form.roles"
|
||||
label="File Download"
|
||||
:value="UserRoles.FILE_DOWNLOAD"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
|
@ -87,6 +101,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { UserRoles } from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
import { email, required } from 'vuelidate/lib/validators'
|
||||
|
||||
|
|
@ -94,6 +109,7 @@ export default Vue.extend({
|
|||
name: 'UserAddDialog',
|
||||
data: () => {
|
||||
return {
|
||||
UserRoles,
|
||||
modalAddUser: true,
|
||||
showPassword: false,
|
||||
snackbar: false,
|
||||
|
|
@ -103,7 +119,7 @@ export default Vue.extend({
|
|||
form: {
|
||||
email: '',
|
||||
password: '',
|
||||
admin: false,
|
||||
roles: [UserRoles.PAGE_STREAMING, UserRoles.FILE_DOWNLOAD],
|
||||
},
|
||||
validationFieldNames: new Map([]),
|
||||
}
|
||||
|
|
@ -149,7 +165,7 @@ export default Vue.extend({
|
|||
return {
|
||||
email: this.form.email,
|
||||
password: this.form.password,
|
||||
roles: this.form.admin ? ['ADMIN'] : [],
|
||||
roles: this.form.roles,
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
|
|
|||
142
komga-webui/src/components/UserEditDialog.vue
Normal file
142
komga-webui/src/components/UserEditDialog.vue
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-dialog v-model="modal"
|
||||
max-width="450"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>Edit user</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<span class="subtitle-1">Roles for {{ user.email }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-checkbox
|
||||
v-model="roles"
|
||||
label="Administrator"
|
||||
:value="UserRoles.ADMIN"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="roles"
|
||||
label="Page Streaming"
|
||||
:value="UserRoles.PAGE_STREAMING"
|
||||
hide-details
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="roles"
|
||||
label="File Download"
|
||||
:value="UserRoles.FILE_DOWNLOAD"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn text @click="dialogCancel">Cancel</v-btn>
|
||||
<v-btn text class="primary--text"
|
||||
@click="dialogConfirm"
|
||||
>Save changes
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
bottom
|
||||
color="error"
|
||||
>
|
||||
{{ snackText }}
|
||||
<v-btn
|
||||
text
|
||||
@click="snackbar = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { UserRoles } from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'UserEditDialog',
|
||||
data: () => {
|
||||
return {
|
||||
UserRoles,
|
||||
snackbar: false,
|
||||
snackText: '',
|
||||
modal: false,
|
||||
roles: [] as string[],
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: Boolean,
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.modal = val
|
||||
},
|
||||
modal (val) {
|
||||
!val && this.dialogCancel()
|
||||
},
|
||||
user (val) {
|
||||
this.dialogReset(val)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
libraries (): LibraryDto[] {
|
||||
return this.$store.state.komgaLibraries.libraries
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dialogReset (user: UserDto) {
|
||||
this.roles = user.roles
|
||||
},
|
||||
dialogCancel () {
|
||||
this.$emit('input', false)
|
||||
this.dialogReset(this.user)
|
||||
},
|
||||
dialogConfirm () {
|
||||
this.editUser()
|
||||
this.$emit('input', false)
|
||||
this.dialogReset(this.user)
|
||||
},
|
||||
showSnack (message: string) {
|
||||
this.snackText = message
|
||||
this.snackbar = true
|
||||
},
|
||||
async editUser () {
|
||||
try {
|
||||
const roles = {
|
||||
roles: this.roles,
|
||||
} as RolesUpdateDto
|
||||
|
||||
await this.$store.dispatch('updateUserRoles', { userId: this.user.id, roles: roles })
|
||||
} catch (e) {
|
||||
this.showSnack(e.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import KomgaUsersService from '@/services/komga-users.service'
|
||||
import { UserRoles } from '@/types/enum-users'
|
||||
import { AxiosInstance } from 'axios'
|
||||
import _Vue from 'vue'
|
||||
import { Module } from 'vuex/types'
|
||||
|
|
@ -11,7 +12,9 @@ const vuexModule: Module<any, any> = {
|
|||
users: [] as UserWithSharedLibrariesDto[],
|
||||
},
|
||||
getters: {
|
||||
meAdmin: state => state.me.hasOwnProperty('roles') && state.me.roles.includes('ADMIN'),
|
||||
meAdmin: state => state.me.hasOwnProperty('roles') && state.me.roles.includes(UserRoles.ADMIN),
|
||||
meFileDownload: state => state.me.hasOwnProperty('roles') && state.me.roles.includes(UserRoles.FILE_DOWNLOAD),
|
||||
mePageStreaming: state => state.me.hasOwnProperty('roles') && state.me.roles.includes(UserRoles.PAGE_STREAMING),
|
||||
authenticated: state => state.me.hasOwnProperty('id'),
|
||||
},
|
||||
mutations: {
|
||||
|
|
@ -46,6 +49,10 @@ const vuexModule: Module<any, any> = {
|
|||
await service.postUser(user)
|
||||
dispatch('getAllUsers')
|
||||
},
|
||||
async updateUserRoles ({ dispatch }, { userId, roles }: { userId: number, roles: RolesUpdateDto }) {
|
||||
await service.patchUserRoles(userId, roles)
|
||||
dispatch('getAllUsers')
|
||||
},
|
||||
async deleteUser ({ dispatch }, user: UserDto) {
|
||||
await service.deleteUser(user)
|
||||
dispatch('getAllUsers')
|
||||
|
|
|
|||
|
|
@ -70,6 +70,18 @@ export default class KomgaUsersService {
|
|||
}
|
||||
}
|
||||
|
||||
async patchUserRoles (userId: number, roles: RolesUpdateDto): Promise<UserDto> {
|
||||
try {
|
||||
return (await this.http.patch(`${API_USERS}/${userId}`, roles)).data
|
||||
} catch (e) {
|
||||
let msg = `An error occurred while trying to patch user '${userId}'`
|
||||
if (e.response.data.message) {
|
||||
msg += `: ${e.response.data.message}`
|
||||
}
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteUser (user: UserDto) {
|
||||
try {
|
||||
await this.http.delete(`${API_USERS}/${user.id}`)
|
||||
|
|
|
|||
5
komga-webui/src/types/enum-users.ts
Normal file
5
komga-webui/src/types/enum-users.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export enum UserRoles {
|
||||
ADMIN = 'ADMIN',
|
||||
FILE_DOWNLOAD = 'FILE_DOWNLOAD',
|
||||
PAGE_STREAMING = 'PAGE_STREAMING'
|
||||
}
|
||||
|
|
@ -29,3 +29,7 @@ interface SharedLibrariesUpdateDto {
|
|||
all: boolean,
|
||||
libraryIds: number[]
|
||||
}
|
||||
|
||||
interface RolesUpdateDto {
|
||||
roles: string[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@
|
|||
<v-btn icon
|
||||
title="Download file"
|
||||
class="pb-1"
|
||||
:disabled="!canDownload"
|
||||
:href="fileUrl">
|
||||
<v-icon>mdi-file-download</v-icon>
|
||||
</v-btn>
|
||||
|
|
@ -117,7 +118,7 @@
|
|||
title="Read book"
|
||||
class="pb-1"
|
||||
:to="{name: 'read-book', params: { bookId: bookId}}"
|
||||
:disabled="book.media.status !== 'READY'"
|
||||
:disabled="book.media.status !== 'READY' || !canReadPages"
|
||||
>
|
||||
<v-icon>mdi-book-open-page-variant</v-icon>
|
||||
</v-btn>
|
||||
|
|
@ -216,6 +217,12 @@ export default Vue.extend({
|
|||
isAdmin (): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
canReadPages (): boolean {
|
||||
return this.$store.getters.mePageStreaming
|
||||
},
|
||||
canDownload (): boolean {
|
||||
return this.$store.getters.meFileDownload
|
||||
},
|
||||
thumbnailUrl (): string {
|
||||
return bookThumbnailUrl(this.bookId)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@
|
|||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-list-item-icon v-on="on">
|
||||
<v-icon v-if="u.roles.includes('ADMIN')" color="red">mdi-account-star</v-icon>
|
||||
<v-icon v-if="u.roles.includes(UserRoles.ADMIN)" color="red">mdi-account-star</v-icon>
|
||||
<v-icon v-else>mdi-account</v-icon>
|
||||
</v-list-item-icon>
|
||||
</template>
|
||||
<span>{{ u.roles.includes('ADMIN') ? 'Administrator' : 'User' }}</span>
|
||||
<span>{{ u.roles.includes(UserRoles.ADMIN) ? 'Administrator' : 'User' }}</span>
|
||||
</v-tooltip>
|
||||
|
||||
<v-list-item-content>
|
||||
|
|
@ -31,7 +31,8 @@
|
|||
<v-list-item-action>
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon @click="editUser(u)" :disabled="u.roles.includes('ADMIN')" v-on="on">
|
||||
<v-btn icon @click="editSharedLibraries(u)" :disabled="u.roles.includes(UserRoles.ADMIN)"
|
||||
v-on="on">
|
||||
<v-icon>mdi-library-books</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
|
@ -39,6 +40,17 @@
|
|||
</v-tooltip>
|
||||
</v-list-item-action>
|
||||
|
||||
<v-list-item-action>
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon @click="editUser(u)" :disabled="u.id === me.id" v-on="on">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Edit user</span>
|
||||
</v-tooltip>
|
||||
</v-list-item-action>
|
||||
|
||||
<v-list-item-action>
|
||||
<v-btn icon @click="promptDeleteUser(u)"
|
||||
:disabled="u.id === me.id"
|
||||
|
|
@ -59,10 +71,14 @@
|
|||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<user-shared-libraries-edit-dialog v-model="modalUserSharedLibraries"
|
||||
<user-shared-libraries-edit-dialog v-model="modalEditSharedLibraries"
|
||||
:user="userToEditSharedLibraries"
|
||||
/>
|
||||
|
||||
<user-edit-dialog v-model="modalEditUser"
|
||||
:user="userToEdit"
|
||||
/>
|
||||
|
||||
<user-delete-dialog v-model="modalDeleteUser"
|
||||
:user="userToDelete">
|
||||
</user-delete-dialog>
|
||||
|
|
@ -76,18 +92,23 @@
|
|||
|
||||
<script lang="ts">
|
||||
import UserDeleteDialog from '@/components/UserDeleteDialog.vue'
|
||||
import UserEditDialog from '@/components/UserEditDialog.vue'
|
||||
import UserSharedLibrariesEditDialog from '@/components/UserSharedLibrariesEditDialog.vue'
|
||||
import { UserRoles } from '@/types/enum-users'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'SettingsUsers',
|
||||
components: { UserSharedLibrariesEditDialog, UserDeleteDialog },
|
||||
components: { UserSharedLibrariesEditDialog, UserDeleteDialog, UserEditDialog },
|
||||
data: () => ({
|
||||
modalDeleteUser: false,
|
||||
UserRoles,
|
||||
modalAddUser: false,
|
||||
modalDeleteUser: false,
|
||||
userToDelete: {} as UserDto,
|
||||
modalUserSharedLibraries: false,
|
||||
modalEditSharedLibraries: false,
|
||||
userToEditSharedLibraries: {} as UserWithSharedLibrariesDto,
|
||||
modalEditUser: false,
|
||||
userToEdit: {} as UserDto,
|
||||
}),
|
||||
computed: {
|
||||
users (): UserWithSharedLibrariesDto[] {
|
||||
|
|
@ -105,9 +126,13 @@ export default Vue.extend({
|
|||
this.userToDelete = user
|
||||
this.modalDeleteUser = true
|
||||
},
|
||||
editUser (user: UserWithSharedLibrariesDto) {
|
||||
editSharedLibraries (user: UserWithSharedLibrariesDto) {
|
||||
this.userToEditSharedLibraries = user
|
||||
this.modalUserSharedLibraries = true
|
||||
this.modalEditSharedLibraries = true
|
||||
},
|
||||
editUser (user: UserDto) {
|
||||
this.userToEdit = user
|
||||
this.modalEditUser = true
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue