From d6bfbb7a13b09ad9e005059e983e73264f02397c Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Thu, 24 Jul 2025 17:52:02 +0800 Subject: [PATCH] add authentication activity pages --- next-ui/src/colada/users.ts | 67 ++++- next-ui/src/components.d.ts | 2 + .../AuthenticationActivityTable.stories.ts | 64 ++++ .../user/AuthenticationActivityTable.vue | 173 +++++++++++ .../layout/app/drawer/menu/Account.vue | 4 +- .../layout/app/drawer/menu/Server.vue | 12 + next-ui/src/mocks/api/handlers/users.ts | 275 +++++++++++++++++- next-ui/src/mocks/api/pageable.ts | 39 +++ next-ui/src/pages/account/activity.vue | 11 +- next-ui/src/pages/server/activity.vue | 10 + next-ui/src/pages/server/announcements.vue | 1 - next-ui/src/pages/server/updates.vue | 1 - next-ui/src/typed-router.d.ts | 1 + next-ui/src/types/PageRequest.ts | 52 ++++ 14 files changed, 702 insertions(+), 10 deletions(-) create mode 100644 next-ui/src/fragments/fragment/user/AuthenticationActivityTable.stories.ts create mode 100644 next-ui/src/fragments/fragment/user/AuthenticationActivityTable.vue create mode 100644 next-ui/src/mocks/api/pageable.ts create mode 100644 next-ui/src/pages/server/activity.vue create mode 100644 next-ui/src/types/PageRequest.ts diff --git a/next-ui/src/colada/users.ts b/next-ui/src/colada/users.ts index 5739c0dc..cbfa6ce9 100644 --- a/next-ui/src/colada/users.ts +++ b/next-ui/src/colada/users.ts @@ -1,4 +1,11 @@ -import { defineMutation, defineQuery, useMutation, useQuery, useQueryCache } from '@pinia/colada' +import { + defineMutation, + defineQuery, + defineQueryOptions, + useMutation, + useQuery, + useQueryCache, +} from '@pinia/colada' import { komgaClient } from '@/api/komga-client' import { UserRoles } from '@/types/UserRoles' import type { components } from '@/generated/openapi/komga' @@ -150,3 +157,61 @@ export const useDeleteApiKey = defineMutation(() => { }, }) }) + +/////////// +// Authentication Activity +/////////// + +export const authenticationActivityQuery = defineQueryOptions( + ({ + page, + size, + sort, + unpaged, + }: { + page?: number + size?: number + sort?: string[] + unpaged?: boolean + }) => ({ + key: ['authentication-activity', { page: page, size: size, sort: sort, unpaged: unpaged }], + query: () => + komgaClient + .GET('/api/v2/users/authentication-activity', { + params: { + query: { page: page, size: size, sort: sort, unpaged: unpaged }, + }, + }) + // unwrap the openapi-fetch structure on success + .then((res) => res.data), + }), +) + +export const myAuthenticationActivityQuery = defineQueryOptions( + ({ + page, + size, + sort, + unpaged, + }: { + page?: number + size?: number + sort?: string[] + unpaged?: boolean + }) => ({ + key: [ + ...QUERY_KEYS_USERS.currentUser, + 'authentication-activity', + { page: page, size: size, sort: sort, unpaged: unpaged }, + ], + query: () => + komgaClient + .GET('/api/v2/users/me/authentication-activity', { + params: { + query: { page: page, size: size, sort: sort, unpaged: unpaged }, + }, + }) + // unwrap the openapi-fetch structure on success + .then((res) => res.data), + }), +) diff --git a/next-ui/src/components.d.ts b/next-ui/src/components.d.ts index 43c102e6..0d4e1c6c 100644 --- a/next-ui/src/components.d.ts +++ b/next-ui/src/components.d.ts @@ -25,6 +25,7 @@ declare module 'vue' { FragmentLocaleSelector: typeof import('./fragments/fragment/LocaleSelector.vue')['default'] FragmentSnackQueue: typeof import('./fragments/fragment/SnackQueue.vue')['default'] FragmentThemeSelector: typeof import('./fragments/fragment/ThemeSelector.vue')['default'] + FragmentUserAuthenticationActivityTable: typeof import('./fragments/fragment/user/AuthenticationActivityTable.vue')['default'] FragmentUserFormCreateEdit: typeof import('./fragments/fragment/user/form/CreateEdit.vue')['default'] FragmentUserTable: typeof import('./fragments/fragment/user/Table.vue')['default'] HelloWorld: typeof import('./components/HelloWorld.vue')['default'] @@ -42,6 +43,7 @@ declare module 'vue' { RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] ServerSettings: typeof import('./components/server/Settings.vue')['default'] + UserAuthenticationActivityTable: typeof import('./components/user/AuthenticationActivityTable.vue')['default'] UserDeletionWarning: typeof import('./components/user/DeletionWarning.vue')['default'] UserDetails: typeof import('./components/user/Details.vue')['default'] UserFormChangePassword: typeof import('./components/user/form/ChangePassword.vue')['default'] diff --git a/next-ui/src/fragments/fragment/user/AuthenticationActivityTable.stories.ts b/next-ui/src/fragments/fragment/user/AuthenticationActivityTable.stories.ts new file mode 100644 index 00000000..82e3b028 --- /dev/null +++ b/next-ui/src/fragments/fragment/user/AuthenticationActivityTable.stories.ts @@ -0,0 +1,64 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import AuthenticationActivityTable from './AuthenticationActivityTable.vue' +import { delay, http } from 'msw' +import { response401Unauthorized } from '@/mocks/api/handlers' +import { httpTyped } from '@/mocks/api/httpTyped' +import { mockPage } from '@/mocks/api/pageable' +import { PageRequest } from '@/types/PageRequest' + +const meta = { + component: AuthenticationActivityTable, + render: (args: object) => ({ + components: { AuthenticationActivityTable }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {}, +} + +export const ForMe: Story = { + args: { + forMe: true, + }, +} + +export const Loading: Story = { + parameters: { + msw: { + handlers: [http.all('*', async () => await delay(5_000))], + }, + }, +} + +export const NoData: Story = { + parameters: { + msw: { + handlers: [ + httpTyped.get('/api/v2/users/authentication-activity', ({ response }) => + response(200).json(mockPage([], new PageRequest())), + ), + ], + }, + }, +} + +export const Error: Story = { + parameters: { + msw: { + handlers: [http.all('*', response401Unauthorized)], + }, + }, +} diff --git a/next-ui/src/fragments/fragment/user/AuthenticationActivityTable.vue b/next-ui/src/fragments/fragment/user/AuthenticationActivityTable.vue new file mode 100644 index 00000000..5ab1f438 --- /dev/null +++ b/next-ui/src/fragments/fragment/user/AuthenticationActivityTable.vue @@ -0,0 +1,173 @@ + + + diff --git a/next-ui/src/fragments/layout/app/drawer/menu/Account.vue b/next-ui/src/fragments/layout/app/drawer/menu/Account.vue index 92691b7e..56f97d0c 100644 --- a/next-ui/src/fragments/layout/app/drawer/menu/Account.vue +++ b/next-ui/src/fragments/layout/app/drawer/menu/Account.vue @@ -49,8 +49,8 @@ :title=" $formatMessage({ description: 'Drawer menu for My Account > Activity', - defaultMessage: 'Activity', - id: 'cGFtPg', + defaultMessage: 'My activity', + id: 'Xx+ITC', }) " /> diff --git a/next-ui/src/fragments/layout/app/drawer/menu/Server.vue b/next-ui/src/fragments/layout/app/drawer/menu/Server.vue index 55f99fb7..e9b988de 100644 --- a/next-ui/src/fragments/layout/app/drawer/menu/Server.vue +++ b/next-ui/src/fragments/layout/app/drawer/menu/Server.vue @@ -34,6 +34,18 @@ }) " /> + + + + + - + diff --git a/next-ui/src/pages/server/activity.vue b/next-ui/src/pages/server/activity.vue new file mode 100644 index 00000000..04a2f312 --- /dev/null +++ b/next-ui/src/pages/server/activity.vue @@ -0,0 +1,10 @@ + + + diff --git a/next-ui/src/pages/server/announcements.vue b/next-ui/src/pages/server/announcements.vue index 092b6bc8..ea79fb58 100644 --- a/next-ui/src/pages/server/announcements.vue +++ b/next-ui/src/pages/server/announcements.vue @@ -55,7 +55,6 @@