feat(ui): implement OAuth login flow and dashboard components
CI / build-and-test (push) Failing after -32m0s
CI / build-and-test (push) Failing after -32m0s
* Add OAuth login handling in app and UI components * Create new components for login and dashboard pages * Refactor sidebar and navigation structure * Introduce types for access entries and status responses
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { api } from '../../api';
|
||||
import type { AgentSkill } from '../../types';
|
||||
|
||||
let skills = $state<AgentSkill[]>([]);
|
||||
let loading = $state(true);
|
||||
let error = $state('');
|
||||
let busy = $state<string | null>(null);
|
||||
|
||||
async function load() {
|
||||
loading = true;
|
||||
error = '';
|
||||
try {
|
||||
skills = await api.skills.list();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to load skills';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(id: string, name: string) {
|
||||
if (!confirm(`Delete skill "${name}"?`)) return;
|
||||
busy = id;
|
||||
try {
|
||||
await api.skills.delete(id);
|
||||
await load();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Delete failed';
|
||||
} finally {
|
||||
busy = null;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-end justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-white">Skills</h2>
|
||||
<p class="mt-1 text-sm text-slate-400">{skills.length} skill{skills.length !== 1 ? 's' : ''}</p>
|
||||
</div>
|
||||
<button
|
||||
class="rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-sm text-slate-200 transition hover:bg-white/10"
|
||||
onclick={load}
|
||||
>Refresh</button>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="rounded-2xl border border-rose-400/30 bg-rose-400/10 px-4 py-4 text-sm text-rose-100">{error}</div>
|
||||
{/if}
|
||||
|
||||
{#if loading}
|
||||
<div class="rounded-2xl border border-dashed border-white/10 bg-slate-950/40 py-12 text-center text-slate-400">Loading…</div>
|
||||
{:else if skills.length === 0}
|
||||
<div class="rounded-2xl border border-dashed border-white/10 bg-slate-950/40 py-12 text-center text-slate-500">No skills registered.</div>
|
||||
{:else}
|
||||
<div class="space-y-3">
|
||||
{#each skills as skill}
|
||||
<div class="rounded-2xl border border-white/10 bg-white/5 p-5">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<p class="font-semibold text-white">{skill.name}</p>
|
||||
{#if skill.description}
|
||||
<p class="mt-1 text-sm text-slate-400">{skill.description}</p>
|
||||
{/if}
|
||||
{#if skill.tags?.length}
|
||||
<div class="mt-2 flex flex-wrap gap-1">
|
||||
{#each skill.tags as tag}
|
||||
<span class="rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-xs text-slate-400">{tag}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="shrink-0 text-xs text-rose-400 hover:text-rose-300 disabled:opacity-40"
|
||||
onclick={() => remove(skill.id, skill.name)}
|
||||
disabled={busy === skill.id}
|
||||
>Delete</button>
|
||||
</div>
|
||||
<details class="mt-3">
|
||||
<summary class="cursor-pointer text-xs text-slate-500 hover:text-slate-300">View content</summary>
|
||||
<pre class="mt-2 overflow-x-auto rounded-xl bg-slate-950/60 p-3 text-xs text-slate-300 whitespace-pre-wrap">{skill.content}</pre>
|
||||
</details>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user