فهرست منبع

feat: add agent api

Thomas Zhang 3 ماه پیش
والد
کامیت
a9d1f5cdb2
2فایلهای تغییر یافته به همراه183 افزوده شده و 0 حذف شده
  1. 102 0
      src/lib/api/agents.ts
  2. 81 0
      src/lib/stores/agents.svelte.ts

+ 102 - 0
src/lib/api/agents.ts

@@ -0,0 +1,102 @@
+import { resolve } from '$app/paths';
+
+import type { AgentCR } from '$lib/types/custom-resources';
+import type { Agent } from '$lib/types/entities';
+import type { Changes, CoreResources } from '$lib/types/workspace';
+
+import { handleResponse } from './types';
+
+const list = (workspaceId: string, fetch = globalThis.fetch): Promise<Agent[]> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]', {
+			workspaceId,
+			resource: 'agents',
+		}),
+	).then(handleResponse<Agent[]>);
+
+const get = (workspaceId: string, id: string, fetch = globalThis.fetch): Promise<Agent> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]', {
+			workspaceId,
+			resource: 'agents',
+			id,
+		}),
+	).then(handleResponse<Agent>);
+
+const getChanges = (workspaceId: string, id: string, fetch = globalThis.fetch): Promise<Changes> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]/changes', {
+			workspaceId,
+			resource: 'agents',
+			id,
+		}),
+	).then(handleResponse<Changes>);
+
+const getStatus = (
+	workspaceId: string,
+	id: string,
+	fetch = globalThis.fetch,
+): Promise<AgentCR['status']> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]/status', {
+			workspaceId,
+			resource: 'agents',
+			id,
+		}),
+	).then(handleResponse<AgentCR['status']>);
+
+const getResources = (
+	workspaceId: string,
+	id: string,
+	fetch = globalThis.fetch,
+): Promise<CoreResources> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]/resources', {
+			workspaceId,
+			resource: 'agents',
+			id,
+		}),
+	).then(handleResponse<CoreResources>);
+
+const create = (workspaceId: string, agent: Agent, fetch = globalThis.fetch): Promise<Agent> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]', {
+			workspaceId,
+			resource: 'agents',
+		}),
+		{
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify(agent),
+		},
+	).then(handleResponse<Agent>);
+
+const update = (workspaceId: string, agent: Agent, fetch = globalThis.fetch): Promise<Agent> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]', {
+			workspaceId,
+			resource: 'agents',
+			id: agent.id,
+		}),
+		{
+			method: 'PUT',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify(agent),
+		},
+	).then(handleResponse<Agent>);
+
+const remove = (workspaceId: string, id: string, fetch = globalThis.fetch): Promise<void> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]', {
+			workspaceId,
+			resource: 'agents',
+			id,
+		}),
+		{
+			method: 'DELETE',
+		},
+	).then(async (res) => {
+		if (!res.ok) await handleResponse<void>(res);
+	});
+
+export { create, get, getChanges, getResources, getStatus, list, remove, update };

+ 81 - 0
src/lib/stores/agents.svelte.ts

@@ -0,0 +1,81 @@
+import * as agentsApi from '$lib/api/agents';
+import { notificationsStore } from '$lib/stores/notifications.svelte';
+import { ResourceStore } from '$lib/stores/resources.svelte';
+import type { AgentCR } from '$lib/types/custom-resources';
+import type { Agent, DeploymentStatus } from '$lib/types/entities';
+
+type CRCondition = { type: string; status: 'True' | 'False' | 'Unknown' };
+
+const readyCondition = (conditions: CRCondition[]) =>
+	conditions.find((c) => c.type === 'Ready')?.status;
+
+const statusFromCR = (status: AgentCR['status']): DeploymentStatus => {
+	if (!status || typeof status !== 'object') return 'not-deployed';
+	if (status.readyReplicas && status.readyReplicas > 0) return 'running';
+	const ready = readyCondition(status.conditions ?? []);
+	if (ready === 'True') return 'running';
+	if (ready === 'False') return 'degraded';
+	return 'pending';
+};
+
+type CRStatus = AgentCR['status'];
+
+class AgentsStore extends ResourceStore<Agent, CRStatus> {
+	constructor() {
+		super('Agent', [
+			{
+				group: 'locostack.com',
+				version: 'v1alpha1',
+				resources: 'agents',
+			},
+		]);
+	}
+
+	protected resourceHref(id: string) {
+		return `/workloads/agents/${id}`;
+	}
+
+	protected apiGet(workspaceId: string, id: string) {
+		return agentsApi.get(workspaceId, id);
+	}
+
+	protected apiGetStatus(workspaceId: string, id: string) {
+		return agentsApi.getStatus(workspaceId, id);
+	}
+
+	protected apiCreate(workspaceId: string, agent: Agent) {
+		return agentsApi.create(workspaceId, agent);
+	}
+
+	protected apiUpdate(workspaceId: string, agent: Agent) {
+		return agentsApi.update(workspaceId, agent);
+	}
+
+	protected apiDelete(workspaceId: string, id: string) {
+		return agentsApi.remove(workspaceId, id);
+	}
+
+	protected onStatusChange(id: string, status: CRStatus, notify = true): void {
+		const item = this.get(id);
+		if (!item) return;
+		const deploymentStatus = statusFromCR(status);
+		notify = notify && item.status !== deploymentStatus;
+		item.status = deploymentStatus;
+		if (notify) {
+			this.#notifyStatusChange(id, item.name, deploymentStatus);
+		}
+	}
+
+	#notifyStatusChange(id: string, name: string, status: string): void {
+		const href = this.resourceHref(id);
+		notificationsStore.push({
+			type: 'success',
+			message: `Agent "${name}" is now ${status}`,
+			href,
+		});
+	}
+}
+
+const agentsStore = new AgentsStore();
+
+export { agentsStore };