ship: project dashboard pages + sidebar/chat overhaul + log tooling
Ships accumulated WIP that was sitting uncommitted: - New (home) dashboard route pages: overview, code, data/tables, hosting, infrastructure, services, domains, integrations, agents, analytics, api, automations, billing, logs, market, marketing(+seo/social), product, security, storage, users, settings(app/auth). - dashboard-sidebar, project-icon-rail, chat-panel updates; mcp + anatomy route changes; package.json/lock dependency bumps. - Coolify log tooling (scripts/fetch-app-logs.mjs + fetch-app-logs-ssh.mjs) and ai-new-thread.md "Fetching Production Logs" section. Excludes throwaway debug scripts and telemetry audit dumps (the latter contain live credentials and must not be committed).
This commit is contained in:
89
vibn-frontend/scripts/fetch-app-logs-ssh.mjs
Normal file
89
vibn-frontend/scripts/fetch-app-logs-ssh.mjs
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Fetch a Coolify app's FULL container logs via SSH + `docker logs`.
|
||||
*
|
||||
* More powerful than the REST API path (scripts/fetch-app-logs.mjs): it includes
|
||||
* timestamps, supports a `--since` date filter, and works for both dockerfile and
|
||||
* dockercompose apps. Use this when the REST `/logs` endpoint returns little or
|
||||
* nothing (e.g. quiet services, or compose apps Coolify can't tail via the API).
|
||||
*
|
||||
* Usage (from the vibn-frontend/ directory):
|
||||
* node --env-file=.env.local scripts/fetch-app-logs-ssh.mjs <appUuid> [sinceISO] [tail]
|
||||
*
|
||||
* Examples:
|
||||
* # Everything since the start of today (UTC)
|
||||
* node --env-file=.env.local scripts/fetch-app-logs-ssh.mjs hou4vy5mtyg5mrx3w4nl2lxv 2026-06-12
|
||||
* # Last 500 lines, no date filter
|
||||
* node --env-file=.env.local scripts/fetch-app-logs-ssh.mjs hou4vy5mtyg5mrx3w4nl2lxv "" 500
|
||||
*
|
||||
* The <appUuid> is the last path segment of the Coolify app URL.
|
||||
* Env (from vibn-frontend/.env.local):
|
||||
* COOLIFY_SSH_HOST, COOLIFY_SSH_PORT, COOLIFY_SSH_USER, COOLIFY_SSH_PRIVATE_KEY_B64
|
||||
*/
|
||||
import ssh2 from "ssh2";
|
||||
const { Client } = ssh2;
|
||||
|
||||
const uuid = process.argv[2];
|
||||
const since = process.argv[3] || ""; // optional ISO date, e.g. 2026-06-12
|
||||
const tail = process.argv[4] || "2000";
|
||||
|
||||
if (!uuid) {
|
||||
console.error(
|
||||
"Usage: node --env-file=.env.local scripts/fetch-app-logs-ssh.mjs <appUuid> [sinceISO] [tail]",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const keyB64 = process.env.COOLIFY_SSH_PRIVATE_KEY_B64;
|
||||
if (!process.env.COOLIFY_SSH_HOST || !keyB64) {
|
||||
console.error("Missing COOLIFY_SSH_HOST / COOLIFY_SSH_PRIVATE_KEY_B64 in .env.local");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cfg = {
|
||||
host: process.env.COOLIFY_SSH_HOST,
|
||||
port: Number(process.env.COOLIFY_SSH_PORT ?? 22),
|
||||
username: process.env.COOLIFY_SSH_USER ?? "vibn-logs",
|
||||
privateKey: Buffer.from(keyB64, "base64").toString("utf8"),
|
||||
readyTimeout: 8000,
|
||||
};
|
||||
|
||||
function runRemote(cmd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = new Client();
|
||||
let out = "";
|
||||
let errOut = "";
|
||||
conn
|
||||
.on("ready", () => {
|
||||
conn.exec(cmd, (err, stream) => {
|
||||
if (err) {
|
||||
conn.end();
|
||||
return reject(err);
|
||||
}
|
||||
stream
|
||||
.on("close", (code) => {
|
||||
conn.end();
|
||||
resolve({ code, out, errOut });
|
||||
})
|
||||
.on("data", (d) => (out += d.toString()))
|
||||
.stderr.on("data", (d) => (errOut += d.toString()));
|
||||
});
|
||||
})
|
||||
.on("error", reject)
|
||||
.connect(cfg);
|
||||
});
|
||||
}
|
||||
|
||||
// Resolve the container by name (Coolify names them with the app UUID), then
|
||||
// dump its logs. The $(...) here runs on the REMOTE host, not locally.
|
||||
const sinceFlag = since ? `--since ${since}` : "";
|
||||
const cmd =
|
||||
`cid=$(docker ps -a --filter name=${uuid} --format '{{.Names}}' | head -1); ` +
|
||||
`if [ -z "$cid" ]; then echo "NO_CONTAINER for ${uuid}"; exit 2; fi; ` +
|
||||
`echo "# container=$cid"; ` +
|
||||
`docker logs --timestamps ${sinceFlag} --tail ${tail} "$cid" 2>&1`;
|
||||
|
||||
const { code, out, errOut } = await runRemote(cmd);
|
||||
if (out) process.stdout.write(out);
|
||||
if (errOut) process.stderr.write(errOut);
|
||||
process.exit(code ?? 0);
|
||||
69
vibn-frontend/scripts/fetch-app-logs.mjs
Normal file
69
vibn-frontend/scripts/fetch-app-logs.mjs
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Fetch a Coolify application's runtime (container stdout/stderr) logs via the
|
||||
* Coolify REST API — no dashboard login needed.
|
||||
*
|
||||
* Usage (from the vibn-frontend/ directory):
|
||||
* node scripts/fetch-app-logs.mjs <appUuid> [lines]
|
||||
*
|
||||
* The <appUuid> is the last path segment of the Coolify app URL, e.g.
|
||||
* https://coolify.vibnai.com/project/.../application/hou4vy5mtyg5mrx3w4nl2lxv
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^ appUuid
|
||||
*
|
||||
* Reads COOLIFY_URL + COOLIFY_API_TOKEN from vibn-frontend/.env.local.
|
||||
* Note: Coolify's /logs endpoint returns the *last N lines* of container output,
|
||||
* not a date range — pull a generous N and filter by date client-side if needed.
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function loadEnv(file) {
|
||||
const out = {};
|
||||
try {
|
||||
for (const line of fs.readFileSync(file, "utf8").split("\n")) {
|
||||
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
|
||||
if (!m) continue;
|
||||
let v = m[2];
|
||||
if (
|
||||
(v.startsWith('"') && v.endsWith('"')) ||
|
||||
(v.startsWith("'") && v.endsWith("'"))
|
||||
)
|
||||
v = v.slice(1, -1);
|
||||
out[m[1]] = v;
|
||||
}
|
||||
} catch {}
|
||||
return out;
|
||||
}
|
||||
|
||||
const env = { ...loadEnv(path.join(__dirname, "../.env.local")), ...process.env };
|
||||
const COOLIFY_URL = env.COOLIFY_URL || "https://coolify.vibnai.com";
|
||||
const TOKEN = env.COOLIFY_API_TOKEN;
|
||||
const uuid = process.argv[2];
|
||||
const lines = Math.max(1, Math.min(parseInt(process.argv[3] || "1000", 10), 5000));
|
||||
|
||||
if (!uuid) {
|
||||
console.error("Usage: node scripts/fetch-app-logs.mjs <appUuid> [lines]");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!TOKEN) {
|
||||
console.error("Missing COOLIFY_API_TOKEN in vibn-frontend/.env.local");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const url = `${COOLIFY_URL}/api/v1/applications/${uuid}/logs?lines=${lines}`;
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${TOKEN}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!res.ok) {
|
||||
console.error(`Coolify API ${res.status} on ${url}:\n${await res.text()}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const data = await res.json();
|
||||
process.stdout.write((data?.logs ?? "").toString());
|
||||
process.stdout.write("\n");
|
||||
Reference in New Issue
Block a user