From f7f25d88cd865f94a95df0361e2c7bd4e6c351a7 Mon Sep 17 00:00:00 2001
From: KodekaTeam
Date: Mon, 8 Jun 2026 00:55:43 +0700
Subject: [PATCH] feat(update): add dynamic environment & docker network
options to the app installer modal and change the style of the installed app
modal.
---
src/app/apps/installed/page.tsx | 495 +++++++++++++++++++++-----------
src/app/apps/page.tsx | 206 ++++++++++++-
src/lib/api.ts | 12 +
src/server/routes/apps.ts | 90 +++++-
4 files changed, 633 insertions(+), 170 deletions(-)
diff --git a/src/app/apps/installed/page.tsx b/src/app/apps/installed/page.tsx
index 2d7f290..709b1e6 100644
--- a/src/app/apps/installed/page.tsx
+++ b/src/app/apps/installed/page.tsx
@@ -6,7 +6,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
AlertTriangle,
Boxes,
+ ChevronDown,
CheckCircle,
+ Container,
Cpu,
HardDrive,
Loader2,
@@ -16,11 +18,9 @@ import {
ScrollText,
Server as ServerIcon,
Square,
- Terminal,
Trash2,
Wrench,
X,
- XCircle,
} from "lucide-react";
import DashboardLayout from "@/components/DashboardLayout";
import ConfirmActionDialog from "@/components/ConfirmActionDialog";
@@ -269,6 +269,8 @@ function InstallOperationsModal({
const [error, setError] = useState("");
const [acting, setActing] = useState>({});
const contentViewportRef = useRef(null);
+ const actionsMenuRef = useRef(null);
+ const [actionsMenuOpen, setActionsMenuOpen] = useState(false);
useEffect(() => {
setActiveTab(initialTab);
@@ -295,12 +297,29 @@ function InstallOperationsModal({
void loadRuntime();
}, [loadRuntime]);
+ useEffect(() => {
+ if (!actionsMenuOpen) return;
+
+ const handlePointerDown = (event: PointerEvent) => {
+ if (
+ actionsMenuRef.current &&
+ !actionsMenuRef.current.contains(event.target as Node)
+ ) {
+ setActionsMenuOpen(false);
+ }
+ };
+
+ document.addEventListener("pointerdown", handlePointerDown);
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
+ }, [actionsMenuOpen]);
+
const handleLifecycle = async (action: "start" | "stop" | "restart") => {
if (!install.containerName) {
onToast({
tone: "error",
title: "Installed Apps",
- message: "Container runtime belum ditemukan untuk app ini",
+ message:
+ "Container name is not available for this app install. Lifecycle actions cannot be performed.",
showProgress: true,
});
return;
@@ -314,7 +333,7 @@ function InstallOperationsModal({
onToast({
tone: "success",
title: "Installed Apps",
- message: `${install.appName} ${action} berhasil dijalankan`,
+ message: `${install.appName} ${action} successfully executed`,
showProgress: true,
});
} catch (err: unknown) {
@@ -338,7 +357,7 @@ function InstallOperationsModal({
onToast({
tone: "success",
title: "Installed Apps",
- message: `${install.appName} berhasil direbuild`,
+ message: `${install.appName} successfully rebuilt`,
showProgress: true,
});
} catch (err: unknown) {
@@ -354,31 +373,47 @@ function InstallOperationsModal({
};
const requestRebuildConfirm = () => {
+ setActionsMenuOpen(false);
onRequestConfirm({
title: "Rebuild Installed App",
description: `Rebuild app "${install.appName}" sekarang?`,
confirmLabel: "Rebuild App",
tone: "warning",
- note: "Container app ini akan dihentikan, dihapus, lalu dijalankan ulang. Doktainer akan mencoba menarik image terbaru terlebih dahulu jika tersedia di registry.",
+ note: "Rebuilding will stop the app if it's currently running, and apply any changes from the original app template or the last successful build. This is useful if the app is not working correctly or if you have made changes to the app's source that you want to apply.",
onConfirm: () => {
void handleRebuild();
},
});
};
- const handleOpenContainers = () => {
- storeServerSelection("containers", install.serverId);
- router.push("/containers");
+ const handleOpenEnvironment = () => {
+ setActionsMenuOpen(false);
+
+ if (!install.environment) {
+ onToast({
+ tone: "error",
+ title: "Installed Apps",
+ message:
+ "This app install does not have an associated environment. It might have been installed before this feature was implemented, or there was an issue linking the environment during installation.",
+ showProgress: true,
+ });
+ return;
+ }
+
+ router.push(
+ `/projects/${install.environment.projectId}/environments/${install.environment.id}/containers/${install.environment.containerId}`,
+ );
onClose();
};
- const handleOpenTerminal = () => {
- const encodedName = encodeURIComponent(install.server?.name ?? "Server");
- const encodedIp = encodeURIComponent(install.server?.ip ?? "");
- router.push(
- `/terminal?serverId=${install.serverId}&name=${encodedName}&ip=${encodedIp}`,
- );
- onClose();
+ const handleMenuLifecycle = (action: "start" | "stop" | "restart") => {
+ setActionsMenuOpen(false);
+ void handleLifecycle(action);
+ };
+
+ const handleMenuRemove = () => {
+ setActionsMenuOpen(false);
+ void onRemove(install);
};
const currentStatus = detail?.container.status ?? install.status;
@@ -492,20 +527,6 @@ function InstallOperationsModal({
-
-