diff --git a/frontend/src/pages/ApplicationDetail.jsx b/frontend/src/pages/ApplicationDetail.jsx index 79547ba2..ae4a4184 100644 --- a/frontend/src/pages/ApplicationDetail.jsx +++ b/frontend/src/pages/ApplicationDetail.jsx @@ -1,9 +1,12 @@ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; +import { GitBranch } from 'lucide-react'; import api from '../services/api'; import { useToast } from '../contexts/ToastContext'; import EnvironmentVariables from '../components/EnvironmentVariables'; import PrivateURLSection from '../components/PrivateURLSection'; +import LinkedAppsSection from '../components/LinkedAppsSection'; +import LinkAppModal from '../components/LinkAppModal'; const ApplicationDetail = () => { const { id } = useParams(); @@ -108,6 +111,13 @@ const ApplicationDetail = () => {
+ {app.environment_type && app.environment_type !== 'standalone' && ( + + {app.environment_type === 'production' ? 'PROD' : + app.environment_type === 'development' ? 'DEV' : 'STAGING'} + {app.has_linked_app && } + + )} {app.status} @@ -231,14 +241,19 @@ const ApplicationDetail = () => { }; const OverviewTab = ({ app, onUpdate }) => { + const navigate = useNavigate(); const [status, setStatus] = useState(null); const [appStatus, setAppStatus] = useState(null); + const [linkedApps, setLinkedApps] = useState([]); + const [showLinkModal, setShowLinkModal] = useState(false); + const [linkLoading, setLinkLoading] = useState(false); useEffect(() => { if (['flask', 'django'].includes(app.app_type)) { loadStatus(); } loadAppStatus(); + loadLinkedApps(); }, [app]); async function loadStatus() { @@ -259,6 +274,36 @@ const OverviewTab = ({ app, onUpdate }) => { } } + async function loadLinkedApps() { + try { + const data = await api.getLinkedApps(app.id); + setLinkedApps(data.linked_apps || []); + } catch (err) { + console.error('Failed to load linked apps:', err); + } + } + + async function handleUnlink() { + if (!window.confirm('Are you sure you want to unlink these apps? Database credentials will remain unchanged.')) { + return; + } + setLinkLoading(true); + try { + await api.unlinkApp(app.id); + onUpdate(); + loadLinkedApps(); + } catch (err) { + console.error('Failed to unlink apps:', err); + } finally { + setLinkLoading(false); + } + } + + function handleLinked() { + onUpdate(); + loadLinkedApps(); + } + return (
@@ -357,6 +402,24 @@ const OverviewTab = ({ app, onUpdate }) => { {app.app_type === 'docker' && app.port && ( )} + + {/* Environment Linking Section */} + setShowLinkModal(true)} + onUnlink={handleUnlink} + onNavigate={(appId) => navigate(`/apps/${appId}`)} + loading={linkLoading} + /> + + {showLinkModal && ( + setShowLinkModal(false)} + onLinked={handleLinked} + /> + )}
); };