|
|
@@ -0,0 +1,106 @@
|
|
|
+import * as modelsApi from '$lib/api/models';
|
|
|
+import { notificationsStore } from '$lib/stores/notifications.svelte';
|
|
|
+import { ResourceStore } from '$lib/stores/resources.svelte';
|
|
|
+import type { ExternalModelCR, ManagedModelCR } from '$lib/types/custom-resources';
|
|
|
+import type { DeploymentStatus, ExternalModelStatus, Model } 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: ManagedModelCR['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: ExternalModelCR['status'],
|
|
|
+): ExternalModelStatus | 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 = ManagedModelCR['status'] | ExternalModelCR['status'];
|
|
|
+
|
|
|
+class ModelsStore extends ResourceStore<Model, CRStatus> {
|
|
|
+ constructor() {
|
|
|
+ super('Model', [
|
|
|
+ {
|
|
|
+ group: 'locostack.com',
|
|
|
+ version: 'v1alpha1',
|
|
|
+ resources: 'managedmodels',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ group: 'locostack.com',
|
|
|
+ version: 'v1alpha1',
|
|
|
+ resources: 'externalmodels',
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected resourceHref(id: string) {
|
|
|
+ return `/workloads/models/${id}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected apiGet(workspaceId: string, id: string) {
|
|
|
+ return modelsApi.get(workspaceId, id);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected apiGetStatus(workspaceId: string, id: string) {
|
|
|
+ return modelsApi.getStatus(workspaceId, id);
|
|
|
+ }
|
|
|
+ protected apiCreate(workspaceId: string, model: Model) {
|
|
|
+ return modelsApi.create(workspaceId, model);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected apiUpdate(workspaceId: string, model: Model) {
|
|
|
+ return modelsApi.update(workspaceId, model);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected apiDelete(workspaceId: string, id: string) {
|
|
|
+ return modelsApi.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 ManagedModelCR['status']);
|
|
|
+ if (item.status !== managedStatus) {
|
|
|
+ newStatus = managedStatus;
|
|
|
+ item.status = managedStatus;
|
|
|
+ }
|
|
|
+ } else if (item.mode === 'external') {
|
|
|
+ const externalStatus = externalStatusFromCR(status as ExternalModelCR['status']);
|
|
|
+ if (item.status !== externalStatus) {
|
|
|
+ newStatus = externalStatus;
|
|
|
+ item.status = externalStatus;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (notify && newStatus !== undefined) {
|
|
|
+ this.#notifyStatusChange(id, item.name, newStatus);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #notifyStatusChange(id: string, name: string, status: string): void {
|
|
|
+ const href = this.resourceHref(id);
|
|
|
+ notificationsStore.push({
|
|
|
+ type: 'success',
|
|
|
+ message: `Model "${name}" is now ${status}`,
|
|
|
+ href,
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const modelsStore = new ModelsStore();
|
|
|
+
|
|
|
+export { modelsStore };
|