feat: add Grafana Faro RUM, fix dashboards, add Grafana to admin nav
All checks were successful
Release / Test backend (push) Successful in 40s
Release / Check ui (push) Successful in 1m5s
Release / Docker / caddy (push) Successful in 1m9s
Release / Docker / backend (push) Successful in 3m14s
Release / Docker / runner (push) Successful in 4m7s
Release / Upload source maps (push) Successful in 2m11s
Release / Docker / ui (push) Successful in 2m23s
Release / Gitea Release (push) Successful in 40s

- Add @grafana/faro-web-sdk to UI; wire initializeFaro in hooks.client.ts
  gated on PUBLIC_FARO_COLLECTOR_URL (no-op in dev)
- Add Grafana Alloy service (faro.receiver) to homelab compose;
  Faro endpoint → alloy:12347 (faro.libnovel.cc via cloudflared)
- Add PUBLIC_FARO_COLLECTOR_URL env var to docker-compose.yml UI service
- Add Web Vitals dashboard (web-vitals.json): LCP/INP/CLS/TTFB/FCP p75
  stats + LCP/TTFB time-series + Faro exception logs from Loki
- Fix runner.json: strip libnovel_ prefix from all metric names
- Fix backend.json: replace 5 dead http_client_* panels with
  spanmetrics-based equivalents (Request Rate by Span Name + Latency
  by Span Name p95)
- Fix OTel collector: add service.telemetry.metrics.address: 0.0.0.0:8888
  so Prometheus can scrape collector self-metrics
- Add Grafana link to admin nav external tools; add admin_nav_grafana
  message key to all 5 locale files; recompile paraglide
This commit is contained in:
Admin
2026-04-05 12:58:16 +05:00
parent eb8a92f0c1
commit 4d3b91af30
18 changed files with 593 additions and 62 deletions

View File

@@ -1,5 +1,6 @@
import * as Sentry from '@sentry/sveltekit';
import { env } from '$env/dynamic/public';
import { initializeFaro, getWebInstrumentations } from '@grafana/faro-web-sdk';
// Sentry / GlitchTip client-side error tracking.
// No-op when PUBLIC_GLITCHTIP_DSN is unset (e.g. local dev).
@@ -13,4 +14,21 @@ if (env.PUBLIC_GLITCHTIP_DSN) {
});
}
// Grafana Faro RUM — browser performance monitoring (Web Vitals, traces, errors).
// No-op when PUBLIC_FARO_COLLECTOR_URL is unset (e.g. local dev).
if (env.PUBLIC_FARO_COLLECTOR_URL) {
initializeFaro({
url: env.PUBLIC_FARO_COLLECTOR_URL,
app: {
name: 'libnovel-ui',
version: env.PUBLIC_BUILD_VERSION || 'dev',
environment: 'production'
},
instrumentations: [
// Core Web Vitals (LCP, CLS, INP, TTFB, FCP) + JS errors + console
...getWebInstrumentations({ captureConsole: false })
]
});
}
export const handleError = Sentry.handleErrorWithSentry();

View File

@@ -381,6 +381,7 @@ export * from './admin_nav_logs.js'
export * from './admin_nav_uptime.js'
export * from './admin_nav_push.js'
export * from './admin_nav_gitea.js'
export * from './admin_nav_grafana.js'
export * from './admin_scrape_status_idle.js'
export * from './admin_scrape_full_catalogue.js'
export * from './admin_scrape_single_book.js'

View File

@@ -0,0 +1,44 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Nav_GrafanaInputs */
const en_admin_nav_grafana = /** @type {(inputs: Admin_Nav_GrafanaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Grafana`)
};
const ru_admin_nav_grafana = /** @type {(inputs: Admin_Nav_GrafanaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Grafana`)
};
const id_admin_nav_grafana = /** @type {(inputs: Admin_Nav_GrafanaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Grafana`)
};
const pt_admin_nav_grafana = /** @type {(inputs: Admin_Nav_GrafanaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Grafana`)
};
const fr_admin_nav_grafana = /** @type {(inputs: Admin_Nav_GrafanaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Grafana`)
};
/**
* | output |
* | --- |
* | "Grafana" |
*
* @param {Admin_Nav_GrafanaInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_nav_grafana = /** @type {((inputs?: Admin_Nav_GrafanaInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_GrafanaInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_nav_grafana(inputs)
if (locale === "ru") return ru_admin_nav_grafana(inputs)
if (locale === "id") return id_admin_nav_grafana(inputs)
if (locale === "pt") return pt_admin_nav_grafana(inputs)
return fr_admin_nav_grafana(inputs)
});

View File

@@ -19,6 +19,7 @@
{ href: 'https://logs.libnovel.cc', label: () => m.admin_nav_logs() },
{ href: 'https://uptime.libnovel.cc', label: () => m.admin_nav_uptime() },
{ href: 'https://push.libnovel.cc', label: () => m.admin_nav_push() },
{ href: 'https://grafana.libnovel.cc', label: () => m.admin_nav_grafana() },
{ href: 'https://gitea.kalekber.cc/kamil/libnovel', label: () => m.admin_nav_gitea() }
];