// 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, ): { form: DeploymentPageFormState; setForm: React.Dispatch>; reset: () => void; serverState: DeploymentPageFormState; } { const serverState = useMemo(() => { const merged = { ...envDefaults, ...(app?.containerConfig ?? {}) } as Record; 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).map(([key, value]) => ({ key, value })) : [], }, sensitiveKeys: { sensitiveKeys: Array.isArray(agentConfig?.sensitiveKeys) ? (agentConfig!.sensitiveKeys as string[]) : [], }, }; }, [app, agentConfig, envDefaults]); const [form, setForm] = useState(serverState); const prevServerStateRef = useRef(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 }; }