├── public ├── favicon.png ├── plus.svg ├── blue-tick.svg ├── right-arrow.svg ├── copy.svg ├── close.svg ├── search.svg ├── github.svg ├── zap.svg ├── component.svg ├── fire.svg ├── file.svg ├── green-tick.svg ├── last9.svg └── logo.svg ├── postcss.config.mjs ├── .eslintrc.json ├── prometheus-server ├── nginx │ └── nginx.conf ├── clickhouse │ └── config.xml ├── haproxy │ └── haproxy.cfg ├── prometheus │ ├── prometheus.yml │ └── rules.yml └── docker-compose.yml ├── next.config.mjs ├── components ├── Divider.tsx ├── Container.tsx ├── Page.tsx ├── RulesCount.tsx ├── Provider.tsx ├── AptLogo.tsx ├── LoadingDots.tsx ├── AlertGroup.tsx ├── ExternalLink.tsx ├── AllRulesApplied.tsx ├── Tab.tsx ├── NoAppliedRules.tsx ├── Loader.tsx ├── SideBarComponent.tsx ├── Button.tsx ├── ServiceIcon.tsx ├── Footer.tsx ├── ui │ ├── checkbox.tsx │ ├── tooltip.tsx │ └── dialog.tsx ├── Input.tsx ├── Sidebar.tsx ├── RuleCard.tsx ├── Header.tsx ├── YamlView.tsx ├── SidebarContent.tsx └── AlertComponent.tsx ├── lib ├── discovery.ts ├── utils.ts └── types.ts ├── components.json ├── styles └── globals.css ├── .prettierrc ├── .gitignore ├── hooks ├── queries │ ├── useGithub.ts │ ├── useAlerts.ts │ ├── useRules.ts │ ├── useDiscovery.ts │ └── usePrometheusQuery.ts ├── useAuth.ts └── useLocalStorage.ts ├── tsconfig.json ├── app ├── layout.tsx ├── api │ ├── rules │ │ └── route.ts │ ├── prometheus │ │ └── route.ts │ ├── discovery │ │ └── route.ts │ └── alerts │ │ └── route.ts ├── home │ └── page.tsx ├── library │ └── page.tsx └── page.tsx ├── package.json ├── tailwind.config.ts └── README.md /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/last9/awesome-prometheus-toolkit/HEAD/public/favicon.png -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@tanstack/query" 4 | ], 5 | "extends": [ 6 | "next/core-web-vitals", 7 | "plugin:@tanstack/eslint-plugin-query/recommended" 8 | ] 9 | } -------------------------------------------------------------------------------- /prometheus-server/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 1024; 3 | } 4 | 5 | http { 6 | server { 7 | listen 80; 8 | 9 | location / { 10 | stub_status; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | dangerouslyAllowSVG: true, 5 | remotePatterns: [{ hostname: "cdn.simpleicons.org" }], 6 | }, 7 | }; 8 | 9 | export default nextConfig; 10 | -------------------------------------------------------------------------------- /components/Divider.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | interface DividerProps { 4 | className?: string; 5 | } 6 | 7 | export const Divider = ({ className }: DividerProps) => { 8 | return
; 9 | }; 10 | -------------------------------------------------------------------------------- /public/plus.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /prometheus-server/clickhouse/config.xml: -------------------------------------------------------------------------------- 1 |{group.toUpperCase()}
8 | 9 |{title}
22 |17 | {"Looks like all our recommendations have\nbeen applied! No new ones as of now."} 18 |
19 |21 | {title} 22 |
23 | 24 | 25 |10 | {"No rules for this component\nwere found in the config."} 11 |
12 | 13 |14 | {"Apply rules from the Recommended\nsection and they’ll start showing up here."} 15 |
16 |27 | {name} 28 |
29 |DISCOVERED
20 |{formattedNumber}
35 | )} 36 |{rule.summary}
40 |{rule.description}
41 |{rule.summary}
47 |{rule.description}
48 |
51 |
--------------------------------------------------------------------------------
/app/api/alerts/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import yaml from "js-yaml";
3 | import { Alert, MappingFile, RuleFile } from "@/lib/types";
4 |
5 | const FILE_BASE = "https://raw.githubusercontent.com/samber/awesome-prometheus-alerts/master";
6 |
7 | // To fetch yml file from a given url
8 | async function fetchYml(url: string) {
9 | const response = await fetch(url);
10 |
11 | if (!response.ok) {
12 | throw 404;
13 | }
14 |
15 | return await response.text();
16 | }
17 |
18 | // Refactoring data to serve it on the frontend
19 | export async function GET() {
20 | try {
21 | const mappingYaml: string = await fetchYml(`${FILE_BASE}/_data/rules.yml`);
22 | const mappings = yaml.load(mappingYaml) as MappingFile;
23 |
24 | const alerts: Alert[] = await Promise.all(
25 | mappings.groups.map(async (group) => {
26 | const components = await Promise.all(
27 | group.services.map(async (service) => {
28 | const name = service.name.replaceAll(" ", "-").toLowerCase().trim();
29 |
30 | return await Promise.all(
31 | service.exporters.flatMap(async (exporter) => {
32 | const ruleYml = await fetchYml(
33 | `${FILE_BASE}/dist/rules/${name}/${exporter.slug}.yml`,
34 | );
35 |
36 | const ruleJson = yaml.load(ruleYml) as RuleFile;
37 |
38 | const rules = ruleJson.groups
39 | .flatMap((group) => group.rules)
40 | .filter((rule) => rule !== null)
41 | .map((rule, index) => ({
42 | summary: exporter.rules[index].name,
43 | description: exporter.rules[index].description,
44 | yml: rule,
45 | }));
46 |
47 | return {
48 | name:
49 | service.exporters.length > 1
50 | ? `${service.name} (${exporter.name})`
51 | : service.name,
52 | rules,
53 | };
54 | }),
55 | );
56 | }),
57 | );
58 |
59 | return {
60 | group: group.name,
61 | components: components.flat(),
62 | };
63 | }),
64 | );
65 |
66 | return NextResponse.json({
67 | status: "success",
68 | data: { alerts },
69 | });
70 | } catch (error) {
71 | let code;
72 | let reason;
73 |
74 | if (typeof error !== "number") {
75 | code = 500;
76 | } else {
77 | code = error;
78 | }
79 |
80 | if (code === 404) {
81 | reason = "Failed to fetch repository contents";
82 | } else if (code === 500) {
83 | reason = "Something went wrong. Please try again later.";
84 | }
85 |
86 | return NextResponse.json(
87 | {
88 | code,
89 | reason,
90 | status: "error",
91 | },
92 | { status: code },
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/components/Header.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { usePathname, useRouter } from "next/navigation";
4 | import useGithub from "@/hooks/queries/useGithub";
5 | import useAuth from "@/hooks/useAuth";
6 | import { useQueryClient } from "@tanstack/react-query";
7 | import AptLogo from "@/components/AptLogo";
8 | import { Divider } from "@/components/Divider";
9 | import { ExternalLink } from "@/components/ExternalLink";
10 | import { Page } from "@/components/Page";
11 | import {
12 | Tooltip,
13 | TooltipArrow,
14 | TooltipContent,
15 | TooltipProvider,
16 | TooltipTrigger,
17 | } from "@/components/ui/tooltip";
18 | import { cn } from "@/lib/utils";
19 |
20 | export const Header = () => {
21 | const router = useRouter();
22 | const { data } = useGithub();
23 | const { promUrl } = useAuth();
24 | const pathname = usePathname();
25 | const queryClient = useQueryClient();
26 |
27 | const changeSourcePath = () => {
28 | queryClient.clear();
29 | localStorage.clear();
30 | router.replace("/");
31 | };
32 |
33 | return (
34 | {promUrl}
56 | 57 | 58 | 59 |63 | Edit 64 |
65 |{component.name}
33 | 34 |38 | {recommended.length} recommended, 39 | {applied.length} applied 40 |
41 |81 | No services were discovered on this Prometheus server. 82 |
83 | 84 | 94 | } 95 | onClick={() => router.push("/library")} 96 | > 97 | Browse Library 98 | 99 |Browse Library
80 | 81 | } 85 | right={ 86 |/
88 |{name}
20 |
23 |
View Alert Rules
59 |115 | {"The most apt alert rules toolkit for\nyour Prometheus setup."} 116 |
117 | 118 |121 | Get alert rule recommendations. 122 |
123 |127 | Browse the alert rule library. 128 |
129 |133 | Simplify setting up Prometheus. 134 |
135 |139 | a companion project to{" "} 140 | window.open(sourceUrl, "_blank")}> 141 | awesome-prometheus-alerts 142 | 143 |
144 |157 | {`To get started, provide your local or\nproduction Prometheus read URL.`} 158 |
159 | 160 |234 | {serverError?.message || discoveryError?.message} 235 |
236 | ) : null} 237 |