refactor: CPU config to millicores, fix replica health, reorder tabs
- Rename cpuShares to cpuRequest (millicores), cpuLimit from cores to millicores. ResolvedContainerConfig translates to Docker-native units via dockerCpuShares() and dockerCpuQuota() helpers. Future K8s orchestrator can pass millicores through directly. - Fix waitForAnyHealthy to wait for ALL replicas instead of returning on first healthy one. Prevents false DEGRADED status with 2+ replicas. - Default app detail to Configuration tab (was Overview) - Reorder config sub-tabs: Monitoring, Resources, Variables, Traces & Taps, Route Recording Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -171,7 +171,7 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
||||
const defaults = env?.defaultContainerConfig ?? {};
|
||||
const [memoryLimit, setMemoryLimit] = useState(String(defaults.memoryLimitMb ?? 512));
|
||||
const [memoryReserve, setMemoryReserve] = useState(String(defaults.memoryReserveMb ?? ''));
|
||||
const [cpuShares, setCpuShares] = useState(String(defaults.cpuShares ?? 512));
|
||||
const [cpuRequest, setCpuRequest] = useState(String(defaults.cpuRequest ?? 500));
|
||||
const [cpuLimit, setCpuLimit] = useState(String(defaults.cpuLimit ?? ''));
|
||||
const [ports, setPorts] = useState<number[]>(Array.isArray(defaults.exposedPorts) ? defaults.exposedPorts as number[] : []);
|
||||
const [newPort, setNewPort] = useState('');
|
||||
@@ -182,7 +182,7 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
||||
const [stripPrefix, setStripPrefix] = useState(true);
|
||||
const [sslOffloading, setSslOffloading] = useState(true);
|
||||
|
||||
const [configTab, setConfigTab] = useState<'variables' | 'monitoring' | 'resources'>('variables');
|
||||
const [configTab, setConfigTab] = useState<'monitoring' | 'resources' | 'variables'>('monitoring');
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [step, setStep] = useState('');
|
||||
|
||||
@@ -191,7 +191,7 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
||||
const d = environments.find((e) => e.id === envId)?.defaultContainerConfig ?? {};
|
||||
setMemoryLimit(String(d.memoryLimitMb ?? 512));
|
||||
setMemoryReserve(String(d.memoryReserveMb ?? ''));
|
||||
setCpuShares(String(d.cpuShares ?? 512));
|
||||
setCpuRequest(String(d.cpuRequest ?? 500));
|
||||
setCpuLimit(String(d.cpuLimit ?? ''));
|
||||
setPorts(Array.isArray(d.exposedPorts) ? d.exposedPorts as number[] : []);
|
||||
}, [envId, environments]);
|
||||
@@ -224,8 +224,8 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
||||
const containerConfig: Record<string, unknown> = {
|
||||
memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
|
||||
memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
|
||||
cpuShares: cpuShares ? parseInt(cpuShares) : null,
|
||||
cpuLimit: cpuLimit ? parseFloat(cpuLimit) : null,
|
||||
cpuRequest: cpuRequest ? parseInt(cpuRequest) : null,
|
||||
cpuLimit: cpuLimit ? parseInt(cpuLimit) : null,
|
||||
exposedPorts: ports,
|
||||
customEnvVars: Object.fromEntries(envVars.filter((v) => v.key.trim()).map((v) => [v.key, v.value])),
|
||||
appPort: appPort ? parseInt(appPort) : 8080,
|
||||
@@ -322,9 +322,9 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
||||
|
||||
{/* Config Tabs */}
|
||||
<div className={styles.subTabs}>
|
||||
<button className={`${styles.subTab} ${configTab === 'variables' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('variables')}>Variables</button>
|
||||
<button className={`${styles.subTab} ${configTab === 'monitoring' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('monitoring')}>Monitoring</button>
|
||||
<button className={`${styles.subTab} ${configTab === 'resources' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('resources')}>Resources</button>
|
||||
<button className={`${styles.subTab} ${configTab === 'variables' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('variables')}>Variables</button>
|
||||
</div>
|
||||
|
||||
{configTab === 'variables' && (
|
||||
@@ -421,13 +421,13 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
||||
{!isProd && <span className={styles.configHint}>Available in production environments only</span>}
|
||||
</div>
|
||||
|
||||
<span className={styles.configLabel}>CPU Shares</span>
|
||||
<Input disabled={busy} value={cpuShares} onChange={(e) => setCpuShares(e.target.value)} style={{ width: 80 }} />
|
||||
<span className={styles.configLabel}>CPU Request</span>
|
||||
<Input disabled={busy} value={cpuRequest} onChange={(e) => setCpuRequest(e.target.value)} style={{ width: 80 }} />
|
||||
|
||||
<span className={styles.configLabel}>CPU Limit</span>
|
||||
<div className={styles.configInline}>
|
||||
<Input disabled={busy} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1.0" style={{ width: 80 }} />
|
||||
<span className={styles.cellMeta}>cores</span>
|
||||
<Input disabled={busy} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1000" style={{ width: 80 }} />
|
||||
<span className={styles.cellMeta}>millicores</span>
|
||||
</div>
|
||||
|
||||
<span className={styles.configLabel}>Exposed Ports</span>
|
||||
@@ -488,7 +488,7 @@ function AppDetailView({ appId: appSlug, environments, selectedEnv }: { appId: s
|
||||
const stopDeployment = useStopDeployment();
|
||||
const deleteApp = useDeleteApp();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [subTab, setSubTab] = useState<'overview' | 'config'>('overview');
|
||||
const [subTab, setSubTab] = useState<'overview' | 'config'>('config');
|
||||
const [deleteConfirm, setDeleteConfirm] = useState(false);
|
||||
|
||||
const envMap = useMemo(() => new Map(environments.map((e) => [e.id, e])), [environments]);
|
||||
@@ -549,8 +549,8 @@ function AppDetailView({ appId: appSlug, environments, selectedEnv }: { appId: s
|
||||
</div>
|
||||
|
||||
<div className={styles.subTabs}>
|
||||
<button className={`${styles.subTab} ${subTab === 'overview' ? styles.subTabActive : ''}`} onClick={() => setSubTab('overview')}>Overview</button>
|
||||
<button className={`${styles.subTab} ${subTab === 'config' ? styles.subTabActive : ''}`} onClick={() => setSubTab('config')}>Configuration</button>
|
||||
<button className={`${styles.subTab} ${subTab === 'overview' ? styles.subTabActive : ''}`} onClick={() => setSubTab('overview')}>Overview</button>
|
||||
</div>
|
||||
|
||||
{subTab === 'overview' && (
|
||||
@@ -702,7 +702,7 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
|
||||
const { data: processorToRoute = {} } = useProcessorRouteMapping(app.slug);
|
||||
const isProd = environment?.production ?? false;
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [configTab, setConfigTab] = useState<'variables' | 'monitoring' | 'traces' | 'recording' | 'resources'>('variables');
|
||||
const [configTab, setConfigTab] = useState<'monitoring' | 'resources' | 'variables' | 'traces' | 'recording'>('monitoring');
|
||||
|
||||
const appRoutes: CatalogRoute[] = useMemo(() => {
|
||||
if (!catalog) return [];
|
||||
@@ -731,7 +731,7 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
|
||||
const merged = useMemo(() => ({ ...defaults, ...app.containerConfig }), [defaults, app.containerConfig]);
|
||||
const [memoryLimit, setMemoryLimit] = useState('512');
|
||||
const [memoryReserve, setMemoryReserve] = useState('');
|
||||
const [cpuShares, setCpuShares] = useState('512');
|
||||
const [cpuRequest, setCpuRequest] = useState('500');
|
||||
const [cpuLimit, setCpuLimit] = useState('');
|
||||
const [ports, setPorts] = useState<number[]>([]);
|
||||
const [newPort, setNewPort] = useState('');
|
||||
@@ -758,7 +758,7 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
|
||||
}
|
||||
setMemoryLimit(String(merged.memoryLimitMb ?? 512));
|
||||
setMemoryReserve(String(merged.memoryReserveMb ?? ''));
|
||||
setCpuShares(String(merged.cpuShares ?? 512));
|
||||
setCpuRequest(String(merged.cpuRequest ?? 500));
|
||||
setCpuLimit(String(merged.cpuLimit ?? ''));
|
||||
setPorts(Array.isArray(merged.exposedPorts) ? merged.exposedPorts as number[] : []);
|
||||
const vars = merged.customEnvVars as Record<string, string> | undefined;
|
||||
@@ -808,8 +808,8 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
|
||||
const containerConfig: Record<string, unknown> = {
|
||||
memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
|
||||
memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
|
||||
cpuShares: cpuShares ? parseInt(cpuShares) : null,
|
||||
cpuLimit: cpuLimit ? parseFloat(cpuLimit) : null,
|
||||
cpuRequest: cpuRequest ? parseInt(cpuRequest) : null,
|
||||
cpuLimit: cpuLimit ? parseInt(cpuLimit) : null,
|
||||
exposedPorts: ports,
|
||||
customEnvVars: Object.fromEntries(envVars.filter((v) => v.key.trim()).map((v) => [v.key, v.value])),
|
||||
appPort: appPort ? parseInt(appPort) : 8080,
|
||||
@@ -910,11 +910,11 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
|
||||
)}
|
||||
|
||||
<div className={styles.subTabs}>
|
||||
<button className={`${styles.subTab} ${configTab === 'variables' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('variables')}>Variables</button>
|
||||
<button className={`${styles.subTab} ${configTab === 'monitoring' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('monitoring')}>Monitoring</button>
|
||||
<button className={`${styles.subTab} ${configTab === 'resources' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('resources')}>Resources</button>
|
||||
<button className={`${styles.subTab} ${configTab === 'variables' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('variables')}>Variables</button>
|
||||
<button className={`${styles.subTab} ${configTab === 'traces' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('traces')}>Traces & Taps</button>
|
||||
<button className={`${styles.subTab} ${configTab === 'recording' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('recording')}>Route Recording</button>
|
||||
<button className={`${styles.subTab} ${configTab === 'resources' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('resources')}>Resources</button>
|
||||
</div>
|
||||
|
||||
{configTab === 'variables' && (
|
||||
@@ -1033,13 +1033,13 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
|
||||
{!isProd && <span className={styles.configHint}>Available in production environments only</span>}
|
||||
</div>
|
||||
|
||||
<span className={styles.configLabel}>CPU Shares</span>
|
||||
<Input disabled={!editing} value={cpuShares} onChange={(e) => setCpuShares(e.target.value)} style={{ width: 80 }} />
|
||||
<span className={styles.configLabel}>CPU Request</span>
|
||||
<Input disabled={!editing} value={cpuRequest} onChange={(e) => setCpuRequest(e.target.value)} style={{ width: 80 }} />
|
||||
|
||||
<span className={styles.configLabel}>CPU Limit</span>
|
||||
<div className={styles.configInline}>
|
||||
<Input disabled={!editing} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1.0" style={{ width: 80 }} />
|
||||
<span className={styles.cellMeta}>cores</span>
|
||||
<Input disabled={!editing} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1000" style={{ width: 80 }} />
|
||||
<span className={styles.cellMeta}>millicores</span>
|
||||
</div>
|
||||
|
||||
<span className={styles.configLabel}>Exposed Ports</span>
|
||||
|
||||
Reference in New Issue
Block a user