mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-01-09 19:58:05 -05:00
feat(pagination): persist items per page and remember page on back navigation
This commit is contained in:
@@ -61,22 +61,26 @@ func Paginate(page int, pageSize int, query *gorm.DB, result interface{}) (Pagin
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var totalItems int64
|
||||
if err := query.Count(&totalItems).Error; err != nil {
|
||||
return PaginationResponse{}, err
|
||||
}
|
||||
|
||||
if err := query.Offset(offset).Limit(pageSize).Find(result).Error; err != nil {
|
||||
return PaginationResponse{}, err
|
||||
}
|
||||
|
||||
totalPages := (totalItems + int64(pageSize) - 1) / int64(pageSize)
|
||||
if totalItems == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
if int64(page) > totalPages {
|
||||
page = int(totalPages)
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
if err := query.Offset(offset).Limit(pageSize).Find(result).Error; err != nil {
|
||||
return PaginationResponse{}, err
|
||||
}
|
||||
|
||||
return PaginationResponse{
|
||||
TotalPages: totalPages,
|
||||
TotalItems: totalItems,
|
||||
|
||||
@@ -6,39 +6,66 @@
|
||||
import * as Table from '$lib/components/ui/table/index.js';
|
||||
import Empty from '$lib/icons/empty.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type {
|
||||
Paginated,
|
||||
SearchPaginationSortRequest,
|
||||
SortRequest
|
||||
} from '$lib/types/pagination.type';
|
||||
import { debounced } from '$lib/utils/debounce-util';
|
||||
import { cn } from '$lib/utils/style';
|
||||
import { ChevronDown } from '@lucide/svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { PersistedState } from 'runed';
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
import Button from './ui/button/button.svelte';
|
||||
import { Skeleton } from './ui/skeleton';
|
||||
|
||||
let {
|
||||
items,
|
||||
requestOptions = $bindable(),
|
||||
id,
|
||||
selectedIds = $bindable(),
|
||||
withoutSearch = false,
|
||||
selectionDisabled = false,
|
||||
onRefresh,
|
||||
fetchCallback,
|
||||
defaultSort,
|
||||
columns,
|
||||
rows
|
||||
}: {
|
||||
items: Paginated<T>;
|
||||
requestOptions: SearchPaginationSortRequest;
|
||||
id: string;
|
||||
selectedIds?: string[];
|
||||
withoutSearch?: boolean;
|
||||
selectionDisabled?: boolean;
|
||||
onRefresh: (requestOptions: SearchPaginationSortRequest) => Promise<Paginated<T>>;
|
||||
fetchCallback: (requestOptions: SearchPaginationSortRequest) => Promise<Paginated<T>>;
|
||||
defaultSort?: SortRequest;
|
||||
columns: { label: string; hidden?: boolean; sortColumn?: string }[];
|
||||
rows: Snippet<[{ item: T }]>;
|
||||
} = $props();
|
||||
|
||||
let items: Paginated<T> | undefined = $state();
|
||||
|
||||
const paginationLimits = new PersistedState<Record<string, number>>('pagination-limits', {});
|
||||
|
||||
const requestOptions = $state<SearchPaginationSortRequest>({
|
||||
sort: defaultSort,
|
||||
pagination: { limit: 20, page: 1 }
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
if (paginationLimits.current[id]) {
|
||||
requestOptions.pagination!.limit = paginationLimits.current[id];
|
||||
}
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const page = parseInt(urlParams.get(`${id}-page`) ?? '') || undefined;
|
||||
if (page) {
|
||||
requestOptions.pagination!.page = page;
|
||||
}
|
||||
await refresh();
|
||||
});
|
||||
|
||||
let searchValue = $state('');
|
||||
let availablePageSizes: number[] = [20, 50, 100];
|
||||
|
||||
let allChecked = $derived.by(() => {
|
||||
if (!selectedIds || items.data.length === 0) return false;
|
||||
for (const item of items.data) {
|
||||
if (!selectedIds || !items || items.data.length === 0) return false;
|
||||
for (const item of items!.data) {
|
||||
if (!selectedIds.includes(item.id)) {
|
||||
return false;
|
||||
}
|
||||
@@ -48,12 +75,12 @@
|
||||
|
||||
const onSearch = debounced(async (search: string) => {
|
||||
requestOptions.search = search;
|
||||
await onRefresh(requestOptions);
|
||||
await refresh();
|
||||
searchValue = search;
|
||||
}, 300);
|
||||
|
||||
async function onAllCheck(checked: boolean) {
|
||||
const pageIds = items.data.map((item) => item.id);
|
||||
const pageIds = items!.data.map((item) => item.id);
|
||||
const current = selectedIds ?? [];
|
||||
|
||||
if (checked) {
|
||||
@@ -73,20 +100,33 @@
|
||||
}
|
||||
|
||||
async function onPageChange(page: number) {
|
||||
requestOptions.pagination = { limit: items.pagination.itemsPerPage, page };
|
||||
onRefresh(requestOptions);
|
||||
changePageState(page);
|
||||
await refresh();
|
||||
}
|
||||
|
||||
async function onPageSizeChange(size: number) {
|
||||
requestOptions.pagination = { limit: size, page: 1 };
|
||||
onRefresh(requestOptions);
|
||||
paginationLimits.current[id] = size;
|
||||
await refresh();
|
||||
}
|
||||
|
||||
async function onSort(column?: string, direction: 'asc' | 'desc' = 'asc') {
|
||||
if (!column) return;
|
||||
|
||||
requestOptions.sort = { column, direction };
|
||||
onRefresh(requestOptions);
|
||||
await refresh();
|
||||
}
|
||||
|
||||
function changePageState(page: number) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set(`${id}-page`, page.toString());
|
||||
history.replaceState({}, '', url.toString());
|
||||
requestOptions.pagination!.page = page;
|
||||
}
|
||||
|
||||
export async function refresh() {
|
||||
items = await fetchCallback(requestOptions);
|
||||
changePageState(items.pagination.currentPage);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -95,7 +135,7 @@
|
||||
value={searchValue}
|
||||
class={cn(
|
||||
'relative z-50 mb-4 max-w-sm',
|
||||
items.data.length == 0 && searchValue == '' && 'hidden'
|
||||
items?.data.length == 0 && searchValue == '' && 'hidden'
|
||||
)}
|
||||
placeholder={m.search()}
|
||||
type="text"
|
||||
@@ -103,13 +143,15 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if items.data.length === 0 && searchValue === ''}
|
||||
{#if items?.pagination.totalItems === 0 && searchValue === ''}
|
||||
<div class="my-5 flex flex-col items-center">
|
||||
<Empty class="text-muted-foreground h-20" />
|
||||
<p class="text-muted-foreground mt-3 text-sm">{m.no_items_found()}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<Table.Root class="min-w-full table-auto overflow-x-auto">
|
||||
<Table.Root
|
||||
class="min-w-full overflow-x-auto {items?.data?.length != 0 ? 'table-auto' : 'table-fixed'}"
|
||||
>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
{#if selectedIds}
|
||||
@@ -121,6 +163,7 @@
|
||||
/>
|
||||
</Table.Head>
|
||||
{/if}
|
||||
|
||||
{#each columns as column}
|
||||
<Table.Head class={cn(column.hidden && 'sr-only', column.sortColumn && 'px-0')}>
|
||||
{#if column.sortColumn}
|
||||
@@ -151,20 +194,30 @@
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each items.data as item}
|
||||
<Table.Row class={selectedIds?.includes(item.id) ? 'bg-muted/20' : ''}>
|
||||
{#if selectedIds}
|
||||
<Table.Cell class="w-12">
|
||||
<Checkbox
|
||||
disabled={selectionDisabled}
|
||||
checked={selectedIds.includes(item.id)}
|
||||
onCheckedChange={(c: boolean) => onCheck(c, item.id)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
{@render rows({ item })}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{#if !items}
|
||||
{#each Array(10) as _}
|
||||
<tr>
|
||||
<td colspan={columns.length + (selectedIds ? 1 : 0)}>
|
||||
<Skeleton class="mt-3 h-[40px] w-full rounded-lg" />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each items.data as item}
|
||||
<Table.Row class={selectedIds?.includes(item.id) ? 'bg-muted/20' : ''}>
|
||||
{#if selectedIds}
|
||||
<Table.Cell class="w-12">
|
||||
<Checkbox
|
||||
disabled={selectionDisabled}
|
||||
checked={selectedIds.includes(item.id)}
|
||||
onCheckedChange={(c: boolean) => onCheck(c, item.id)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
{@render rows({ item })}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
@@ -173,11 +226,11 @@
|
||||
<p class="text-sm font-medium">{m.items_per_page()}</p>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={items.pagination.itemsPerPage.toString()}
|
||||
value={items?.pagination.itemsPerPage.toString()}
|
||||
onValueChange={(v) => onPageSizeChange(Number(v))}
|
||||
>
|
||||
<Select.Trigger class="h-9 w-[80px]">
|
||||
{items.pagination.itemsPerPage}
|
||||
{items?.pagination.itemsPerPage}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each availablePageSizes as size}
|
||||
@@ -188,10 +241,10 @@
|
||||
</div>
|
||||
<Pagination.Root
|
||||
class="mx-0 w-auto"
|
||||
count={items.pagination.totalItems}
|
||||
perPage={items.pagination.itemsPerPage}
|
||||
count={items?.pagination.totalItems || 0}
|
||||
perPage={items?.pagination.itemsPerPage}
|
||||
{onPageChange}
|
||||
page={items.pagination.currentPage}
|
||||
page={items?.pagination.currentPage}
|
||||
>
|
||||
{#snippet children({ pages })}
|
||||
<Pagination.Content class="flex justify-end">
|
||||
@@ -201,7 +254,7 @@
|
||||
{#each pages as page (page.key)}
|
||||
{#if page.type !== 'ellipsis' && page.value != 0}
|
||||
<Pagination.Item>
|
||||
<Pagination.Link {page} isActive={items.pagination.currentPage === page.value}>
|
||||
<Pagination.Link {page} isActive={items?.pagination.currentPage === page.value}>
|
||||
{page.value}
|
||||
</Pagination.Link>
|
||||
</Pagination.Item>
|
||||
|
||||
@@ -3,31 +3,34 @@
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import {translateAuditLogEvent} from "$lib/utils/audit-log-translator";
|
||||
import AuditLogService from '$lib/services/audit-log-service';
|
||||
import type { AuditLog } from '$lib/types/audit-log.type';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { AuditLog, AuditLogFilter } from '$lib/types/audit-log.type';
|
||||
import { translateAuditLogEvent } from '$lib/utils/audit-log-translator';
|
||||
|
||||
let {
|
||||
auditLogs,
|
||||
isAdmin = false,
|
||||
requestOptions
|
||||
filters
|
||||
}: {
|
||||
auditLogs: Paginated<AuditLog>;
|
||||
isAdmin?: boolean;
|
||||
requestOptions: SearchPaginationSortRequest;
|
||||
filters?: AuditLogFilter;
|
||||
} = $props();
|
||||
|
||||
const auditLogService = new AuditLogService();
|
||||
let tableRef: AdvancedTable<AuditLog>;
|
||||
|
||||
export async function refresh() {
|
||||
await tableRef.refresh();
|
||||
}
|
||||
</script>
|
||||
|
||||
<AdvancedTable
|
||||
items={auditLogs}
|
||||
{requestOptions}
|
||||
onRefresh={async (options) =>
|
||||
id="audit-log-list"
|
||||
bind:this={tableRef}
|
||||
fetchCallback={async (options) =>
|
||||
isAdmin
|
||||
? (auditLogs = await auditLogService.listAllLogs(options))
|
||||
: (auditLogs = await auditLogService.list(options))}
|
||||
? await auditLogService.listAllLogs(options, filters)
|
||||
: await auditLogService.list(options)}
|
||||
defaultSort={{ column: 'createdAt', direction: 'desc' }}
|
||||
columns={[
|
||||
{ label: m.time(), sortColumn: 'createdAt' },
|
||||
...(isAdmin ? [{ label: 'Username' }] : []),
|
||||
|
||||
@@ -9,22 +9,15 @@
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { SignupTokenDto } from '$lib/types/signup-token.type';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { Copy, Ellipsis, Trash2 } from '@lucide/svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let {
|
||||
open = $bindable(),
|
||||
signupTokens = $bindable(),
|
||||
signupTokensRequestOptions,
|
||||
onTokenDeleted
|
||||
open = $bindable()
|
||||
}: {
|
||||
open: boolean;
|
||||
signupTokens: Paginated<SignupTokenDto>;
|
||||
signupTokensRequestOptions: SearchPaginationSortRequest;
|
||||
onTokenDeleted?: () => Promise<void>;
|
||||
} = $props();
|
||||
|
||||
const userService = new UserService();
|
||||
@@ -45,11 +38,6 @@
|
||||
try {
|
||||
await userService.deleteSignupToken(token.id);
|
||||
toast.success(m.signup_token_deleted_successfully());
|
||||
|
||||
// Refresh the tokens
|
||||
if (onTokenDeleted) {
|
||||
await onTokenDeleted();
|
||||
}
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
}
|
||||
@@ -111,14 +99,9 @@
|
||||
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<AdvancedTable
|
||||
items={signupTokens}
|
||||
requestOptions={signupTokensRequestOptions}
|
||||
id="signup-token-list"
|
||||
withoutSearch={true}
|
||||
onRefresh={async (options) => {
|
||||
const result = await userService.listSignupTokens(options);
|
||||
signupTokens = result;
|
||||
return result;
|
||||
}}
|
||||
fetchCallback={userService.listSignupTokens}
|
||||
columns={[
|
||||
{ label: m.token() },
|
||||
{ label: m.status() },
|
||||
|
||||
@@ -13,11 +13,9 @@
|
||||
import { mode } from 'mode-watcher';
|
||||
|
||||
let {
|
||||
open = $bindable(),
|
||||
onTokenCreated
|
||||
open = $bindable()
|
||||
}: {
|
||||
open: boolean;
|
||||
onTokenCreated?: () => Promise<void>;
|
||||
} = $props();
|
||||
|
||||
const userService = new UserService();
|
||||
@@ -37,12 +35,11 @@
|
||||
|
||||
async function createSignupToken() {
|
||||
try {
|
||||
signupToken = await userService.createSignupToken(availableExpirations[selectedExpiration], usageLimit);
|
||||
signupToken = await userService.createSignupToken(
|
||||
availableExpirations[selectedExpiration],
|
||||
usageLimit
|
||||
);
|
||||
signupLink = `${page.url.origin}/st/${signupToken}`;
|
||||
|
||||
if (onTokenCreated) {
|
||||
await onTokenCreated();
|
||||
}
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
}
|
||||
|
||||
7
frontend/src/lib/components/ui/skeleton/index.ts
Normal file
7
frontend/src/lib/components/ui/skeleton/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./skeleton.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Skeleton,
|
||||
};
|
||||
17
frontend/src/lib/components/ui/skeleton/skeleton.svelte
Normal file
17
frontend/src/lib/components/ui/skeleton/skeleton.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils/style.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="skeleton"
|
||||
class={cn("bg-accent animate-pulse rounded-md", className)}
|
||||
{...restProps}
|
||||
></div>
|
||||
@@ -3,9 +3,6 @@
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserGroupService from '$lib/services/user-group-service';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { UserGroup } from '$lib/types/user-group.type';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let {
|
||||
selectionDisabled = false,
|
||||
@@ -16,31 +13,17 @@
|
||||
} = $props();
|
||||
|
||||
const userGroupService = new UserGroupService();
|
||||
|
||||
let groups: Paginated<UserGroup> | undefined = $state();
|
||||
let requestOptions: SearchPaginationSortRequest = $state({
|
||||
sort: {
|
||||
column: 'friendlyName',
|
||||
direction: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
groups = await userGroupService.list(requestOptions);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if groups}
|
||||
<AdvancedTable
|
||||
items={groups}
|
||||
{requestOptions}
|
||||
onRefresh={async (o) => (groups = await userGroupService.list(o))}
|
||||
columns={[{ label: m.name(), sortColumn: 'friendlyName' }]}
|
||||
bind:selectedIds={selectedGroupIds}
|
||||
{selectionDisabled}
|
||||
>
|
||||
{#snippet rows({ item })}
|
||||
<Table.Cell>{item.friendlyName}</Table.Cell>
|
||||
{/snippet}
|
||||
</AdvancedTable>
|
||||
{/if}
|
||||
<AdvancedTable
|
||||
id="user-group-selection"
|
||||
fetchCallback={userGroupService.list}
|
||||
defaultSort={{ column: 'friendlyName', direction: 'asc' }}
|
||||
columns={[{ label: m.name(), sortColumn: 'friendlyName' }]}
|
||||
bind:selectedIds={selectedGroupIds}
|
||||
{selectionDisabled}
|
||||
>
|
||||
{#snippet rows({ item })}
|
||||
<Table.Cell>{item.friendlyName}</Table.Cell>
|
||||
{/snippet}
|
||||
</AdvancedTable>
|
||||
|
||||
@@ -3,19 +3,17 @@ import type { Paginated, SearchPaginationSortRequest } from '$lib/types/paginati
|
||||
import APIService from './api-service';
|
||||
|
||||
export default class ApiKeyService extends APIService {
|
||||
async list(options?: SearchPaginationSortRequest) {
|
||||
const res = await this.api.get('/api-keys', {
|
||||
params: options
|
||||
});
|
||||
return res.data as Paginated<ApiKey>;
|
||||
}
|
||||
list = async (options?: SearchPaginationSortRequest) => {
|
||||
const res = await this.api.get('/api-keys', { params: options });
|
||||
return res.data as Paginated<ApiKey>;
|
||||
};
|
||||
|
||||
async create(data: ApiKeyCreate): Promise<ApiKeyResponse> {
|
||||
const res = await this.api.post('/api-keys', data);
|
||||
return res.data as ApiKeyResponse;
|
||||
}
|
||||
create = async (data: ApiKeyCreate): Promise<ApiKeyResponse> => {
|
||||
const res = await this.api.post('/api-keys', data);
|
||||
return res.data as ApiKeyResponse;
|
||||
};
|
||||
|
||||
async revoke(id: string): Promise<void> {
|
||||
await this.api.delete(`/api-keys/${id}`);
|
||||
}
|
||||
revoke = async (id: string): Promise<void> => {
|
||||
await this.api.delete(`/api-keys/${id}`);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import axios from 'axios';
|
||||
|
||||
abstract class APIService {
|
||||
api = axios.create({
|
||||
baseURL: '/api'
|
||||
});
|
||||
protected api = axios.create({ baseURL: '/api' });
|
||||
|
||||
constructor() {
|
||||
if (typeof process !== 'undefined' && process?.env?.DEVELOPMENT_BACKEND_URL) {
|
||||
this.api.defaults.baseURL = process.env.DEVELOPMENT_BACKEND_URL;
|
||||
}
|
||||
}
|
||||
constructor() {
|
||||
if (typeof process !== 'undefined' && process?.env?.DEVELOPMENT_BACKEND_URL) {
|
||||
this.api.defaults.baseURL = process.env.DEVELOPMENT_BACKEND_URL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default APIService;
|
||||
|
||||
@@ -3,92 +3,81 @@ import { cachedApplicationLogo, cachedBackgroundImage } from '$lib/utils/cached-
|
||||
import APIService from './api-service';
|
||||
|
||||
export default class AppConfigService extends APIService {
|
||||
async list(showAll = false) {
|
||||
list = async (showAll = false) => {
|
||||
let url = '/application-configuration';
|
||||
if (showAll) {
|
||||
url += '/all';
|
||||
}
|
||||
|
||||
if (showAll) url += '/all';
|
||||
const { data } = await this.api.get<AppConfigRawResponse>(url);
|
||||
return this.parseConfigList(data);
|
||||
}
|
||||
return parseConfigList(data);
|
||||
};
|
||||
|
||||
async update(appConfig: AllAppConfig) {
|
||||
update = async (appConfig: AllAppConfig) => {
|
||||
// Convert all values to string, stringifying JSON where needed
|
||||
const appConfigConvertedToString: Record<string, string> = {};
|
||||
for (const key in appConfig) {
|
||||
const value = (appConfig as any)[key];
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
appConfigConvertedToString[key] = JSON.stringify(value);
|
||||
} else {
|
||||
appConfigConvertedToString[key] = String(value);
|
||||
}
|
||||
appConfigConvertedToString[key] =
|
||||
typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value);
|
||||
}
|
||||
const res = await this.api.put('/application-configuration', appConfigConvertedToString);
|
||||
return this.parseConfigList(res.data);
|
||||
}
|
||||
return parseConfigList(res.data);
|
||||
};
|
||||
|
||||
async updateFavicon(favicon: File) {
|
||||
updateFavicon = async (favicon: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', favicon!);
|
||||
|
||||
await this.api.put(`/application-configuration/favicon`, formData);
|
||||
}
|
||||
};
|
||||
|
||||
async updateLogo(logo: File, light = true) {
|
||||
updateLogo = async (logo: File, light = true) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', logo!);
|
||||
|
||||
await this.api.put(`/application-configuration/logo`, formData, {
|
||||
params: { light }
|
||||
});
|
||||
await this.api.put(`/application-configuration/logo`, formData, { params: { light } });
|
||||
cachedApplicationLogo.bustCache(light);
|
||||
}
|
||||
};
|
||||
|
||||
async updateBackgroundImage(backgroundImage: File) {
|
||||
updateBackgroundImage = async (backgroundImage: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', backgroundImage!);
|
||||
|
||||
await this.api.put(`/application-configuration/background-image`, formData);
|
||||
cachedBackgroundImage.bustCache();
|
||||
}
|
||||
};
|
||||
|
||||
async sendTestEmail() {
|
||||
sendTestEmail = async () => {
|
||||
await this.api.post('/application-configuration/test-email');
|
||||
}
|
||||
};
|
||||
|
||||
async syncLdap() {
|
||||
syncLdap = async () => {
|
||||
await this.api.post('/application-configuration/sync-ldap');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private parseConfigList(data: AppConfigRawResponse) {
|
||||
const appConfig: Partial<AllAppConfig> = {};
|
||||
data.forEach(({ key, value }) => {
|
||||
(appConfig as any)[key] = this.parseValue(value);
|
||||
});
|
||||
function parseConfigList(data: AppConfigRawResponse) {
|
||||
const appConfig: Partial<AllAppConfig> = {};
|
||||
data.forEach(({ key, value }) => {
|
||||
(appConfig as any)[key] = parseValue(value);
|
||||
});
|
||||
|
||||
return appConfig as AllAppConfig;
|
||||
}
|
||||
return appConfig as AllAppConfig;
|
||||
}
|
||||
|
||||
private parseValue(value: string) {
|
||||
// Try to parse JSON first
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (typeof parsed === 'object' && parsed !== null) {
|
||||
return parsed;
|
||||
}
|
||||
value = String(parsed);
|
||||
} catch {}
|
||||
|
||||
// Handle rest of the types
|
||||
if (value === 'true') {
|
||||
return true;
|
||||
} else if (value === 'false') {
|
||||
return false;
|
||||
} else if (/^-?\d+(\.\d+)?$/.test(value)) {
|
||||
return parseFloat(value);
|
||||
} else {
|
||||
return value;
|
||||
function parseValue(value: string) {
|
||||
// Try to parse JSON first
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (typeof parsed === 'object' && parsed !== null) {
|
||||
return parsed;
|
||||
}
|
||||
value = String(parsed);
|
||||
} catch {}
|
||||
|
||||
// Handle rest of the types
|
||||
if (value === 'true') {
|
||||
return true;
|
||||
} else if (value === 'false') {
|
||||
return false;
|
||||
} else if (/^-?\d+(\.\d+)?$/.test(value)) {
|
||||
return parseFloat(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,33 +2,24 @@ import type { AuditLog, AuditLogFilter } from '$lib/types/audit-log.type';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import APIService from './api-service';
|
||||
|
||||
class AuditLogService extends APIService {
|
||||
async list(options?: SearchPaginationSortRequest) {
|
||||
const res = await this.api.get('/audit-logs', {
|
||||
params: options
|
||||
});
|
||||
return res.data as Paginated<AuditLog>;
|
||||
}
|
||||
export default class AuditLogService extends APIService {
|
||||
list = async (options?: SearchPaginationSortRequest) => {
|
||||
const res = await this.api.get('/audit-logs', { params: options });
|
||||
return res.data as Paginated<AuditLog>;
|
||||
};
|
||||
|
||||
async listAllLogs(options?: SearchPaginationSortRequest, filters?: AuditLogFilter) {
|
||||
const res = await this.api.get('/audit-logs/all', {
|
||||
params: {
|
||||
...options,
|
||||
filters
|
||||
}
|
||||
});
|
||||
return res.data as Paginated<AuditLog>;
|
||||
}
|
||||
listAllLogs = async (options?: SearchPaginationSortRequest, filters?: AuditLogFilter) => {
|
||||
const res = await this.api.get('/audit-logs/all', { params: { ...options, filters } });
|
||||
return res.data as Paginated<AuditLog>;
|
||||
};
|
||||
|
||||
async listClientNames() {
|
||||
const res = await this.api.get<string[]>('/audit-logs/filters/client-names');
|
||||
return res.data;
|
||||
}
|
||||
listClientNames = async () => {
|
||||
const res = await this.api.get<string[]>('/audit-logs/filters/client-names');
|
||||
return res.data;
|
||||
};
|
||||
|
||||
async listUsers() {
|
||||
const res = await this.api.get<Record<string, string>>('/audit-logs/filters/users');
|
||||
return res.data;
|
||||
}
|
||||
listUsers = async () => {
|
||||
const res = await this.api.get<Record<string, string>>('/audit-logs/filters/users');
|
||||
return res.data;
|
||||
};
|
||||
}
|
||||
|
||||
export default AuditLogService;
|
||||
|
||||
@@ -2,18 +2,18 @@ import type { CustomClaim } from '$lib/types/custom-claim.type';
|
||||
import APIService from './api-service';
|
||||
|
||||
export default class CustomClaimService extends APIService {
|
||||
async getSuggestions() {
|
||||
getSuggestions = async () => {
|
||||
const res = await this.api.get('/custom-claims/suggestions');
|
||||
return res.data as string[];
|
||||
}
|
||||
};
|
||||
|
||||
async updateUserCustomClaims(userId: string, claims: CustomClaim[]) {
|
||||
updateUserCustomClaims = async (userId: string, claims: CustomClaim[]) => {
|
||||
const res = await this.api.put(`/custom-claims/user/${userId}`, claims);
|
||||
return res.data as CustomClaim[];
|
||||
}
|
||||
};
|
||||
|
||||
async updateUserGroupCustomClaims(userGroupId: string, claims: CustomClaim[]) {
|
||||
updateUserGroupCustomClaims = async (userGroupId: string, claims: CustomClaim[]) => {
|
||||
const res = await this.api.put(`/custom-claims/user-group/${userGroupId}`, claims);
|
||||
return res.data as CustomClaim[];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { cachedOidcClientLogo } from '$lib/utils/cached-image-util';
|
||||
import APIService from './api-service';
|
||||
|
||||
class OidcService extends APIService {
|
||||
async authorize(
|
||||
authorize = async (
|
||||
clientId: string,
|
||||
scope: string,
|
||||
callbackURL: string,
|
||||
@@ -22,7 +22,7 @@ class OidcService extends APIService {
|
||||
codeChallenge?: string,
|
||||
codeChallengeMethod?: string,
|
||||
reauthenticationToken?: string
|
||||
) {
|
||||
) => {
|
||||
const res = await this.api.post('/oidc/authorize', {
|
||||
scope,
|
||||
nonce,
|
||||
@@ -34,45 +34,41 @@ class OidcService extends APIService {
|
||||
});
|
||||
|
||||
return res.data as AuthorizeResponse;
|
||||
}
|
||||
};
|
||||
|
||||
async isAuthorizationRequired(clientId: string, scope: string) {
|
||||
isAuthorizationRequired = async (clientId: string, scope: string) => {
|
||||
const res = await this.api.post('/oidc/authorization-required', {
|
||||
scope,
|
||||
clientId
|
||||
});
|
||||
|
||||
return res.data.authorizationRequired as boolean;
|
||||
}
|
||||
};
|
||||
|
||||
async listClients(options?: SearchPaginationSortRequest) {
|
||||
listClients = async (options?: SearchPaginationSortRequest) => {
|
||||
const res = await this.api.get('/oidc/clients', {
|
||||
params: options
|
||||
});
|
||||
return res.data as Paginated<OidcClientWithAllowedUserGroupsCount>;
|
||||
}
|
||||
};
|
||||
|
||||
async createClient(client: OidcClientCreate) {
|
||||
return (await this.api.post('/oidc/clients', client)).data as OidcClient;
|
||||
}
|
||||
createClient = async (client: OidcClientCreate) =>
|
||||
(await this.api.post('/oidc/clients', client)).data as OidcClient;
|
||||
|
||||
async removeClient(id: string) {
|
||||
removeClient = async (id: string) => {
|
||||
await this.api.delete(`/oidc/clients/${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
async getClient(id: string) {
|
||||
return (await this.api.get(`/oidc/clients/${id}`)).data as OidcClientWithAllowedUserGroups;
|
||||
}
|
||||
getClient = async (id: string) =>
|
||||
(await this.api.get(`/oidc/clients/${id}`)).data as OidcClientWithAllowedUserGroups;
|
||||
|
||||
async getClientMetaData(id: string) {
|
||||
return (await this.api.get(`/oidc/clients/${id}/meta`)).data as OidcClientMetaData;
|
||||
}
|
||||
getClientMetaData = async (id: string) =>
|
||||
(await this.api.get(`/oidc/clients/${id}/meta`)).data as OidcClientMetaData;
|
||||
|
||||
async updateClient(id: string, client: OidcClientUpdate) {
|
||||
return (await this.api.put(`/oidc/clients/${id}`, client)).data as OidcClient;
|
||||
}
|
||||
updateClient = async (id: string, client: OidcClientUpdate) =>
|
||||
(await this.api.put(`/oidc/clients/${id}`, client)).data as OidcClient;
|
||||
|
||||
async updateClientLogo(client: OidcClient, image: File | null) {
|
||||
updateClientLogo = async (client: OidcClient, image: File | null) => {
|
||||
if (client.hasLogo && !image) {
|
||||
await this.removeClientLogo(client.id);
|
||||
return;
|
||||
@@ -86,49 +82,45 @@ class OidcService extends APIService {
|
||||
|
||||
await this.api.post(`/oidc/clients/${client.id}/logo`, formData);
|
||||
cachedOidcClientLogo.bustCache(client.id);
|
||||
}
|
||||
};
|
||||
|
||||
async removeClientLogo(id: string) {
|
||||
removeClientLogo = async (id: string) => {
|
||||
await this.api.delete(`/oidc/clients/${id}/logo`);
|
||||
cachedOidcClientLogo.bustCache(id);
|
||||
}
|
||||
};
|
||||
|
||||
async createClientSecret(id: string) {
|
||||
return (await this.api.post(`/oidc/clients/${id}/secret`)).data.secret as string;
|
||||
}
|
||||
createClientSecret = async (id: string) =>
|
||||
(await this.api.post(`/oidc/clients/${id}/secret`)).data.secret as string;
|
||||
|
||||
async updateAllowedUserGroups(id: string, userGroupIds: string[]) {
|
||||
updateAllowedUserGroups = async (id: string, userGroupIds: string[]) => {
|
||||
const res = await this.api.put(`/oidc/clients/${id}/allowed-user-groups`, { userGroupIds });
|
||||
return res.data as OidcClientWithAllowedUserGroups;
|
||||
}
|
||||
};
|
||||
|
||||
async verifyDeviceCode(userCode: string) {
|
||||
verifyDeviceCode = async (userCode: string) => {
|
||||
return await this.api.post(`/oidc/device/verify?code=${userCode}`);
|
||||
}
|
||||
};
|
||||
|
||||
async getDeviceCodeInfo(userCode: string): Promise<OidcDeviceCodeInfo> {
|
||||
getDeviceCodeInfo = async (userCode: string): Promise<OidcDeviceCodeInfo> => {
|
||||
const response = await this.api.get(`/oidc/device/info?code=${userCode}`);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
||||
async getClientPreview(id: string, userId: string, scopes: string) {
|
||||
getClientPreview = async (id: string, userId: string, scopes: string) => {
|
||||
const response = await this.api.get(`/oidc/clients/${id}/preview/${userId}`, {
|
||||
params: { scopes }
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async listOwnAccessibleClients(options?: SearchPaginationSortRequest) {
|
||||
const res = await this.api.get('/oidc/users/me/clients', {
|
||||
params: options
|
||||
});
|
||||
};
|
||||
|
||||
listOwnAccessibleClients = async (options?: SearchPaginationSortRequest) => {
|
||||
const res = await this.api.get('/oidc/users/me/clients', { params: options });
|
||||
return res.data as Paginated<AccessibleOidcClient>;
|
||||
}
|
||||
};
|
||||
|
||||
async revokeOwnAuthorizedClient(clientId: string) {
|
||||
revokeOwnAuthorizedClient = async (clientId: string) => {
|
||||
await this.api.delete(`/oidc/users/me/authorized-clients/${clientId}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default OidcService;
|
||||
|
||||
@@ -1,40 +1,34 @@
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type {
|
||||
UserGroupCreate,
|
||||
UserGroupWithUserCount,
|
||||
UserGroupWithUsers
|
||||
} from '$lib/types/user-group.type';
|
||||
import type { UserGroupCreate, UserGroupWithUserCount, UserGroupWithUsers } from '$lib/types/user-group.type';
|
||||
import APIService from './api-service';
|
||||
|
||||
export default class UserGroupService extends APIService {
|
||||
async list(options?: SearchPaginationSortRequest) {
|
||||
const res = await this.api.get('/user-groups', {
|
||||
params: options
|
||||
});
|
||||
list = async (options?: SearchPaginationSortRequest) => {
|
||||
const res = await this.api.get('/user-groups', { params: options });
|
||||
return res.data as Paginated<UserGroupWithUserCount>;
|
||||
}
|
||||
};
|
||||
|
||||
async get(id: string) {
|
||||
get = async (id: string) => {
|
||||
const res = await this.api.get(`/user-groups/${id}`);
|
||||
return res.data as UserGroupWithUsers;
|
||||
}
|
||||
};
|
||||
|
||||
async create(user: UserGroupCreate) {
|
||||
create = async (user: UserGroupCreate) => {
|
||||
const res = await this.api.post('/user-groups', user);
|
||||
return res.data as UserGroupWithUsers;
|
||||
}
|
||||
};
|
||||
|
||||
async update(id: string, user: UserGroupCreate) {
|
||||
update = async (id: string, user: UserGroupCreate) => {
|
||||
const res = await this.api.put(`/user-groups/${id}`, user);
|
||||
return res.data as UserGroupWithUsers;
|
||||
}
|
||||
};
|
||||
|
||||
async remove(id: string) {
|
||||
remove = async (id: string) => {
|
||||
await this.api.delete(`/user-groups/${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
async updateUsers(id: string, userIds: string[]) {
|
||||
updateUsers = async (id: string, userIds: string[]) => {
|
||||
const res = await this.api.put(`/user-groups/${id}/users`, { userIds });
|
||||
return res.data as UserGroupWithUsers;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,125 +8,113 @@ import { get } from 'svelte/store';
|
||||
import APIService from './api-service';
|
||||
|
||||
export default class UserService extends APIService {
|
||||
async list(options?: SearchPaginationSortRequest) {
|
||||
const res = await this.api.get('/users', {
|
||||
params: options
|
||||
});
|
||||
list = async (options?: SearchPaginationSortRequest) => {
|
||||
const res = await this.api.get('/users', { params: options });
|
||||
return res.data as Paginated<User>;
|
||||
}
|
||||
};
|
||||
|
||||
async get(id: string) {
|
||||
get = async (id: string) => {
|
||||
const res = await this.api.get(`/users/${id}`);
|
||||
return res.data as User;
|
||||
}
|
||||
};
|
||||
|
||||
async getCurrent() {
|
||||
getCurrent = async () => {
|
||||
const res = await this.api.get('/users/me');
|
||||
return res.data as User;
|
||||
}
|
||||
};
|
||||
|
||||
async create(user: UserCreate) {
|
||||
create = async (user: UserCreate) => {
|
||||
const res = await this.api.post('/users', user);
|
||||
return res.data as User;
|
||||
}
|
||||
};
|
||||
|
||||
async getUserGroups(userId: string) {
|
||||
getUserGroups = async (userId: string) => {
|
||||
const res = await this.api.get(`/users/${userId}/groups`);
|
||||
return res.data as UserGroup[];
|
||||
}
|
||||
};
|
||||
|
||||
async update(id: string, user: UserCreate) {
|
||||
update = async (id: string, user: UserCreate) => {
|
||||
const res = await this.api.put(`/users/${id}`, user);
|
||||
return res.data as User;
|
||||
}
|
||||
};
|
||||
|
||||
async updateCurrent(user: UserCreate) {
|
||||
updateCurrent = async (user: UserCreate) => {
|
||||
const res = await this.api.put('/users/me', user);
|
||||
return res.data as User;
|
||||
}
|
||||
};
|
||||
|
||||
async remove(id: string) {
|
||||
remove = async (id: string) => {
|
||||
await this.api.delete(`/users/${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
async updateProfilePicture(userId: string, image: File) {
|
||||
updateProfilePicture = async (userId: string, image: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', image!);
|
||||
|
||||
await this.api.put(`/users/${userId}/profile-picture`, formData);
|
||||
cachedProfilePicture.bustCache(userId);
|
||||
}
|
||||
};
|
||||
|
||||
async updateCurrentUsersProfilePicture(image: File) {
|
||||
updateCurrentUsersProfilePicture = async (image: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', image!);
|
||||
|
||||
await this.api.put('/users/me/profile-picture', formData);
|
||||
cachedProfilePicture.bustCache(get(userStore)!.id);
|
||||
}
|
||||
};
|
||||
|
||||
async resetCurrentUserProfilePicture() {
|
||||
resetCurrentUserProfilePicture = async () => {
|
||||
await this.api.delete(`/users/me/profile-picture`);
|
||||
cachedProfilePicture.bustCache(get(userStore)!.id);
|
||||
}
|
||||
};
|
||||
|
||||
async resetProfilePicture(userId: string) {
|
||||
resetProfilePicture = async (userId: string) => {
|
||||
await this.api.delete(`/users/${userId}/profile-picture`);
|
||||
cachedProfilePicture.bustCache(userId);
|
||||
}
|
||||
};
|
||||
|
||||
async createOneTimeAccessToken(userId: string = 'me', ttl?: string|number) {
|
||||
const res = await this.api.post(`/users/${userId}/one-time-access-token`, {
|
||||
userId,
|
||||
ttl,
|
||||
});
|
||||
createOneTimeAccessToken = async (userId: string = 'me', ttl?: string | number) => {
|
||||
const res = await this.api.post(`/users/${userId}/one-time-access-token`, { userId, ttl });
|
||||
return res.data.token;
|
||||
}
|
||||
};
|
||||
|
||||
async createSignupToken(ttl: string|number, usageLimit: number) {
|
||||
const res = await this.api.post(`/signup-tokens`, {
|
||||
ttl,
|
||||
usageLimit
|
||||
});
|
||||
createSignupToken = async (ttl: string | number, usageLimit: number) => {
|
||||
const res = await this.api.post(`/signup-tokens`, { ttl, usageLimit });
|
||||
return res.data.token;
|
||||
}
|
||||
};
|
||||
|
||||
async exchangeOneTimeAccessToken(token: string) {
|
||||
exchangeOneTimeAccessToken = async (token: string) => {
|
||||
const res = await this.api.post(`/one-time-access-token/${token}`);
|
||||
return res.data as User;
|
||||
}
|
||||
};
|
||||
|
||||
async requestOneTimeAccessEmailAsUnauthenticatedUser(email: string, redirectPath?: string) {
|
||||
requestOneTimeAccessEmailAsUnauthenticatedUser = async (email: string, redirectPath?: string) => {
|
||||
await this.api.post('/one-time-access-email', { email, redirectPath });
|
||||
}
|
||||
};
|
||||
|
||||
async requestOneTimeAccessEmailAsAdmin(userId: string, ttl: string|number) {
|
||||
requestOneTimeAccessEmailAsAdmin = async (userId: string, ttl: string | number) => {
|
||||
await this.api.post(`/users/${userId}/one-time-access-email`, { ttl });
|
||||
}
|
||||
};
|
||||
|
||||
async updateUserGroups(id: string, userGroupIds: string[]) {
|
||||
updateUserGroups = async (id: string, userGroupIds: string[]) => {
|
||||
const res = await this.api.put(`/users/${id}/user-groups`, { userGroupIds });
|
||||
return res.data as User;
|
||||
}
|
||||
};
|
||||
|
||||
async signup(data: UserSignUp) {
|
||||
signup = async (data: UserSignUp) => {
|
||||
const res = await this.api.post(`/signup`, data);
|
||||
return res.data as User;
|
||||
}
|
||||
};
|
||||
|
||||
async signupInitialUser(data: UserSignUp) {
|
||||
signupInitialUser = async (data: UserSignUp) => {
|
||||
const res = await this.api.post(`/signup/setup`, data);
|
||||
return res.data as User;
|
||||
}
|
||||
};
|
||||
|
||||
async listSignupTokens(options?: SearchPaginationSortRequest) {
|
||||
const res = await this.api.get('/signup-tokens', {
|
||||
params: options
|
||||
});
|
||||
listSignupTokens = async (options?: SearchPaginationSortRequest) => {
|
||||
const res = await this.api.get('/signup-tokens', { params: options });
|
||||
return res.data as Paginated<SignupTokenDto>;
|
||||
}
|
||||
};
|
||||
|
||||
async deleteSignupToken(tokenId: string) {
|
||||
deleteSignupToken = async (tokenId: string) => {
|
||||
await this.api.delete(`/signup-tokens/${tokenId}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
import { version as currentVersion } from '$app/environment';
|
||||
import axios from 'axios';
|
||||
import APIService from './api-service';
|
||||
|
||||
async function getNewestVersion() {
|
||||
const response = await axios
|
||||
.get('/api/version/latest', {
|
||||
timeout: 2000
|
||||
})
|
||||
.then((res) => res.data);
|
||||
export default class VersionService extends APIService {
|
||||
getNewestVersion = async () => {
|
||||
const response = await this.api
|
||||
.get('/api/version/latest', { timeout: 2000 })
|
||||
.then((res) => res.data);
|
||||
return response.latestVersion;
|
||||
};
|
||||
|
||||
return response.latestVersion;
|
||||
getCurrentVersion = () => currentVersion;
|
||||
}
|
||||
|
||||
function getCurrentVersion() {
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
export default {
|
||||
getNewestVersion,
|
||||
getCurrentVersion,
|
||||
};
|
||||
|
||||
@@ -3,45 +3,36 @@ import type { User } from '$lib/types/user.type';
|
||||
import APIService from './api-service';
|
||||
import userStore from '$lib/stores/user-store';
|
||||
import type { AuthenticationResponseJSON, RegistrationResponseJSON } from '@simplewebauthn/browser';
|
||||
|
||||
class WebAuthnService extends APIService {
|
||||
async getRegistrationOptions() {
|
||||
return (await this.api.get(`/webauthn/register/start`)).data;
|
||||
}
|
||||
getRegistrationOptions = async () => (await this.api.get(`/webauthn/register/start`)).data;
|
||||
|
||||
async finishRegistration(body: RegistrationResponseJSON) {
|
||||
return (await this.api.post(`/webauthn/register/finish`, body)).data as Passkey;
|
||||
}
|
||||
finishRegistration = async (body: RegistrationResponseJSON) =>
|
||||
(await this.api.post(`/webauthn/register/finish`, body)).data as Passkey;
|
||||
|
||||
async getLoginOptions() {
|
||||
return (await this.api.get(`/webauthn/login/start`)).data;
|
||||
}
|
||||
getLoginOptions = async () => (await this.api.get(`/webauthn/login/start`)).data;
|
||||
|
||||
async finishLogin(body: AuthenticationResponseJSON) {
|
||||
return (await this.api.post(`/webauthn/login/finish`, body)).data as User;
|
||||
}
|
||||
finishLogin = async (body: AuthenticationResponseJSON) =>
|
||||
(await this.api.post(`/webauthn/login/finish`, body)).data as User;
|
||||
|
||||
async logout() {
|
||||
await this.api.post(`/webauthn/logout`);
|
||||
userStore.clearUser();
|
||||
}
|
||||
logout = async () => {
|
||||
await this.api.post(`/webauthn/logout`);
|
||||
userStore.clearUser();
|
||||
};
|
||||
|
||||
async listCredentials() {
|
||||
return (await this.api.get(`/webauthn/credentials`)).data as Passkey[];
|
||||
}
|
||||
listCredentials = async () => (await this.api.get(`/webauthn/credentials`)).data as Passkey[];
|
||||
|
||||
async removeCredential(id: string) {
|
||||
await this.api.delete(`/webauthn/credentials/${id}`);
|
||||
}
|
||||
removeCredential = async (id: string) => {
|
||||
await this.api.delete(`/webauthn/credentials/${id}`);
|
||||
};
|
||||
|
||||
async updateCredentialName(id: string, name: string) {
|
||||
await this.api.patch(`/webauthn/credentials/${id}`, { name });
|
||||
}
|
||||
updateCredentialName = async (id: string, name: string) => {
|
||||
await this.api.patch(`/webauthn/credentials/${id}`, { name });
|
||||
};
|
||||
|
||||
async reauthenticate(body?: AuthenticationResponseJSON) {
|
||||
const res = await this.api.post('/webauthn/reauthenticate', body);
|
||||
return res.data.reauthenticationToken as string;
|
||||
}
|
||||
reauthenticate = async (body?: AuthenticationResponseJSON) => {
|
||||
const res = await this.api.post('/webauthn/reauthenticate', body);
|
||||
return res.data.reauthenticationToken as string;
|
||||
};
|
||||
}
|
||||
|
||||
export default WebAuthnService;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import versionService from '$lib/services/version-service';
|
||||
import VersionService from '$lib/services/version-service';
|
||||
import type { AppVersionInformation } from '$lib/types/application-configuration';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const load: LayoutLoad = async () => {
|
||||
const versionService = new VersionService();
|
||||
const currentVersion = versionService.getCurrentVersion();
|
||||
|
||||
let newestVersion = null;
|
||||
|
||||
@@ -11,22 +11,16 @@
|
||||
import ApiKeyForm from './api-key-form.svelte';
|
||||
import ApiKeyList from './api-key-list.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let apiKeys = $state(data.apiKeys);
|
||||
let apiKeysRequestOptions = $state(data.apiKeysRequestOptions);
|
||||
|
||||
const apiKeyService = new ApiKeyService();
|
||||
let expandAddApiKey = $state(false);
|
||||
let apiKeyResponse = $state<ApiKeyResponse | null>(null);
|
||||
let listRef: ApiKeyList;
|
||||
|
||||
async function createApiKey(apiKeyData: ApiKeyCreate) {
|
||||
try {
|
||||
const response = await apiKeyService.create(apiKeyData);
|
||||
apiKeyResponse = response;
|
||||
|
||||
// After creation, reload the list of API keys
|
||||
apiKeys = await apiKeyService.list(apiKeysRequestOptions);
|
||||
|
||||
listRef.refresh();
|
||||
return true;
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
@@ -78,7 +72,7 @@
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<ApiKeyList {apiKeys} requestOptions={apiKeysRequestOptions} />
|
||||
<ApiKeyList bind:this={listRef} />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import ApiKeyService from '$lib/services/api-key-service';
|
||||
import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const apiKeyService = new ApiKeyService();
|
||||
|
||||
const apiKeysRequestOptions: SearchPaginationSortRequest = {
|
||||
sort: {
|
||||
column: 'lastUsedAt',
|
||||
direction: 'desc' as const
|
||||
}
|
||||
};
|
||||
|
||||
const apiKeys = await apiKeyService.list(apiKeysRequestOptions);
|
||||
|
||||
return { apiKeys, apiKeysRequestOptions };
|
||||
};
|
||||
@@ -6,21 +6,16 @@
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import ApiKeyService from '$lib/services/api-key-service';
|
||||
import type { ApiKey } from '$lib/types/api-key.type';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { LucideBan } from '@lucide/svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let {
|
||||
apiKeys,
|
||||
requestOptions
|
||||
}: {
|
||||
apiKeys: Paginated<ApiKey>;
|
||||
requestOptions: SearchPaginationSortRequest;
|
||||
} = $props();
|
||||
|
||||
const apiKeyService = new ApiKeyService();
|
||||
|
||||
let tableRef: AdvancedTable<ApiKey>;
|
||||
|
||||
export const refresh = () => tableRef.refresh();
|
||||
|
||||
function formatDate(dateStr: string | undefined) {
|
||||
if (!dateStr) return m.never();
|
||||
return new Date(dateStr).toLocaleString();
|
||||
@@ -38,7 +33,7 @@
|
||||
action: async () => {
|
||||
try {
|
||||
await apiKeyService.revoke(apiKey.id);
|
||||
apiKeys = await apiKeyService.list(requestOptions);
|
||||
await tableRef.refresh();
|
||||
toast.success(m.api_key_revoked_successfully());
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
@@ -50,9 +45,10 @@
|
||||
</script>
|
||||
|
||||
<AdvancedTable
|
||||
items={apiKeys}
|
||||
{requestOptions}
|
||||
onRefresh={async (o) => (apiKeys = await apiKeyService.list(o))}
|
||||
id="api-key-list"
|
||||
bind:this={tableRef}
|
||||
fetchCallback={apiKeyService.list}
|
||||
defaultSort={{ column: 'lastUsedAt', direction: 'desc' }}
|
||||
withoutSearch
|
||||
columns={[
|
||||
{ label: m.name(), sortColumn: 'name' },
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
import OIDCClientForm from './oidc-client-form.svelte';
|
||||
import OIDCClientList from './oidc-client-list.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let clients = $state(data.clients);
|
||||
let clientsRequestOptions = $state(data.clientsRequestOptions);
|
||||
let expandAddClient = $state(false);
|
||||
|
||||
const oidcService = new OIDCService();
|
||||
@@ -86,7 +83,7 @@
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<OIDCClientList {clients} requestOptions={clientsRequestOptions} />
|
||||
<OIDCClientList />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import OIDCService from '$lib/services/oidc-service';
|
||||
import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const oidcService = new OIDCService();
|
||||
|
||||
const clientsRequestOptions: SearchPaginationSortRequest = {
|
||||
sort: {
|
||||
column: 'name',
|
||||
direction: 'asc'
|
||||
}
|
||||
};
|
||||
|
||||
const clients = await oidcService.listClients(clientsRequestOptions);
|
||||
|
||||
return { clients, clientsRequestOptions };
|
||||
};
|
||||
@@ -7,21 +7,13 @@
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import OIDCService from '$lib/services/oidc-service';
|
||||
import type { OidcClient, OidcClientWithAllowedUserGroupsCount } from '$lib/types/oidc.type';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import { cachedOidcClientLogo } from '$lib/utils/cached-image-util';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { LucidePencil, LucideTrash } from '@lucide/svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let {
|
||||
clients = $bindable(),
|
||||
requestOptions
|
||||
}: {
|
||||
clients: Paginated<OidcClientWithAllowedUserGroupsCount>;
|
||||
requestOptions: SearchPaginationSortRequest;
|
||||
} = $props();
|
||||
|
||||
const oidcService = new OIDCService();
|
||||
let tableRef : AdvancedTable<OidcClientWithAllowedUserGroupsCount>;
|
||||
|
||||
async function deleteClient(client: OidcClient) {
|
||||
openConfirmDialog({
|
||||
@@ -33,7 +25,7 @@
|
||||
action: async () => {
|
||||
try {
|
||||
await oidcService.removeClient(client.id);
|
||||
clients = await oidcService.listClients(requestOptions!);
|
||||
await tableRef.refresh();
|
||||
toast.success(m.oidc_client_deleted_successfully());
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
@@ -45,9 +37,10 @@
|
||||
</script>
|
||||
|
||||
<AdvancedTable
|
||||
items={clients}
|
||||
{requestOptions}
|
||||
onRefresh={async (o) => (clients = await oidcService.listClients(o))}
|
||||
id="oidc-client-list"
|
||||
bind:this={tableRef}
|
||||
fetchCallback={oidcService.listClients}
|
||||
defaultSort={{ column: 'name', direction: 'asc' }}
|
||||
columns={[
|
||||
{ label: m.logo() },
|
||||
{ label: m.name(), sortColumn: 'name' },
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
import UserGroupForm from './user-group-form.svelte';
|
||||
import UserGroupList from './user-group-list.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let userGroups = $state(data.userGroups);
|
||||
let userGroupsRequestOptions = $state(data.userGroupsRequestOptions);
|
||||
let expandAddUserGroup = $state(false);
|
||||
|
||||
const userGroupService = new UserGroupService();
|
||||
@@ -79,7 +76,7 @@
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<UserGroupList {userGroups} requestOptions={userGroupsRequestOptions} />
|
||||
<UserGroupList />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import UserGroupService from '$lib/services/user-group-service';
|
||||
import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const userGroupService = new UserGroupService();
|
||||
|
||||
const userGroupsRequestOptions: SearchPaginationSortRequest = {
|
||||
sort: {
|
||||
column: 'friendlyName',
|
||||
direction: 'asc'
|
||||
}
|
||||
};
|
||||
|
||||
const userGroups = await userGroupService.list(userGroupsRequestOptions);
|
||||
return { userGroups, userGroupsRequestOptions };
|
||||
};
|
||||
@@ -8,22 +8,14 @@
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserGroupService from '$lib/services/user-group-service';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { UserGroup, UserGroupWithUserCount } from '$lib/types/user-group.type';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { LucidePencil, LucideTrash } from '@lucide/svelte';
|
||||
import Ellipsis from '@lucide/svelte/icons/ellipsis';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let {
|
||||
userGroups,
|
||||
requestOptions
|
||||
}: {
|
||||
userGroups: Paginated<UserGroupWithUserCount>;
|
||||
requestOptions: SearchPaginationSortRequest;
|
||||
} = $props();
|
||||
|
||||
const userGroupService = new UserGroupService();
|
||||
let tableRef: AdvancedTable<UserGroupWithUserCount>;
|
||||
|
||||
async function deleteUserGroup(userGroup: UserGroup) {
|
||||
openConfirmDialog({
|
||||
@@ -35,7 +27,7 @@
|
||||
action: async () => {
|
||||
try {
|
||||
await userGroupService.remove(userGroup.id);
|
||||
userGroups = await userGroupService.list(requestOptions!);
|
||||
await tableRef.refresh();
|
||||
toast.success(m.user_group_deleted_successfully());
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
@@ -47,9 +39,10 @@
|
||||
</script>
|
||||
|
||||
<AdvancedTable
|
||||
items={userGroups}
|
||||
onRefresh={async (o) => (userGroups = await userGroupService.list(o))}
|
||||
{requestOptions}
|
||||
id="user-group-list"
|
||||
bind:this={tableRef}
|
||||
fetchCallback={userGroupService.list}
|
||||
defaultSort={{ column: 'friendlyName', direction: 'asc' }}
|
||||
columns={[
|
||||
{ label: m.friendly_name(), sortColumn: 'friendlyName' },
|
||||
{ label: m.name(), sortColumn: 'name' },
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { User } from '$lib/types/user.type';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let {
|
||||
selectionDisabled = false,
|
||||
@@ -16,35 +13,21 @@
|
||||
} = $props();
|
||||
|
||||
const userService = new UserService();
|
||||
|
||||
let users: Paginated<User> | undefined = $state();
|
||||
let requestOptions: SearchPaginationSortRequest = $state({
|
||||
sort: {
|
||||
column: 'firstName',
|
||||
direction: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
users = await userService.list(requestOptions);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if users}
|
||||
<AdvancedTable
|
||||
items={users}
|
||||
onRefresh={async (o) => (users = await userService.list(o))}
|
||||
{requestOptions}
|
||||
columns={[
|
||||
{ label: m.name(), sortColumn: 'firstName' },
|
||||
{ label: m.email(), sortColumn: 'email' }
|
||||
]}
|
||||
bind:selectedIds={selectedUserIds}
|
||||
{selectionDisabled}
|
||||
>
|
||||
{#snippet rows({ item })}
|
||||
<Table.Cell>{item.firstName} {item.lastName}</Table.Cell>
|
||||
<Table.Cell>{item.email}</Table.Cell>
|
||||
{/snippet}
|
||||
</AdvancedTable>
|
||||
{/if}
|
||||
<AdvancedTable
|
||||
id="user-selection"
|
||||
fetchCallback={userService.list}
|
||||
defaultSort={{ column: 'firstName', direction: 'asc' }}
|
||||
columns={[
|
||||
{ label: m.name(), sortColumn: 'firstName' },
|
||||
{ label: m.email(), sortColumn: 'email' }
|
||||
]}
|
||||
bind:selectedIds={selectedUserIds}
|
||||
{selectionDisabled}
|
||||
>
|
||||
{#snippet rows({ item })}
|
||||
<Table.Cell>{item.firstName} {item.lastName}</Table.Cell>
|
||||
<Table.Cell>{item.email}</Table.Cell>
|
||||
{/snippet}
|
||||
</AdvancedTable>
|
||||
|
||||
@@ -15,17 +15,12 @@
|
||||
import UserForm from './user-form.svelte';
|
||||
import UserList from './user-list.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let users = $state(data.users);
|
||||
let usersRequestOptions = $state(data.usersRequestOptions);
|
||||
let signupTokens = $state(data.signupTokens);
|
||||
let signupTokensRequestOptions = $state(data.signupTokensRequestOptions);
|
||||
|
||||
let selectedCreateOptions = $state(m.add_user());
|
||||
let expandAddUser = $state(false);
|
||||
let signupTokenModalOpen = $state(false);
|
||||
let signupTokenListModalOpen = $state(false);
|
||||
|
||||
let userListRef: UserList;
|
||||
const userService = new UserService();
|
||||
|
||||
async function createUser(user: UserCreate) {
|
||||
@@ -38,13 +33,9 @@
|
||||
success = false;
|
||||
});
|
||||
|
||||
users = await userService.list(usersRequestOptions);
|
||||
await userListRef.refresh();
|
||||
return success;
|
||||
}
|
||||
|
||||
async function refreshSignupTokens() {
|
||||
signupTokens = await userService.listSignupTokens(signupTokensRequestOptions);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -117,15 +108,10 @@
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<UserList {users} requestOptions={usersRequestOptions} />
|
||||
<UserList bind:this={userListRef} />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
<SignupTokenModal bind:open={signupTokenModalOpen} onTokenCreated={refreshSignupTokens} />
|
||||
<SignupTokenListModal
|
||||
bind:open={signupTokenListModalOpen}
|
||||
bind:signupTokens
|
||||
{signupTokensRequestOptions}
|
||||
onTokenDeleted={refreshSignupTokens}
|
||||
/>
|
||||
<SignupTokenModal bind:open={signupTokenModalOpen} />
|
||||
<SignupTokenListModal bind:open={signupTokenListModalOpen} />
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import UserService from '$lib/services/user-service';
|
||||
import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const userService = new UserService();
|
||||
|
||||
const usersRequestOptions: SearchPaginationSortRequest = {
|
||||
sort: {
|
||||
column: 'firstName',
|
||||
direction: 'asc'
|
||||
}
|
||||
};
|
||||
|
||||
const signupTokensRequestOptions: SearchPaginationSortRequest = {
|
||||
sort: {
|
||||
column: 'createdAt',
|
||||
direction: 'desc'
|
||||
}
|
||||
};
|
||||
|
||||
const [users, signupTokens] = await Promise.all([
|
||||
userService.list(usersRequestOptions),
|
||||
userService.listSignupTokens(signupTokensRequestOptions)
|
||||
]);
|
||||
|
||||
return {
|
||||
users,
|
||||
usersRequestOptions,
|
||||
signupTokens,
|
||||
signupTokensRequestOptions
|
||||
};
|
||||
};
|
||||
@@ -11,7 +11,6 @@
|
||||
import UserService from '$lib/services/user-service';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import userStore from '$lib/stores/user-store';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { User } from '$lib/types/user.type';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import {
|
||||
@@ -24,15 +23,13 @@
|
||||
import Ellipsis from '@lucide/svelte/icons/ellipsis';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let {
|
||||
users = $bindable(),
|
||||
requestOptions
|
||||
}: { users: Paginated<User>; requestOptions: SearchPaginationSortRequest } = $props();
|
||||
|
||||
let userIdToCreateOneTimeLink: string | null = $state(null);
|
||||
let tableRef: AdvancedTable<User>;
|
||||
|
||||
const userService = new UserService();
|
||||
|
||||
export const refresh = () => tableRef.refresh();
|
||||
|
||||
async function deleteUser(user: User) {
|
||||
openConfirmDialog({
|
||||
title: m.delete_firstname_lastname({
|
||||
@@ -46,7 +43,7 @@
|
||||
action: async () => {
|
||||
try {
|
||||
await userService.remove(user.id);
|
||||
users = await userService.list(requestOptions!);
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
}
|
||||
@@ -62,9 +59,9 @@
|
||||
...user,
|
||||
disabled: false
|
||||
})
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
toast.success(m.user_enabled_successfully());
|
||||
userService.list(requestOptions!).then((updatedUsers) => (users = updatedUsers));
|
||||
await refresh();
|
||||
})
|
||||
.catch(axiosErrorToast);
|
||||
}
|
||||
@@ -85,7 +82,7 @@
|
||||
...user,
|
||||
disabled: true
|
||||
});
|
||||
users = await userService.list(requestOptions!);
|
||||
await refresh();
|
||||
toast.success(m.user_disabled_successfully());
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
@@ -97,9 +94,9 @@
|
||||
</script>
|
||||
|
||||
<AdvancedTable
|
||||
items={users}
|
||||
{requestOptions}
|
||||
onRefresh={async (options) => (users = await userService.list(options))}
|
||||
id="user-list"
|
||||
bind:this={tableRef}
|
||||
fetchCallback={userService.list}
|
||||
columns={[
|
||||
{ label: m.first_name(), sortColumn: 'firstName' },
|
||||
{ label: m.last_name(), sortColumn: 'lastName' },
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
import userStore from '$lib/stores/user-store';
|
||||
import { LogsIcon } from '@lucide/svelte';
|
||||
import AuditLogSwitcher from './audit-log-switcher.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let auditLogsRequestOptions = $state(data.auditLogsRequestOptions);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -28,7 +25,7 @@
|
||||
<Card.Description>{m.see_your_account_activities_from_the_last_3_months()}</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<AuditLogList auditLogs={data.auditLogs} requestOptions={auditLogsRequestOptions} />
|
||||
<AuditLogList />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import AuditLogService from '$lib/services/audit-log-service';
|
||||
import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const auditLogService = new AuditLogService();
|
||||
const auditLogsRequestOptions: SearchPaginationSortRequest = {
|
||||
sort: {
|
||||
column: 'createdAt',
|
||||
direction: 'desc'
|
||||
}
|
||||
};
|
||||
const auditLogs = await auditLogService.list(auditLogsRequestOptions);
|
||||
return { auditLogs, auditLogsRequestOptions };
|
||||
};
|
||||
@@ -6,15 +6,11 @@
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import AuditLogService from '$lib/services/audit-log-service';
|
||||
import type { AuditLogFilter } from '$lib/types/audit-log.type';
|
||||
import { eventTypes as eventTranslations } from '$lib/utils/audit-log-translator';
|
||||
import AuditLogSwitcher from '../audit-log-switcher.svelte';
|
||||
import {eventTypes as eventTranslations} from "$lib/utils/audit-log-translator";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const auditLogService = new AuditLogService();
|
||||
|
||||
let auditLogs = $state(data.auditLogs);
|
||||
let requestOptions = $state(data.requestOptions);
|
||||
let auditLogListRef: AuditLogList;
|
||||
|
||||
let filters: AuditLogFilter = $state({
|
||||
userId: '',
|
||||
@@ -31,7 +27,7 @@
|
||||
const eventTypes = $state(eventTranslations);
|
||||
|
||||
$effect(() => {
|
||||
auditLogService.listAllLogs(requestOptions, filters).then((response) => (auditLogs = response));
|
||||
auditLogListRef?.refresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -125,6 +121,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AuditLogList isAdmin={true} {auditLogs} {requestOptions} />
|
||||
<AuditLogList bind:this={auditLogListRef} isAdmin={true} {filters} />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import AuditLogService from '$lib/services/audit-log-service';
|
||||
import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const auditLogService = new AuditLogService();
|
||||
|
||||
const requestOptions: SearchPaginationSortRequest = {
|
||||
sort: {
|
||||
column: 'createdAt',
|
||||
direction: 'desc'
|
||||
}
|
||||
};
|
||||
|
||||
const auditLogs = await auditLogService.listAllLogs(requestOptions);
|
||||
|
||||
return {
|
||||
auditLogs,
|
||||
requestOptions
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user