awesome-prometheus-alerts/site/src/pages/rules/[group]/[service].astro
2026-04-26 16:52:07 +02:00

183 lines
7.7 KiB
Text

---
import BaseLayout from '../../../layouts/BaseLayout.astro';
import Breadcrumbs from '../../../components/Breadcrumbs.astro';
import Sidebar from '../../../components/Sidebar.astro';
import ExporterSection from '../../../components/ExporterSection.astro';
import CautionBanner from '../../../components/CautionBanner.astro';
import { data, getGroupSlug, getServiceSlug, getRuleCount, getAllServices } from '../../../data/rules';
import { SITE_URL, schemaAuthor, schemaPublisher, schemaWebSite, SITE_DATE_PUBLISHED, SCHEMA_IN_LANGUAGE } from '../../../data/site';
export function getStaticPaths() {
return getAllServices().map(({ group, service, groupSlug, serviceSlug }) => ({
params: { group: groupSlug, service: serviceSlug },
props: { group, service },
}));
}
const { group, service } = Astro.props;
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
const groupSlug = getGroupSlug(group);
const serviceSlug = getServiceSlug(service);
const ruleCount = getRuleCount(service);
const groupIndex = data.groups.findIndex((g) => getGroupSlug(g) === groupSlug) + 1;
const serviceIndex = group.services.findIndex((s) => getServiceSlug(s) === serviceSlug) + 1;
// Build exporters summary for keywords (kept for <meta keywords> but removed from description)
const exporterNames = service.exporters.map((e) => e.name).filter(Boolean).join(', ');
// Description: lead with count + service + format signals. Exporter names go in keywords, not here.
const metaDesc = `${ruleCount} ready-to-use Prometheus alert rules for ${service.name}. Critical and warning YAML snippets — copy-paste into your Prometheus config or wget download.`;
// FAQ JSON-LD for GEO (AI search engines)
const faqItems = service.exporters.flatMap((exp) =>
(exp.rules ?? []).map((rule) => ({
'@type': 'Question',
name: `What is the Prometheus alert rule for "${rule.name}"?`,
acceptedAnswer: {
'@type': 'Answer',
text: `${rule.description} PromQL expression: ${rule.query}. Severity: ${rule.severity}${rule.for ? `. Duration: ${rule.for}` : ''}.`,
},
}))
);
const buildDate = new Date().toISOString().slice(0, 10);
const keywords = [
'Prometheus', 'alerting rules', service.name, 'monitoring', 'PromQL',
...service.exporters.map((e) => e.name).filter(Boolean),
].join(', ');
const pageUrl = `${SITE_URL}rules/${groupSlug}/${serviceSlug}/`;
const jsonLd = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'TechArticle',
'@id': `${pageUrl}#article`,
headline: `${service.name} Prometheus Alert Rules (${ruleCount})`,
description: metaDesc,
about: `Prometheus monitoring for ${service.name}`,
url: pageUrl,
inLanguage: SCHEMA_IN_LANGUAGE,
datePublished: SITE_DATE_PUBLISHED,
dateModified: buildDate,
author: schemaAuthor,
publisher: schemaPublisher,
isPartOf: schemaWebSite,
},
...(faqItems.length > 0 ? [{
'@type': 'FAQPage',
'@id': `${pageUrl}#faq`,
mainEntity: faqItems,
}] : []),
],
};
---
<BaseLayout
title={`${service.name} Prometheus Alert Rules (${ruleCount}) | Awesome Prometheus Alerts`}
description={metaDesc}
ogType="article"
keywords={keywords}
jsonLd={jsonLd}
datePublished={SITE_DATE_PUBLISHED}
dateModified={buildDate}
>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Breadcrumbs
items={[
{ label: 'Rules', href: `${base}/rules/` },
{ label: group.name, href: `${base}/rules/${groupSlug}/` },
{ label: service.name },
]}
base={base}
/>
<div class="flex gap-10 mt-6">
<!-- Left sidebar -->
<Sidebar
groups={data.groups}
currentGroupSlug={groupSlug}
currentServiceSlug={serviceSlug}
currentService={service}
base={base}
/>
<!-- Main content -->
<div class="flex-1 min-w-0">
<!-- Page header -->
<div class="mb-6 pb-6 border-b border-slate-200 dark:border-slate-800">
<h1 class="text-2xl font-bold text-slate-900 dark:text-white mb-2">{service.name} Prometheus Alert Rules</h1>
<p class="text-slate-500 dark:text-slate-400 text-sm leading-relaxed mb-2">
{ruleCount} Prometheus alerting rule{ruleCount !== 1 ? 's' : ''} for {service.name}.
{exporterNames && `Exported via ${exporterNames}.`}
These rules cover critical and warning conditions — copy and paste the YAML into your Prometheus configuration.
</p>
</div>
<CautionBanner />
<!-- Exporters -->
{service.exporters.map((exporter, expIdx) => (
<ExporterSection
exporter={exporter}
service={service}
groupIndex={groupIndex}
groupName={group.name}
groupSlug={groupSlug}
serviceIndex={serviceIndex}
serviceSlug={serviceSlug}
exporterIndex={expIdx + 1}
showExporterNumber={service.exporters.length > 1}
/>
))}
<!-- Prev/Next navigation -->
<nav class="mt-10 pt-6 border-t border-slate-200 dark:border-slate-800 flex justify-between gap-4" aria-label="Service navigation">
{(() => {
const allSvcs = getAllServices();
const idx = allSvcs.findIndex(s => s.groupSlug === groupSlug && s.serviceSlug === serviceSlug);
const prev = idx > 0 ? allSvcs[idx - 1] : null;
const next = idx < allSvcs.length - 1 ? allSvcs[idx + 1] : null;
return (
<>
{prev ? (
<a href={`${base}/rules/${prev.groupSlug}/${prev.serviceSlug}/`} class="group flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400 hover:text-brand dark:hover:text-brand-dark transition-colors">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
<span>{prev.service.name}</span>
</a>
) : <div />}
{next ? (
<a href={`${base}/rules/${next.groupSlug}/${next.serviceSlug}/`} class="group flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400 hover:text-brand dark:hover:text-brand-dark transition-colors">
<span>{next.service.name}</span>
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
</a>
) : <div />}
</>
);
})()}
</nav>
<!-- More in group -->
{group.services.length > 1 && (
<div class="mt-8 pt-6 border-t border-slate-200 dark:border-slate-800">
<h2 class="text-xs font-semibold uppercase tracking-wide text-slate-400 dark:text-slate-500 mb-3">
More in <a href={`${base}/rules/${groupSlug}/`} class="hover:text-brand dark:hover:text-brand-dark transition-colors">{group.name}</a>
</h2>
<div class="flex flex-wrap gap-2">
{group.services
.filter((s) => getServiceSlug(s) !== serviceSlug)
.map((s) => (
<a
href={`${base}/rules/${groupSlug}/${getServiceSlug(s)}/`}
class="inline-flex items-center px-3 py-1 text-xs rounded-full bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-brand/10 dark:hover:bg-brand-dark/10 hover:text-brand dark:hover:text-brand-dark border border-slate-200 dark:border-slate-700 transition-colors"
>
{s.name}
</a>
))}
</div>
</div>
)}
</div>
</div>
</div>
</BaseLayout>