mirror of
https://github.com/dcarrillo/atalaya.git
synced 2026-04-18 02:24:05 +00:00
227 lines
5.7 KiB
Plaintext
227 lines
5.7 KiB
Plaintext
---
|
|
import Layout from '../layouts/Layout.astro';
|
|
import Header from '../components/Header.astro';
|
|
import MonitorCard from '../components/MonitorCard.astro';
|
|
import Footer from '../components/Footer.astro';
|
|
import { getStatusApiData } from '../lib/api.js';
|
|
import { parseConfig } from '../../../src/config/index.js';
|
|
import { interpolateSecrets } from '../../../src/utils/interpolate.js';
|
|
import { env } from 'cloudflare:workers';
|
|
|
|
let data: Awaited<ReturnType<typeof getStatusApiData>> | null = null;
|
|
let error: Error | null = null;
|
|
let banner: { url: string; link?: string } | undefined = undefined;
|
|
|
|
try {
|
|
// TypeScript doesn't know about MONITORS_CONFIG in cloudflare:workers env
|
|
const envAny = env as any;
|
|
const monitorsConfig = envAny.MONITORS_CONFIG;
|
|
if (typeof monitorsConfig !== 'string' || !monitorsConfig) {
|
|
throw new Error('MONITORS_CONFIG environment variable is not set or is not a string');
|
|
}
|
|
const configYaml = interpolateSecrets(monitorsConfig, envAny);
|
|
const config = parseConfig(configYaml);
|
|
data = await getStatusApiData(env.DB, config);
|
|
|
|
const bannerUrl = envAny.STATUS_BANNER_URL;
|
|
const bannerLink = envAny.STATUS_BANNER_LINK;
|
|
banner = bannerUrl ? { url: bannerUrl, link: bannerLink } : undefined;
|
|
} catch (err) {
|
|
console.error('Failed to fetch status data:', err);
|
|
error = err as Error;
|
|
}
|
|
|
|
// Sort: down monitors first, then unknown, then up
|
|
const sortOrder = { down: 0, maintenance: 1, unknown: 2, up: 3 } as const;
|
|
const sortedMonitors = data ? [...data.monitors].sort(
|
|
(a, b) => (sortOrder[a.status] ?? 1) - (sortOrder[b.status] ?? 1)
|
|
) : [];
|
|
---
|
|
|
|
<Layout title={data!.title}>
|
|
<main id="main-content" class="container">
|
|
{error ? (
|
|
<div class="error-state">
|
|
<div class="error-icon">⚠️</div>
|
|
<h2>Service Temporarily Unavailable</h2>
|
|
<p>We're having trouble loading status data. Please try again in a moment.</p>
|
|
<button class="retry-button" onclick="window.location.reload()" aria-label="Retry loading status data">Retry</button>
|
|
</div>
|
|
) : !data ? (
|
|
<div class="loading-state">
|
|
<div class="loading-spinner"></div>
|
|
<p>Loading status data...</p>
|
|
</div>
|
|
) : data!.monitors.length === 0 ? (
|
|
<div class="empty-state">
|
|
<div class="empty-icon">📊</div>
|
|
<h2>No Monitors Configured</h2>
|
|
<p>Add monitors to start tracking your services.</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<Header summary={data!.summary} lastUpdated={data!.lastUpdated} title={data!.title} banner={banner} />
|
|
<div class="monitors">
|
|
{sortedMonitors.map(monitor => <MonitorCard monitor={monitor} />)}
|
|
</div>
|
|
</>
|
|
)}
|
|
<Footer />
|
|
</main>
|
|
</Layout>
|
|
|
|
<style>
|
|
.container {
|
|
max-width: min(1000px, 90vw);
|
|
margin: 0 auto;
|
|
padding: var(--space-8) var(--space-6);
|
|
position: relative;
|
|
}
|
|
|
|
.container::before {
|
|
content: '';
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-image:
|
|
linear-gradient(var(--border-subtle) 1px, transparent 1px),
|
|
linear-gradient(90deg, var(--border-subtle) 1px, transparent 1px);
|
|
background-size: 40px 40px;
|
|
opacity: 0.15;
|
|
pointer-events: none;
|
|
z-index: -1;
|
|
}
|
|
|
|
[data-theme='dark'] .container::before {
|
|
background-image:
|
|
linear-gradient(var(--border) 1px, transparent 1px),
|
|
linear-gradient(90deg, var(--border) 1px, transparent 1px);
|
|
opacity: 0.1;
|
|
}
|
|
|
|
.monitors {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-4);
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.container {
|
|
padding: var(--space-16) var(--space-12) var(--space-20);
|
|
}
|
|
|
|
.monitors {
|
|
gap: var(--space-5);
|
|
}
|
|
}
|
|
|
|
.error-state,
|
|
.loading-state,
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: var(--space-20) var(--space-5);
|
|
background: var(--bg-card);
|
|
border-radius: var(--radius);
|
|
border: 1px solid var(--border);
|
|
margin-bottom: var(--space-8);
|
|
}
|
|
|
|
.error-icon,
|
|
.empty-icon {
|
|
font-size: 48px;
|
|
margin-bottom: 24px;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.error-state h2,
|
|
.empty-state h2 {
|
|
font-size: 24px;
|
|
margin-bottom: 12px;
|
|
color: var(--text);
|
|
}
|
|
|
|
.error-state p,
|
|
.empty-state p,
|
|
.loading-state p {
|
|
color: var(--text-muted);
|
|
margin-bottom: 24px;
|
|
max-width: min(400px, 90vw);
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
}
|
|
|
|
.retry-button {
|
|
background: var(--accent);
|
|
color: white;
|
|
border: none;
|
|
padding: var(--space-3) var(--space-6);
|
|
border-radius: var(--radius-sm);
|
|
font-family: inherit;
|
|
font-weight: var(--font-semibold);
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.retry-button:hover {
|
|
background: color-mix(in oklch, var(--accent), white 20%);
|
|
}
|
|
|
|
.retry-button:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
.retry-button:hover {
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.retry-button:active {
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
border: 3px solid var(--border);
|
|
border-top-color: var(--accent);
|
|
border-radius: 50%;
|
|
margin: 0 auto var(--space-6);
|
|
}
|
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
.loading-spinner {
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.container {
|
|
padding: var(--space-5) var(--space-4);
|
|
}
|
|
|
|
.error-state,
|
|
.loading-state,
|
|
.empty-state {
|
|
padding: var(--space-12) var(--space-4);
|
|
}
|
|
|
|
.error-icon,
|
|
.empty-icon {
|
|
font-size: 2.25rem;
|
|
}
|
|
|
|
.error-state h2,
|
|
.empty-state h2 {
|
|
font-size: var(--text-xl);
|
|
}
|
|
}
|
|
</style>
|