Re structured lib and core

This commit is contained in:
2026-01-02 19:51:20 +01:00
parent 3f2d562299
commit caa1191736
26 changed files with 416 additions and 420 deletions
+1 -5
View File
@@ -1,11 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import { StatusCodes } from "http-status-codes";
import {
installApp,
getApplications,
removeApp,
} from "@/core/server/application";
import { installApp, getApplications, removeApp } from "@/core/application";
export async function GET(req: NextRequest): Promise<NextResponse> {
const auth = await testAuth(req);
+1 -1
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import { StatusCodes } from "http-status-codes";
import { getDocker, removeContainer } from "@/core/server/docker";
import { getDocker, removeContainer } from "@/core/docker";
import { Docker } from "@/lib/types";
export async function GET(req: NextRequest): Promise<NextResponse> {
+1 -1
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../../auth";
import { StatusCodes } from "http-status-codes";
import { startContainer, stopContainer } from "@/core/server/docker";
import { startContainer, stopContainer } from "@/core/docker";
export async function POST(req: NextRequest): Promise<NextResponse> {
const auth = await testAuth(req);
+1 -1
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../../auth";
import { StatusCodes } from "http-status-codes";
import { getContainerStats } from "@/core/server/docker";
import { getContainerStats } from "@/core/docker";
export async function GET(req: NextRequest): Promise<NextResponse> {
const auth = await testAuth(req);
+1 -1
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import { StatusCodes } from "http-status-codes";
import { getEnv, setEnv } from "@/core/server/application";
import { getEnv, setEnv } from "@/core/application";
export async function GET(req: NextRequest): Promise<NextResponse> {
const auth = await testAuth(req);
+1 -1
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import { StatusCodes } from "http-status-codes";
import { getAllContainerLogs } from "@/core/server/docker";
import { getAllContainerLogs } from "@/core/docker";
export async function GET(req: NextRequest): Promise<NextResponse> {
const auth = await testAuth(req);
+1 -1
View File
@@ -14,7 +14,7 @@ import { Button } from "@/components/ui/button";
import { MoreVertical, Plus, Rocket } from "lucide-react";
import InstallApplicationDialog from "@/components/dialogs/InstallApplication";
import { useEffect, useState } from "react";
import { getApplications, removeApp } from "@/core/application";
import { getApplications, removeApp } from "@/lib/dockship/application";
import RemoveDialog from "@/components/dialogs/Remove";
import { Application } from "@/lib/types";
+2 -2
View File
@@ -14,8 +14,8 @@ import {
} from "lucide-react";
import { useEffect, useState } from "react";
import { Application, Deployment } from "@/lib/types";
import { getDeployments } from "@/core/server/deployment";
import { getApplications } from "@/core/application";
import { getDeployments } from "@/core/deployment";
import { getApplications } from "@/lib/dockship/application";
import { cn } from "@/lib/utils";
export default function DeploymentsPage() {
+1 -1
View File
@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Trash2, Eye, EyeOff, Copy, Save, Lock, Loader2 } from "lucide-react";
import { Env } from "@/lib/types";
import { getEnv, setEnv } from "@/core/application";
import { getEnv, setEnv } from "@/lib/dockship/application";
import { AddVariableDialog } from "@/components/dialogs/AddVariable";
import {
DropdownMenu,
+1 -1
View File
@@ -8,7 +8,7 @@ import { Badge } from "@/components/ui/badge";
import { Pause, Play, Download, Filter, RefreshCw } from "lucide-react";
import { cn } from "@/lib/utils";
import { Log } from "@/lib/types";
import { getAllContainerLogs } from "@/core/docker";
import { getAllContainerLogs } from "@/lib/dockship/docker";
export default function LogsPage() {
const [isPaused, setIsPaused] = useState(false);
+3 -3
View File
@@ -5,13 +5,13 @@ import { Progress } from "@/components/ui/progress";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { type ChartConfig } from "@/components/ui/chart";
import GradientAreaChart from "@/components/GradientAreaChart";
import { getMetrics } from "@/core/metrics";
import { getMetrics } from "@/lib/dockship/metrics";
import { useEffect, useState } from "react";
import { useIsMobile } from "@/hooks/use-mobile";
import RadialChart from "@/components/RadialChart";
import { getLiveNodes, getNodes } from "@/core/node";
import { getLiveNodes, getNodes } from "@/lib/dockship/node";
import { average } from "@/lib/format";
import { getContainerStats, getDocker } from "@/core/docker";
import { getContainerStats, getDocker } from "@/lib/dockship/docker";
import { SimpleStats } from "@/lib/types";
const cpuChartConfig = {
+3 -3
View File
@@ -8,11 +8,11 @@ import { Server, Plus, Settings } from "lucide-react";
import { Progress } from "@/components/ui/progress";
import { Docker, Node, NodeLiveData } from "@/lib/types";
import { useEffect, useState } from "react";
import { getLiveNodes } from "@/core/node";
import { getLiveNodes } from "@/lib/dockship/node";
import { formatBytes } from "@/lib/format";
import { setNodes as updateNodes } from "@/core/node";
import { setNodes as updateNodes } from "@/lib/dockship/node";
import AddNode from "@/components/dialogs/AddNode";
import { getDocker } from "@/core/docker";
import { getDocker } from "@/lib/dockship/docker";
import RemoveDialog from "@/components/dialogs/Remove";
function DiskUsage({ liveData }: NodeLiveData) {
+3 -3
View File
@@ -16,11 +16,11 @@ import {
} from "lucide-react";
import { Progress } from "@/components/ui/progress";
import Link from "next/link";
import { getLiveNodes } from "@/core/node";
import { getLiveNodes } from "@/lib/dockship/node";
import { useEffect, useState } from "react";
import { Application, Docker, Node, NodeLiveData } from "@/lib/types";
import { getDocker } from "@/core/docker";
import { getApplications } from "@/core/application";
import { getDocker } from "@/lib/dockship/docker";
import { getApplications } from "@/lib/dockship/application";
import { cn } from "@/lib/utils";
const recentDeployments = [
+1 -1
View File
@@ -2,7 +2,7 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import redirectAuth from "@/core/auth/redirect";
import redirectAuth from "@/lib/dockship/redirect";
import { GithubIcon } from "lucide-react";
import Image from "next/image";
+1 -1
View File
@@ -18,7 +18,7 @@ import {
authNode,
getNodes,
setNodes as updateNodes,
} from "@/core/node";
} from "@/lib/dockship/node";
export default function AddNode({ children }: { children: ReactNode }) {
const [step, setStep] = useState<1 | 2>(1);
@@ -13,10 +13,10 @@ import {
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { getLiveNodes } from "@/core/node";
import { getLiveNodes } from "@/lib/dockship/node";
import { Combobox } from "../ui/combobox";
import { Node, NodeLiveData } from "@/lib/types";
import { installApp } from "@/core/application";
import { installApp } from "@/lib/dockship/application";
export default function InstallApplicationDialog({
children,
+105 -65
View File
@@ -1,88 +1,128 @@
"use server";
import { verifySession } from "./auth/session";
import { getNodes } from "./node";
import axios from "axios";
import { Env, EnvVariable, Application } from "@/lib/types";
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 { Application, Env, EnvVariable } from "@/lib/types";
export async function installApp(name: string, repo: string, nodeId: string) {
if (await verifySession()) {
throw new Error("Unauthorized");
}
const nodes = await getNodes();
const targetNode = nodes.find((node) => node.node_id === nodeId);
return await (
await axios.post(
`http://${targetNode?.ip}:3000/api/applications`,
{ name, repo, nodeId },
{
headers: { Authorization: `ApiKey ${targetNode?.key}` },
}
)
).data.applications;
}
const docker = new Docker();
const execAsync = util.promisify(exec);
const DATA_DIR = path.join(process.cwd(), "data", "apps");
export async function getApplications(): Promise<Application[]> {
const nodes = await getNodes();
return fs
.readdirSync(DATA_DIR)
.map((appId) =>
JSON.parse(
fs.readFileSync(path.join(DATA_DIR, appId, "app.json")).toString()
)
);
}
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();
export async function installApp(name: string, repo: string, nodeId: string) {
// Create app ID
const appId = `app_${randomUUID()}`;
const appDir = path.join(DATA_DIR, appId);
const repoDir = path.join(appDir, "repo");
// Create directories
fs.mkdirSync(repoDir, { recursive: true });
// Write app.json
const appConfig: Application = {
id: appId,
name,
repo,
nodeId,
createdAt: new Date().toISOString(),
deployments: [],
};
fs.writeFileSync(
path.join(appDir, "app.json"),
JSON.stringify(appConfig, null, 2)
);
// Write env.json
fs.writeFileSync(path.join(appDir, "env.json"), JSON.stringify([], null, 2));
// Clone repository
try {
await execAsync(`git clone ${repo} ${repoDir}`);
} catch (err) {
throw new Error("Failed to clone repository");
}
// Validate Dockerfile
const dockerfilePath = path.join(repoDir, "Dockerfile");
try {
fs.accessSync(dockerfilePath);
} catch {
fs.rmSync(appDir, { recursive: true });
throw new Error("Dockerfile not found in repository root");
}
return {
success: true,
appId,
};
}
export async function getEnv(): Promise<Record<string, Env>> {
const nodes = await getNodes();
const apps = fs.readdirSync(DATA_DIR);
const envRecords = await Promise.all(
nodes.map(async (n) => {
const response = await axios.get(`http://${n.ip}:3000/api/env`, {
headers: { Authorization: `ApiKey ${n.key}` },
});
return response.data as Record<string, Env>;
})
return apps.reduce((acc, appId) => {
const appPath = path.join(DATA_DIR, appId, "app.json");
const name = JSON.parse(fs.readFileSync(appPath).toString()).name;
const envPath = path.join(DATA_DIR, appId, "env.json");
if (fs.existsSync(envPath)) {
const envData: Record<string, EnvVariable> = JSON.parse(
fs.readFileSync(envPath, "utf-8")
);
const merged: Record<string, Env> = Object.assign({}, ...envRecords);
return merged;
acc[appId] = { variables: envData, id: appId, name };
}
return acc;
}, {} as Record<string, Env>);
}
export async function setEnv(
appId: string,
variables: Record<string, EnvVariable>
) {
const nodes = await getNodes();
export async function setEnv(appId: string, variables: EnvVariable[]) {
const apps = fs.readdirSync(DATA_DIR);
nodes.forEach(async (n) => {
await axios.post(
`http://${n.ip}:3000/api/env`,
{ appId, variables },
{
headers: { Authorization: `ApiKey ${n.key}` },
apps.forEach((id) => {
if (id === appId) {
const envPath = path.join(DATA_DIR, appId, "env.json");
fs.writeFileSync(envPath, JSON.stringify(variables));
}
);
});
}
export async function removeApp(appId: string) {
const nodes = await getNodes();
const appPath = path.join(DATA_DIR, appId);
nodes.forEach(async (n) => {
await axios.delete(`http://${n.ip}:3000/api/applications`, {
params: { appId },
headers: { Authorization: `ApiKey ${n.key}` },
const appMeta: Application = JSON.parse(
fs.readFileSync(path.join(appPath, "app.json")).toString()
);
appMeta.deployments.forEach(async (imgId) => {
// Remove containers
(await docker.listContainers({ all: true }))
.filter((con) => con.ImageID === imgId)
.forEach((con) => {
const container = docker.getContainer(con.Id);
container.stop();
container.remove();
});
// Remove image
const image = docker.getImage(imgId);
image.remove();
});
fs.rmSync(appPath, { recursive: true });
}
+86 -84
View File
@@ -1,110 +1,112 @@
"use server";
import axios from "axios";
import { Deployment, Docker, Log, Node, SimpleStats } from "@/lib/types";
import { verifySession } from "./auth/session";
import { getNodes } from "./node";
import { CPUusage } from "@/lib/server/calc";
import { Log, SimpleStats } from "@/lib/types";
import Docker from "dockerode";
export async function getDocker(nodes: Node[]): Promise<Docker[]> {
if (await verifySession()) return [];
const docker = new Docker();
return await Promise.all(
nodes.map(async (node) => {
const data = await (
await axios.get(`http://${node.ip}:3000/api/container/`, {
headers: { Authorization: `ApiKey ${node.key}` },
})
).data;
return { ...node, containers: data.containers, version: data.version };
})
);
export async function getDocker() {
const version = await docker.version();
const containers = await docker.listContainers({ all: true });
return { version, containers };
}
export async function getContainerStats(
containerId: string
): Promise<SimpleStats | undefined> {
const nodes = await getNodes();
const container = docker.getContainer(containerId);
return (
await Promise.all(
nodes.map(
async (n) =>
await (
await axios.get(`http://${n.ip}:3000/api/container/stats`, {
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
})
).data?.stats
)
)
).find((s) => s);
const data = await container.inspect();
if (!data.State.Running) {
return;
}
const stats = await container.stats({ stream: false });
return {
cpu: CPUusage(stats),
memory: (stats.memory_stats.usage / stats.memory_stats.limit) * 100,
};
}
export async function startContainer(containerId: string) {
const nodes = await getNodes();
nodes.forEach(
async (n) =>
await (
await axios.post(
`http://${n.ip}:3000/api/container/start`,
{ containerId },
{
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
}
)
).data
);
await docker.getContainer(containerId).start();
}
export async function stopContainer(containerId: string) {
const nodes = await getNodes();
nodes.forEach(
async (n) =>
await (
await axios.delete(`http://${n.ip}:3000/api/container/start`, {
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
})
).data
);
await docker.getContainer(containerId).stop();
}
export async function removeContainer(containerId: string) {
const nodes = await getNodes();
await docker.getContainer(containerId).remove();
}
nodes.forEach(
async (n) =>
await (
await axios.delete(`http://${n.ip}:3000/api/container`, {
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
})
).data
);
function inferLevel(message: string): "info" | "warn" | "error" {
const msg = message.toLowerCase();
if (msg.includes("error") || msg.includes("fatal")) return "error";
if (msg.includes("warn")) return "warn";
return "info";
}
function demuxLogs(buffer: Buffer): string {
let offset = 0;
let output = "";
while (offset < buffer.length) {
const streamType = buffer[offset];
const length = buffer.readUInt32BE(offset + 4);
offset += 8;
output += buffer.slice(offset, offset + length).toString("utf8");
offset += length;
}
return output;
}
async function getContainerLogs(
containerId: string,
name: string
): Promise<Log[]> {
const container = docker.getContainer(containerId);
const raw = await container.logs({
stdout: true,
stderr: true,
timestamps: true,
});
const text = demuxLogs(raw);
return text
.split("\n")
.filter(Boolean)
.map((line) => {
const firstSpace = line.indexOf(" ");
const timestamp = line.slice(0, firstSpace);
const message = line.slice(firstSpace + 1);
return {
timestamp,
level: inferLevel(message),
source: name,
message,
} satisfies Log;
});
}
export async function getAllContainerLogs(): Promise<Log[]> {
const nodes = await getNodes();
const containers = await docker.listContainers({ all: true });
return (
await Promise.all(
nodes.map(
async (n) =>
await (
await axios.get(`http://${n.ip}:3000/api/logs`, {
headers: { Authorization: `ApiKey ${n.key}` },
})
).data
)
)
)
.flat()
.sort(
(a, b) =>
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
const logs = await Promise.all(
containers.map((c) => getContainerLogs(c.Id, c.Names[0].replace("/", "")))
);
return logs
.flat()
.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
}
-128
View File
@@ -1,128 +0,0 @@
"use server";
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 { 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<Application[]> {
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) {
// Create app ID
const appId = `app_${randomUUID()}`;
const appDir = path.join(DATA_DIR, appId);
const repoDir = path.join(appDir, "repo");
// Create directories
fs.mkdirSync(repoDir, { recursive: true });
// Write app.json
const appConfig: Application = {
id: appId,
name,
repo,
nodeId,
createdAt: new Date().toISOString(),
deployments: [],
};
fs.writeFileSync(
path.join(appDir, "app.json"),
JSON.stringify(appConfig, null, 2)
);
// Write env.json
fs.writeFileSync(path.join(appDir, "env.json"), JSON.stringify([], null, 2));
// Clone repository
try {
await execAsync(`git clone ${repo} ${repoDir}`);
} catch (err) {
throw new Error("Failed to clone repository");
}
// Validate Dockerfile
const dockerfilePath = path.join(repoDir, "Dockerfile");
try {
fs.accessSync(dockerfilePath);
} catch {
fs.rmSync(appDir, { recursive: true });
throw new Error("Dockerfile not found in repository root");
}
return {
success: true,
appId,
};
}
export async function getEnv(): Promise<Record<string, Env>> {
const apps = fs.readdirSync(DATA_DIR);
return apps.reduce((acc, appId) => {
const appPath = path.join(DATA_DIR, appId, "app.json");
const name = JSON.parse(fs.readFileSync(appPath).toString()).name;
const envPath = path.join(DATA_DIR, appId, "env.json");
if (fs.existsSync(envPath)) {
const envData: Record<string, EnvVariable> = JSON.parse(
fs.readFileSync(envPath, "utf-8")
);
acc[appId] = { variables: envData, id: appId, name };
}
return acc;
}, {} as Record<string, Env>);
}
export async function setEnv(appId: string, variables: EnvVariable[]) {
const apps = fs.readdirSync(DATA_DIR);
apps.forEach((id) => {
if (id === appId) {
const envPath = path.join(DATA_DIR, appId, "env.json");
fs.writeFileSync(envPath, JSON.stringify(variables));
}
});
}
export async function removeApp(appId: string) {
const appPath = path.join(DATA_DIR, appId);
const appMeta: Application = JSON.parse(
fs.readFileSync(path.join(appPath, "app.json")).toString()
);
appMeta.deployments.forEach(async (imgId) => {
// Remove containers
(await docker.listContainers({ all: true }))
.filter((con) => con.ImageID === imgId)
.forEach((con) => {
const container = docker.getContainer(con.Id);
container.stop();
container.remove();
});
// Remove image
const image = docker.getImage(imgId);
image.remove();
});
fs.rmSync(appPath, { recursive: true });
}
-112
View File
@@ -1,112 +0,0 @@
"use server";
import { CPUusage } from "@/lib/server/calc";
import { Log, SimpleStats } from "@/lib/types";
import Docker from "dockerode";
const docker = new Docker();
export async function getDocker() {
const version = await docker.version();
const containers = await docker.listContainers({ all: true });
return { version, containers };
}
export async function getContainerStats(
containerId: string
): Promise<SimpleStats | undefined> {
const container = docker.getContainer(containerId);
const data = await container.inspect();
if (!data.State.Running) {
return;
}
const stats = await container.stats({ stream: false });
return {
cpu: CPUusage(stats),
memory: (stats.memory_stats.usage / stats.memory_stats.limit) * 100,
};
}
export async function startContainer(containerId: string) {
await docker.getContainer(containerId).start();
}
export async function stopContainer(containerId: string) {
await docker.getContainer(containerId).stop();
}
export async function removeContainer(containerId: string) {
await docker.getContainer(containerId).remove();
}
function inferLevel(message: string): "info" | "warn" | "error" {
const msg = message.toLowerCase();
if (msg.includes("error") || msg.includes("fatal")) return "error";
if (msg.includes("warn")) return "warn";
return "info";
}
function demuxLogs(buffer: Buffer): string {
let offset = 0;
let output = "";
while (offset < buffer.length) {
const streamType = buffer[offset];
const length = buffer.readUInt32BE(offset + 4);
offset += 8;
output += buffer.slice(offset, offset + length).toString("utf8");
offset += length;
}
return output;
}
async function getContainerLogs(
containerId: string,
name: string
): Promise<Log[]> {
const container = docker.getContainer(containerId);
const raw = await container.logs({
stdout: true,
stderr: true,
timestamps: true,
});
const text = demuxLogs(raw);
return text
.split("\n")
.filter(Boolean)
.map((line) => {
const firstSpace = line.indexOf(" ");
const timestamp = line.slice(0, firstSpace);
const message = line.slice(firstSpace + 1);
return {
timestamp,
level: inferLevel(message),
source: name,
message,
} satisfies Log;
});
}
export async function getAllContainerLogs(): Promise<Log[]> {
const containers = await docker.listContainers({ all: true });
const logs = await Promise.all(
containers.map((c) => getContainerLogs(c.Id, c.Names[0].replace("/", "")))
);
return logs
.flat()
.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
}
+88
View File
@@ -0,0 +1,88 @@
"use server";
import { verifySession } from "../../core/auth/session";
import { getNodes } from "@/lib/dockship/node";
import axios from "axios";
import { Env, EnvVariable, Application } from "@/lib/types";
export async function installApp(name: string, repo: string, nodeId: string) {
if (await verifySession()) {
throw new Error("Unauthorized");
}
const nodes = await getNodes();
const targetNode = nodes.find((node) => node.node_id === nodeId);
return await (
await axios.post(
`http://${targetNode?.ip}:3000/api/applications`,
{ name, repo, nodeId },
{
headers: { Authorization: `ApiKey ${targetNode?.key}` },
}
)
).data.applications;
}
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();
}
export async function getEnv(): Promise<Record<string, Env>> {
const nodes = await getNodes();
const envRecords = await Promise.all(
nodes.map(async (n) => {
const response = await axios.get(`http://${n.ip}:3000/api/env`, {
headers: { Authorization: `ApiKey ${n.key}` },
});
return response.data as Record<string, Env>;
})
);
const merged: Record<string, Env> = Object.assign({}, ...envRecords);
return merged;
}
export async function setEnv(
appId: string,
variables: Record<string, EnvVariable>
) {
const nodes = await getNodes();
nodes.forEach(async (n) => {
await axios.post(
`http://${n.ip}:3000/api/env`,
{ appId, variables },
{
headers: { Authorization: `ApiKey ${n.key}` },
}
);
});
}
export async function removeApp(appId: string) {
const nodes = await getNodes();
nodes.forEach(async (n) => {
await axios.delete(`http://${n.ip}:3000/api/applications`, {
params: { appId },
headers: { Authorization: `ApiKey ${n.key}` },
});
});
}
+110
View File
@@ -0,0 +1,110 @@
"use server";
import axios from "axios";
import { Deployment, Docker, Log, Node, SimpleStats } from "@/lib/types";
import { verifySession } from "../../core/auth/session";
import { getNodes } from "./node";
export async function getDocker(nodes: Node[]): Promise<Docker[]> {
if (await verifySession()) return [];
return await Promise.all(
nodes.map(async (node) => {
const data = await (
await axios.get(`http://${node.ip}:3000/api/container/`, {
headers: { Authorization: `ApiKey ${node.key}` },
})
).data;
return { ...node, containers: data.containers, version: data.version };
})
);
}
export async function getContainerStats(
containerId: string
): Promise<SimpleStats | undefined> {
const nodes = await getNodes();
return (
await Promise.all(
nodes.map(
async (n) =>
await (
await axios.get(`http://${n.ip}:3000/api/container/stats`, {
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
})
).data?.stats
)
)
).find((s) => s);
}
export async function startContainer(containerId: string) {
const nodes = await getNodes();
nodes.forEach(
async (n) =>
await (
await axios.post(
`http://${n.ip}:3000/api/container/start`,
{ containerId },
{
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
}
)
).data
);
}
export async function stopContainer(containerId: string) {
const nodes = await getNodes();
nodes.forEach(
async (n) =>
await (
await axios.delete(`http://${n.ip}:3000/api/container/start`, {
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
})
).data
);
}
export async function removeContainer(containerId: string) {
const nodes = await getNodes();
nodes.forEach(
async (n) =>
await (
await axios.delete(`http://${n.ip}:3000/api/container`, {
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
})
).data
);
}
export async function getAllContainerLogs(): Promise<Log[]> {
const nodes = await getNodes();
return (
await Promise.all(
nodes.map(
async (n) =>
await (
await axios.get(`http://${n.ip}:3000/api/logs`, {
headers: { Authorization: `ApiKey ${n.key}` },
})
).data
)
)
)
.flat()
.sort(
(a, b) =>
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
);
}
@@ -3,7 +3,7 @@
import { read, write } from "@/app/api/file";
import { Node, NodeLiveData } from "@/lib/types";
import axios from "axios";
import { verifySession } from "./auth/session";
import { verifySession } from "../../core/auth/session";
export async function getNodes(): Promise<Node[]> {
if (await verifySession()) return [];