Thomas Zhang 3 месяцев назад
Родитель
Сommit
789178a80b
2 измененных файлов с 207 добавлено и 0 удалено
  1. 102 0
      src/lib/api/tools.ts
  2. 105 0
      src/lib/stores/tools.svelte.ts

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

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

+ 105 - 0
src/lib/stores/tools.svelte.ts

@@ -0,0 +1,105 @@
+import * as toolsApi from '$lib/api/tools';
+import { notificationsStore } from '$lib/stores/notifications.svelte';
+import { ResourceStore } from '$lib/stores/resources.svelte';
+import type { ExternalToolCR, ManagedToolCR } from '$lib/types/custom-resources';
+import type { DeploymentStatus, ExternalToolStatus, Tool } from '$lib/types/entities';
+
+type CRCondition = { type: string; status: 'True' | 'False' | 'Unknown' };
+
+const readyCondition = (conditions: CRCondition[]) =>
+	conditions.find((c) => c.type === 'Ready')?.status;
+
+const managedStatusFromCR = (status: ManagedToolCR['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';
+};
+
+const externalStatusFromCR = (status: ExternalToolCR['status']): ExternalToolStatus | undefined => {
+	if (!status || typeof status !== 'object') return undefined;
+	const ready = readyCondition(status.conditions ?? []);
+	if (ready === 'True') return 'available';
+	if (ready === 'False') return 'error';
+	return undefined;
+};
+
+type CRStatus = ManagedToolCR['status'] | ExternalToolCR['status'];
+
+class ToolsStore extends ResourceStore<Tool, CRStatus> {
+	constructor() {
+		super('Tool', [
+			{
+				group: 'locostack.com',
+				version: 'v1alpha1',
+				resources: 'managedtools',
+			},
+			{
+				group: 'locostack.com',
+				version: 'v1alpha1',
+				resources: 'externaltools',
+			},
+		]);
+	}
+
+	protected resourceHref(id: string) {
+		return `/workloads/tools/${id}`;
+	}
+
+	protected apiGet(workspaceId: string, id: string) {
+		return toolsApi.get(workspaceId, id);
+	}
+
+	protected apiGetStatus(workspaceId: string, id: string) {
+		return toolsApi.getStatus(workspaceId, id);
+	}
+
+	protected apiCreate(workspaceId: string, tool: Tool) {
+		return toolsApi.create(workspaceId, tool);
+	}
+
+	protected apiUpdate(workspaceId: string, tool: Tool) {
+		return toolsApi.update(workspaceId, tool);
+	}
+
+	protected apiDelete(workspaceId: string, id: string) {
+		return toolsApi.remove(workspaceId, id);
+	}
+
+	protected onStatusChange(id: string, status: CRStatus, notify = true): void {
+		const item = this.get(id);
+		if (!item) return;
+		let newStatus: string | undefined;
+		if (item.mode === 'managed') {
+			const managedStatus = managedStatusFromCR(status as ManagedToolCR['status']);
+			if (item.status !== managedStatus) {
+				newStatus = managedStatus;
+				item.status = managedStatus;
+			}
+		} else if (item.mode === 'external') {
+			const externalStatus = externalStatusFromCR(status as ExternalToolCR['status']);
+			if (item.status !== externalStatus) {
+				newStatus = externalStatus;
+				item.status = externalStatus;
+			}
+		}
+		if (notify && newStatus) {
+			this.#notifyStatusChange(id, item.name, newStatus);
+		}
+	}
+
+	#notifyStatusChange(id: string, name: string, status: string): void {
+		const href = this.resourceHref(id);
+		notificationsStore.push({
+			type: 'success',
+			message: `Tool "${name}" is now ${status}`,
+			href,
+		});
+	}
+}
+
+const toolsStore = new ToolsStore();
+
+export { toolsStore };