From 3f9fd44ea5e3a1aa5b28a05667a0c902edd3d66b Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:28:11 +0200 Subject: [PATCH] fix: wrap app config in section cards, replace manual table with DataTable - Add sectionStyles and tableStyles imports to AppsTab.tsx - Wrap CreateAppView identity section and each config tab (Monitoring, Resources, Variables) in sectionStyles.section cards - Wrap ConfigSubTab config tabs (Monitoring, Resources, Variables, Traces & Taps, Route Recording) in sectionStyles.section cards - Replace manual in OverviewSubTab with DataTable inside a tableStyles.tableSection card wrapper; pre-compute enriched row data via useMemo; handle muted non-selected-env rows via inline opacity - Remove unused .table, .table th, .table td, .table tr:hover td, and .mutedRow CSS rules from AppsTab.module.css Co-Authored-By: Claude Sonnet 4.6 --- ui/src/pages/AppsTab/AppsTab.module.css | 32 -- ui/src/pages/AppsTab/AppsTab.tsx | 399 +++++++++++++----------- 2 files changed, 213 insertions(+), 218 deletions(-) diff --git a/ui/src/pages/AppsTab/AppsTab.module.css b/ui/src/pages/AppsTab/AppsTab.module.css index 8e9aa076..3f943187 100644 --- a/ui/src/pages/AppsTab/AppsTab.module.css +++ b/ui/src/pages/AppsTab/AppsTab.module.css @@ -119,38 +119,6 @@ gap: 8px; } -/* Table */ -.table { - width: 100%; - border-collapse: collapse; - margin-bottom: 16px; -} - -.table th { - text-align: left; - padding: 8px 12px; - font-size: 11px; - font-weight: 600; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; - border-bottom: 1px solid var(--border-subtle); -} - -.table td { - padding: 10px 12px; - border-bottom: 1px solid var(--border-subtle); - font-size: 13px; - vertical-align: middle; -} - -.table tr:hover td { - background: var(--bg-hover); -} - -.mutedRow td { - opacity: 0.45; -} .mutedMono { font-family: var(--font-mono); diff --git a/ui/src/pages/AppsTab/AppsTab.tsx b/ui/src/pages/AppsTab/AppsTab.tsx index f89f73f2..9140af35 100644 --- a/ui/src/pages/AppsTab/AppsTab.tsx +++ b/ui/src/pages/AppsTab/AppsTab.tsx @@ -40,6 +40,8 @@ import { DeploymentProgress } from '../../components/DeploymentProgress'; import { timeAgo } from '../../utils/format-utils'; import { applyTracedProcessorUpdate, applyRouteRecordingUpdate } from '../../utils/config-draft-utils'; import styles from './AppsTab.module.css'; +import sectionStyles from '../../styles/section-card.module.css'; +import tableStyles from '../../styles/table-section.module.css'; function formatBytes(bytes: number): string { if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`; @@ -290,35 +292,37 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen {step &&
{step}
} {/* Identity Section */} - Identity & Artifact -
- Application Name - setName(e.target.value)} placeholder="e.g. Payment Gateway" disabled={busy} /> +
+ Identity & Artifact +
+ Application Name + setName(e.target.value)} placeholder="e.g. Payment Gateway" disabled={busy} /> - External URL - /{env?.slug ?? '...'}/{slug || '...'}/ + External URL + /{env?.slug ?? '...'}/{slug || '...'}/ - Environment - setEnvId(e.target.value)} disabled={busy} + options={environments.filter((e) => e.enabled).map((e) => ({ value: e.id, label: `${e.displayName} (${e.slug})` }))} /> - Application JAR -
- setFile(e.target.files?.[0] ?? null)} /> - - {file && {file.name} ({formatBytes(file.size)})} -
+ Application JAR +
+ setFile(e.target.files?.[0] ?? null)} /> + + {file && {file.name} ({formatBytes(file.size)})} +
- Deploy -
- setDeploy(!deploy)} disabled={busy} /> - - {deploy ? 'Deploy immediately after creation' : 'Create only (deploy later)'} - + Deploy +
+ setDeploy(!deploy)} disabled={busy} /> + + {deploy ? 'Deploy immediately after creation' : 'Create only (deploy later)'} + +
@@ -334,7 +338,8 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen /> {configTab === 'variables' && ( - <> +
+ Variables {envVars.map((v, i) => (
{ @@ -348,68 +353,71 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
))} - +
)} {configTab === 'monitoring' && ( -
- Engine Level - setEngineLevel(e.target.value)} + options={[{ value: 'NONE', label: 'NONE' }, { value: 'MINIMAL', label: 'MINIMAL' }, { value: 'REGULAR', label: 'REGULAR' }, { value: 'COMPLETE', label: 'COMPLETE' }]} /> - Payload Capture - setPayloadCapture(e.target.value)} + options={[{ value: 'NONE', label: 'NONE' }, { value: 'INPUT', label: 'INPUT' }, { value: 'OUTPUT', label: 'OUTPUT' }, { value: 'BOTH', label: 'BOTH' }]} /> - Max Payload Size -
- setPayloadSize(e.target.value)} className={styles.inputMd} /> - setPayloadSize(e.target.value)} className={styles.inputMd} /> + setAppLogLevel(e.target.value)} - options={['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'].map((l) => ({ value: l, label: l }))} /> + App Log Level + setAgentLogLevel(e.target.value)} - options={['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'].map((l) => ({ value: l, label: l }))} /> + Agent Log Level + setMetricsInterval(e.target.value)} className={styles.inputXs} /> - s -
+ Metrics +
+ !busy && setMetricsEnabled(!metricsEnabled)} disabled={busy} /> + {metricsEnabled ? 'Enabled' : 'Disabled'} + Interval + setMetricsInterval(e.target.value)} className={styles.inputXs} /> + s +
- Sampling Rate - setSamplingRate(e.target.value)} className={styles.inputLg} /> + Sampling Rate + setSamplingRate(e.target.value)} className={styles.inputLg} /> - Compress Success -
- !busy && setCompressSuccess(!compressSuccess)} disabled={busy} /> - {compressSuccess ? 'Enabled' : 'Disabled'} -
+ Compress Success +
+ !busy && setCompressSuccess(!compressSuccess)} disabled={busy} /> + {compressSuccess ? 'Enabled' : 'Disabled'} +
- Replay -
- !busy && setReplayEnabled(!replayEnabled)} disabled={busy} /> - {replayEnabled ? 'Enabled' : 'Disabled'} -
+ Replay +
+ !busy && setReplayEnabled(!replayEnabled)} disabled={busy} /> + {replayEnabled ? 'Enabled' : 'Disabled'} +
- Route Control -
- !busy && setRouteControlEnabled(!routeControlEnabled)} disabled={busy} /> - {routeControlEnabled ? 'Enabled' : 'Disabled'} + Route Control +
+ !busy && setRouteControlEnabled(!routeControlEnabled)} disabled={busy} /> + {routeControlEnabled ? 'Enabled' : 'Disabled'} +
)} {configTab === 'resources' && ( - <> +
Container Resources
Memory Limit @@ -472,7 +480,7 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen {sslOffloading ? 'Enabled' : 'Disabled'}
- +
)}
); @@ -615,70 +623,84 @@ function OverviewSubTab({ app, deployments, versions, environments, envMap, sele [environments, selectedEnv], ); + type DeploymentRow = Deployment & { + dEnv: Environment | undefined; + version: AppVersion | undefined; + isSelectedEnv: boolean; + canAct: boolean; + canStart: boolean; + configChanged: boolean; + url: string; + }; + + const deploymentRows: DeploymentRow[] = useMemo(() => deployments.map((d) => { + const dEnv = envMap.get(d.environmentId); + const version = versions.find((v) => v.id === d.appVersionId); + const isSelectedEnv = !selectedEnvId || d.environmentId === selectedEnvId; + const canAct = isSelectedEnv && (d.status === 'RUNNING' || d.status === 'STARTING'); + const canStart = isSelectedEnv && d.status === 'STOPPED'; + const configChanged = canAct && !!d.deployedAt && new Date(app.updatedAt) > new Date(d.deployedAt); + const url = dEnv ? `/${dEnv.slug}/${app.slug}/` : ''; + return { ...d, dEnv, version, isSelectedEnv, canAct, canStart, configChanged, url }; + }), [deployments, envMap, versions, selectedEnvId, app]); + + const deploymentColumns: Column[] = useMemo(() => [ + { key: 'environmentId', header: 'Environment', render: (_v, row) => ( + + + + )}, + { key: 'appVersionId', header: 'Version', render: (_v, row) => ( + + + + )}, + { key: 'status', header: 'Status', render: (_v, row) => ( +
+ + +
+ )}, + { key: 'replicaStates', header: 'Replicas', render: (_v, row) => ( + + {row.replicaStates && row.replicaStates.length > 0 + ? {row.replicaStates.filter((r) => r.status === 'RUNNING').length}/{row.replicaStates.length} + : <>{'—'}} + + )}, + { key: 'url' as any, header: 'URL', render: (_v, row) => ( + + {row.status === 'RUNNING' + ? {row.url} + : {row.url}} + + )}, + { key: 'deployedAt', header: 'Deployed', render: (_v, row) => ( + + {row.deployedAt ? timeAgo(row.deployedAt) : '—'} + + )}, + { key: 'actions' as any, header: '', render: (_v, row) => ( +
+ {row.configChanged && } + {row.canAct && } + {row.canStart && } + {!row.isSelectedEnv && switch env to manage} +
+ )}, + ], [onDeploy, onStop]); + return ( <> - Deployments - {deployments.length === 0 &&

No deployments yet.

} - {deployments.length > 0 && ( -
- - - - - - - - - - - - - {deployments.map((d) => { - const dEnv = envMap.get(d.environmentId); - const version = versions.find((v) => v.id === d.appVersionId); - const isSelectedEnv = !selectedEnvId || d.environmentId === selectedEnvId; - const canAct = isSelectedEnv && (d.status === 'RUNNING' || d.status === 'STARTING'); - const canStart = isSelectedEnv && d.status === 'STOPPED'; - const configChanged = canAct && d.deployedAt && new Date(app.updatedAt) > new Date(d.deployedAt); - const url = dEnv ? `/${dEnv.slug}/${app.slug}/` : ''; - - return ( - - - - - - - - - - ); - })} - -
EnvironmentVersionStatusReplicasURLDeployedActions
- - - - - - {d.replicaStates && d.replicaStates.length > 0 ? ( - - {d.replicaStates.filter((r) => r.status === 'RUNNING').length}/{d.replicaStates.length} - - ) : '—'} - - {d.status === 'RUNNING' ? ( - {url} - ) : ( - {url} - )} - {d.deployedAt ? timeAgo(d.deployedAt) : '—'} - {configChanged && } - {canAct && } - {canStart && } - {!isSelectedEnv && switch env to manage} -
- )} +
+
+ Deployments +
+ {deploymentRows.length === 0 + ?

No deployments yet.

+ : columns={deploymentColumns} data={deploymentRows} flush /> + } +
{deployments.filter((d) => d.deployStage).map((d) => (
{d.containerName} @@ -949,7 +971,8 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen /> {configTab === 'variables' && ( - <> +
+ Variables {envVars.map((v, i) => (
{ @@ -966,87 +989,91 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen )} {envVars.length === 0 && !editing &&

No environment variables configured.

} - +
)} {configTab === 'monitoring' && ( -
- Engine Level - setEngineLevel(e.target.value)} + options={[{ value: 'NONE', label: 'NONE' }, { value: 'MINIMAL', label: 'MINIMAL' }, { value: 'REGULAR', label: 'REGULAR' }, { value: 'COMPLETE', label: 'COMPLETE' }]} /> - Payload Capture - setPayloadCapture(e.target.value)} + options={[{ value: 'NONE', label: 'NONE' }, { value: 'INPUT', label: 'INPUT' }, { value: 'OUTPUT', label: 'OUTPUT' }, { value: 'BOTH', label: 'BOTH' }]} /> - Max Payload Size -
- setPayloadSize(e.target.value)} className={styles.inputMd} /> - setPayloadSize(e.target.value)} className={styles.inputMd} /> + setAppLogLevel(e.target.value)} - options={['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'].map((l) => ({ value: l, label: l }))} /> + App Log Level + setAgentLogLevel(e.target.value)} - options={['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'].map((l) => ({ value: l, label: l }))} /> + Agent Log Level + setMetricsInterval(e.target.value)} className={styles.inputXs} /> - s -
+ Metrics +
+ editing && setMetricsEnabled(!metricsEnabled)} disabled={!editing} /> + {metricsEnabled ? 'Enabled' : 'Disabled'} + Interval + setMetricsInterval(e.target.value)} className={styles.inputXs} /> + s +
- Sampling Rate - setSamplingRate(e.target.value)} className={styles.inputLg} /> + Sampling Rate + setSamplingRate(e.target.value)} className={styles.inputLg} /> - Compress Success -
- editing && setCompressSuccess(!compressSuccess)} disabled={!editing} /> - {compressSuccess ? 'Enabled' : 'Disabled'} -
+ Compress Success +
+ editing && setCompressSuccess(!compressSuccess)} disabled={!editing} /> + {compressSuccess ? 'Enabled' : 'Disabled'} +
- Replay -
- editing && setReplayEnabled(!replayEnabled)} disabled={!editing} /> - {replayEnabled ? 'Enabled' : 'Disabled'} -
+ Replay +
+ editing && setReplayEnabled(!replayEnabled)} disabled={!editing} /> + {replayEnabled ? 'Enabled' : 'Disabled'} +
- Route Control -
- editing && setRouteControlEnabled(!routeControlEnabled)} disabled={!editing} /> - {routeControlEnabled ? 'Enabled' : 'Disabled'} + Route Control +
+ editing && setRouteControlEnabled(!routeControlEnabled)} disabled={!editing} /> + {routeControlEnabled ? 'Enabled' : 'Disabled'} +
)} {configTab === 'traces' && ( - <> +
+ Traces & Taps {tracedCount} traced · {tapCount} taps {tracedTapRows.length > 0 ? columns={tracedTapColumns} data={tracedTapRows} pageSize={20} flush /> :

No processor traces or taps configured.

} - +
)} {configTab === 'recording' && ( - <> +
+ Route Recording {recordingCount} of {routeRecordingRows.length} routes recording {routeRecordingRows.length > 0 ? columns={routeRecordingColumns} data={routeRecordingRows} pageSize={20} flush /> :

No routes found for this application.

} - +
)} {configTab === 'resources' && ( - <> - {/* Container Resources */} +
Container Resources
Memory Limit @@ -1109,7 +1136,7 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen {sslOffloading ? 'Enabled' : 'Disabled'}
- +
)} );