This commit is contained in:
nate 2026-04-10 11:31:06 +04:00
parent 2083326418
commit b7ca30cbb9
2 changed files with 49 additions and 33 deletions

View File

@ -684,9 +684,11 @@ export const dashboard = new Elysia()
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`
SELECT id, slug, title, description, theme, custom_domain SELECT sp.id, sp.slug, sp.title, sp.description, sp.theme, sp.custom_domain,
FROM status_pages WHERE account_id = ${resolved.accountId} sp.created_at, sp.password_hash IS NOT NULL AS protected,
ORDER BY created_at DESC (SELECT count(*)::int FROM status_page_monitors spm WHERE spm.status_page_id = sp.id) AS monitor_count
FROM status_pages sp WHERE sp.account_id = ${resolved.accountId}
ORDER BY sp.created_at DESC
`; `;
return html("status-pages", { nav: "pages", pages }); return html("status-pages", { nav: "pages", pages });
}) })

View File

@ -1,46 +1,60 @@
<%~ include('./partials/head', { title: 'Status pages' }) %> <%~ include('./partials/head', { title: 'Status pages' }) %>
<%~ include('./partials/nav', { nav: '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-6">
<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/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">
Public pages people can visit during an outage. Each page picks a slug, the monitors to display, and optional branding.
</p>
<% if (!it.pages || it.pages.length === 0) { %> <% if (!it.pages || it.pages.length === 0) { %>
<section class="card-static p-6 text-sm text-gray-500"> <div class="text-center py-20">
No status pages yet. Create one to get a public URL like <code class="text-blue-400">pages.pingql.com/your-slug</code>. <div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gray-800/50 border border-border-subtle flex items-center justify-center">
</section> <svg class="w-8 h-8 text-gray-600" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5a17.92 17.92 0 01-8.716-2.247m0 0A8.966 8.966 0 013 12c0-1.97.633-3.794 1.708-5.282"/></svg>
</div>
<p class="text-gray-500 mb-1">No status pages yet</p>
<p class="text-gray-600 text-sm mb-5">Create a public page so visitors can check your service status</p>
<a href="/dashboard/pages/new" class="btn-primary text-sm px-6 py-3 inline-block">Create your first page</a>
</div>
<% } else { %> <% } else { %>
<div class="space-y-3">
<% it.pages.forEach(function(p) { %> <% it.pages.forEach(function(p) { %>
<section class="card-static p-6"> <a href="/dashboard/pages/<%= p.id %>" class="block monitor-card rounded-xl p-4 group">
<div class="flex items-start justify-between gap-4"> <div class="flex items-center justify-between">
<div class="flex-1 min-w-0"> <div class="flex items-center gap-3 min-w-0">
<div class="flex items-center gap-2 mb-1"> <span class="w-2.5 h-2.5 rounded-full shrink-0 dot-up"></span>
<h2 class="text-sm font-semibold text-gray-200 truncate"><%= p.title %></h2> <div class="min-w-0">
<span class="text-xs px-2 py-0.5 rounded-full bg-gray-800/50 border border-border-subtle text-gray-400 font-mono"><%= p.theme %></span> <div class="font-medium text-gray-100 group-hover:text-white truncate"><%= p.title %></div>
</div> <div class="text-xs text-gray-500 truncate">
<% if (p.custom_domain) { %> <% if (p.custom_domain) { %>
<a href="https://<%= p.custom_domain %>" target="_blank" class="text-xs text-blue-400 hover:text-blue-300 font-mono break-all"><%= p.custom_domain %></a> <span class="text-blue-400"><%= p.custom_domain %></span>
<% } else { %> <% } else { %>
<a href="https://pages.pingql.com/<%= p.slug %>" target="_blank" class="text-xs text-blue-400 hover:text-blue-300 font-mono break-all">pages.pingql.com/<%= p.slug %></a> <span class="text-blue-400">pages.pingql.com/<%= p.slug %></span>
<% } %> <% } %>
<% if (p.description) { %><p class="text-xs text-gray-500 mt-1"><%= p.description %></p><% } %> <% if (p.description) { %><span class="text-gray-600 mx-1.5">-</span><span><%= p.description %></span><% } %>
</div> </div>
<div class="flex items-center gap-2 shrink-0"> </div>
<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> </div>
<form action="/dashboard/pages/<%= p.id %>/delete" method="POST" class="inline" onsubmit="return confirm('Delete status page \'<%= p.title %>\'? This cannot be undone.')"> <div class="flex items-center gap-3 shrink-0 ml-4">
<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> <div class="text-right hidden sm:block">
<div class="text-sm text-gray-300"><%= p.monitor_count %> monitor<%= p.monitor_count !== 1 ? 's' : '' %></div>
<div class="text-xs text-gray-500"><%~ it.timeAgoSSR(p.created_at) %></div>
</div>
<div class="flex items-center gap-1.5">
<% if (p.protected) { %>
<span class="text-xs px-2 py-1 rounded bg-yellow-900/20 text-yellow-500 border border-yellow-800/30" title="Password protected">locked</span>
<% } %>
<span class="text-xs px-2 py-1 rounded bg-gray-800/50 text-gray-400 border border-border-subtle"><%= p.theme %></span>
</div>
<form action="/dashboard/pages/<%= p.id %>/delete" method="POST" class="inline" onclick="event.stopPropagation(); event.preventDefault(); if(confirm('Delete status page \'<%= p.title %>\'? This cannot be undone.')) this.submit();">
<button type="button" 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>
</div> </div>
</section> </a>
<% }) %> <% }) %>
</div>
<% } %> <% } %>
</main> </main>