Import source: backend/src/routes/panel

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:09:29 +03:30
parent d86779259c
commit e41589f3f5
+435
View File
@@ -0,0 +1,435 @@
import type { FastifyPluginAsync } from "fastify";
import { requireSession, accountIdFor } from "../lib/auth.js";
import { fail, ok } from "../lib/envelope.js";
import {
databaseDto,
mailboxDto,
resourceUsageDto,
serverDto,
userDto,
} from "../lib/serializers.js";
const services = [
{ name: "Web server", status: "healthy", uptime: "18 days", detail: "nginx active" },
{ name: "PHP-FPM", status: "healthy", uptime: "18 days", detail: "8 pools running" },
{ name: "Database", status: "healthy", uptime: "18 days", detail: "PostgreSQL responding" },
{ name: "DNS", status: "healthy", uptime: "18 days", detail: "authoritative zones synced" },
{ name: "Email", status: "warning", uptime: "7 days", detail: "14 deferred messages" },
{ name: "Redis", status: "healthy", uptime: "18 days", detail: "queue cache ready" },
{ name: "Queue", status: "healthy", uptime: "18 days", detail: "workers idle" },
{ name: "Firewall", status: "healthy", uptime: "18 days", detail: "38 IPs blocked" },
{ name: "Backup service", status: "healthy", uptime: "18 days", detail: "last run verified" },
{
name: "Monitoring service",
status: "healthy",
uptime: "18 days",
detail: "all probes green",
},
] as const;
const files = [
{
id: "file-1",
name: "public_html",
path: "/home/coaxys/public_html",
type: "directory",
size: 0,
modifiedAt: "2026-06-12T10:12:00.000Z",
owner: "coaxys",
permissions: "0755",
mime: "inode/directory",
},
{
id: "file-2",
name: "index.php",
path: "/home/coaxys/public_html/index.php",
type: "file",
size: 18432,
modifiedAt: "2026-06-12T08:44:00.000Z",
owner: "coaxys",
permissions: "0644",
mime: "application/x-php",
},
{
id: "file-3",
name: ".env",
path: "/home/coaxys/app/.env",
type: "file",
size: 1024,
modifiedAt: "2026-06-11T22:03:00.000Z",
owner: "coaxys",
permissions: "0600",
mime: "text/plain",
hidden: true,
},
] as const;
const runtimes = [
{
id: "php",
name: "PHP",
versions: ["8.1", "8.2", "8.3", "8.4"],
extensions: ["opcache", "intl", "imagick", "redis", "pdo_mysql"],
processManagers: ["PHP-FPM"],
environmentSchema: ["APP_ENV", "APP_DEBUG", "PHP_MEMORY_LIMIT"],
},
{
id: "nodejs",
name: "Node.js",
versions: ["20", "22", "24"],
extensions: ["npm", "pnpm", "yarn"],
processManagers: ["pm2", "systemd"],
environmentSchema: ["NODE_ENV", "PORT", "NPM_CONFIG_PRODUCTION"],
},
{
id: "python",
name: "Python",
versions: ["3.11", "3.12", "3.13"],
extensions: ["pip", "venv", "uvicorn", "gunicorn"],
processManagers: ["systemd", "supervisor"],
environmentSchema: ["PYTHONPATH", "VIRTUAL_ENV", "PORT"],
},
] as const;
function accountDto(account: {
id: string;
username: string;
primaryDomain: string;
homeDirectory: string;
sharedIp: string;
dedicatedIp: string | null;
theme: string;
}) {
return {
id: account.id,
username: account.username,
primaryDomain: account.primaryDomain,
homeDirectory: account.homeDirectory,
sharedIp: account.sharedIp,
dedicatedIp: account.dedicatedIp ?? undefined,
theme: account.theme,
};
}
function iso(value: Date | null) {
return value?.toISOString() ?? new Date(0).toISOString();
}
export const panelRoutes: FastifyPluginAsync = async (app) => {
app.get("/api/dashboard/summary", async (request) => {
const session = await requireSession(app, request);
const accountId = accountIdFor(session);
const [account, accounts, serverList, resources, notifications, metrics] =
await Promise.all([
app.prisma.hostingAccount.findUniqueOrThrow({ where: { id: accountId } }),
app.prisma.hostingAccount.findMany({
where: { users: { some: { userId: session.userId } } },
orderBy: { username: "asc" },
}),
app.prisma.server.findMany({ orderBy: { name: "asc" } }),
app.prisma.resourceUsage.findMany({ where: { accountId }, orderBy: { key: "asc" } }),
app.prisma.notification.findMany({
where: { accountId },
orderBy: { createdAt: "desc" },
take: 8,
}),
app.prisma.metricPoint.findMany({
where: { accountId },
orderBy: { timestamp: "asc" },
take: 48,
}),
]);
return ok(request, {
user: userDto(session.user),
account: accountDto(account),
server: serverDto(serverList[0]!),
accounts: accounts.map(accountDto),
servers: serverList.map(serverDto),
resources: resources.map(resourceUsageDto),
services,
notifications: notifications.map((notification) => ({
id: notification.id,
title: notification.title,
category: notification.category,
read: notification.read,
createdAt: notification.createdAt.toISOString(),
body: notification.body,
})),
metrics: metrics.map((point) => ({
timestamp: point.timestamp.toISOString(),
cpu: point.cpu,
memory: point.memory,
disk: point.disk,
bandwidth: point.bandwidth,
requests: point.requests,
})),
});
});
app.get("/api/files", async (request) => {
await requireSession(app, request);
return ok(request, files);
});
app.post("/api/files/operations", async (request) => {
await requireSession(app, request);
const body = request.body as { operation?: string } | undefined;
return ok(request, {
id: `job-${Date.now()}`,
label: body?.operation ?? "File operation",
status: "running",
progress: 64,
startedAt: new Date().toISOString(),
message: "Operation is queued in the control plane and scoped to the hosting account.",
});
});
app.get("/api/databases", async (request) => {
const session = await requireSession(app, request);
const databases = await app.prisma.database.findMany({
where: { accountId: accountIdFor(session) },
include: { users: true },
orderBy: { name: "asc" },
});
return ok(request, databases.map(databaseDto));
});
app.post("/api/databases", async (request, reply) => {
const session = await requireSession(app, request);
const accountId = accountIdFor(session);
const body = request.body as { name?: string } | undefined;
const name = body?.name?.trim();
if (!name || name.length < 2) {
return fail(request, reply, 400, {
code: "VALIDATION_ERROR",
message: "Database name is required.",
fields: { name: "Use at least two characters." },
});
}
const existingDatabase = await app.prisma.database.findFirst({
where: { accountId, name },
select: { id: true },
});
if (existingDatabase) {
return fail(request, reply, 409, {
code: "VALIDATION_ERROR",
message: "A database with this name already exists.",
fields: { name: "Choose a unique database name for this account." },
});
}
const database = await app.prisma.database.create({
data: {
name,
engine: "MySQL",
size: 0n,
tables: 0,
connections: 0,
lastBackup: new Date(),
accountId,
},
});
return reply
.status(201)
.send(ok(request, { id: database.id, name: database.name, status: "created" }));
});
app.get("/api/email/accounts", async (request) => {
const session = await requireSession(app, request);
const mailboxes = await app.prisma.mailbox.findMany({
where: { accountId: accountIdFor(session) },
include: { domain: true },
orderBy: { address: "asc" },
});
return ok(request, mailboxes.map(mailboxDto));
});
app.post("/api/email/accounts", async (request, reply) => {
const session = await requireSession(app, request);
const accountId = accountIdFor(session);
const body = request.body as { address?: string } | undefined;
const address = body?.address?.trim().toLowerCase();
const domainName = address?.split("@")[1];
const domain = domainName
? await app.prisma.domain.findFirst({
where: { accountId, name: domainName },
})
: null;
if (!address || !address.includes("@") || !domain) {
return fail(request, reply, 400, {
code: "VALIDATION_ERROR",
message: "Mailbox address is invalid or its domain is not managed by this account.",
fields: { address: "Use an address on a managed domain." },
});
}
const existingMailbox = await app.prisma.mailbox.findFirst({
where: { accountId, address },
select: { id: true },
});
if (existingMailbox) {
return fail(request, reply, 409, {
code: "VALIDATION_ERROR",
message: "A mailbox with this address already exists.",
fields: { address: "Choose a unique mailbox address." },
});
}
const mailbox = await app.prisma.mailbox.create({
data: {
address,
quota: 5_000_000_000n,
used: 0n,
forwarding: false,
spamScore: 0,
authStatus: "healthy",
domainId: domain.id,
accountId,
},
});
return reply
.status(201)
.send(ok(request, { id: mailbox.id, address: mailbox.address, status: "created" }));
});
app.get("/api/metrics/history", async (request) => {
const session = await requireSession(app, request);
const points = await app.prisma.metricPoint.findMany({
where: { accountId: accountIdFor(session) },
orderBy: { timestamp: "asc" },
take: 96,
});
return ok(
request,
points.map((point) => ({
timestamp: point.timestamp.toISOString(),
cpu: point.cpu,
memory: point.memory,
disk: point.disk,
bandwidth: point.bandwidth,
requests: point.requests,
})),
);
});
app.get("/api/security/findings", async (request) => {
const session = await requireSession(app, request);
const findings = await app.prisma.securityFinding.findMany({
where: { accountId: accountIdFor(session) },
orderBy: [{ severity: "asc" }, { title: "asc" }],
});
return ok(request, findings);
});
app.get("/api/backups", async (request) => {
const session = await requireSession(app, request);
const backups = await app.prisma.backup.findMany({
where: { accountId: accountIdFor(session) },
orderBy: { createdAt: "desc" },
});
return ok(
request,
backups.map((backup) => ({
id: backup.id,
name: backup.name,
scope: backup.scope,
destination: backup.destination,
size: Number(backup.size),
status: backup.status,
encrypted: backup.encrypted,
createdAt: backup.createdAt.toISOString(),
})),
);
});
app.post("/api/backups", async (request) => {
await requireSession(app, request);
const body = request.body as { scope?: string } | undefined;
return ok(request, {
id: `job-${Date.now()}`,
label: `Create ${body?.scope ?? "full"} backup`,
status: "queued",
progress: 0,
startedAt: new Date().toISOString(),
});
});
app.get("/api/automation", async (request) => {
const session = await requireSession(app, request);
const automations = await app.prisma.automation.findMany({
where: { accountId: accountIdFor(session) },
orderBy: { name: "asc" },
});
return ok(
request,
automations.map((automation) => ({
id: automation.id,
name: automation.name,
trigger: automation.trigger,
status: automation.status,
lastRun: iso(automation.lastRun),
successRate: automation.successRate,
})),
);
});
app.get("/api/notifications", async (request) => {
const session = await requireSession(app, request);
const notifications = await app.prisma.notification.findMany({
where: { accountId: accountIdFor(session) },
orderBy: { createdAt: "desc" },
});
return ok(
request,
notifications.map((notification) => ({
id: notification.id,
title: notification.title,
category: notification.category,
read: notification.read,
createdAt: notification.createdAt.toISOString(),
body: notification.body,
})),
);
});
app.get("/api/audit-logs", async (request) => {
const session = await requireSession(app, request);
const entries = await app.prisma.auditEntry.findMany({
where: { accountId: accountIdFor(session) },
orderBy: { createdAt: "desc" },
});
return ok(
request,
entries.map((entry) => ({
id: entry.id,
actor: entry.actor,
action: entry.action,
target: entry.target,
ipAddress: entry.ipAddress,
createdAt: entry.createdAt.toISOString(),
requestId: entry.requestId,
})),
);
});
app.get("/api/software/runtimes", async (request) => {
await requireSession(app, request);
return ok(request, runtimes);
});
app.get("/api/servers", async (request) => {
await requireSession(app, request);
const servers = await app.prisma.server.findMany({ orderBy: { name: "asc" } });
return ok(request, servers.map(serverDto));
});
app.get("/api/servers/services", async (request) => {
await requireSession(app, request);
return ok(request, services);
});
};