Files
cameleer-server/ui/src/pages/AppsTab/AppDeploymentPage/ConfigTabs/ResourcesTab.tsx
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

210 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react';
import { Select, Input, Toggle } from '@cameleer/design-system';
import type { ResourcesFormState } from '../hooks/useDeploymentPageState';
import styles from '../AppDeploymentPage.module.css';
interface Props {
value: ResourcesFormState;
onChange: (next: ResourcesFormState) => void;
disabled?: boolean;
isProd?: boolean;
}
export function ResourcesTab({ value, onChange, disabled, isProd = false }: Props) {
const [newNetwork, setNewNetwork] = useState('');
const update = <K extends keyof ResourcesFormState>(key: K, v: ResourcesFormState[K]) =>
onChange({ ...value, [key]: v });
function addNetwork() {
const v = newNetwork.trim();
if (v && !value.extraNetworks.includes(v)) {
onChange({ ...value, extraNetworks: [...value.extraNetworks, v] });
setNewNetwork('');
}
}
function removeNetwork(network: string) {
if (!disabled) update('extraNetworks', value.extraNetworks.filter((x) => x !== network));
}
return (
<div className={styles.configGrid}>
<span className={styles.configLabel}>Runtime Type</span>
<Select
disabled={disabled}
value={value.runtimeType}
onChange={(e) => update('runtimeType', e.target.value)}
options={[
{ value: 'auto', label: 'Auto (detect from JAR)' },
{ value: 'spring-boot', label: 'Spring Boot' },
{ value: 'quarkus', label: 'Quarkus' },
{ value: 'plain-java', label: 'Plain Java' },
{ value: 'native', label: 'Native' },
]}
/>
<span className={styles.configLabel}>Custom Arguments</span>
<div>
<Input
disabled={disabled}
value={value.customArgs}
onChange={(e) => update('customArgs', e.target.value)}
placeholder="-Xmx256m -Dfoo=bar"
className={styles.inputLg}
/>
<span className={styles.configHint}>
{value.runtimeType === 'native'
? 'Arguments passed to the native binary'
: 'Additional JVM arguments appended to the start command'}
</span>
</div>
<span className={styles.configLabel}>Memory Limit</span>
<div className={styles.configInline}>
<Input
disabled={disabled}
value={value.memoryLimit}
onChange={(e) => update('memoryLimit', e.target.value)}
className={styles.inputLg}
placeholder="e.g. 512"
/>
<span className={styles.cellMeta}>MB</span>
</div>
<span className={styles.configLabel}>Memory Reserve</span>
<div>
<div className={styles.configInline}>
<Input
disabled={!isProd || disabled}
value={value.memoryReserve}
onChange={(e) => update('memoryReserve', e.target.value)}
placeholder="e.g. 256"
className={styles.inputLg}
/>
<span className={styles.cellMeta}>MB</span>
</div>
{!isProd && (
<span className={styles.configHint}>Available in production environments only</span>
)}
</div>
<span className={styles.configLabel}>CPU Request</span>
<Input
disabled={disabled}
value={value.cpuRequest}
onChange={(e) => update('cpuRequest', e.target.value)}
className={styles.inputLg}
placeholder="e.g. 500 millicores"
/>
<span className={styles.configLabel}>CPU Limit</span>
<div className={styles.configInline}>
<Input
disabled={disabled}
value={value.cpuLimit}
onChange={(e) => update('cpuLimit', e.target.value)}
placeholder="e.g. 1000"
className={styles.inputLg}
/>
<span className={styles.cellMeta}>millicores</span>
</div>
<span className={styles.configLabel}>App Port</span>
<Input
disabled={disabled}
value={value.appPort}
onChange={(e) => update('appPort', e.target.value)}
className={styles.inputLg}
placeholder="e.g. 8080"
/>
<span className={styles.configLabel}>Replicas</span>
<Input
disabled={disabled}
value={value.replicas}
onChange={(e) => update('replicas', e.target.value)}
className={styles.inputSm}
type="number"
placeholder="1"
/>
<span className={styles.configLabel}>Deploy Strategy</span>
<div>
<Select
disabled={disabled}
value={value.deployStrategy}
onChange={(e) => update('deployStrategy', e.target.value)}
options={[
{ value: 'blue-green', label: 'Blue/Green' },
{ value: 'rolling', label: 'Rolling' },
]}
/>
<span className={styles.configHint}>
{value.deployStrategy === 'rolling'
? 'Replace one replica at a time; peak = replicas + 1. Partial failure leaves remaining old replicas serving.'
: 'Start all new replicas, swap once all are healthy; peak = 2 × replicas. Partial failure preserves the previous deployment.'}
</span>
</div>
<span className={styles.configLabel}>Strip Path Prefix</span>
<div className={styles.configInline}>
<Toggle
checked={value.stripPrefix}
onChange={() => !disabled && update('stripPrefix', !value.stripPrefix)}
disabled={disabled}
/>
<span className={value.stripPrefix ? styles.toggleEnabled : styles.toggleDisabled}>
{value.stripPrefix ? 'Enabled' : 'Disabled'}
</span>
</div>
<span className={styles.configLabel}>SSL Offloading</span>
<div className={styles.configInline}>
<Toggle
checked={value.sslOffloading}
onChange={() => !disabled && update('sslOffloading', !value.sslOffloading)}
disabled={disabled}
/>
<span className={value.sslOffloading ? styles.toggleEnabled : styles.toggleDisabled}>
{value.sslOffloading ? 'Enabled' : 'Disabled'}
</span>
</div>
<span className={styles.configLabel}>Extra Networks</span>
<div>
<div className={styles.portPills}>
{value.extraNetworks.map((n) => (
<span key={n} className={styles.portPill}>
{n}
<button
className={styles.portPillDelete}
disabled={disabled}
onClick={() => removeNetwork(n)}
>
&times;
</button>
</span>
))}
<input
className={styles.portAddInput}
disabled={disabled}
placeholder="+ network"
value={newNetwork}
onChange={(e) => setNewNetwork(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
addNetwork();
}
}}
/>
</div>
<span className={styles.configHint}>
Additional Docker networks to join (e.g., monitoring, prometheus)
</span>
</div>
</div>
);
}