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 (
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; +}