Files
libnovel/homelab/otel/grafana/provisioning/dashboards/web-vitals.json
root e399b1ce01
Some checks failed
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Failing after 32s
Release / Docker (push) Has been skipped
Release / Gitea Release (push) Has been skipped
feat: admin UX overhaul — status filters, retry/cancel, mobile cards, i18n, shelf pre-populate
- Admin layout: SVG icons, active highlight, divider between nav sections
- Scrape page: status filter pills with counts, text + status combined search
- Audio page: status filter pills, cancel jobs, retry failed jobs, mobile cards for cache tab
- Translation page: status filter pills (incl. cancelled), cancel + retry jobs, mobile cancel/retry cards, i18n for all labels
- AI Jobs page: fix concurrent cancel (Set instead of single slot), per-job cancel errors inline, full mobile card layout, i18n title/heading
- Text-gen page: tagline editable input + copy, warnings copy, i18n title/heading
- Book page: chapter cover Save button, audio monitor link, currentShelf pre-populated from server
- pocketbase.ts: add getBookShelf(), shelf field on UserLibraryEntry
- New API route: POST /api/admin/translation/bulk (proxy for translation retry)
- i18n: 15 new admin_translation_*, admin_ai_jobs_*, admin_text_gen_* keys across all 5 locales
2026-04-08 18:30:35 +05:00

804 lines
25 KiB
JSON

{
"uid": "libnovel-web-vitals",
"title": "Web Vitals (RUM)",
"description": "Core Web Vitals from @grafana/faro-web-sdk. Data: browser \u2192 Alloy faro.receiver \u2192 Loki ({service_name=unknown_service}). Log format: key=value pairs, e.g. lcp=767.000000 fcp=767.000000. Use | regexp to extract.",
"tags": [
"libnovel",
"frontend",
"rum",
"web-vitals"
],
"timezone": "browser",
"refresh": "1m",
"time": {
"from": "now-24h",
"to": "now"
},
"schemaVersion": 39,
"panels": [
{
"id": 1,
"type": "stat",
"title": "LCP \u2014 p75 (Largest Contentful Paint)",
"description": "Good < 2.5s, needs improvement < 4s, poor >= 4s. Source: Loki {service_name=unknown_service} Faro measurements.",
"gridPos": {
"x": 0,
"y": 0,
"w": 4,
"h": 4
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
]
},
"colorMode": "background",
"graphMode": "none"
},
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 2500
},
{
"color": "red",
"value": 4000
}
]
}
}
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.75, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `lcp=(?P<lcp>\\d+\\.?\\d*)` | unwrap lcp [1h])",
"legendFormat": "LCP p75",
"instant": true
}
]
},
{
"id": 2,
"type": "stat",
"title": "INP \u2014 p75 (Interaction to Next Paint)",
"description": "Good < 200ms, needs improvement < 500ms, poor >= 500ms.",
"gridPos": {
"x": 4,
"y": 0,
"w": 4,
"h": 4
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
]
},
"colorMode": "background",
"graphMode": "none"
},
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 200
},
{
"color": "red",
"value": 500
}
]
}
}
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.75, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `inp=(?P<inp>\\d+\\.?\\d*)` | unwrap inp [1h])",
"legendFormat": "INP p75",
"instant": true
}
]
},
{
"id": 3,
"type": "stat",
"title": "CLS \u2014 p75 (Cumulative Layout Shift)",
"description": "Good < 0.1, needs improvement < 0.25, poor >= 0.25.",
"gridPos": {
"x": 8,
"y": 0,
"w": 4,
"h": 4
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
]
},
"colorMode": "background",
"graphMode": "none"
},
"fieldConfig": {
"defaults": {
"unit": "short",
"decimals": 3,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 0.1
},
{
"color": "red",
"value": 0.25
}
]
}
}
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.75, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `cls=(?P<cls>\\d+\\.?\\d*)` | unwrap cls [1h])",
"legendFormat": "CLS p75",
"instant": true
}
]
},
{
"id": 4,
"type": "stat",
"title": "TTFB \u2014 p75 (Time to First Byte)",
"description": "Good < 800ms, needs improvement < 1800ms, poor >= 1800ms.",
"gridPos": {
"x": 12,
"y": 0,
"w": 4,
"h": 4
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
]
},
"colorMode": "background",
"graphMode": "none"
},
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 800
},
{
"color": "red",
"value": 1800
}
]
}
}
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.75, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `ttfb=(?P<ttfb>\\d+\\.?\\d*)` | unwrap ttfb [1h])",
"legendFormat": "TTFB p75",
"instant": true
}
]
},
{
"id": 5,
"type": "stat",
"title": "FCP \u2014 p75 (First Contentful Paint)",
"description": "Good < 1.8s, needs improvement < 3s, poor >= 3s.",
"gridPos": {
"x": 16,
"y": 0,
"w": 4,
"h": 4
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
]
},
"colorMode": "background",
"graphMode": "none"
},
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1800
},
{
"color": "red",
"value": 3000
}
]
}
}
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.75, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `fcp=(?P<fcp>\\d+\\.?\\d*)` | unwrap fcp [1h])",
"legendFormat": "FCP p75",
"instant": true
}
]
},
{
"id": 6,
"type": "stat",
"title": "Measurements / min",
"description": "Number of Faro measurement events in the last 5 minutes (activity indicator).",
"gridPos": {
"x": 20,
"y": 0,
"w": 4,
"h": 4
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
]
},
"colorMode": "value",
"graphMode": "area"
},
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
}
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "sum(count_over_time({service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" [5m]))",
"legendFormat": "measurements",
"instant": true
}
]
},
{
"id": 10,
"type": "timeseries",
"title": "LCP over time (p50 / p75 / p95)",
"gridPos": {
"x": 0,
"y": 4,
"w": 12,
"h": 8
},
"options": {
"tooltip": {
"mode": "multi"
},
"legend": {
"displayMode": "list",
"placement": "bottom"
}
},
"fieldConfig": {
"defaults": {
"unit": "ms",
"custom": {
"lineWidth": 2,
"fillOpacity": 10
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Good (2.5s)"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "green",
"mode": "fixed"
}
},
{
"id": "custom.lineStyle",
"value": {
"fill": "dash",
"dash": [
4,
4
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Poor (4s)"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "red",
"mode": "fixed"
}
},
{
"id": "custom.lineStyle",
"value": {
"fill": "dash",
"dash": [
4,
4
]
}
}
]
}
]
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.50, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `lcp=(?P<lcp>\\d+\\.?\\d*)` | unwrap lcp [5m])",
"legendFormat": "p50"
},
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.75, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `lcp=(?P<lcp>\\d+\\.?\\d*)` | unwrap lcp [5m])",
"legendFormat": "p75"
},
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.95, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `lcp=(?P<lcp>\\d+\\.?\\d*)` | unwrap lcp [5m])",
"legendFormat": "p95"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"expr": "2500",
"legendFormat": "Good (2.5s)"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"expr": "4000",
"legendFormat": "Poor (4s)"
}
]
},
{
"id": 11,
"type": "timeseries",
"title": "TTFB over time (p50 / p75 / p95)",
"gridPos": {
"x": 12,
"y": 4,
"w": 12,
"h": 8
},
"options": {
"tooltip": {
"mode": "multi"
},
"legend": {
"displayMode": "list",
"placement": "bottom"
}
},
"fieldConfig": {
"defaults": {
"unit": "ms",
"custom": {
"lineWidth": 2,
"fillOpacity": 10
}
}
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.50, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `ttfb=(?P<ttfb>\\d+\\.?\\d*)` | unwrap ttfb [5m])",
"legendFormat": "p50"
},
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.75, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `ttfb=(?P<ttfb>\\d+\\.?\\d*)` | unwrap ttfb [5m])",
"legendFormat": "p75"
},
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "quantile_over_time(0.95, {service_name=\"unknown_service\"} |= \"kind=measurement\" |= \"type=web-vitals\" | regexp `ttfb=(?P<ttfb>\\d+\\.?\\d*)` | unwrap ttfb [5m])",
"legendFormat": "p95"
}
]
},
{
"id": 20,
"type": "logs",
"title": "Frontend Errors & Exceptions",
"description": "JS exceptions captured by Faro. kind=exception events.",
"gridPos": {
"x": 0,
"y": 12,
"w": 24,
"h": 10
},
"options": {
"showTime": true,
"showLabels": true,
"wrapLogMessage": true,
"prettifyLogMessage": true,
"enableLogDetails": true,
"sortOrder": "Descending",
"dedupStrategy": "none"
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "{service_name=\"unknown_service\"} | regexp `(?P<kind>\\w+)` | kind = \"exception\"",
"legendFormat": ""
}
]
},
{
"id": 21,
"type": "logs",
"title": "Web Vitals Measurements",
"description": "All Faro measurement events.",
"gridPos": {
"x": 0,
"y": 22,
"w": 24,
"h": 10
},
"options": {
"showTime": true,
"showLabels": true,
"wrapLogMessage": false,
"prettifyLogMessage": true,
"enableLogDetails": true,
"sortOrder": "Descending",
"dedupStrategy": "none"
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "{service_name=\"unknown_service\"} | regexp `(?P<kind>\\w+)` | kind = \"measurement\"",
"legendFormat": ""
}
]
},
{
"id": 30,
"type": "row",
"title": "API Performance (Upstream Requests)",
"gridPos": { "x": 0, "y": 32, "w": 24, "h": 1 },
"collapsed": false
},
{
"id": 31,
"type": "timeseries",
"title": "API Request Duration — p50 / p75 / p95 by endpoint",
"description": "Duration of all libnovel.cc/api/* fetch requests captured by Faro faro.performance.resource events. Values in ms.",
"gridPos": { "x": 0, "y": 33, "w": 24, "h": 10 },
"options": {
"tooltip": { "mode": "multi" },
"legend": { "displayMode": "table", "placement": "bottom", "calcs": ["mean", "max", "lastNotNull"] }
},
"fieldConfig": {
"defaults": {
"unit": "ms",
"custom": { "lineWidth": 2, "fillOpacity": 5 }
}
},
"targets": [
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.50, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/progress/audio-time\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p50 /api/progress/audio-time"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.95, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/progress/audio-time\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p95 /api/progress/audio-time"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.50, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/presign/audio\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p50 /api/presign/audio"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.95, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/presign/audio\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p95 /api/presign/audio"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.50, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/progress\" !~ \"audio-time\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p50 /api/progress"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.95, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/progress\" !~ \"audio-time\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p95 /api/progress"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.50, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/comments\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p50 /api/comments"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.95, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/comments\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p95 /api/comments"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.50, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/settings\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p50 /api/settings"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.95, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/settings\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p95 /api/settings"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.50, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/catalogue-page\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p50 /api/catalogue-page"
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "quantile_over_time(0.95, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/catalogue-page\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [5m])",
"legendFormat": "p95 /api/catalogue-page"
}
]
},
{
"id": 32,
"type": "barchart",
"title": "API Avg Duration — last 1h",
"description": "Average duration per endpoint over the last hour. Useful for spotting the slowest APIs at a glance.",
"gridPos": { "x": 0, "y": 43, "w": 12, "h": 8 },
"options": {
"orientation": "horizontal",
"legend": { "displayMode": "list", "placement": "bottom" },
"tooltip": { "mode": "single" },
"xTickLabelRotation": 0
},
"fieldConfig": {
"defaults": { "unit": "ms", "color": { "mode": "palette-classic" } }
},
"targets": [
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "avg_over_time({service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/progress/audio-time\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [1h])",
"legendFormat": "/api/progress/audio-time",
"instant": true
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "avg_over_time({service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/presign/audio\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [1h])",
"legendFormat": "/api/presign/audio",
"instant": true
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "avg_over_time({service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/progress\" !~ \"audio-time\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [1h])",
"legendFormat": "/api/progress",
"instant": true
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "avg_over_time({service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/comments\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [1h])",
"legendFormat": "/api/comments",
"instant": true
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "avg_over_time({service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/settings\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [1h])",
"legendFormat": "/api/settings",
"instant": true
},
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "avg_over_time({service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api/catalogue-page\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [1h])",
"legendFormat": "/api/catalogue-page",
"instant": true
}
]
},
{
"id": 33,
"type": "stat",
"title": "Slowest API call — p95 last 1h",
"description": "p95 duration of the single slowest endpoint in the last hour.",
"gridPos": { "x": 12, "y": 43, "w": 6, "h": 4 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "background",
"graphMode": "none"
},
"fieldConfig": {
"defaults": {
"unit": "ms",
"decimals": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 500 },
{ "color": "red", "value": 1000 }
]
}
}
},
"targets": [
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "max(quantile_over_time(0.95, {service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur [1h]))",
"legendFormat": "p95 max",
"instant": true
}
]
},
{
"id": 34,
"type": "stat",
"title": "API Requests / min",
"description": "Rate of libnovel.cc API requests captured by Faro in the last 5 minutes.",
"gridPos": { "x": 18, "y": 43, "w": 6, "h": 4 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "value",
"graphMode": "area"
},
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }
}
},
"targets": [
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "sum(count_over_time({service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api\" [5m])) / 5",
"legendFormat": "req/min",
"instant": true
}
]
},
{
"id": 35,
"type": "logs",
"title": "Slow API Requests (>500ms)",
"description": "Individual faro.performance.resource events where duration > 500ms. Useful for debugging outliers.",
"gridPos": { "x": 0, "y": 47, "w": 24, "h": 8 },
"options": {
"showTime": true,
"showLabels": false,
"wrapLogMessage": false,
"prettifyLogMessage": false,
"enableLogDetails": true,
"sortOrder": "Descending",
"dedupStrategy": "none"
},
"targets": [
{
"datasource": { "type": "loki", "uid": "loki" },
"expr": "{service_name=\"unknown_service\"} |= \"faro.performance.resource\" |= \"libnovel.cc/api\" | regexp `event_data_duration=(?P<dur>[0-9.]+)` | unwrap dur | dur > 500",
"legendFormat": ""
}
]
}
]
}