Applications
This commit is contained in:
@@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user