From 99bc8596a18a563e73e0cc0ce994aa09d61a0664 Mon Sep 17 00:00:00 2001 From: Juan Denis Date: Mon, 19 Jan 2026 10:50:44 -0500 Subject: [PATCH] Add environment settings to Settings tab Add environment configuration section in SettingsTab component with: - Current environment type display and dropdown for standalone apps - Disabled state with badge for linked apps (environment locked) - Warning section for linked apps with unlink button - Settings row styling for consistent layout Co-Authored-By: Claude Opus 4.5 --- frontend/src/pages/ApplicationDetail.jsx | 95 ++++++++++++++++++++++++ frontend/src/styles/pages/_settings.less | 76 +++++++++++++++++++ 2 files changed, 171 insertions(+) diff --git a/frontend/src/pages/ApplicationDetail.jsx b/frontend/src/pages/ApplicationDetail.jsx index ae4a4184..1a55c983 100644 --- a/frontend/src/pages/ApplicationDetail.jsx +++ b/frontend/src/pages/ApplicationDetail.jsx @@ -1722,6 +1722,16 @@ const LogsTab = ({ app }) => { const SettingsTab = ({ app, onUpdate }) => { const navigate = useNavigate(); const [deleting, setDeleting] = useState(false); + const [environmentType, setEnvironmentType] = useState(app.environment_type || 'standalone'); + const [savingEnvironment, setSavingEnvironment] = useState(false); + const [unlinking, setUnlinking] = useState(false); + + const envLabels = { + standalone: 'Standalone', + production: 'Production', + development: 'Development', + staging: 'Staging' + }; async function handleDelete() { if (!confirm(`Delete ${app.name}? This action cannot be undone.`)) return; @@ -1737,10 +1747,95 @@ const SettingsTab = ({ app, onUpdate }) => { } } + async function handleEnvironmentChange(newType) { + if (newType === app.environment_type) return; + + setSavingEnvironment(true); + try { + await api.updateAppEnvironment(app.id, newType); + setEnvironmentType(newType); + onUpdate(); + } catch (err) { + console.error('Failed to update environment:', err); + setEnvironmentType(app.environment_type || 'standalone'); + } finally { + setSavingEnvironment(false); + } + } + + async function handleUnlink() { + if (!confirm(`Unlink ${app.name} from its linked application? Both apps will become standalone.`)) return; + + setUnlinking(true); + try { + await api.unlinkApp(app.id); + onUpdate(); + } catch (err) { + console.error('Failed to unlink app:', err); + } finally { + setUnlinking(false); + } + } + return (

Application Settings

+ {/* Environment Configuration */} +
+

Environment Configuration

+
+
+ Environment Type + + {app.has_linked_app + ? 'This app is linked. Unlink to change environment type.' + : 'Set how this application is used in your workflow.'} + +
+
+ {app.has_linked_app ? ( + + {envLabels[app.environment_type] || app.environment_type} + + ) : ( + + )} + {savingEnvironment && Saving...} +
+
+ + {app.has_linked_app && ( +
+
+ Linked Application + + This app is linked to another application. Unlinking will reset both apps to standalone mode. + +
+
+ +
+
+ )} +
+

Danger Zone

Once you delete an application, there is no going back.

diff --git a/frontend/src/styles/pages/_settings.less b/frontend/src/styles/pages/_settings.less index 436119ed..46733e61 100644 --- a/frontend/src/styles/pages/_settings.less +++ b/frontend/src/styles/pages/_settings.less @@ -813,3 +813,79 @@ color: @text-secondary; font-size: @font-size-sm; } + +// Settings Row (for app settings tab) +.settings-row { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: @space-4; + background: @bg-hover; + border-radius: @radius-md; + gap: @space-4; + + &:not(:last-child) { + margin-bottom: @space-3; + } + + &.settings-linked-warning { + background: fade(@warning, 10%); + border: 1px solid fade(@warning, 20%); + } +} + +.settings-label { + flex: 1; + + > span:first-child { + display: block; + font-weight: @font-weight-medium; + color: @text-primary; + margin-bottom: @space-1; + } + + .settings-hint { + display: block; + font-size: @font-size-xs; + color: @text-tertiary; + } +} + +.settings-control { + display: flex; + align-items: center; + gap: @space-2; + flex-shrink: 0; +} + +.settings-select { + min-width: 160px; + padding: @space-2 @space-3; + background: @bg-card; + border: 1px solid @border-subtle; + border-radius: @radius-md; + color: @text-primary; + font-size: @font-size-sm; + cursor: pointer; + transition: border-color @transition-fast; + + &:hover { + border-color: @border-default; + } + + &:focus { + outline: none; + border-color: @accent-primary; + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } +} + +.settings-saving { + font-size: @font-size-xs; + color: @text-tertiary; + font-style: italic; +}