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}
+ />
+ )}
);
};