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:
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user