- Replace per-model admin forms with the shared generic AdminTable/AdminModal CRUD - Add ProclamationGroup model and admin + API v2 routes (replaces subscription levels) - Drop subscription levels and flatten proclamation categories - Update v2 serializers and seed data accordingly
112 lines
2.7 KiB
Svelte
112 lines
2.7 KiB
Svelte
<script lang="ts">
|
|
import type { PageProps } from './$types';
|
|
import { enhance } from '$app/forms';
|
|
|
|
let { data }: PageProps = $props();
|
|
|
|
let newKey = $state('');
|
|
let mode = $state<'normal' | 'emergency' | null>(null);
|
|
let expiryDate = $state<string | null>(null);
|
|
let toastMessage = $state('');
|
|
|
|
function handleRotate(emergency: boolean) {
|
|
return ({ cancel }: any) => {
|
|
const msg = emergency
|
|
? 'EMERGENCY ROTATION: The current key will be revoked IMMEDIATELY. Continue?'
|
|
: 'Rotate the API key? The current key will remain valid for 48 hours.';
|
|
if (!confirm(msg)) {
|
|
cancel();
|
|
return;
|
|
}
|
|
|
|
return async ({ result }: any) => {
|
|
if (result.type === 'success' && result.status === 200) {
|
|
if (result.data?.newKey) {
|
|
newKey = result.data.newKey;
|
|
mode = result.data.mode ?? null;
|
|
expiryDate = result.data.expiresAt ?? null;
|
|
}
|
|
} else {
|
|
toastMessage = 'Rotation failed';
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
function copyKey() {
|
|
navigator.clipboard.writeText(newKey);
|
|
toastMessage = 'Copied to clipboard';
|
|
}
|
|
|
|
function dismissKey() {
|
|
newKey = '';
|
|
mode = null;
|
|
expiryDate = null;
|
|
}
|
|
</script>
|
|
|
|
<div>
|
|
<h1>API Key Management</h1>
|
|
|
|
{#if newKey}
|
|
<div>
|
|
<p>
|
|
{mode === 'emergency' ? 'Emergency Rotation Complete' : 'New API Key Generated'}
|
|
</p>
|
|
<p>This key will not be shown again. Copy it now.</p>
|
|
<div>
|
|
<code>
|
|
{newKey}
|
|
</code>
|
|
</div>
|
|
<div>
|
|
<button type="button" onclick={copyKey}> Copy to Clipboard </button>
|
|
<button type="button" onclick={dismissKey}> Dismiss </button>
|
|
</div>
|
|
{#if mode === 'normal' && expiryDate}
|
|
<p>
|
|
The previous key will remain valid until {new Date(expiryDate).toLocaleString()}
|
|
to give in-field devices time to update.
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
{#if toastMessage}
|
|
<div>
|
|
{toastMessage}
|
|
</div>
|
|
{/if}
|
|
|
|
<div>
|
|
<h2>Current Key Status</h2>
|
|
{#if data.activeKey}
|
|
<div>
|
|
<div>
|
|
Prefix: <code>{data.activeKey.prefix}</code>
|
|
</div>
|
|
<div>Created: {new Date(data.activeKey.createdAt).toLocaleString()}</div>
|
|
{#if data.activeKey.expiresAt}
|
|
<div>Expires: {new Date(data.activeKey.expiresAt).toLocaleString()}</div>
|
|
{/if}
|
|
</div>
|
|
{:else}
|
|
<p>No active API key. Generate one below.</p>
|
|
{/if}
|
|
{#if data.totalRotated > 0}
|
|
<p>
|
|
{data.totalRotated} previous key(s) have been rotated.
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
|
|
<div>
|
|
<form method="POST" action="?/rotate" use:enhance={handleRotate(false)}>
|
|
<button type="submit"> Generate New API Key </button>
|
|
</form>
|
|
<form method="POST" action="?/rotateEmergency" use:enhance={handleRotate(true)}>
|
|
<button type="submit"> Emergency Rotation </button>
|
|
</form>
|
|
</div>
|
|
</div>
|