From 493746b14c1251a45b061d2e3edd9160c929d2b9 Mon Sep 17 00:00:00 2001 From: benj Date: Fri, 10 Apr 2026 11:13:34 +0800 Subject: a basic ui and landing web interface for tidyindex.com --- web/ui/src/routes/dashboard/keys/+page.server.ts | 68 +++++++ web/ui/src/routes/dashboard/keys/+page.svelte | 220 +++++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 web/ui/src/routes/dashboard/keys/+page.server.ts create mode 100644 web/ui/src/routes/dashboard/keys/+page.svelte (limited to 'web/ui/src/routes/dashboard/keys') diff --git a/web/ui/src/routes/dashboard/keys/+page.server.ts b/web/ui/src/routes/dashboard/keys/+page.server.ts new file mode 100644 index 0000000..5491283 --- /dev/null +++ b/web/ui/src/routes/dashboard/keys/+page.server.ts @@ -0,0 +1,68 @@ +import { fail, type Actions } from '@sveltejs/kit'; + +import { + createKey, + listKeys, + revokeKey, + countActiveKeys +} from '$lib/server/keys'; +import { PLANS } from '$lib/plans'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ locals }) => { + const account = locals.account!; + const keys = listKeys(account.id); + return { + keys, + activeCount: countActiveKeys(account.id), + plan: PLANS[account.plan] + }; +}; + +export const actions: Actions = { + create: async ({ request, locals }) => { + const account = locals.account!; + const form = await request.formData(); + const name = ((form.get('name') ?? '') as string).trim(); + const scopes = form.getAll('scopes').map((s) => s.toString()); + + if (!name) { + return fail(400, { error: 'Give the key a name so you can recognize it later.' }); + } + + const plan = PLANS[account.plan]; + const active = countActiveKeys(account.id); + if (Number.isFinite(plan.maxKeys) && active >= plan.maxKeys) { + return fail(403, { + error: `Your ${plan.name} plan allows ${plan.maxKeys} active key${ + plan.maxKeys === 1 ? '' : 's' + }. Revoke one or upgrade your plan first.` + }); + } + + const created = createKey({ + accountId: account.id, + name, + scopes + }); + + return { + created: { + id: created.id, + plaintext: created.plaintext, + name: created.name + } + }; + }, + + revoke: async ({ request, locals }) => { + const account = locals.account!; + const form = await request.formData(); + const id = (form.get('id') ?? '').toString(); + if (!id) return fail(400, { error: 'Missing key id.' }); + + const ok = revokeKey(account.id, id); + if (!ok) return fail(404, { error: 'Key not found.' }); + return { revokedId: id }; + } +}; diff --git a/web/ui/src/routes/dashboard/keys/+page.svelte b/web/ui/src/routes/dashboard/keys/+page.svelte new file mode 100644 index 0000000..b0a066f --- /dev/null +++ b/web/ui/src/routes/dashboard/keys/+page.svelte @@ -0,0 +1,220 @@ + + +

ยง 01  ·  api keys

+
+
+

Your API keys.

+

+ {data.activeCount} active, {data.keys.length - data.activeCount} revoked. Your + {data.plan.name} plan allows + {Number.isFinite(data.plan.maxKeys) ? data.plan.maxKeys : 'unlimited'} + active key{data.plan.maxKeys === 1 ? '' : 's'}. +

+
+ {#if !showCreate} + + {/if} +
+ +{#if form && 'created' in form && form.created} +
+
+ New key + Copy this key now โ€” it won't be shown again. +
+
+ {form.created.plaintext} + +
+

+ Store it somewhere safe. Once you leave this page we only keep the hash. +

+
+{/if} + +{#if showCreate} +
+
+

New key

+ +
+ +
+ + +

Just for you โ€” how you'll recognize this key in the list.

+
+ +
+ dataset scope +
+ + {#each DATASETS as slug} + + {/each} +
+

+ {#if allScopes} + This key will work against every dataset in the catalog. + {:else} + This key will only work against the {selected.size} selected dataset{selected.size === 1 ? '' : 's'}. + {/if} +

+
+ + {#if allScopes} + + {:else} + {#each [...selected] as slug} + + {/each} + {/if} + +
+ + +
+
+{/if} + +{#if data.keys.length === 0} +
+

No keys yet.

+

Click "+ New key" to create your first one.

+
+{:else} + {#each data.keys as key (key.id)} +
+
+
+
+ {key.name} + {#if key.active} + Active + {:else} + Revoked + {/if} +
+
{maskKey(key.key_prefix)}
+
+ scope: {scopeSummary(key.scopes)} +
+
+ created {fmtDate(key.created_at)} + {#if key.last_used_at} + last used {fmtDate(key.last_used_at)} + {:else} + last used never + {/if} +
+
+ + {#if key.active} +
+ + +
+ {/if} +
+
+ {/each} +{/if} -- cgit v1.2.3