improve pages

This commit is contained in:
nate 2026-04-10 06:05:50 +04:00
parent b1ad8f513b
commit 9339c67ba7
5 changed files with 31 additions and 24 deletions

View File

@ -680,7 +680,7 @@ export const dashboard = new Elysia()
}) })
// ── Status pages ────────────────────────────────────────────────── // ── Status pages ──────────────────────────────────────────────────
.get("/dashboard/status-pages", async ({ cookie, headers }) => { .get("/dashboard/pages", async ({ cookie, headers }) => {
const resolved = await getAccountId(cookie, headers); const resolved = await getAccountId(cookie, headers);
if (!resolved?.accountId) return redirect("/dashboard"); if (!resolved?.accountId) return redirect("/dashboard");
const pages = await sql` const pages = await sql`
@ -688,25 +688,25 @@ export const dashboard = new Elysia()
FROM status_pages WHERE account_id = ${resolved.accountId} FROM status_pages WHERE account_id = ${resolved.accountId}
ORDER BY created_at DESC ORDER BY created_at DESC
`; `;
return html("status-pages", { nav: "status-pages", pages }); return html("status-pages", { nav: "pages", pages });
}) })
.get("/dashboard/status-pages/new", async ({ cookie, headers }) => { .get("/dashboard/pages/new", async ({ cookie, headers }) => {
const resolved = await getAccountId(cookie, headers); const resolved = await getAccountId(cookie, headers);
if (!resolved?.accountId) return redirect("/dashboard"); if (!resolved?.accountId) return redirect("/dashboard");
const allMonitors = await sql` const allMonitors = await sql`
SELECT id, name FROM monitors WHERE account_id = ${resolved.accountId} ORDER BY created_at DESC SELECT id, name FROM monitors WHERE account_id = ${resolved.accountId} ORDER BY created_at DESC
`; `;
return html("status-page-edit", { nav: "status-pages", isNew: true, page: null, allMonitors }); return html("status-page-edit", { nav: "pages", isNew: true, page: null, allMonitors });
}) })
.get("/dashboard/status-pages/:id", async ({ cookie, headers, params }) => { .get("/dashboard/pages/:id", async ({ cookie, headers, params }) => {
const resolved = await getAccountId(cookie, headers); const resolved = await getAccountId(cookie, headers);
if (!resolved?.accountId) return redirect("/dashboard"); if (!resolved?.accountId) return redirect("/dashboard");
const [page] = await sql` const [page] = await sql`
SELECT * FROM status_pages WHERE id = ${params.id} AND account_id = ${resolved.accountId} SELECT * FROM status_pages WHERE id = ${params.id} AND account_id = ${resolved.accountId}
`; `;
if (!page) return redirect("/dashboard/status-pages"); if (!page) return redirect("/dashboard/pages");
const [monitors, groups, allMonitors] = await Promise.all([ const [monitors, groups, allMonitors] = await Promise.all([
sql`SELECT monitor_id, display_name, group_id FROM status_page_monitors WHERE status_page_id = ${params.id} ORDER BY position ASC`, sql`SELECT monitor_id, display_name, group_id FROM status_page_monitors WHERE status_page_id = ${params.id} ORDER BY position ASC`,
sql`SELECT id, name, position FROM status_page_groups WHERE status_page_id = ${params.id} ORDER BY position ASC`, sql`SELECT id, name, position FROM status_page_groups WHERE status_page_id = ${params.id} ORDER BY position ASC`,
@ -714,10 +714,10 @@ export const dashboard = new Elysia()
]); ]);
page.monitors = monitors; page.monitors = monitors;
page.groups = groups; page.groups = groups;
return html("status-page-edit", { nav: "status-pages", isNew: false, page, allMonitors }); return html("status-page-edit", { nav: "pages", isNew: false, page, allMonitors });
}) })
.post("/dashboard/status-pages/new", async ({ cookie, headers, body }) => { .post("/dashboard/pages/new", async ({ cookie, headers, body }) => {
const resolved = await getAccountId(cookie, headers); const resolved = await getAccountId(cookie, headers);
if (!resolved?.accountId) return redirect("/dashboard"); if (!resolved?.accountId) return redirect("/dashboard");
const b = body as any; const b = body as any;
@ -752,10 +752,10 @@ export const dashboard = new Elysia()
}), }),
}); });
} catch {} } catch {}
return redirect("/dashboard/status-pages"); return redirect("/dashboard/pages");
}) })
.post("/dashboard/status-pages/:id/edit", async ({ cookie, headers, params, body }) => { .post("/dashboard/pages/:id/edit", async ({ cookie, headers, params, body }) => {
const resolved = await getAccountId(cookie, headers); const resolved = await getAccountId(cookie, headers);
if (!resolved?.accountId) return redirect("/dashboard"); if (!resolved?.accountId) return redirect("/dashboard");
const b = body as any; const b = body as any;
@ -796,10 +796,10 @@ export const dashboard = new Elysia()
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
} catch {} } catch {}
return redirect("/dashboard/status-pages"); return redirect("/dashboard/pages");
}) })
.post("/dashboard/status-pages/:id/delete", async ({ cookie, headers, params }) => { .post("/dashboard/pages/:id/delete", async ({ cookie, headers, params }) => {
const resolved = await getAccountId(cookie, headers); const resolved = await getAccountId(cookie, headers);
if (!resolved?.accountId) return redirect("/dashboard"); if (!resolved?.accountId) return redirect("/dashboard");
try { try {
@ -810,7 +810,7 @@ export const dashboard = new Elysia()
headers: { "Authorization": `Bearer ${key}` }, headers: { "Authorization": `Bearer ${key}` },
}); });
} catch {} } catch {}
return redirect("/dashboard/status-pages"); return redirect("/dashboard/pages");
}) })
// ── Incidents ───────────────────────────────────────────────────── // ── Incidents ─────────────────────────────────────────────────────

View File

@ -68,7 +68,7 @@
<div> <div>
<label class="block text-sm text-gray-400 mb-1.5">Show on status pages</label> <label class="block text-sm text-gray-400 mb-1.5">Show on status pages</label>
<% if (allPages.length === 0) { %> <% if (allPages.length === 0) { %>
<p class="text-xs text-gray-600">No status pages yet. <a href="/dashboard/status-pages/new" class="text-blue-400 hover:text-blue-300">Create one</a>.</p> <p class="text-xs text-gray-600">No status pages yet. <a href="/dashboard/pages/new" class="text-blue-400 hover:text-blue-300">Create one</a>.</p>
<% } else { %> <% } else { %>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<% allPages.forEach(function(p) { %> <% allPages.forEach(function(p) { %>

View File

@ -2,7 +2,7 @@
<a href="/dashboard/home" class="text-xl font-bold tracking-tight group">Ping<span class="text-blue-400 transition-all group-hover:drop-shadow-[0_0_8px_rgba(59,130,246,0.4)]">QL</span></a> <a href="/dashboard/home" class="text-xl font-bold tracking-tight group">Ping<span class="text-blue-400 transition-all group-hover:drop-shadow-[0_0_8px_rgba(59,130,246,0.4)]">QL</span></a>
<div class="flex items-center gap-5 text-sm text-gray-500"> <div class="flex items-center gap-5 text-sm text-gray-500">
<a href="/dashboard/home" class="<%= it.nav === 'monitors' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Monitors</a> <a href="/dashboard/home" class="<%= it.nav === 'monitors' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Monitors</a>
<a href="/dashboard/status-pages" class="<%= it.nav === 'status-pages' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Pages</a> <a href="/dashboard/pages" class="<%= it.nav === 'pages' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Pages</a>
<a href="/dashboard/incidents" class="<%= it.nav === 'incidents' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Incidents</a> <a href="/dashboard/incidents" class="<%= it.nav === 'incidents' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Incidents</a>
<a href="/dashboard/notifications" class="<%= it.nav === 'notifications' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Notifications</a> <a href="/dashboard/notifications" class="<%= it.nav === 'notifications' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Notifications</a>
<a href="/dashboard/settings" class="<%= it.nav === 'settings' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Settings</a> <a href="/dashboard/settings" class="<%= it.nav === 'settings' ? 'text-gray-200 relative after:absolute after:bottom-[-18px] after:left-0 after:right-0 after:h-[2px] after:bg-blue-500 after:rounded-full' : 'hover:text-gray-300' %> transition-colors">Settings</a>

View File

@ -1,5 +1,5 @@
<%~ include('./partials/head', { title: it.isNew ? 'New status page' : 'Edit status page' }) %> <%~ include('./partials/head', { title: it.isNew ? 'New status page' : 'Edit status page' }) %>
<%~ include('./partials/nav', { nav: 'status-pages' }) %> <%~ include('./partials/nav', { nav: 'pages' }) %>
<% <%
const p = it.page || {}; const p = it.page || {};
@ -31,17 +31,24 @@
<main class="max-w-3xl mx-auto px-8 py-10"> <main class="max-w-3xl mx-auto px-8 py-10">
<div class="mb-6"> <div class="mb-6">
<a href="/dashboard/status-pages" class="text-sm text-gray-500 hover:text-gray-300 transition-colors">&larr; Back to status pages</a> <a href="/dashboard/pages" class="text-sm text-gray-500 hover:text-gray-300 transition-colors">&larr; Back to status pages</a>
<h1 class="text-xl font-semibold text-white mt-2"><%= it.isNew ? 'New status page' : 'Edit status page' %></h1> <h1 class="text-xl font-semibold text-white mt-2"><%= it.isNew ? 'New status page' : 'Edit status page' %></h1>
</div> </div>
<form method="POST" action="<%= it.isNew ? '/dashboard/status-pages/new' : '/dashboard/status-pages/' + p.id + '/edit' %>" class="space-y-6"> <form method="POST" action="<%= it.isNew ? '/dashboard/pages/new' : '/dashboard/pages/' + p.id + '/edit' %>" class="space-y-6">
<div> <div>
<label class="block text-sm text-gray-400 mb-1.5">Slug</label> <label class="block text-sm text-gray-400 mb-1.5">Slug</label>
<input name="slug" type="text" required value="<%= p.slug || '' %>" placeholder="my-app" pattern="^[a-z0-9][a-z0-9-]*$" <input id="slug-input" name="slug" type="text" required value="<%= p.slug || '' %>" placeholder="my-app" pattern="^[a-z0-9][a-z0-9-]*$"
class="w-full bg-surface-solid border border-border-subtle rounded-lg px-4 py-2.5 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 font-mono text-sm"> class="w-full bg-surface-solid border border-border-subtle rounded-lg px-4 py-2.5 text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 font-mono text-sm">
<p class="text-xs text-gray-600 mt-1">Public URL: <span class="text-blue-400 font-mono">pages.pingql.com/<your-slug></span></p> <p class="text-xs text-gray-600 mt-1">Public URL: <a id="slug-url" href="https://pages.pingql.com/<%= p.slug || '' %>" target="_blank" class="text-blue-400 hover:text-blue-300 font-mono">pages.pingql.com/<span id="slug-preview"><%= p.slug || '' %></span></a></p>
<script>
document.getElementById('slug-input').addEventListener('input', function() {
var v = this.value.trim();
document.getElementById('slug-preview').textContent = v || '';
document.getElementById('slug-url').href = 'https://pages.pingql.com/' + encodeURIComponent(v);
});
</script>
</div> </div>
<div> <div>

View File

@ -1,11 +1,11 @@
<%~ include('./partials/head', { title: 'Status pages' }) %> <%~ include('./partials/head', { title: 'Status pages' }) %>
<%~ include('./partials/nav', { nav: 'status-pages' }) %> <%~ include('./partials/nav', { nav: 'pages' }) %>
<main class="max-w-3xl mx-auto px-8 py-10 space-y-8"> <main class="max-w-3xl mx-auto px-8 py-10 space-y-8">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h1 class="text-xl font-semibold text-white">Status pages</h1> <h1 class="text-xl font-semibold text-white">Status pages</h1>
<a href="/dashboard/status-pages/new" class="btn-primary inline-flex items-center gap-2 px-4 py-2 text-sm">+ New page</a> <a href="/dashboard/pages/new" class="btn-primary inline-flex items-center gap-2 px-4 py-2 text-sm">+ New page</a>
</div> </div>
<p class="text-sm text-gray-500 leading-relaxed"> <p class="text-sm text-gray-500 leading-relaxed">
@ -33,8 +33,8 @@
<% if (p.description) { %><p class="text-xs text-gray-500 mt-1"><%= p.description %></p><% } %> <% if (p.description) { %><p class="text-xs text-gray-500 mt-1"><%= p.description %></p><% } %>
</div> </div>
<div class="flex items-center gap-2 shrink-0"> <div class="flex items-center gap-2 shrink-0">
<a href="/dashboard/status-pages/<%= p.id %>" class="px-3 py-1.5 rounded-lg border border-border-subtle text-gray-400 hover:text-gray-200 text-xs transition-colors">Edit</a> <a href="/dashboard/pages/<%= p.id %>" class="px-3 py-1.5 rounded-lg border border-border-subtle text-gray-400 hover:text-gray-200 text-xs transition-colors">Edit</a>
<form action="/dashboard/status-pages/<%= p.id %>/delete" method="POST" class="inline" onsubmit="return confirm('Delete status page \'<%= p.title %>\'? This cannot be undone.')"> <form action="/dashboard/pages/<%= p.id %>/delete" method="POST" class="inline" onsubmit="return confirm('Delete status page \'<%= p.title %>\'? This cannot be undone.')">
<button type="submit" class="px-3 py-1.5 rounded-lg border border-red-900/30 text-red-400 hover:bg-red-900/20 hover:border-red-800/40 text-xs transition-colors">Delete</button> <button type="submit" class="px-3 py-1.5 rounded-lg border border-red-900/30 text-red-400 hover:bg-red-900/20 hover:border-red-800/40 text-xs transition-colors">Delete</button>
</form> </form>
</div> </div>