Files
cameleer-server/ui/src/api/queries/admin/apps.ts

230 lines
7.3 KiB
TypeScript
Raw Normal View History

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { config } from '../../../config';
import { useAuthStore } from '../../../auth/auth-store';
export interface App {
id: string;
environmentId: string;
slug: string;
displayName: string;
containerConfig: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}
export interface AppVersion {
id: string;
appId: string;
version: number;
jarPath: string;
jarChecksum: string;
jarFilename: string;
jarSizeBytes: number;
detectedRuntimeType: string | null;
detectedMainClass: string | null;
uploadedAt: string;
}
export interface Deployment {
id: string;
appId: string;
appVersionId: string;
environmentId: string;
status: 'STOPPED' | 'STARTING' | 'RUNNING' | 'DEGRADED' | 'STOPPING' | 'FAILED';
targetState: string;
deploymentStrategy: string;
replicaStates: { index: number; containerId: string; containerName: string; status: string; oomKilled?: boolean }[];
deployStage: string | null;
containerId: string | null;
containerName: string | null;
errorMessage: string | null;
deployedAt: string | null;
stoppedAt: string | null;
createdAt: string;
}
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
/**
* Authenticated fetch. `path` is relative to apiBaseUrl, must include the
* leading slash. All app/deployment endpoints now live under
* /api/v1/environments/{envSlug}/...
*/
async function apiFetch<T>(path: string, options?: RequestInit): Promise<T> {
const token = useAuthStore.getState().accessToken;
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
const res = await fetch(`${config.apiBaseUrl}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
'X-Cameleer-Protocol-Version': '1',
...options?.headers,
},
});
if (res.status === 401 || res.status === 403) {
useAuthStore.getState().logout();
throw new Error('Unauthorized');
}
if (!res.ok) throw new Error(`API error: ${res.status}`);
if (res.status === 204) return undefined as T;
const text = await res.text();
if (!text) return undefined as T;
return JSON.parse(text);
}
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
function envBase(envSlug: string): string {
return `/environments/${encodeURIComponent(envSlug)}/apps`;
}
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
// --- Apps ---
export function useApps(envSlug: string | undefined) {
return useQuery({
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
queryKey: ['apps', envSlug],
queryFn: () => apiFetch<App[]>(envBase(envSlug!)),
enabled: !!envSlug,
});
}
export function useCreateApp() {
const qc = useQueryClient();
return useMutation({
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
mutationFn: ({ envSlug, slug, displayName }: { envSlug: string; slug: string; displayName: string }) =>
apiFetch<App>(envBase(envSlug), {
method: 'POST',
body: JSON.stringify({ slug, displayName }),
}),
onSuccess: () => qc.invalidateQueries({ queryKey: ['apps'] }),
});
}
export function useDeleteApp() {
const qc = useQueryClient();
return useMutation({
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
mutationFn: ({ envSlug, appSlug }: { envSlug: string; appSlug: string }) =>
apiFetch<void>(`${envBase(envSlug)}/${encodeURIComponent(appSlug)}`, { method: 'DELETE' }),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['apps'] });
qc.invalidateQueries({ queryKey: ['catalog'] });
},
});
}
export function useUpdateContainerConfig() {
const qc = useQueryClient();
return useMutation({
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
mutationFn: ({ envSlug, appSlug, config }: { envSlug: string; appSlug: string; config: Record<string, unknown> }) =>
apiFetch<App>(`${envBase(envSlug)}/${encodeURIComponent(appSlug)}/container-config`, {
method: 'PUT',
body: JSON.stringify(config),
}),
onSuccess: () => qc.invalidateQueries({ queryKey: ['apps'] }),
});
}
// --- Versions ---
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
export function useAppVersions(envSlug: string | undefined, appSlug: string | undefined) {
return useQuery({
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
queryKey: ['apps', envSlug, appSlug, 'versions'],
queryFn: () => apiFetch<AppVersion[]>(`${envBase(envSlug!)}/${encodeURIComponent(appSlug!)}/versions`),
enabled: !!envSlug && !!appSlug,
});
}
export function useUploadJar() {
const qc = useQueryClient();
return useMutation({
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
mutationFn: async ({ envSlug, appSlug, file }: { envSlug: string; appSlug: string; file: File }) => {
const token = useAuthStore.getState().accessToken;
const form = new FormData();
form.append('file', file);
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
const res = await fetch(
`${config.apiBaseUrl}${envBase(envSlug)}/${encodeURIComponent(appSlug)}/versions`, {
method: 'POST',
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
'X-Cameleer-Protocol-Version': '1',
},
body: form,
});
if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
return res.json() as Promise<AppVersion>;
},
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
onSuccess: (_data, { envSlug, appSlug }) =>
qc.invalidateQueries({ queryKey: ['apps', envSlug, appSlug, 'versions'] }),
});
}
// --- Deployments ---
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
export function useDeployments(envSlug: string | undefined, appSlug: string | undefined) {
return useQuery({
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
queryKey: ['apps', envSlug, appSlug, 'deployments'],
queryFn: () => apiFetch<Deployment[]>(`${envBase(envSlug!)}/${encodeURIComponent(appSlug!)}/deployments`),
enabled: !!envSlug && !!appSlug,
refetchInterval: 5000,
});
}
export function useCreateDeployment() {
const qc = useQueryClient();
return useMutation({
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
mutationFn: ({ envSlug, appSlug, appVersionId }: { envSlug: string; appSlug: string; appVersionId: string }) =>
apiFetch<Deployment>(
`${envBase(envSlug)}/${encodeURIComponent(appSlug)}/deployments`,
{ method: 'POST', body: JSON.stringify({ appVersionId }) },
),
onSuccess: (_data, { envSlug, appSlug }) =>
qc.invalidateQueries({ queryKey: ['apps', envSlug, appSlug, 'deployments'] }),
});
}
export function useStopDeployment() {
const qc = useQueryClient();
return useMutation({
feat!: move apps & deployments under /api/v1/environments/{envSlug}/apps/{appSlug}/... P3B of the taxonomy migration. App and deployment routes are now env-scoped in the URL itself, making the (env, app_slug) uniqueness key explicit. Previously /api/v1/apps/{appSlug} was ambiguous: with the same app deployed to multiple environments (dev/staging/prod), the handler called AppService.getBySlug(slug) which returns the first row matching slug regardless of env. Server: - AppController: @RequestMapping("/api/v1/environments/{envSlug}/ apps"). Every handler now calls appService.getByEnvironmentAndSlug(env.id(), appSlug) — 404 if the app doesn't exist in *this* env. CreateAppRequest body drops environmentId (it's in the path). - DeploymentController: @RequestMapping("/api/v1/environments/ {envSlug}/apps/{appSlug}/deployments"). DeployRequest body drops environmentId. PromoteRequest body switches from targetEnvironmentId (UUID) to targetEnvironment (slug); promote handler resolves the target env by slug and looks up the app with the same slug in the target env (fails with 404 if the target app doesn't exist yet — apps must exist in both source and target before promote). - AppService: added getByEnvironmentAndSlug helper; createApp now validates slug against ^[a-z0-9][a-z0-9-]{0,63}$ (400 on invalid). SPA: - queries/admin/apps.ts: rewritten. Hooks take envSlug where env-scoped. Removed useAllApps (no flat endpoint). Renamed path param naming: appId → appSlug throughout. Added usePromoteDeployment. Query keys include envSlug so cache is env-scoped. - AppsTab.tsx: call sites updated. When no environment is selected, the managed-app list is empty — cross-env discovery lives in the Runtime tab (catalog). handleDeploy/handleStop/etc. pass envSlug to the new hook signatures. BREAKING CHANGE: /api/v1/apps/** paths removed. Clients must use /api/v1/environments/{envSlug}/apps/{appSlug}/**. Request bodies for POST /apps and POST /apps/{slug}/deployments no longer accept environmentId (use the URL path instead). Promote body uses slug not UUID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:38:37 +02:00
mutationFn: ({ envSlug, appSlug, deploymentId }: { envSlug: string; appSlug: string; deploymentId: string }) =>
apiFetch<Deployment>(
`${envBase(envSlug)}/${encodeURIComponent(appSlug)}/deployments/${deploymentId}/stop`,
{ method: 'POST' },
),
onSuccess: (_data, { envSlug, appSlug }) =>
qc.invalidateQueries({ queryKey: ['apps', envSlug, appSlug, 'deployments'] }),
});
}
export function usePromoteDeployment() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ envSlug, appSlug, deploymentId, targetEnvironment }:
{ envSlug: string; appSlug: string; deploymentId: string; targetEnvironment: string }) =>
apiFetch<Deployment>(
`${envBase(envSlug)}/${encodeURIComponent(appSlug)}/deployments/${deploymentId}/promote`,
{ method: 'POST', body: JSON.stringify({ targetEnvironment }) },
),
onSuccess: () => qc.invalidateQueries({ queryKey: ['apps'] }),
});
}
// --- Dirty State ---
export interface DirtyStateDifference {
field: string;
staged: string;
deployed: string;
}
export interface DirtyState {
dirty: boolean;
lastSuccessfulDeploymentId: string | null;
differences: DirtyStateDifference[];
}
export function useDirtyState(envSlug: string | undefined, appSlug: string | undefined) {
return useQuery({
queryKey: ['apps', envSlug, appSlug, 'dirty-state'],
queryFn: () => apiFetch<DirtyState>(
`${envBase(envSlug!)}/${encodeURIComponent(appSlug!)}/dirty-state`,
),
enabled: !!envSlug && !!appSlug,
});
}