feat: enhance project structure with server and workflow management

- Introduced a new server package with Fastify for handling API requests and workflows.
- Implemented user authentication and authorization with JWT and cookie management.
- Added endpoints for managing workflows, runs, and user accounts.
- Established a dashboard for monitoring workflow status and statistics.
- Included a script sandbox for executing user-defined scripts securely.
- Updated README with setup instructions and API documentation.
- Configured database migrations for user management.
- Enhanced logging capabilities for better traceability.
This commit is contained in:
2026-08-14 14:25:37 +07:00
parent 5574d6f723
commit 023cf83cfa
48 changed files with 6127 additions and 359 deletions
+51
View File
@@ -0,0 +1,51 @@
export function formatTime(iso) {
if (!iso) return "—";
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return "—";
return d.toLocaleString();
}
export function StatusBadge({ status }) {
const cls =
status === "success"
? "badge-success"
: status === "failed"
? "badge-error"
: status === "running"
? "badge-warning"
: "badge-ghost";
return <span className={`badge badge-sm ${cls}`}>{status ?? "—"}</span>;
}
/** Config load + last-run health for the workflows list. */
export function WorkflowStatusBadge({ workflow }) {
if (workflow.loadError) {
return (
<span className="badge badge-error badge-sm" title={workflow.loadError}>
broken
</span>
);
}
if (!workflow.enabled) {
return <span className="badge badge-ghost badge-sm">disabled</span>;
}
if (workflow.lastStatus === "failed") {
return <span className="badge badge-error badge-sm">failed</span>;
}
if (workflow.lastStatus === "running") {
return <span className="badge badge-warning badge-sm">running</span>;
}
if (workflow.lastStatus === "success") {
return <span className="badge badge-success badge-sm">working</span>;
}
return <span className="badge badge-ghost badge-sm">never run</span>;
}
export function levelName(level) {
if (level <= 10) return "trace";
if (level <= 20) return "debug";
if (level <= 30) return "info";
if (level <= 40) return "warn";
if (level <= 50) return "error";
return "fatal";
}
+67
View File
@@ -0,0 +1,67 @@
function sanitize(label) {
const cleaned = String(label ?? "")
.replace(/[^A-Za-z0-9]+/g, "_")
.replace(/_+/g, "_")
.replace(/^_|_$/g, "")
.slice(0, 48);
return cleaned || "node";
}
function parseStep(step) {
if (typeof step === "string") return step;
if (step?.script) return step.script;
return null;
}
export function workflowToArchitecture(parsed) {
/** @type {Record<string, string>} */
const scriptIds = {};
if (!parsed || typeof parsed !== "object") {
return { chart: "", scriptIds };
}
const groupId = "wf";
const name = sanitize(parsed.name || "workflow");
const lines = ["architecture-beta", ` group ${groupId}[${name}]`];
const triggerIds = [];
const triggers = Array.isArray(parsed.triggers) ? parsed.triggers : [];
triggers.forEach((t, i) => {
const id = `t${i}`;
const icon = t?.type === "cron" ? "cloud" : "internet";
const label =
t?.type === "cron"
? `cron ${t.schedule ?? ""}`
: `${t?.method ?? "POST"} ${t?.path ?? ""}`;
lines.push(` service ${id}(${icon})[${sanitize(label)}] in ${groupId}`);
triggerIds.push(id);
});
const scripts = Array.isArray(parsed.scripts) ? parsed.scripts : [];
let prev = null;
let firstScript = null;
scripts.forEach((step, i) => {
const script = parseStep(step);
if (!script) return;
const id = `s${i}`;
scriptIds[id] = script;
lines.push(` service ${id}(server)[${sanitize(script)}] in ${groupId}`);
if (!firstScript) firstScript = id;
if (prev) lines.push(` ${prev}:R -- L:${id}`);
prev = id;
});
// All triggers point at the first script (not chained to each other).
if (firstScript) {
for (const tid of triggerIds) {
lines.push(` ${tid}:R -- L:${firstScript}`);
}
}
// Keep multiple triggers in one column so they don't overlap.
if (triggerIds.length >= 2) {
lines.push(` align column ${triggerIds.join(" ")}`);
}
return { chart: lines.join("\n"), scriptIds };
}