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:
@@ -37,8 +37,8 @@ public class DeploymentExecutor {
|
|||||||
@Value("${cameleer.runtime.container-memory-limit:512m}")
|
@Value("${cameleer.runtime.container-memory-limit:512m}")
|
||||||
private String globalMemoryLimit;
|
private String globalMemoryLimit;
|
||||||
|
|
||||||
@Value("${cameleer.runtime.container-cpu-shares:512}")
|
@Value("${cameleer.runtime.container-cpu-request:500}")
|
||||||
private int globalCpuShares;
|
private int globalCpuRequest;
|
||||||
|
|
||||||
@Value("${cameleer.runtime.health-check-timeout:60}")
|
@Value("${cameleer.runtime.health-check-timeout:60}")
|
||||||
private int healthCheckTimeout;
|
private int healthCheckTimeout;
|
||||||
@@ -86,7 +86,7 @@ public class DeploymentExecutor {
|
|||||||
|
|
||||||
var globalDefaults = new ConfigMerger.GlobalRuntimeDefaults(
|
var globalDefaults = new ConfigMerger.GlobalRuntimeDefaults(
|
||||||
parseMemoryLimitMb(globalMemoryLimit),
|
parseMemoryLimitMb(globalMemoryLimit),
|
||||||
globalCpuShares,
|
globalCpuRequest,
|
||||||
globalRoutingMode,
|
globalRoutingMode,
|
||||||
globalRoutingDomain,
|
globalRoutingDomain,
|
||||||
globalServerUrl.isBlank() ? "http://cameleer3-server:8081" : globalServerUrl
|
globalServerUrl.isBlank() ? "http://cameleer3-server:8081" : globalServerUrl
|
||||||
@@ -126,7 +126,6 @@ public class DeploymentExecutor {
|
|||||||
|
|
||||||
for (int i = 0; i < config.replicas(); i++) {
|
for (int i = 0; i < config.replicas(); i++) {
|
||||||
String containerName = env.slug() + "-" + app.slug() + "-" + i;
|
String containerName = env.slug() + "-" + app.slug() + "-" + i;
|
||||||
Long cpuQuota = config.cpuLimit() != null ? (long) (config.cpuLimit() * 100_000) : null;
|
|
||||||
|
|
||||||
String volumeName = jarDockerVolume != null && !jarDockerVolume.isBlank() ? jarDockerVolume : null;
|
String volumeName = jarDockerVolume != null && !jarDockerVolume.isBlank() ? jarDockerVolume : null;
|
||||||
ContainerRequest request = new ContainerRequest(
|
ContainerRequest request = new ContainerRequest(
|
||||||
@@ -136,7 +135,7 @@ public class DeploymentExecutor {
|
|||||||
envNet != null ? List.of(envNet) : List.of(),
|
envNet != null ? List.of(envNet) : List.of(),
|
||||||
baseEnvVars, labels,
|
baseEnvVars, labels,
|
||||||
config.memoryLimitBytes(), config.memoryReserveBytes(),
|
config.memoryLimitBytes(), config.memoryReserveBytes(),
|
||||||
config.cpuShares(), cpuQuota,
|
config.dockerCpuShares(), config.dockerCpuQuota(),
|
||||||
config.exposedPorts(), agentHealthPort,
|
config.exposedPorts(), agentHealthPort,
|
||||||
"on-failure", 3
|
"on-failure", 3
|
||||||
);
|
);
|
||||||
@@ -273,19 +272,21 @@ public class DeploymentExecutor {
|
|||||||
|
|
||||||
private int waitForAnyHealthy(List<String> containerIds, int timeoutSeconds) {
|
private int waitForAnyHealthy(List<String> containerIds, int timeoutSeconds) {
|
||||||
long deadline = System.currentTimeMillis() + (timeoutSeconds * 1000L);
|
long deadline = System.currentTimeMillis() + (timeoutSeconds * 1000L);
|
||||||
|
int lastHealthy = 0;
|
||||||
while (System.currentTimeMillis() < deadline) {
|
while (System.currentTimeMillis() < deadline) {
|
||||||
int healthy = 0;
|
int healthy = 0;
|
||||||
for (String cid : containerIds) {
|
for (String cid : containerIds) {
|
||||||
ContainerStatus status = orchestrator.getContainerStatus(cid);
|
ContainerStatus status = orchestrator.getContainerStatus(cid);
|
||||||
if ("healthy".equals(status.state())) healthy++;
|
if ("healthy".equals(status.state())) healthy++;
|
||||||
}
|
}
|
||||||
if (healthy > 0) return healthy;
|
lastHealthy = healthy;
|
||||||
|
if (healthy == containerIds.size()) return healthy;
|
||||||
try { Thread.sleep(2000); } catch (InterruptedException e) {
|
try { Thread.sleep(2000); } catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
return 0;
|
return lastHealthy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return lastHealthy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Map<String, Object>> updateReplicaHealth(List<Map<String, Object>> replicas,
|
private List<Map<String, Object>> updateReplicaHealth(List<Map<String, Object>> replicas,
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ public final class ConfigMerger {
|
|||||||
return new ResolvedContainerConfig(
|
return new ResolvedContainerConfig(
|
||||||
intVal(appConfig, envConfig, "memoryLimitMb", global.memoryLimitMb()),
|
intVal(appConfig, envConfig, "memoryLimitMb", global.memoryLimitMb()),
|
||||||
intOrNull(appConfig, envConfig, "memoryReserveMb"),
|
intOrNull(appConfig, envConfig, "memoryReserveMb"),
|
||||||
intVal(appConfig, envConfig, "cpuShares", global.cpuShares()),
|
intVal(appConfig, envConfig, "cpuRequest", global.cpuRequest()),
|
||||||
doubleOrNull(appConfig, envConfig, "cpuLimit"),
|
intOrNull(appConfig, envConfig, "cpuLimit"),
|
||||||
intVal(appConfig, envConfig, "appPort", 8080),
|
intVal(appConfig, envConfig, "appPort", 8080),
|
||||||
intList(appConfig, envConfig, "exposedPorts"),
|
intList(appConfig, envConfig, "exposedPorts"),
|
||||||
stringMap(appConfig, envConfig, "customEnvVars"),
|
stringMap(appConfig, envConfig, "customEnvVars"),
|
||||||
@@ -88,7 +88,7 @@ public final class ConfigMerger {
|
|||||||
/** Global defaults extracted from application.yml @Value fields */
|
/** Global defaults extracted from application.yml @Value fields */
|
||||||
public record GlobalRuntimeDefaults(
|
public record GlobalRuntimeDefaults(
|
||||||
int memoryLimitMb,
|
int memoryLimitMb,
|
||||||
int cpuShares,
|
int cpuRequest,
|
||||||
String routingMode,
|
String routingMode,
|
||||||
String routingDomain,
|
String routingDomain,
|
||||||
String serverUrl
|
String serverUrl
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import java.util.Map;
|
|||||||
public record ResolvedContainerConfig(
|
public record ResolvedContainerConfig(
|
||||||
int memoryLimitMb,
|
int memoryLimitMb,
|
||||||
Integer memoryReserveMb,
|
Integer memoryReserveMb,
|
||||||
int cpuShares,
|
int cpuRequest,
|
||||||
Double cpuLimit,
|
Integer cpuLimit,
|
||||||
int appPort,
|
int appPort,
|
||||||
List<Integer> exposedPorts,
|
List<Integer> exposedPorts,
|
||||||
Map<String, String> customEnvVars,
|
Map<String, String> customEnvVars,
|
||||||
@@ -26,4 +26,14 @@ public record ResolvedContainerConfig(
|
|||||||
public Long memoryReserveBytes() {
|
public Long memoryReserveBytes() {
|
||||||
return memoryReserveMb != null ? (long) memoryReserveMb * 1024 * 1024 : null;
|
return memoryReserveMb != null ? (long) memoryReserveMb * 1024 * 1024 : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Convert cpuRequest (millicores) to Docker CPU shares (proportional to 1024 = 1 core). */
|
||||||
|
public int dockerCpuShares() {
|
||||||
|
return cpuRequest * 1024 / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert cpuLimit (millicores) to Docker CPU quota (microseconds per 100ms period). */
|
||||||
|
public Long dockerCpuQuota() {
|
||||||
|
return cpuLimit != null ? (long) cpuLimit * 100 : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ function DefaultResourcesSection({ environment, onSave, saving }: {
|
|||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [memoryLimit, setMemoryLimit] = useState('');
|
const [memoryLimit, setMemoryLimit] = useState('');
|
||||||
const [memoryReserve, setMemoryReserve] = useState('');
|
const [memoryReserve, setMemoryReserve] = useState('');
|
||||||
const [cpuShares, setCpuShares] = useState('');
|
const [cpuRequest, setCpuRequest] = useState('');
|
||||||
const [cpuLimit, setCpuLimit] = useState('');
|
const [cpuLimit, setCpuLimit] = useState('');
|
||||||
const [routingMode, setRoutingMode] = useState(String(defaults.routingMode ?? 'path'));
|
const [routingMode, setRoutingMode] = useState(String(defaults.routingMode ?? 'path'));
|
||||||
const [routingDomain, setRoutingDomain] = useState(String(defaults.routingDomain ?? ''));
|
const [routingDomain, setRoutingDomain] = useState(String(defaults.routingDomain ?? ''));
|
||||||
@@ -341,7 +341,7 @@ function DefaultResourcesSection({ environment, onSave, saving }: {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMemoryLimit(String(defaults.memoryLimitMb ?? ''));
|
setMemoryLimit(String(defaults.memoryLimitMb ?? ''));
|
||||||
setMemoryReserve(String(defaults.memoryReserveMb ?? ''));
|
setMemoryReserve(String(defaults.memoryReserveMb ?? ''));
|
||||||
setCpuShares(String(defaults.cpuShares ?? ''));
|
setCpuRequest(String(defaults.cpuRequest ?? ''));
|
||||||
setCpuLimit(String(defaults.cpuLimit ?? ''));
|
setCpuLimit(String(defaults.cpuLimit ?? ''));
|
||||||
setRoutingMode(String(environment.defaultContainerConfig.routingMode ?? 'path'));
|
setRoutingMode(String(environment.defaultContainerConfig.routingMode ?? 'path'));
|
||||||
setRoutingDomain(String(environment.defaultContainerConfig.routingDomain ?? ''));
|
setRoutingDomain(String(environment.defaultContainerConfig.routingDomain ?? ''));
|
||||||
@@ -353,7 +353,7 @@ function DefaultResourcesSection({ environment, onSave, saving }: {
|
|||||||
function handleCancel() {
|
function handleCancel() {
|
||||||
setMemoryLimit(String(defaults.memoryLimitMb ?? ''));
|
setMemoryLimit(String(defaults.memoryLimitMb ?? ''));
|
||||||
setMemoryReserve(String(defaults.memoryReserveMb ?? ''));
|
setMemoryReserve(String(defaults.memoryReserveMb ?? ''));
|
||||||
setCpuShares(String(defaults.cpuShares ?? ''));
|
setCpuRequest(String(defaults.cpuRequest ?? ''));
|
||||||
setCpuLimit(String(defaults.cpuLimit ?? ''));
|
setCpuLimit(String(defaults.cpuLimit ?? ''));
|
||||||
setRoutingMode(String(defaults.routingMode ?? 'path'));
|
setRoutingMode(String(defaults.routingMode ?? 'path'));
|
||||||
setRoutingDomain(String(defaults.routingDomain ?? ''));
|
setRoutingDomain(String(defaults.routingDomain ?? ''));
|
||||||
@@ -366,8 +366,8 @@ function DefaultResourcesSection({ environment, onSave, saving }: {
|
|||||||
await onSave({
|
await onSave({
|
||||||
memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
|
memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
|
||||||
memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
|
memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
|
||||||
cpuShares: cpuShares ? parseInt(cpuShares) : null,
|
cpuRequest: cpuRequest ? parseInt(cpuRequest) : null,
|
||||||
cpuLimit: cpuLimit ? parseFloat(cpuLimit) : null,
|
cpuLimit: cpuLimit ? parseInt(cpuLimit) : null,
|
||||||
routingMode,
|
routingMode,
|
||||||
routingDomain: routingDomain || null,
|
routingDomain: routingDomain || null,
|
||||||
serverUrl: serverUrl || null,
|
serverUrl: serverUrl || null,
|
||||||
@@ -393,15 +393,15 @@ function DefaultResourcesSection({ environment, onSave, saving }: {
|
|||||||
? <Input value={memoryReserve} onChange={(e) => setMemoryReserve(e.target.value)} placeholder="e.g. 256" style={{ width: 100 }} />
|
? <Input value={memoryReserve} onChange={(e) => setMemoryReserve(e.target.value)} placeholder="e.g. 256" style={{ width: 100 }} />
|
||||||
: <span className={styles.metaValue}>{defaults.memoryReserveMb ? `${defaults.memoryReserveMb} MB` : '—'}</span>}
|
: <span className={styles.metaValue}>{defaults.memoryReserveMb ? `${defaults.memoryReserveMb} MB` : '—'}</span>}
|
||||||
|
|
||||||
<span className={styles.metaLabel}>CPU Shares</span>
|
<span className={styles.metaLabel}>CPU Request</span>
|
||||||
{editing
|
{editing
|
||||||
? <Input value={cpuShares} onChange={(e) => setCpuShares(e.target.value)} placeholder="e.g. 512" style={{ width: 100 }} />
|
? <Input value={cpuRequest} onChange={(e) => setCpuRequest(e.target.value)} placeholder="e.g. 500" style={{ width: 100 }} />
|
||||||
: <span className={styles.metaValue}>{String(defaults.cpuShares ?? '—')}</span>}
|
: <span className={styles.metaValue}>{defaults.cpuRequest ? `${defaults.cpuRequest}m` : '—'}</span>}
|
||||||
|
|
||||||
<span className={styles.metaLabel}>CPU Limit</span>
|
<span className={styles.metaLabel}>CPU Limit</span>
|
||||||
{editing
|
{editing
|
||||||
? <Input value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1.0" style={{ width: 100 }} />
|
? <Input value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1000" style={{ width: 100 }} />
|
||||||
: <span className={styles.metaValue}>{defaults.cpuLimit ? `${defaults.cpuLimit} cores` : '—'}</span>}
|
: <span className={styles.metaValue}>{defaults.cpuLimit ? `${defaults.cpuLimit}m` : '—'}</span>}
|
||||||
|
|
||||||
<span className={styles.metaLabel}>Routing Mode</span>
|
<span className={styles.metaLabel}>Routing Mode</span>
|
||||||
{editing
|
{editing
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
|||||||
const defaults = env?.defaultContainerConfig ?? {};
|
const defaults = env?.defaultContainerConfig ?? {};
|
||||||
const [memoryLimit, setMemoryLimit] = useState(String(defaults.memoryLimitMb ?? 512));
|
const [memoryLimit, setMemoryLimit] = useState(String(defaults.memoryLimitMb ?? 512));
|
||||||
const [memoryReserve, setMemoryReserve] = useState(String(defaults.memoryReserveMb ?? ''));
|
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 [cpuLimit, setCpuLimit] = useState(String(defaults.cpuLimit ?? ''));
|
||||||
const [ports, setPorts] = useState<number[]>(Array.isArray(defaults.exposedPorts) ? defaults.exposedPorts as number[] : []);
|
const [ports, setPorts] = useState<number[]>(Array.isArray(defaults.exposedPorts) ? defaults.exposedPorts as number[] : []);
|
||||||
const [newPort, setNewPort] = useState('');
|
const [newPort, setNewPort] = useState('');
|
||||||
@@ -182,7 +182,7 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
|||||||
const [stripPrefix, setStripPrefix] = useState(true);
|
const [stripPrefix, setStripPrefix] = useState(true);
|
||||||
const [sslOffloading, setSslOffloading] = 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 [busy, setBusy] = useState(false);
|
||||||
const [step, setStep] = useState('');
|
const [step, setStep] = useState('');
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
|||||||
const d = environments.find((e) => e.id === envId)?.defaultContainerConfig ?? {};
|
const d = environments.find((e) => e.id === envId)?.defaultContainerConfig ?? {};
|
||||||
setMemoryLimit(String(d.memoryLimitMb ?? 512));
|
setMemoryLimit(String(d.memoryLimitMb ?? 512));
|
||||||
setMemoryReserve(String(d.memoryReserveMb ?? ''));
|
setMemoryReserve(String(d.memoryReserveMb ?? ''));
|
||||||
setCpuShares(String(d.cpuShares ?? 512));
|
setCpuRequest(String(d.cpuRequest ?? 500));
|
||||||
setCpuLimit(String(d.cpuLimit ?? ''));
|
setCpuLimit(String(d.cpuLimit ?? ''));
|
||||||
setPorts(Array.isArray(d.exposedPorts) ? d.exposedPorts as number[] : []);
|
setPorts(Array.isArray(d.exposedPorts) ? d.exposedPorts as number[] : []);
|
||||||
}, [envId, environments]);
|
}, [envId, environments]);
|
||||||
@@ -224,8 +224,8 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
|||||||
const containerConfig: Record<string, unknown> = {
|
const containerConfig: Record<string, unknown> = {
|
||||||
memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
|
memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
|
||||||
memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
|
memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
|
||||||
cpuShares: cpuShares ? parseInt(cpuShares) : null,
|
cpuRequest: cpuRequest ? parseInt(cpuRequest) : null,
|
||||||
cpuLimit: cpuLimit ? parseFloat(cpuLimit) : null,
|
cpuLimit: cpuLimit ? parseInt(cpuLimit) : null,
|
||||||
exposedPorts: ports,
|
exposedPorts: ports,
|
||||||
customEnvVars: Object.fromEntries(envVars.filter((v) => v.key.trim()).map((v) => [v.key, v.value])),
|
customEnvVars: Object.fromEntries(envVars.filter((v) => v.key.trim()).map((v) => [v.key, v.value])),
|
||||||
appPort: appPort ? parseInt(appPort) : 8080,
|
appPort: appPort ? parseInt(appPort) : 8080,
|
||||||
@@ -322,9 +322,9 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
|||||||
|
|
||||||
{/* Config Tabs */}
|
{/* Config Tabs */}
|
||||||
<div className={styles.subTabs}>
|
<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 === '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 === 'resources' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('resources')}>Resources</button>
|
||||||
|
<button className={`${styles.subTab} ${configTab === 'variables' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('variables')}>Variables</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{configTab === 'variables' && (
|
{configTab === 'variables' && (
|
||||||
@@ -421,13 +421,13 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
|
|||||||
{!isProd && <span className={styles.configHint}>Available in production environments only</span>}
|
{!isProd && <span className={styles.configHint}>Available in production environments only</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className={styles.configLabel}>CPU Shares</span>
|
<span className={styles.configLabel}>CPU Request</span>
|
||||||
<Input disabled={busy} value={cpuShares} onChange={(e) => setCpuShares(e.target.value)} style={{ width: 80 }} />
|
<Input disabled={busy} value={cpuRequest} onChange={(e) => setCpuRequest(e.target.value)} style={{ width: 80 }} />
|
||||||
|
|
||||||
<span className={styles.configLabel}>CPU Limit</span>
|
<span className={styles.configLabel}>CPU Limit</span>
|
||||||
<div className={styles.configInline}>
|
<div className={styles.configInline}>
|
||||||
<Input disabled={busy} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1.0" style={{ width: 80 }} />
|
<Input disabled={busy} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1000" style={{ width: 80 }} />
|
||||||
<span className={styles.cellMeta}>cores</span>
|
<span className={styles.cellMeta}>millicores</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className={styles.configLabel}>Exposed Ports</span>
|
<span className={styles.configLabel}>Exposed Ports</span>
|
||||||
@@ -488,7 +488,7 @@ function AppDetailView({ appId: appSlug, environments, selectedEnv }: { appId: s
|
|||||||
const stopDeployment = useStopDeployment();
|
const stopDeployment = useStopDeployment();
|
||||||
const deleteApp = useDeleteApp();
|
const deleteApp = useDeleteApp();
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
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 [deleteConfirm, setDeleteConfirm] = useState(false);
|
||||||
|
|
||||||
const envMap = useMemo(() => new Map(environments.map((e) => [e.id, e])), [environments]);
|
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>
|
||||||
|
|
||||||
<div className={styles.subTabs}>
|
<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 === 'config' ? styles.subTabActive : ''}`} onClick={() => setSubTab('config')}>Configuration</button>
|
||||||
|
<button className={`${styles.subTab} ${subTab === 'overview' ? styles.subTabActive : ''}`} onClick={() => setSubTab('overview')}>Overview</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{subTab === 'overview' && (
|
{subTab === 'overview' && (
|
||||||
@@ -702,7 +702,7 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
|
|||||||
const { data: processorToRoute = {} } = useProcessorRouteMapping(app.slug);
|
const { data: processorToRoute = {} } = useProcessorRouteMapping(app.slug);
|
||||||
const isProd = environment?.production ?? false;
|
const isProd = environment?.production ?? false;
|
||||||
const [editing, setEditing] = useState(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(() => {
|
const appRoutes: CatalogRoute[] = useMemo(() => {
|
||||||
if (!catalog) return [];
|
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 merged = useMemo(() => ({ ...defaults, ...app.containerConfig }), [defaults, app.containerConfig]);
|
||||||
const [memoryLimit, setMemoryLimit] = useState('512');
|
const [memoryLimit, setMemoryLimit] = useState('512');
|
||||||
const [memoryReserve, setMemoryReserve] = useState('');
|
const [memoryReserve, setMemoryReserve] = useState('');
|
||||||
const [cpuShares, setCpuShares] = useState('512');
|
const [cpuRequest, setCpuRequest] = useState('500');
|
||||||
const [cpuLimit, setCpuLimit] = useState('');
|
const [cpuLimit, setCpuLimit] = useState('');
|
||||||
const [ports, setPorts] = useState<number[]>([]);
|
const [ports, setPorts] = useState<number[]>([]);
|
||||||
const [newPort, setNewPort] = useState('');
|
const [newPort, setNewPort] = useState('');
|
||||||
@@ -758,7 +758,7 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
|
|||||||
}
|
}
|
||||||
setMemoryLimit(String(merged.memoryLimitMb ?? 512));
|
setMemoryLimit(String(merged.memoryLimitMb ?? 512));
|
||||||
setMemoryReserve(String(merged.memoryReserveMb ?? ''));
|
setMemoryReserve(String(merged.memoryReserveMb ?? ''));
|
||||||
setCpuShares(String(merged.cpuShares ?? 512));
|
setCpuRequest(String(merged.cpuRequest ?? 500));
|
||||||
setCpuLimit(String(merged.cpuLimit ?? ''));
|
setCpuLimit(String(merged.cpuLimit ?? ''));
|
||||||
setPorts(Array.isArray(merged.exposedPorts) ? merged.exposedPorts as number[] : []);
|
setPorts(Array.isArray(merged.exposedPorts) ? merged.exposedPorts as number[] : []);
|
||||||
const vars = merged.customEnvVars as Record<string, string> | undefined;
|
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> = {
|
const containerConfig: Record<string, unknown> = {
|
||||||
memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
|
memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
|
||||||
memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
|
memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
|
||||||
cpuShares: cpuShares ? parseInt(cpuShares) : null,
|
cpuRequest: cpuRequest ? parseInt(cpuRequest) : null,
|
||||||
cpuLimit: cpuLimit ? parseFloat(cpuLimit) : null,
|
cpuLimit: cpuLimit ? parseInt(cpuLimit) : null,
|
||||||
exposedPorts: ports,
|
exposedPorts: ports,
|
||||||
customEnvVars: Object.fromEntries(envVars.filter((v) => v.key.trim()).map((v) => [v.key, v.value])),
|
customEnvVars: Object.fromEntries(envVars.filter((v) => v.key.trim()).map((v) => [v.key, v.value])),
|
||||||
appPort: appPort ? parseInt(appPort) : 8080,
|
appPort: appPort ? parseInt(appPort) : 8080,
|
||||||
@@ -910,11 +910,11 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.subTabs}>
|
<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 === '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 === '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 === 'recording' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('recording')}>Route Recording</button>
|
||||||
<button className={`${styles.subTab} ${configTab === 'resources' ? styles.subTabActive : ''}`} onClick={() => setConfigTab('resources')}>Resources</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{configTab === 'variables' && (
|
{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>}
|
{!isProd && <span className={styles.configHint}>Available in production environments only</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className={styles.configLabel}>CPU Shares</span>
|
<span className={styles.configLabel}>CPU Request</span>
|
||||||
<Input disabled={!editing} value={cpuShares} onChange={(e) => setCpuShares(e.target.value)} style={{ width: 80 }} />
|
<Input disabled={!editing} value={cpuRequest} onChange={(e) => setCpuRequest(e.target.value)} style={{ width: 80 }} />
|
||||||
|
|
||||||
<span className={styles.configLabel}>CPU Limit</span>
|
<span className={styles.configLabel}>CPU Limit</span>
|
||||||
<div className={styles.configInline}>
|
<div className={styles.configInline}>
|
||||||
<Input disabled={!editing} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1.0" style={{ width: 80 }} />
|
<Input disabled={!editing} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1000" style={{ width: 80 }} />
|
||||||
<span className={styles.cellMeta}>cores</span>
|
<span className={styles.cellMeta}>millicores</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className={styles.configLabel}>Exposed Ports</span>
|
<span className={styles.configLabel}>Exposed Ports</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user