feat(ui): implement OAuth login flow and dashboard components
Some checks failed
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:
2026-04-26 09:12:46 +02:00
parent bdc78cc2a3
commit 71845d38d3
22 changed files with 1440 additions and 334 deletions

81
ui/src/api.ts Normal file
View File

@@ -0,0 +1,81 @@
import { GlobalStateStore } from './shellState';
function authHeaders(): HeadersInit {
const token = GlobalStateStore.getState().session.authToken;
return token ? { Authorization: `Bearer ${token}` } : {};
}
async function get<T>(path: string): Promise<T> {
const res = await fetch(path, { headers: authHeaders() });
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
return res.json() as Promise<T>;
}
async function post<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders() },
body: JSON.stringify(body)
});
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
if (res.status === 204) return undefined as T;
return res.json() as Promise<T>;
}
async function del(path: string): Promise<void> {
const res = await fetch(path, { method: 'DELETE', headers: authHeaders() });
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
}
export const api = {
projects: {
list: () => get<import('./types').ProjectSummary[]>('/api/admin/projects'),
create: (name: string, description: string) =>
post<import('./types').Project>('/api/admin/projects', { name, description })
},
thoughts: {
list: (params: { q?: string; project_id?: string; limit?: number; include_archived?: boolean }) => {
const qs = new URLSearchParams();
if (params.q) qs.set('q', params.q);
if (params.project_id) qs.set('project_id', params.project_id);
if (params.limit) qs.set('limit', String(params.limit));
if (params.include_archived) qs.set('include_archived', 'true');
return get<(import('./types').Thought | import('./types').SearchResult)[]>(
`/api/admin/thoughts${qs.size ? '?' + qs : ''}`
);
},
get: (id: string) => get<import('./types').Thought>(`/api/admin/thoughts/${id}`),
delete: (id: string) => del(`/api/admin/thoughts/${id}`),
archive: (id: string) => post<void>(`/api/admin/thoughts/${id}/archive`, {})
},
skills: {
list: (tag?: string) => {
const qs = tag ? `?tag=${encodeURIComponent(tag)}` : '';
return get<import('./types').AgentSkill[]>(`/api/admin/skills${qs}`);
},
delete: (id: string) => del(`/api/admin/skills/${id}`)
},
guardrails: {
list: (params?: { tag?: string; severity?: string }) => {
const qs = new URLSearchParams();
if (params?.tag) qs.set('tag', params.tag);
if (params?.severity) qs.set('severity', params.severity);
return get<import('./types').AgentGuardrail[]>(
`/api/admin/guardrails${qs.size ? '?' + qs : ''}`
);
},
delete: (id: string) => del(`/api/admin/guardrails/${id}`)
},
files: {
list: (params?: { project_id?: string; thought_id?: string; kind?: string }) => {
const qs = new URLSearchParams();
if (params?.project_id) qs.set('project_id', params.project_id);
if (params?.thought_id) qs.set('thought_id', params.thought_id);
if (params?.kind) qs.set('kind', params.kind);
return get<import('./types').StoredFile[]>(
`/api/admin/files${qs.size ? '?' + qs : ''}`
);
}
},
stats: () => get<import('./types').ThoughtStats>('/api/admin/stats')
};