Applications

This commit is contained in:
2025-12-30 14:31:26 +01:00
parent c1eacf996d
commit fb05b455d5
6 changed files with 129 additions and 80 deletions
+22
View File
@@ -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<NextResponse> {
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,
});
}
+17 -79
View File
@@ -1,89 +1,27 @@
"use client"; "use client";
import { DashboardLayout } from "@/components/dashboard/DashboardLayout"; 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 { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Play, Square, RotateCcw, MoreVertical, Plus } from "lucide-react"; import { Play, Square, RotateCcw, MoreVertical, Plus } from "lucide-react";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
import DeployApplication from "@/components/dialogs/DeployApplication"; import DeployApplication from "@/components/dialogs/DeployApplication";
import { useEffect, useState } from "react";
const applications = [ import { Application } from "@/lib/types";
{ import { getApplications } from "@/core/application";
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"],
},
];
export default function ApplicationsPage() { export default function ApplicationsPage() {
const [applications, setApplications] = useState<Application[]>([]);
useEffect(() => {
const fetch = async () => {
setApplications(await getApplications());
};
fetch();
}, []);
return ( return (
<DashboardLayout <DashboardLayout
title="Applications" title="Applications"
@@ -107,8 +45,8 @@ export default function ApplicationsPage() {
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{applications.map((app) => ( {applications.map((app, i) => (
<Card key={app.id}> <Card key={i}>
<CardContent className="p-4"> <CardContent className="p-4">
<div className="grid grid-cols-1 md:grid-cols-12 gap-4 md:items-center"> <div className="grid grid-cols-1 md:grid-cols-12 gap-4 md:items-center">
{/* Status & Name */} {/* Status & Name */}
@@ -166,7 +104,7 @@ export default function ApplicationsPage() {
Ports Ports
</div> </div>
<div className="flex gap-1 flex-wrap"> <div className="flex gap-1 flex-wrap">
{app.ports.length > 0 ? ( {app.ports?.length > 0 ? (
app.ports.map((port) => ( app.ports.map((port) => (
<Badge key={port} className="text-[10px]"> <Badge key={port} className="text-[10px]">
{port} {port}
+20
View File
@@ -6,6 +6,9 @@ import fs from "fs/promises";
import path from "path"; import path from "path";
import { exec } from "child_process"; import { exec } from "child_process";
import util from "util"; import util from "util";
import { getNodes } from "./node";
import axios from "axios";
import { Application } from "@/lib/types";
const execAsync = util.promisify(exec); const execAsync = util.promisify(exec);
@@ -70,3 +73,20 @@ export async function deployNewApp(name: string, repo: string, nodeId: string) {
image: imageTag, image: imageTag,
}; };
} }
export async function getApplications(): Promise<Application[]> {
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();
}
+56
View File
@@ -0,0 +1,56 @@
"use server";
import { Application } from "@/lib/types";
import Docker from "dockerode";
const docker = new Docker();
export async function getApplications(): Promise<Application[]> {
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<string>();
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;
}
+1 -1
View File
@@ -13,7 +13,7 @@ export async function getDocker() {
export async function getContainerStats( export async function getContainerStats(
containerId: string containerId: string
): Promise<ContainerStats | undefined> { ): Promise<ContainerStats | undefined> {
return await (await docker.getContainer(containerId)).stats(); return await docker.getContainer(containerId).stats();
} }
export async function startContainer(containerId: string) { export async function startContainer(containerId: string) {
+13
View File
@@ -47,3 +47,16 @@ export interface Docker {
version: string; version: string;
containers: ContainerInfo[]; 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[];
}