Files
cameleer-server/ui/src/pages/AppsTab/AppDeploymentPage/hooks/useDeploymentPageState.ts
hsiegeln ade1733418
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m25s
CI / docker (push) Successful in 1m4s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s
ui(deploy): remove Exposed Ports field from Resources tab
The field was cosmetic — `containerConfig.exposedPorts` only fed Docker's
`Config.ExposedPorts` metadata via `withExposedPorts(...)`. It never
published a host port and Traefik routing uses `appPort` from the label
builder, not this list. Users reading the label "Exposed Ports" reasonably
expected it to expose their port externally; removing it until real
multi-port Traefik routing lands (tracked in #149).

Backend DTOs (`ContainerRequest.exposedPorts`, `ConfigMerger.intList
("exposedPorts")`) are left in place so existing containerConfig JSONB
rows continue to deserialize. New writes from the UI will no longer
include the field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:51:46 +02:00

149 lines
5.7 KiB
TypeScript

// ui/src/pages/AppsTab/AppDeploymentPage/hooks/useDeploymentPageState.ts
import { useState, useEffect, useMemo, useRef } from 'react';
import type { ApplicationConfig } from '../../../../api/queries/commands';
import type { App } from '../../../../api/queries/admin/apps';
export interface MonitoringFormState {
engineLevel: string;
payloadCaptureMode: string;
payloadSize: string;
payloadUnit: string;
applicationLogLevel: string;
agentLogLevel: string;
metricsEnabled: boolean;
metricsInterval: string;
samplingRate: string;
compressSuccess: boolean;
replayEnabled: boolean;
routeControlEnabled: boolean;
}
export interface ResourcesFormState {
memoryLimit: string;
memoryReserve: string;
cpuRequest: string;
cpuLimit: string;
appPort: string;
replicas: string;
deployStrategy: string;
stripPrefix: boolean;
sslOffloading: boolean;
runtimeType: string;
customArgs: string;
extraNetworks: string[];
}
export interface VariablesFormState {
envVars: { key: string; value: string }[];
}
export interface SensitiveKeysFormState {
sensitiveKeys: string[];
}
export interface DeploymentPageFormState {
monitoring: MonitoringFormState;
resources: ResourcesFormState;
variables: VariablesFormState;
sensitiveKeys: SensitiveKeysFormState;
}
export const defaultForm: DeploymentPageFormState = {
monitoring: {
engineLevel: 'REGULAR',
payloadCaptureMode: 'BOTH',
payloadSize: '4',
payloadUnit: 'KB',
applicationLogLevel: 'INFO',
agentLogLevel: 'INFO',
metricsEnabled: true,
metricsInterval: '60',
samplingRate: '1.0',
compressSuccess: false,
replayEnabled: true,
routeControlEnabled: true,
},
resources: {
memoryLimit: '512', memoryReserve: '', cpuRequest: '500', cpuLimit: '',
appPort: '8080', replicas: '1', deployStrategy: 'blue-green',
stripPrefix: true, sslOffloading: true, runtimeType: 'auto', customArgs: '',
extraNetworks: [],
},
variables: { envVars: [] },
sensitiveKeys: { sensitiveKeys: [] },
};
export function useDeploymentPageState(
app: App | null,
agentConfig: ApplicationConfig | null,
envDefaults: Record<string, unknown>,
): {
form: DeploymentPageFormState;
setForm: React.Dispatch<React.SetStateAction<DeploymentPageFormState>>;
reset: () => void;
serverState: DeploymentPageFormState;
} {
const serverState = useMemo<DeploymentPageFormState>(() => {
const merged = { ...envDefaults, ...(app?.containerConfig ?? {}) } as Record<string, unknown>;
return {
monitoring: {
engineLevel: (agentConfig?.engineLevel as string) ?? defaultForm.monitoring.engineLevel,
payloadCaptureMode: (agentConfig?.payloadCaptureMode as string) ?? defaultForm.monitoring.payloadCaptureMode,
payloadSize: defaultForm.monitoring.payloadSize,
payloadUnit: defaultForm.monitoring.payloadUnit,
applicationLogLevel: (agentConfig?.applicationLogLevel as string) ?? defaultForm.monitoring.applicationLogLevel,
agentLogLevel: (agentConfig?.agentLogLevel as string) ?? defaultForm.monitoring.agentLogLevel,
metricsEnabled: agentConfig?.metricsEnabled ?? defaultForm.monitoring.metricsEnabled,
metricsInterval: defaultForm.monitoring.metricsInterval,
samplingRate: agentConfig?.samplingRate !== undefined
? String(agentConfig.samplingRate)
: defaultForm.monitoring.samplingRate,
compressSuccess: agentConfig?.compressSuccess ?? defaultForm.monitoring.compressSuccess,
replayEnabled: defaultForm.monitoring.replayEnabled,
routeControlEnabled: defaultForm.monitoring.routeControlEnabled,
},
resources: {
memoryLimit: String(merged.memoryLimitMb ?? defaultForm.resources.memoryLimit),
memoryReserve: merged.memoryReserveMb != null ? String(merged.memoryReserveMb) : defaultForm.resources.memoryReserve,
cpuRequest: String(merged.cpuRequest ?? defaultForm.resources.cpuRequest),
cpuLimit: merged.cpuLimit != null ? String(merged.cpuLimit) : defaultForm.resources.cpuLimit,
appPort: String(merged.appPort ?? defaultForm.resources.appPort),
replicas: String(merged.replicas ?? defaultForm.resources.replicas),
deployStrategy: String(merged.deploymentStrategy ?? defaultForm.resources.deployStrategy),
stripPrefix: merged.stripPathPrefix !== false,
sslOffloading: merged.sslOffloading !== false,
runtimeType: String(merged.runtimeType ?? defaultForm.resources.runtimeType),
customArgs: String(merged.customArgs ?? defaultForm.resources.customArgs),
extraNetworks: Array.isArray(merged.extraNetworks) ? (merged.extraNetworks as string[]) : defaultForm.resources.extraNetworks,
},
variables: {
envVars: merged.customEnvVars
? Object.entries(merged.customEnvVars as Record<string, string>).map(([key, value]) => ({ key, value }))
: [],
},
sensitiveKeys: {
sensitiveKeys: Array.isArray(agentConfig?.sensitiveKeys)
? (agentConfig!.sensitiveKeys as string[])
: [],
},
};
}, [app, agentConfig, envDefaults]);
const [form, setForm] = useState<DeploymentPageFormState>(serverState);
const prevServerStateRef = useRef<DeploymentPageFormState>(serverState);
useEffect(() => {
// Only overwrite form if the current form value still matches the previous
// server state (i.e., the user has no local edits). Otherwise preserve
// user edits through background refetches.
setForm((current) => {
const hadLocalEdits =
JSON.stringify(current) !== JSON.stringify(prevServerStateRef.current);
prevServerStateRef.current = serverState;
return hadLocalEdits ? current : serverState;
});
}, [serverState]);
return { form, setForm, reset: () => setForm(serverState), serverState };
}