From 767afd9fef750fc046925994c0ef6a51973da409 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Fri, 2 Jan 2026 12:37:46 +0100 Subject: [PATCH] Restructured applications --- src/app/applications/page.tsx | 121 +++++++++------------------------ src/core/application.ts | 4 +- src/core/docker.ts | 2 +- src/core/server/application.ts | 91 ++++--------------------- src/core/server/deployment.ts | 53 +++++++++++++++ src/lib/types.ts | 12 ++-- 6 files changed, 106 insertions(+), 177 deletions(-) create mode 100644 src/core/server/deployment.ts diff --git a/src/app/applications/page.tsx b/src/app/applications/page.tsx index 0882281..d4c0890 100644 --- a/src/app/applications/page.tsx +++ b/src/app/applications/page.tsx @@ -4,20 +4,20 @@ import { DashboardLayout } from "@/components/dashboard/DashboardLayout"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Play, Square, MoreVertical, Plus, Rocket } from "lucide-react"; -import { Progress } from "@/components/ui/progress"; +import { MoreVertical, Plus, Rocket } from "lucide-react"; import InstallApplicationDialog from "@/components/dialogs/InstallApplication"; import { useEffect, useState } from "react"; -import { ImageApp } from "@/lib/types"; import { getApplications, removeApp } from "@/core/application"; import RemoveDialog from "@/components/dialogs/Remove"; +import { Application } from "@/lib/types"; export default function ApplicationsPage() { - const [applications, setApplications] = useState([]); + const [applications, setApplications] = useState([]); useEffect(() => { const fetch = async () => { - setApplications(await getApplications()); + const apps = await getApplications(); + setApplications(apps); }; fetch(); @@ -40,110 +40,53 @@ export default function ApplicationsPage() {
{applications.length} apps - - {applications.filter((a) => a.status === "running").length} running -
-
- -
- {applications.map((app, i) => ( - +
+ {applications.map((app) => ( + -
+
{/* Status & Name */} -
- {/* {app.status} */} +
-
- {app.app?.name || app.name} +
+ {app.name}
- {app.image} + {app.repo}
- {/* Containers */} -
- - Containers + {/* Deployments */} +
+ + Deployments -
{app.replicas}
-
- - {/* CPU */} -
-
CPU
-
- - - {app.cpu.toFixed(1)}% - -
-
- - {/* Memory */} -
-
- Memory -
-
- - - {app.memory.toFixed(1)}% - -
-
- - {/* Network */} -
- - Network + + {app.deployments?.length ?? 0} -
{app.network}
-
- - {/* Ports */} -
-
- Ports -
-
- {app.ports?.length > 0 ? ( - app.ports.map((port) => ( - - {port} - - )) - ) : ( - — - )} -
{/* Actions */} -
- {app.status === "running" ? ( - - ) : ( - - )} - app.app?.id && remove(app.app?.id)} - /> - + + {app.id && remove(app.id)} />} + +
diff --git a/src/core/application.ts b/src/core/application.ts index 515d696..1499eef 100644 --- a/src/core/application.ts +++ b/src/core/application.ts @@ -3,7 +3,7 @@ import { verifySession } from "./auth/session"; import { getNodes } from "./node"; import axios from "axios"; -import { Env, EnvVariable, ImageApp } from "@/lib/types"; +import { Env, EnvVariable, Application } from "@/lib/types"; export async function installApp(name: string, repo: string, nodeId: string) { if (await verifySession()) { @@ -25,7 +25,7 @@ export async function installApp(name: string, repo: string, nodeId: string) { ).data.applications; } -export async function getApplications(): Promise { +export async function getApplications(): Promise { const nodes = await getNodes(); return ( diff --git a/src/core/docker.ts b/src/core/docker.ts index 00b8d67..8906e56 100644 --- a/src/core/docker.ts +++ b/src/core/docker.ts @@ -1,7 +1,7 @@ "use server"; import axios from "axios"; -import { Docker, Log, Node, SimpleStats } from "@/lib/types"; +import { Deployment, Docker, Log, Node, SimpleStats } from "@/lib/types"; import { verifySession } from "./auth/session"; import { getNodes } from "./node"; diff --git a/src/core/server/application.ts b/src/core/server/application.ts index e858f93..f203a12 100644 --- a/src/core/server/application.ts +++ b/src/core/server/application.ts @@ -1,80 +1,25 @@ "use server"; -import Docker, { Container, ContainerStats } from "dockerode"; +import Docker from "dockerode"; import { randomUUID } from "crypto"; import fs from "fs"; import path from "path"; import { exec } from "child_process"; import util from "util"; -import { Env, EnvVariable, ImageApp } from "@/lib/types"; -import { CPUusage } from "@/lib/server/calc"; +import { Application, Env, EnvVariable } from "@/lib/types"; const docker = new Docker(); const execAsync = util.promisify(exec); const DATA_DIR = path.join(process.cwd(), "data", "apps"); -export async function getApplications(): Promise { - const images = await docker.listImages(); - const containers = await docker.listContainers({ all: true }); - - const apps: ImageApp[] = []; - - for (const image of images) { - const imageName = image.RepoTags?.[0]; - if (!imageName) continue; - - const relatedContainers = containers.filter((c) => c.Image === imageName); - - let totalCpu = 0; - let totalMem = 0; - let running = 0; - const ports = new Set(); - - for (const containerInfo of relatedContainers) { - const container = docker.getContainer(containerInfo.Id); - - if (containerInfo.State === "running") running++; - - containerInfo.Ports?.forEach((p) => { - if (p.PublicPort && p.PrivatePort) { - ports.add(`${p.PublicPort}:${p.PrivatePort}`); - } - }); - - if (containerInfo.State === "running") { - const stats = await container.stats({ stream: false }); - - totalMem += (stats.memory_stats.usage / stats.memory_stats.limit) * 100; - totalCpu += CPUusage(stats); - } - } - - const name = imageName.split("/").pop()?.split(":")[0] ?? imageName; - - var app; - - try { - app = JSON.parse( - fs.readFileSync(path.join(DATA_DIR, name, "app.json")).toString() - ); - } catch {} - - apps.push({ - app, - name, - id: image.Id.slice(7, 15), - image: imageName, - containers: relatedContainers.length, - replicas: `${running}/${relatedContainers.length}`, - cpu: totalCpu, - memory: totalMem, - network: "—", - status: running > 0 ? ("running" as const) : ("stopped" as const), - ports: [...ports], - }); - } - - return apps; +export async function getApplications(): Promise { + return fs + .readdirSync(DATA_DIR) + .map((appId) => + JSON.parse( + fs.readFileSync(path.join(DATA_DIR, appId, "app.json")).toString() + ) + ); } export async function installApp(name: string, repo: string, nodeId: string) { @@ -87,12 +32,13 @@ export async function installApp(name: string, repo: string, nodeId: string) { fs.mkdirSync(repoDir, { recursive: true }); // Write app.json - const appConfig = { + const appConfig: Application = { id: appId, name, repo, nodeId, createdAt: new Date().toISOString(), + deployments: [], }; fs.writeFileSync( @@ -100,6 +46,7 @@ export async function installApp(name: string, repo: string, nodeId: string) { JSON.stringify(appConfig, null, 2) ); + // Write env.json fs.writeFileSync(path.join(appDir, "env.json"), JSON.stringify([], null, 2)); // Clone repository @@ -115,23 +62,13 @@ export async function installApp(name: string, repo: string, nodeId: string) { try { fs.accessSync(dockerfilePath); } catch { + fs.rmSync(appDir, { recursive: true }); throw new Error("Dockerfile not found in repository root"); } - const imageTag = `dockship/${appId}:latest`; - - try { - await execAsync(`docker build -t ${imageTag} .`, { - cwd: repoDir, - }); - } catch { - throw new Error("Docker build failed"); - } - return { success: true, appId, - image: imageTag, }; } diff --git a/src/core/server/deployment.ts b/src/core/server/deployment.ts new file mode 100644 index 0000000..30284cd --- /dev/null +++ b/src/core/server/deployment.ts @@ -0,0 +1,53 @@ +"use server"; + +import Docker from "dockerode"; +import { Application, Deployment } from "@/lib/types"; +import { CPUusage } from "@/lib/server/calc"; + +const docker = new Docker(); + +export async function getDeployments( + apps: Application[] +): Promise<(null | Deployment)[]> { + const containers = await docker.listContainers({ all: true }); + + return await Promise.all( + apps + .map((app) => app.deployments) + .flat() + .map(async (imageId) => { + const containerInfo = containers.find( + (container) => container.ImageID === imageId + ); + + if (!containerInfo) return null; + + const container = docker.getContainer(containerInfo.Id); + + let cpu = 0; + let memory = 0; + let ports: Set<[number, number]> = new Set(); + + if (containerInfo.State === "running") { + const stats = await container.stats({ stream: false }); + memory += (stats.memory_stats.usage / stats.memory_stats.limit) * 100; + cpu += CPUusage(stats); + } + + containerInfo.Ports?.forEach((p) => { + if (p.PublicPort && p.PrivatePort) { + ports.add([p.PublicPort, p.PrivatePort]); + } + }); + + return { + image: containerInfo.ImageID, + container: containerInfo.Id, + cpu, + memory, + status: containerInfo.State === "running" ? "running" : "stopped", + ports: [...ports], + }; + }) + ); +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 50ff4f4..1d51e62 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -54,20 +54,16 @@ export interface Application { repo: string; nodeId: string; createdAt: string; + deployments: string[]; } -export interface ImageApp { - app?: Application; - id: string; - name: string; +export interface Deployment { image: string; - containers: number; - replicas: string; + container: string; cpu: number; memory: number; - network: string; status: "running" | "stopped"; - ports: string[]; + ports: [number, number][]; } export interface SimpleStats {