feat: add custom banner to status page (#4)

This commit is contained in:
2026-04-12 19:38:16 +02:00
committed by GitHub
parent a3acea4d35
commit 81cf70cbe3
5 changed files with 92 additions and 10 deletions

View File

@@ -7,9 +7,13 @@ interface Props {
};
lastUpdated: number;
title: string;
banner?: {
url?: string;
link?: string;
};
}
const { summary, lastUpdated, title } = Astro.props;
const { summary, lastUpdated, title, banner } = Astro.props;
function formatAbsoluteTime(unixTimestamp: number): string {
const date = new Date(unixTimestamp * 1000);
@@ -28,7 +32,19 @@ const absoluteTime = formatAbsoluteTime(lastUpdated);
<header class="header">
<div class="header-top">
<h1>{title}</h1>
{banner?.url ? (
<div class="banner-container">
{banner.link ? (
<a href={banner.link} class="banner-link" aria-label={title} target="_self">
<img src={banner.url} alt={title} class="banner-image" />
</a>
) : (
<img src={banner.url} alt={title} class="banner-image" />
)}
</div>
) : (
<h1>{title}</h1>
)}
<button class="theme-toggle" aria-label="Toggle theme" title="Toggle light/dark theme">
<svg class="theme-icon sun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"/>
@@ -156,6 +172,29 @@ const absoluteTime = formatAbsoluteTime(lastUpdated);
outline-offset: 2px;
}
.banner-container {
display: flex;
justify-content: center;
align-items: center;
max-width: 100%;
}
.banner-link {
display: block;
text-decoration: none;
transition: opacity var(--transition-normal);
}
.banner-link:hover {
opacity: 0.9;
}
.banner-image {
max-height: 60px;
max-width: 100%;
object-fit: contain;
}
.theme-icon {
transition: opacity var(--transition-normal), transform var(--transition-normal);
}
@@ -275,6 +314,10 @@ const absoluteTime = formatAbsoluteTime(lastUpdated);
font-size: var(--text-2xl);
}
.banner-image {
max-height: 50px;
}
.theme-toggle {
width: 2rem;
height: 2rem;
@@ -302,6 +345,10 @@ const absoluteTime = formatAbsoluteTime(lastUpdated);
font-size: var(--text-xl);
}
.banner-image {
max-height: 40px;
}
.summary-counts {
flex-direction: column;
align-items: center;

View File

@@ -10,6 +10,7 @@ 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
@@ -21,6 +22,10 @@ try {
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;
@@ -55,7 +60,7 @@ const sortedMonitors = data ? [...data.monitors].sort(
</div>
) : (
<>
<Header summary={data!.summary} lastUpdated={data!.lastUpdated} title={data!.title} />
<Header summary={data!.summary} lastUpdated={data!.lastUpdated} title={data!.title} banner={banner} />
<div class="monitors">
{sortedMonitors.map(monitor => <MonitorCard monitor={monitor} />)}
</div>
@@ -80,7 +85,7 @@ const sortedMonitors = data ? [...data.monitors].sort(
left: 0;
width: 100%;
height: 100%;
background-image:
background-image:
linear-gradient(var(--border-subtle) 1px, transparent 1px),
linear-gradient(90deg, var(--border-subtle) 1px, transparent 1px);
background-size: 40px 40px;
@@ -90,7 +95,7 @@ const sortedMonitors = data ? [...data.monitors].sort(
}
[data-theme='dark'] .container::before {
background-image:
background-image:
linear-gradient(var(--border) 1px, transparent 1px),
linear-gradient(90deg, var(--border) 1px, transparent 1px);
opacity: 0.1;
@@ -106,7 +111,7 @@ const sortedMonitors = data ? [...data.monitors].sort(
.container {
padding: var(--space-16) var(--space-12) var(--space-20);
}
.monitors {
gap: var(--space-5);
}
@@ -201,18 +206,18 @@ const sortedMonitors = data ? [...data.monitors].sort(
.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);