docs: add CLAUDE.md with project guidelines and conventions, document new devii admin quota reset endpoints and CLI commands, update README with coverage CI and devii_admin_daily_usd config, add European date normalization to ProjectForm model

This commit is contained in:
2026-06-08 22:30:25 +00:00
parent 841c1c7417
commit 547e4eda8c
45 changed files with 1844 additions and 99 deletions
+8 -4
View File
@@ -1,6 +1,7 @@
// retoor <retoor@molodetz.nl>
import { Http } from "./Http.js";
import { formatDate } from "./DateFormat.js";
class AiUsageMonitor {
constructor() {
@@ -122,7 +123,7 @@ class AiUsageMonitor {
render(data) {
this.root.textContent = "";
if (this.generated && data.generated_at) {
this.generated.textContent = `Window: ${data.window_hours}h - updated ${data.generated_at.slice(0, 19).replace("T", " ")}`;
this.generated.textContent = `Window: ${data.window_hours}h - updated ${formatDate(data.generated_at, true)}`;
}
if (!data.requests) {
this.root.appendChild(this.el("p", "admin-empty", "No AI gateway traffic recorded in this window yet."));
@@ -233,8 +234,10 @@ class AiUsageMonitor {
(b.user_agents || []).map((item) => [item.key, this.integer(item.requests)]))));
this.root.appendChild(divided);
const hourlyRows = (data.hourly || []).map((h) => [
h.hour.replace("T", " ") + ":00",
const hourlyRows = (data.hourly || []).map((h) => {
const [day, hour] = String(h.hour).split("T");
return [
`${formatDate(day)} ${(hour || "00").padStart(2, "0")}:00`,
this.integer(h.requests),
this.integer(h.success),
this.integer(h.failed),
@@ -245,7 +248,8 @@ class AiUsageMonitor {
this.integer(h.avg_latency_ms),
this.integer(h.active_owners),
this.integer(h.new_owners),
]);
];
});
this.root.appendChild(this.section("Hourly breakdown",
this.table(["Hour", "Requests", "OK", "Failed", "Prompt", "Completion", "Total", "Cost", "Avg ms", "Callers", "New"], hourlyRows)));
}
+20
View File
@@ -0,0 +1,20 @@
// retoor <retoor@molodetz.nl>
export function formatDate(value, includeTime = false) {
if (!value) return "-";
let input = value;
if (typeof input === "string" && /^\d{4}-\d{2}-\d{2}$/.test(input)) {
input = `${input}T00:00:00`;
}
const date = new Date(input);
if (Number.isNaN(date.getTime())) return String(value).slice(0, 19);
const dd = String(date.getDate()).padStart(2, "0");
const mm = String(date.getMonth() + 1).padStart(2, "0");
const yyyy = date.getFullYear();
if (includeTime) {
const hh = String(date.getHours()).padStart(2, "0");
const mi = String(date.getMinutes()).padStart(2, "0");
return `${dd}/${mm}/${yyyy} ${hh}:${mi}`;
}
return `${dd}/${mm}/${yyyy}`;
}
+4 -21
View File
@@ -1,5 +1,6 @@
import { Http } from "./Http.js";
import { Toast } from "./Toast.js";
import { formatDate } from "./DateFormat.js";
class ServiceMonitor {
constructor() {
@@ -110,9 +111,9 @@ class ServiceMonitor {
statusEl.className = "service-status " + svc.status;
}
this.setMeta(root, "uptime", svc.uptime || "-");
this.setMeta(root, "last_run", svc.last_run ? this.formatDate(svc.last_run, true) : "-");
this.setMeta(root, "next_run", svc.next_run ? this.formatDate(svc.next_run, true) : "-");
this.setMeta(root, "heartbeat", svc.heartbeat ? this.formatDate(svc.heartbeat, true) : "-");
this.setMeta(root, "last_run", svc.last_run ? formatDate(svc.last_run, true) : "-");
this.setMeta(root, "next_run", svc.next_run ? formatDate(svc.next_run, true) : "-");
this.setMeta(root, "heartbeat", svc.heartbeat ? formatDate(svc.heartbeat, true) : "-");
this.setMeta(root, "interval", "every " + svc.interval_seconds + "s");
const logPre = root.querySelector(".log-output");
@@ -181,24 +182,6 @@ class ServiceMonitor {
}
}
formatDate(isoStr, includeTime) {
if (!isoStr) return "-";
try {
const date = new Date(isoStr);
if (isNaN(date.getTime())) return isoStr.slice(0, 19);
const dd = String(date.getDate()).padStart(2, "0");
const mm = String(date.getMonth() + 1).padStart(2, "0");
const yyyy = date.getFullYear();
if (includeTime) {
const hh = String(date.getHours()).padStart(2, "0");
const mi = String(date.getMinutes()).padStart(2, "0");
return `${dd}/${mm}/${yyyy} ${hh}:${mi}`;
}
return `${dd}/${mm}/${yyyy}`;
} catch {
return isoStr.slice(0, 19);
}
}
}
window.ServiceMonitor = ServiceMonitor;
+2 -4
View File
@@ -1,4 +1,5 @@
import { Http } from "./Http.js";
import { formatDate } from "./DateFormat.js";
class UserAiUsage {
constructor() {
@@ -80,10 +81,7 @@ class UserAiUsage {
}
when(value) {
if (!value) return "-";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "-";
return date.toLocaleString();
return formatDate(value, true);
}
escape(text) {