diff --git a/src/app/api/applications/route.ts b/src/app/api/applications/route.ts new file mode 100644 index 0000000..4c7d444 --- /dev/null +++ b/src/app/api/applications/route.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from "next/server"; +import testAuth from "../auth"; +import { StatusCodes } from "http-status-codes"; +import { getApplications } from "@/core/server/application"; + +export async function GET(req: NextRequest): Promise { + const auth = await testAuth(req); + + if (auth) { + return NextResponse.json( + { message: auth }, + { status: StatusCodes.UNAUTHORIZED } + ); + } + + const applications = await getApplications(); + + return NextResponse.json({ + message: "ok", + applications, + }); +} diff --git a/src/app/applications/page.tsx b/src/app/applications/page.tsx index 1edffb2..ca959e5 100644 --- a/src/app/applications/page.tsx +++ b/src/app/applications/page.tsx @@ -1,89 +1,27 @@ "use client"; import { DashboardLayout } from "@/components/dashboard/DashboardLayout"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Play, Square, RotateCcw, MoreVertical, Plus } from "lucide-react"; import { Progress } from "@/components/ui/progress"; import DeployApplication from "@/components/dialogs/DeployApplication"; - -const applications = [ - { - id: "app-001", - name: "api-gateway", - image: "dockship/api-gateway:v2.4.1", - containers: 3, - replicas: "3/3", - cpu: 45, - memory: 62, - network: "12.4 MB/s", - status: "running" as const, - ports: ["80:8080", "443:8443"], - }, - { - id: "app-002", - name: "auth-service", - image: "dockship/auth:v1.8.0", - containers: 2, - replicas: "2/2", - cpu: 28, - memory: 45, - network: "3.2 MB/s", - status: "running" as const, - ports: ["9000:9000"], - }, - { - id: "app-003", - name: "postgres-db", - image: "postgres:15-alpine", - containers: 1, - replicas: "1/1", - cpu: 65, - memory: 78, - network: "8.1 MB/s", - status: "running" as const, - ports: ["5432:5432"], - }, - { - id: "app-004", - name: "redis-cache", - image: "redis:7-alpine", - containers: 1, - replicas: "1/1", - cpu: 12, - memory: 34, - network: "1.8 MB/s", - status: "running" as const, - ports: ["6379:6379"], - }, - { - id: "app-005", - name: "worker-queue", - image: "dockship/worker:v3.1.2", - containers: 4, - replicas: "3/4", - cpu: 55, - memory: 48, - network: "5.6 MB/s", - status: "pending" as const, - ports: [], - }, - { - id: "app-006", - name: "metrics-collector", - image: "dockship/metrics:v1.2.0", - containers: 2, - replicas: "0/2", - cpu: 0, - memory: 0, - network: "0 MB/s", - status: "error" as const, - ports: ["9090:9090"], - }, -]; +import { useEffect, useState } from "react"; +import { Application } from "@/lib/types"; +import { getApplications } from "@/core/application"; export default function ApplicationsPage() { + const [applications, setApplications] = useState([]); + + useEffect(() => { + const fetch = async () => { + setApplications(await getApplications()); + }; + + fetch(); + }, []); + return (
- {applications.map((app) => ( - + {applications.map((app, i) => ( +
{/* Status & Name */} @@ -166,7 +104,7 @@ export default function ApplicationsPage() { Ports
- {app.ports.length > 0 ? ( + {app.ports?.length > 0 ? ( app.ports.map((port) => ( {port} diff --git a/src/core/application.ts b/src/core/application.ts index 710446a..9139abd 100644 --- a/src/core/application.ts +++ b/src/core/application.ts @@ -6,6 +6,9 @@ import fs from "fs/promises"; import path from "path"; import { exec } from "child_process"; import util from "util"; +import { getNodes } from "./node"; +import axios from "axios"; +import { Application } from "@/lib/types"; const execAsync = util.promisify(exec); @@ -70,3 +73,20 @@ export async function deployNewApp(name: string, repo: string, nodeId: string) { image: imageTag, }; } + +export async function getApplications(): Promise { + const nodes = await getNodes(); + + return ( + await Promise.all( + nodes.map( + async (n) => + await ( + await axios.get(`http://${n.ip}:3000/api/applications`, { + headers: { Authorization: `ApiKey ${n.key}` }, + }) + ).data.applications + ) + ) + ).flat(); +} diff --git a/src/core/server/application.ts b/src/core/server/application.ts new file mode 100644 index 0000000..61b39ab --- /dev/null +++ b/src/core/server/application.ts @@ -0,0 +1,56 @@ +"use server"; + +import { Application } from "@/lib/types"; +import Docker from "dockerode"; + +const docker = new Docker(); + +export async function getApplications(): Promise { + const images = await docker.listImages(); + const containers = await docker.listContainers({ all: true }); + + const apps: Application[] = []; + + 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 }); + } + } + + apps.push({ + id: image.Id.slice(7, 15), + name: imageName.split("/").pop()?.split(":")[0] ?? imageName, + image: imageName, + containers: relatedContainers.length, + replicas: `${running}/${relatedContainers.length}`, + cpu: Math.round(totalCpu), + memory: Math.round(totalMem), + network: "—", + status: running > 0 ? ("running" as const) : ("stopped" as const), + ports: [...ports], + }); + } + + return apps; +} diff --git a/src/core/server/docker.ts b/src/core/server/docker.ts index 2013235..b1e9f20 100644 --- a/src/core/server/docker.ts +++ b/src/core/server/docker.ts @@ -13,7 +13,7 @@ export async function getDocker() { export async function getContainerStats( containerId: string ): Promise { - return await (await docker.getContainer(containerId)).stats(); + return await docker.getContainer(containerId).stats(); } export async function startContainer(containerId: string) { diff --git a/src/lib/types.ts b/src/lib/types.ts index 1cca76c..8ce361f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -47,3 +47,16 @@ export interface Docker { version: string; containers: ContainerInfo[]; } + +export interface Application { + id: string; + name: string; + image: string; + containers: number; + replicas: string; + cpu: number; + memory: number; + network: string; + status: "running" | "stopped"; + ports: string[]; +}