feat: extract Variables as first config tab in create and detail views
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m23s
CI / docker (push) Successful in 1m3s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s

Environment Variables moved from Resources into a dedicated "Variables"
tab, placed first in the tab order since it's the most commonly needed
config when creating new apps.

Tab order:
- Create page: Variables | Monitoring | Resources
- Detail page: Variables | Monitoring | Traces & Taps | Route Recording | Resources

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-08 18:47:58 +02:00
parent 0a3733f9ba
commit 0ccb8bc68d

View File

@@ -175,7 +175,7 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
const [newPort, setNewPort] = useState('');
const [envVars, setEnvVars] = useState<{ key: string; value: string }[]>([]);
const [configTab, setConfigTab] = useState<'monitoring' | 'resources'>('monitoring');
const [configTab, setConfigTab] = useState<'variables' | 'monitoring' | 'resources'>('variables');
const [busy, setBusy] = useState(false);
const [step, setStep] = useState('');
@@ -309,10 +309,29 @@ 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>
</div>
{configTab === 'variables' && (
<>
{envVars.map((v, i) => (
<div key={i} className={styles.envVarRow}>
<Input disabled={busy} value={v.key} onChange={(e) => {
const next = [...envVars]; next[i] = { ...v, key: e.target.value }; setEnvVars(next);
}} className={styles.envVarKey} placeholder="KEY" />
<Input disabled={busy} value={v.value} onChange={(e) => {
const next = [...envVars]; next[i] = { ...v, value: e.target.value }; setEnvVars(next);
}} className={styles.envVarValue} placeholder="value" />
<button className={styles.envVarDelete} disabled={busy}
onClick={() => !busy && setEnvVars(envVars.filter((_, j) => j !== i))}>&times;</button>
</div>
))}
<Button size="sm" variant="secondary" disabled={busy} onClick={() => setEnvVars([...envVars, { key: '', value: '' }])}>+ Add Variable</Button>
</>
)}
{configTab === 'monitoring' && (
<div className={styles.configGrid}>
<span className={styles.configLabel}>Engine Level</span>
@@ -412,21 +431,6 @@ function CreateAppView({ environments, selectedEnv }: { environments: Environmen
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addPort(); } }} />
</div>
</div>
<SectionHeader>Environment Variables</SectionHeader>
{envVars.map((v, i) => (
<div key={i} className={styles.envVarRow}>
<Input disabled={busy} value={v.key} onChange={(e) => {
const next = [...envVars]; next[i] = { ...v, key: e.target.value }; setEnvVars(next);
}} className={styles.envVarKey} placeholder="KEY" />
<Input disabled={busy} value={v.value} onChange={(e) => {
const next = [...envVars]; next[i] = { ...v, value: e.target.value }; setEnvVars(next);
}} className={styles.envVarValue} placeholder="value" />
<button className={styles.envVarDelete} disabled={busy}
onClick={() => !busy && setEnvVars(envVars.filter((_, j) => j !== i))}>&times;</button>
</div>
))}
<Button size="sm" variant="secondary" disabled={busy} onClick={() => setEnvVars([...envVars, { key: '', value: '' }])}>+ Add Variable</Button>
</>
)}
</div>
@@ -647,7 +651,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<'monitoring' | 'traces' | 'recording' | 'resources'>('monitoring');
const [configTab, setConfigTab] = useState<'variables' | 'monitoring' | 'traces' | 'recording' | 'resources'>('variables');
const appRoutes: RouteSummary[] = useMemo(() => {
if (!catalog) return [];
@@ -840,12 +844,34 @@ 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 === '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' && (
<>
{envVars.map((v, i) => (
<div key={i} className={styles.envVarRow}>
<Input disabled={!editing} value={v.key} onChange={(e) => {
const next = [...envVars]; next[i] = { ...v, key: e.target.value }; setEnvVars(next);
}} className={styles.envVarKey} placeholder="KEY" />
<Input disabled={!editing} value={v.value} onChange={(e) => {
const next = [...envVars]; next[i] = { ...v, value: e.target.value }; setEnvVars(next);
}} className={styles.envVarValue} placeholder="value" />
<button className={styles.envVarDelete} disabled={!editing}
onClick={() => editing && setEnvVars(envVars.filter((_, j) => j !== i))}>&times;</button>
</div>
))}
{editing && (
<Button size="sm" variant="secondary" onClick={() => setEnvVars([...envVars, { key: '', value: '' }])}>+ Add Variable</Button>
)}
{envVars.length === 0 && !editing && <p className={styles.emptyNote}>No environment variables configured.</p>}
</>
)}
{configTab === 'monitoring' && (
<div className={styles.configGrid}>
<span className={styles.configLabel}>Engine Level</span>
@@ -964,24 +990,6 @@ function ConfigSubTab({ app, environment }: { app: App; environment?: Environmen
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addPort(); } }} />
</div>
</div>
{/* Environment Variables */}
<SectionHeader>Environment Variables</SectionHeader>
{envVars.map((v, i) => (
<div key={i} className={styles.envVarRow}>
<Input disabled={!editing} value={v.key} onChange={(e) => {
const next = [...envVars]; next[i] = { ...v, key: e.target.value }; setEnvVars(next);
}} className={styles.envVarKey} />
<Input disabled={!editing} value={v.value} onChange={(e) => {
const next = [...envVars]; next[i] = { ...v, value: e.target.value }; setEnvVars(next);
}} className={styles.envVarValue} />
<button className={styles.envVarDelete} disabled={!editing}
onClick={() => editing && setEnvVars(envVars.filter((_, j) => j !== i))}>&times;</button>
</div>
))}
{editing && (
<Button size="sm" variant="secondary" onClick={() => setEnvVars([...envVars, { key: '', value: '' }])}>+ Add Variable</Button>
)}
</>
)}
</>