feat: improve status page
This commit is contained in:
@@ -636,7 +636,7 @@ export const dashboard = new Elysia()
|
||||
`;
|
||||
if (!page) return redirect("/dashboard/status-pages");
|
||||
const monitors = await sql`
|
||||
SELECT monitor_id FROM status_page_monitors WHERE status_page_id = ${params.id}
|
||||
SELECT monitor_id, display_name FROM status_page_monitors WHERE status_page_id = ${params.id}
|
||||
`;
|
||||
const allMonitors = await sql`
|
||||
SELECT id, name FROM monitors WHERE account_id = ${resolved.accountId} ORDER BY created_at DESC
|
||||
@@ -650,6 +650,21 @@ export const dashboard = new Elysia()
|
||||
if (!resolved?.accountId) return redirect("/dashboard");
|
||||
const b = body as any;
|
||||
const monitorIds = Array.isArray(b.monitor_ids) ? b.monitor_ids : (b.monitor_ids ? [b.monitor_ids] : []);
|
||||
// The form posts per-monitor display name overrides as display_name[<id>].
|
||||
// Bun's body parser surfaces them as nested object keys (display_name) or
|
||||
// as flat "display_name[id]" string keys depending on parser version, so
|
||||
// handle both. Empty strings mean "no override" — don't send them.
|
||||
const displayNames: Record<string, string> = {};
|
||||
if (b.display_name && typeof b.display_name === "object" && !Array.isArray(b.display_name)) {
|
||||
for (const [k, v] of Object.entries(b.display_name)) {
|
||||
if (typeof v === "string" && v.trim()) displayNames[k] = v.trim();
|
||||
}
|
||||
} else {
|
||||
for (const k of Object.keys(b)) {
|
||||
const m = k.match(/^display_name\[(.+)\]$/);
|
||||
if (m && typeof b[k] === "string" && b[k].trim()) displayNames[m[1]!] = b[k].trim();
|
||||
}
|
||||
}
|
||||
try {
|
||||
const apiUrl = process.env.API_URL || "https://api.pingql.com";
|
||||
const key = cookie?.pingql_key?.value;
|
||||
@@ -662,13 +677,14 @@ export const dashboard = new Elysia()
|
||||
description: b.description || null,
|
||||
theme: b.theme || "auto",
|
||||
default_window: b.default_window || "24h",
|
||||
display_mode: b.display_mode || "expanded",
|
||||
show_response_time: !!b.show_response_time,
|
||||
show_powered_by: !!b.show_powered_by,
|
||||
index_search: !!b.index_search,
|
||||
password: b.password || undefined,
|
||||
custom_css: b.custom_css || null,
|
||||
footer_text: b.footer_text || null,
|
||||
monitors: monitorIds.map((id: string, i: number) => ({ monitor_id: id, position: i })),
|
||||
monitors: monitorIds.map((id: string, i: number) => ({ monitor_id: id, position: i, display_name: displayNames[id] ?? null })),
|
||||
}),
|
||||
});
|
||||
} catch {}
|
||||
@@ -680,6 +696,17 @@ export const dashboard = new Elysia()
|
||||
if (!resolved?.accountId) return redirect("/dashboard");
|
||||
const b = body as any;
|
||||
const monitorIds = Array.isArray(b.monitor_ids) ? b.monitor_ids : (b.monitor_ids ? [b.monitor_ids] : []);
|
||||
const displayNames: Record<string, string> = {};
|
||||
if (b.display_name && typeof b.display_name === "object" && !Array.isArray(b.display_name)) {
|
||||
for (const [k, v] of Object.entries(b.display_name)) {
|
||||
if (typeof v === "string" && v.trim()) displayNames[k] = v.trim();
|
||||
}
|
||||
} else {
|
||||
for (const k of Object.keys(b)) {
|
||||
const m = k.match(/^display_name\[(.+)\]$/);
|
||||
if (m && typeof b[k] === "string" && b[k].trim()) displayNames[m[1]!] = b[k].trim();
|
||||
}
|
||||
}
|
||||
try {
|
||||
const apiUrl = process.env.API_URL || "https://api.pingql.com";
|
||||
const key = cookie?.pingql_key?.value;
|
||||
@@ -689,12 +716,13 @@ export const dashboard = new Elysia()
|
||||
description: b.description || null,
|
||||
theme: b.theme || "auto",
|
||||
default_window: b.default_window || "24h",
|
||||
display_mode: b.display_mode || "expanded",
|
||||
show_response_time: !!b.show_response_time,
|
||||
show_powered_by: !!b.show_powered_by,
|
||||
index_search: !!b.index_search,
|
||||
custom_css: b.custom_css || null,
|
||||
footer_text: b.footer_text || null,
|
||||
monitors: monitorIds.map((id: string, i: number) => ({ monitor_id: id, position: i })),
|
||||
monitors: monitorIds.map((id: string, i: number) => ({ monitor_id: id, position: i, display_name: displayNames[id] ?? null })),
|
||||
};
|
||||
// Only send `password` if the user actually typed something. An empty box
|
||||
// means "leave the existing password as-is" — sending null would clear it.
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
<%
|
||||
const p = it.page || {};
|
||||
const allMonitors = it.allMonitors || [];
|
||||
const attached = new Set((it.page?.monitors || []).map(m => m.monitor_id));
|
||||
const attachedRows = (it.page?.monitors || []);
|
||||
const attached = new Set(attachedRows.map(m => m.monitor_id));
|
||||
// monitor_id → existing display_name override (or empty)
|
||||
const displayNames = {};
|
||||
for (const r of attachedRows) displayNames[r.monitor_id] = r.display_name || '';
|
||||
%>
|
||||
|
||||
<main class="max-w-3xl mx-auto px-8 py-10">
|
||||
@@ -52,19 +56,35 @@
|
||||
<% }) %>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="block text-sm text-gray-400 mb-1.5">Display mode</label>
|
||||
<select name="display_mode" class="w-full bg-surface-solid border border-border-subtle rounded-lg px-4 py-2.5 text-gray-100 focus:outline-none focus:border-blue-500">
|
||||
<% [['expanded','Expanded — show full detail per monitor'],['compact','Compact — one line per monitor, click to expand']].forEach(function([v, label]) { %>
|
||||
<option value="<%= v %>" <%= (p.display_mode || 'expanded') === v ? 'selected' : '' %>><%= label %></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm text-gray-400 mb-1.5">Monitors</label>
|
||||
<p class="text-xs text-gray-600 mb-2">Optional "Show as" field overrides the monitor name on this status page only. Leave blank to use the monitor's real name.</p>
|
||||
<% if (allMonitors.length === 0) { %>
|
||||
<p class="text-xs text-gray-600">No monitors yet. <a href="/dashboard/monitors/new" class="text-blue-400 hover:text-blue-300">Create one</a> first.</p>
|
||||
<% } else { %>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<% allMonitors.forEach(function(m) { %>
|
||||
<label class="flex items-center gap-2 bg-gray-900 border border-gray-800 hover:border-gray-600 rounded-lg px-3 py-2 cursor-pointer transition-colors">
|
||||
<input type="checkbox" name="monitor_ids" value="<%= m.id %>" class="accent-blue-500" <%= attached.has(m.id) ? 'checked' : '' %>>
|
||||
<span class="text-sm text-gray-300"><%= m.name %></span>
|
||||
</label>
|
||||
<div class="space-y-2">
|
||||
<% allMonitors.forEach(function(m) {
|
||||
const isAttached = attached.has(m.id);
|
||||
const displayName = displayNames[m.id] || '';
|
||||
%>
|
||||
<div class="flex items-center gap-3 bg-gray-900 border border-gray-800 rounded-lg px-3 py-2">
|
||||
<label class="flex items-center gap-2 cursor-pointer min-w-0 flex-1">
|
||||
<input type="checkbox" name="monitor_ids" value="<%= m.id %>" class="accent-blue-500" <%= isAttached ? 'checked' : '' %>>
|
||||
<span class="text-sm text-gray-300 truncate"><%= m.name %></span>
|
||||
</label>
|
||||
<input type="text" name="display_name[<%= m.id %>]" value="<%= displayName %>" placeholder="Show as (optional)"
|
||||
class="text-xs bg-gray-950 border border-gray-800 rounded px-2 py-1 text-gray-200 placeholder-gray-600 focus:outline-none focus:border-blue-500 w-48 shrink-0">
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
Reference in New Issue
Block a user