mirror of
https://github.com/samber/awesome-prometheus-alerts.git
synced 2026-06-24 02:17:00 +08:00
183 lines
7.7 KiB
Text
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>
|