Skip to content

Commit 8cfc0d2

Browse files
committed
Some more work on ApiTokens page
1 parent 61c2a89 commit 8cfc0d2

9 files changed

Lines changed: 200 additions & 160 deletions

File tree

src/lib/api/internal/v1/models/TokenCreatedResponse.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
*/
1414

1515
import { mapValues } from '../runtime';
16+
import type { PermissionType } from './PermissionType';
17+
import {
18+
PermissionTypeFromJSON,
19+
PermissionTypeFromJSONTyped,
20+
PermissionTypeToJSON,
21+
PermissionTypeToJSONTyped,
22+
} from './PermissionType';
23+
1624
/**
1725
*
1826
* @export
@@ -24,21 +32,56 @@ export interface TokenCreatedResponse {
2432
* @type {string}
2533
* @memberof TokenCreatedResponse
2634
*/
27-
token: string;
35+
id: string;
2836
/**
2937
*
3038
* @type {string}
3139
* @memberof TokenCreatedResponse
3240
*/
33-
id: string;
41+
name: string;
42+
/**
43+
*
44+
* @type {string}
45+
* @memberof TokenCreatedResponse
46+
*/
47+
token: string;
48+
/**
49+
*
50+
* @type {Date}
51+
* @memberof TokenCreatedResponse
52+
*/
53+
createdAt: Date;
54+
/**
55+
*
56+
* @type {Date}
57+
* @memberof TokenCreatedResponse
58+
*/
59+
validUntil: Date | null;
60+
/**
61+
*
62+
* @type {Date}
63+
* @memberof TokenCreatedResponse
64+
*/
65+
lastUsed: Date;
66+
/**
67+
*
68+
* @type {Array<PermissionType>}
69+
* @memberof TokenCreatedResponse
70+
*/
71+
permissions: Array<PermissionType>;
3472
}
3573

3674
/**
3775
* Check if a given object implements the TokenCreatedResponse interface.
3876
*/
3977
export function instanceOfTokenCreatedResponse(value: object): value is TokenCreatedResponse {
40-
if (!('token' in value) || value['token'] === undefined) return false;
4178
if (!('id' in value) || value['id'] === undefined) return false;
79+
if (!('name' in value) || value['name'] === undefined) return false;
80+
if (!('token' in value) || value['token'] === undefined) return false;
81+
if (!('createdAt' in value) || value['createdAt'] === undefined) return false;
82+
if (!('validUntil' in value) || value['validUntil'] === undefined) return false;
83+
if (!('lastUsed' in value) || value['lastUsed'] === undefined) return false;
84+
if (!('permissions' in value) || value['permissions'] === undefined) return false;
4285
return true;
4386
}
4487

@@ -52,8 +95,13 @@ export function TokenCreatedResponseFromJSONTyped(json: any, ignoreDiscriminator
5295
}
5396
return {
5497

55-
'token': json['token'],
5698
'id': json['id'],
99+
'name': json['name'],
100+
'token': json['token'],
101+
'createdAt': (new Date(json['createdAt'])),
102+
'validUntil': (json['validUntil'] == null ? null : new Date(json['validUntil'])),
103+
'lastUsed': (new Date(json['lastUsed'])),
104+
'permissions': ((json['permissions'] as Array<any>).map(PermissionTypeFromJSON)),
57105
};
58106
}
59107

@@ -68,8 +116,13 @@ export function TokenCreatedResponseToJSONTyped(value?: TokenCreatedResponse | n
68116

69117
return {
70118

71-
'token': value['token'],
72119
'id': value['id'],
120+
'name': value['name'],
121+
'token': value['token'],
122+
'createdAt': ((value['createdAt']).toISOString()),
123+
'validUntil': ((value['validUntil'] as any).toISOString()),
124+
'lastUsed': ((value['lastUsed']).toISOString()),
125+
'permissions': ((value['permissions'] as Array<any>).map(PermissionTypeToJSON)),
73126
};
74127
}
75128

src/lib/stores/ApiTokensStore.ts

Lines changed: 0 additions & 56 deletions
This file was deleted.

src/routes/(authenticated)/settings/api-tokens/+page.svelte

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,96 @@
22
import Plus from '@lucide/svelte/icons/plus';
33
import RotateCcw from '@lucide/svelte/icons/rotate-ccw';
44
import type { SortingState } from '@tanstack/table-core';
5+
import type { ColumnDef } from '@tanstack/table-core';
6+
import { apiTokensApi } from '$lib/api';
7+
import type { TokenCreatedResponse, TokenResponse } from '$lib/api/internal/v1';
58
import Container from '$lib/components/Container.svelte';
9+
import {
10+
CreateSortableColumnDef,
11+
LocaleDateRenderer,
12+
RenderCell,
13+
TimeSinceRelativeOrNeverRenderer,
14+
} from '$lib/components/Table/ColumnUtils';
615
import DataTable from '$lib/components/Table/DataTableTemplate.svelte';
716
import Button from '$lib/components/ui/button/button.svelte';
817
import * as Card from '$lib/components/ui/card';
9-
import { ApiTokensStore, refreshApiTokens } from '$lib/stores/ApiTokensStore';
18+
import { renderComponent } from '$lib/components/ui/data-table';
19+
import type { ProblemDetails } from '$lib/errorhandling/ProblemDetails';
20+
import { handleApiError } from '$lib/errorhandling/apiErrorHandling';
1021
import { onMount } from 'svelte';
1122
import { toast } from 'svelte-sonner';
12-
import { columns } from './columns';
13-
import TokenGenerateDialog from './dialog-token-generate.svelte';
23+
import DataTableActions from './data-table-actions.svelte';
24+
import TokenCreateDialog from './dialog-token-create.svelte';
25+
import TokenCreatedDialog from './dialog-token-created.svelte';
1426
15-
let data = $derived(Array.from($ApiTokensStore.values()));
27+
let loading = $state<boolean>(false);
28+
let data = $state<TokenResponse[]>([]);
1629
let sorting = $state<SortingState>([]);
1730
31+
function onCreated(token: TokenCreatedResponse) {
32+
data = [
33+
...data,
34+
{
35+
id: token.id,
36+
name: token.name,
37+
createdOn: token.createdAt,
38+
validUntil: token.validUntil,
39+
lastUsed: token.lastUsed,
40+
permissions: token.permissions,
41+
},
42+
];
43+
createdTokenSecret = token.token;
44+
toast.success('Token created successfully');
45+
}
46+
47+
function onEdit(id: string, updater: (token: TokenResponse) => TokenResponse) {
48+
data = data.map((token) => (token.id === id ? updater(token) : token));
49+
}
50+
51+
async function onDeleted(id: string) {
52+
data = data.filter((token) => token.id !== id);
53+
}
54+
55+
const columns: ColumnDef<TokenResponse>[] = [
56+
CreateSortableColumnDef('name', 'Name', RenderCell),
57+
CreateSortableColumnDef('createdOn', 'Created at', LocaleDateRenderer),
58+
CreateSortableColumnDef('validUntil', 'Expires at', TimeSinceRelativeOrNeverRenderer),
59+
CreateSortableColumnDef('lastUsed', 'Last used', TimeSinceRelativeOrNeverRenderer),
60+
{
61+
id: 'actions',
62+
cell: ({ row }) => {
63+
// You can pass whatever you need from `row.original` to the component
64+
return renderComponent(DataTableActions, { token: row.original, onEdit, onDeleted });
65+
},
66+
},
67+
];
68+
1869
let showGenerateTokenModal = $state<boolean>(false);
70+
let createdTokenSecret = $state<string | null>(null);
71+
72+
function handleProblem(problem: ProblemDetails): boolean {
73+
return false;
74+
}
75+
76+
async function loadTokens(successMessage?: string) {
77+
loading = true;
78+
try {
79+
data = await apiTokensApi.tokensListTokens();
80+
if (successMessage) {
81+
toast.success(successMessage);
82+
}
83+
} catch (error) {
84+
await handleApiError(error, handleProblem);
85+
} finally {
86+
loading = false;
87+
}
88+
}
1989
20-
onMount(refreshApiTokens);
90+
onMount(loadTokens);
2191
</script>
2292

23-
<TokenGenerateDialog bind:open={showGenerateTokenModal} />
93+
<TokenCreateDialog bind:open={showGenerateTokenModal} {onCreated} />
94+
<TokenCreatedDialog bind:token={createdTokenSecret} />
2495

2596
<Container>
2697
<Card.Header class="w-full">
@@ -31,12 +102,7 @@
31102
<Plus />
32103
Generate Token
33104
</Button>
34-
<Button
35-
onclick={() => {
36-
refreshApiTokens();
37-
toast.success('Tokens refreshed successfully');
38-
}}
39-
>
105+
<Button onclick={() => loadTokens('Tokens refreshed successfully')}>
40106
<RotateCcw />
41107
Refresh
42108
</Button>

src/routes/(authenticated)/settings/api-tokens/columns.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/routes/(authenticated)/settings/api-tokens/data-table-actions.svelte

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
1010
interface Props {
1111
token: TokenResponse;
12+
onEdit: (id: string, updater: (token: TokenResponse) => TokenResponse) => void;
13+
onDeleted: (id: string) => void;
1214
}
1315
14-
let { token }: Props = $props();
16+
let { token, onEdit, onDeleted }: Props = $props();
1517
1618
let editDialogOpen = $state<boolean>(false);
1719
let deleteDialogOpen = $state<boolean>(false);
@@ -23,11 +25,12 @@
2325
2426
function openDeleteDialog() {
2527
deleteDialogOpen = true;
28+
console.log('Delete dialog opened for token:', $state.snapshot(token));
2629
}
2730
</script>
2831

29-
<TokenEditDialog open={editDialogOpen} {token} />
30-
<TokenDeleteDialog open={deleteDialogOpen} {token} />
32+
<TokenEditDialog open={editDialogOpen} {token} {onEdit} />
33+
<TokenDeleteDialog open={deleteDialogOpen} {token} {onDeleted} />
3134

3235
<DropdownMenu.Root>
3336
<DropdownMenu.Trigger>
@@ -39,8 +42,10 @@
3942
{/snippet}
4043
</DropdownMenu.Trigger>
4144
<DropdownMenu.Content>
42-
<DropdownMenu.Item onclick={copyId}>Copy ID</DropdownMenu.Item>
43-
<DropdownMenu.Item onclick={() => (editDialogOpen = true)}>Edit</DropdownMenu.Item>
44-
<DropdownMenu.Item onclick={openDeleteDialog}>Delete</DropdownMenu.Item>
45+
<DropdownMenu.Item class="cursor-pointer" onclick={copyId}>Copy ID</DropdownMenu.Item>
46+
<DropdownMenu.Item class="cursor-pointer" onclick={() => (editDialogOpen = true)}
47+
>Edit</DropdownMenu.Item
48+
>
49+
<DropdownMenu.Item class="cursor-pointer" onclick={openDeleteDialog}>Delete</DropdownMenu.Item>
4550
</DropdownMenu.Content>
4651
</DropdownMenu.Root>

0 commit comments

Comments
 (0)