feat: extract Variables as first config tab in create and detail views
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:
@@ -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))}>×</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))}>×</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))}>×</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))}>×</button>
|
||||
</div>
|
||||
))}
|
||||
{editing && (
|
||||
<Button size="sm" variant="secondary" onClick={() => setEnvVars([...envVars, { key: '', value: '' }])}>+ Add Variable</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user