Skip to content

Commit cf073f5

Browse files
committed
first shot at users view
1 parent c5ecc71 commit cf073f5

File tree

5 files changed

+133
-5
lines changed

5 files changed

+133
-5
lines changed

next-ui/openapi-generator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const DATE = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Dat
1111
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull()); // `null`
1212

1313
const ast = await openapiTS(mySchema, {
14-
transform(schemaObject, metadata) {
14+
transform(schemaObject) {
1515
if (schemaObject.format === "date-time") {
1616
return schemaObject.nullable
1717
? ts.factory.createUnionTypeNode([DATE, NULL])

next-ui/src/colada/queries/users.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {defineQuery, useQuery} from '@pinia/colada'
2+
import {komgaClient} from '@/api/komga-client'
3+
4+
export const useUsers = defineQuery(() => {
5+
return useQuery({
6+
key: () => ['users'],
7+
query: () => komgaClient.GET('/api/v2/users')
8+
// unwrap the openapi-fetch structure on success
9+
.then((res) => res.data),
10+
})
11+
})

next-ui/src/pages/server/updates.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
v-if="release.version == currentVersion"
5151
class="mx-2 mt-n3"
5252
size="small"
53-
label
53+
rounded
5454
color="info"
5555
>
5656
Currently installed
@@ -59,7 +59,7 @@
5959
v-if="release.version == latest?.version"
6060
class="mx-2 mt-n3"
6161
size="small"
62-
label
62+
rounded
6363
>
6464
Latest
6565
</v-chip>

next-ui/src/pages/server/users.vue

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,122 @@
11
<template>
2-
<h1>Users</h1>
2+
<v-alert
3+
v-if="error"
4+
type="error"
5+
variant="tonal"
6+
>
7+
Error loading data
8+
</v-alert>
9+
10+
<template v-else>
11+
<v-data-table
12+
:loading="isLoading"
13+
:items="users"
14+
:headers="headers"
15+
:hide-default-footer="users?.length < 11"
16+
>
17+
<template #[`item.roles`]="{ value }">
18+
<div class="d-flex ga-1">
19+
<v-chip
20+
v-for="role in value"
21+
:key="role"
22+
:color="getRoleColor(role)"
23+
:text="role"
24+
size="x-small"
25+
rounded
26+
/>
27+
</div>
28+
</template>
29+
30+
<template #[`item.actions`]="{ item }">
31+
<div class="d-flex ga-1 justify-end">
32+
<v-icon-btn
33+
v-tooltip:bottom="'Reset password'"
34+
icon="mdi-lock-reset"
35+
@click="changePassword(item.id)"
36+
/>
37+
<v-icon-btn
38+
v-tooltip:bottom="'Edit restrictions'"
39+
icon="mdi-book-lock"
40+
:disabled="me?.id == item.id"
41+
@click="editRestrictions(item.id)"
42+
/>
43+
<v-icon-btn
44+
v-tooltip:bottom="'Edit user'"
45+
icon="mdi-pencil"
46+
:disabled="me?.id == item.id"
47+
@click="editUser(item.id)"
48+
/>
49+
<v-icon-btn
50+
v-tooltip:bottom="'Delete user'"
51+
icon="mdi-delete"
52+
:disabled="me?.id == item.id"
53+
@click="deleteUser(item.id)"
54+
/>
55+
</div>
56+
</template>
57+
</v-data-table>
58+
</template>
359
</template>
460

561
<script lang="ts" setup>
6-
//
62+
import {useUsers} from '@/colada/queries/users.ts'
63+
import {komgaClient} from '@/api/komga-client.ts'
64+
import type {components} from '@/generated/openapi/komga'
65+
import {useCurrentUser} from '@/colada/queries/current-user.ts'
66+
import {UserRoles} from '@/types/UserRoles.ts'
67+
68+
const {data: users, error, isLoading} = useUsers()
69+
const {data: me} = useCurrentUser()
70+
71+
const headers = [
72+
{title: 'Email', key: 'email'},
73+
{title: 'Latest Activity', key: 'activity', value: (item: components["schemas"]["UserDto"]) => latestActivity[item.id]},
74+
{title: 'Roles', value: 'roles', sortable: false},
75+
{title: 'Actions', key: 'actions', align: 'end', sortable: false},
76+
]
77+
78+
function getRoleColor(role: UserRoles) {
79+
if(role === UserRoles.ADMIN) return 'error'
80+
}
81+
82+
83+
// store each user's latest activity in a map
84+
// when the 'users' change, we call the API for each user
85+
const latestActivity: Record<string, Date | undefined> = reactive({})
86+
87+
function getLatestActivity(userId: string) {
88+
komgaClient.GET('/api/v2/users/{id}/authentication-activity/latest', {
89+
params: {
90+
path: { id: userId }
91+
}
92+
})
93+
// unwrap the openapi-fetch structure on success
94+
.then((res) => latestActivity[userId] = res.data?.dateTime)
95+
.catch(() => {})
96+
}
97+
98+
watch(users, (users) => {
99+
if(users) for (const user of users) {
100+
getLatestActivity(user.id)
101+
}
102+
})
103+
104+
105+
function editRestrictions(userId: string) {
106+
console.log('edit restrictions: ', userId)
107+
}
108+
109+
function deleteUser(userId: string) {
110+
console.log('delete: ', userId)
111+
}
112+
113+
function editUser(userId: string) {
114+
console.log('edit: ', userId)
115+
}
116+
117+
function changePassword(userId: string) {
118+
console.log('change password: ', userId)
119+
}
7120
</script>
8121

9122
<route lang="yaml">

next-ui/src/plugins/vuetify.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'vuetify/styles'
1111
// Composables
1212
import {createVuetify} from 'vuetify'
1313
import {md3} from 'vuetify/blueprints'
14+
import {VIconBtn} from 'vuetify/labs/components'
1415

1516

1617
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
@@ -37,4 +38,7 @@ export default createVuetify({
3738
},
3839
},
3940
blueprint: md3,
41+
components: {
42+
VIconBtn,
43+
},
4044
})

0 commit comments

Comments
 (0)