feat(infra): add web vitals Grafana dashboard and admin audit log migration
- Add Grafana dashboard for web vitals metrics visualization - Add Prisma migration for admin audit log table Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
397
monitoring/grafana/dashboards/web-vitals.json
Normal file
397
monitoring/grafana/dashboards/web-vitals.json
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
{
|
||||||
|
"annotations": { "list": [] },
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 1,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"title": "Core Web Vitals — Overview",
|
||||||
|
"type": "row",
|
||||||
|
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 },
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LCP (Largest Contentful Paint)",
|
||||||
|
"description": "Target: < 2.5s (good), < 4.0s (needs improvement)",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 6, "w": 6, "x": 0, "y": 1 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_lcp_seconds_bucket[5m])) by (le))",
|
||||||
|
"legendFormat": "p75 LCP",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"min": 0,
|
||||||
|
"max": 10,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 2.5 },
|
||||||
|
{ "color": "red", "value": 4.0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "FCP (First Contentful Paint)",
|
||||||
|
"description": "Target: < 1.8s (good), < 3.0s (needs improvement)",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 6, "w": 6, "x": 6, "y": 1 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_fcp_seconds_bucket[5m])) by (le))",
|
||||||
|
"legendFormat": "p75 FCP",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"min": 0,
|
||||||
|
"max": 5,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 1.8 },
|
||||||
|
{ "color": "red", "value": 3.0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "CLS (Cumulative Layout Shift)",
|
||||||
|
"description": "Target: < 0.1 (good), < 0.25 (needs improvement)",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 6, "w": 6, "x": 12, "y": 1 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_cls_bucket[5m])) by (le))",
|
||||||
|
"legendFormat": "p75 CLS",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"min": 0,
|
||||||
|
"max": 1,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 0.1 },
|
||||||
|
{ "color": "red", "value": 0.25 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "TTFB (Time to First Byte)",
|
||||||
|
"description": "Target: < 800ms (good)",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 6, "w": 6, "x": 18, "y": 1 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_ttfb_seconds_bucket[5m])) by (le))",
|
||||||
|
"legendFormat": "p75 TTFB",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"min": 0,
|
||||||
|
"max": 5,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 0.8 },
|
||||||
|
{ "color": "red", "value": 1.8 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Rating Distribution",
|
||||||
|
"type": "row",
|
||||||
|
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 7 },
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Web Vital Ratings (Good / Needs Improvement / Poor)",
|
||||||
|
"type": "piechart",
|
||||||
|
"gridPos": { "h": 8, "w": 8, "x": 0, "y": 8 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "sum(goodgo_web_vitals_total) by (rating)",
|
||||||
|
"legendFormat": "{{rating}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {},
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byName", "options": "good" }, "properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }] },
|
||||||
|
{ "matcher": { "id": "byName", "options": "needs-improvement" }, "properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }] },
|
||||||
|
{ "matcher": { "id": "byName", "options": "poor" }, "properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Events by Vital Type",
|
||||||
|
"type": "barchart",
|
||||||
|
"gridPos": { "h": 8, "w": 8, "x": 8, "y": 8 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "sum(goodgo_web_vitals_total) by (name)",
|
||||||
|
"legendFormat": "{{name}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "short" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "INP (Interaction to Next Paint)",
|
||||||
|
"description": "Target: < 200ms (good), < 500ms (needs improvement)",
|
||||||
|
"type": "gauge",
|
||||||
|
"gridPos": { "h": 8, "w": 8, "x": 16, "y": 8 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_inp_seconds_bucket[5m])) by (le))",
|
||||||
|
"legendFormat": "p75 INP",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"min": 0,
|
||||||
|
"max": 1,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 0.2 },
|
||||||
|
{ "color": "red", "value": 0.5 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Trends Over Time",
|
||||||
|
"type": "row",
|
||||||
|
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 16 },
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LCP Trend (p50 / p75 / p95)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 17 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.50, sum(rate(goodgo_web_vitals_lcp_seconds_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p50",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_lcp_seconds_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p75",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(goodgo_web_vitals_lcp_seconds_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p95",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"custom": { "drawStyle": "line", "fillOpacity": 10, "pointSize": 5 },
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 2.5 },
|
||||||
|
{ "color": "red", "value": 4.0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "CLS Trend (p50 / p75 / p95)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 17 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.50, sum(rate(goodgo_web_vitals_cls_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p50",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_cls_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p75",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(goodgo_web_vitals_cls_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p95",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"custom": { "drawStyle": "line", "fillOpacity": 10, "pointSize": 5 },
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 0.1 },
|
||||||
|
{ "color": "red", "value": 0.25 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "TTFB Trend (p50 / p75 / p95)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 25 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.50, sum(rate(goodgo_web_vitals_ttfb_seconds_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p50",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_ttfb_seconds_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p75",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(goodgo_web_vitals_ttfb_seconds_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p95",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"custom": { "drawStyle": "line", "fillOpacity": 10, "pointSize": 5 },
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 0.8 },
|
||||||
|
{ "color": "red", "value": 1.8 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "INP Trend (p50 / p75 / p95)",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 25 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.50, sum(rate(goodgo_web_vitals_inp_seconds_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p50",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_inp_seconds_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p75",
|
||||||
|
"refId": "B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.95, sum(rate(goodgo_web_vitals_inp_seconds_bucket[15m])) by (le))",
|
||||||
|
"legendFormat": "p95",
|
||||||
|
"refId": "C"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"custom": { "drawStyle": "line", "fillOpacity": 10, "pointSize": 5 },
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{ "color": "green", "value": null },
|
||||||
|
{ "color": "yellow", "value": 0.2 },
|
||||||
|
{ "color": "red", "value": 0.5 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Page Breakdown",
|
||||||
|
"type": "row",
|
||||||
|
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 33 },
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LCP by Page (p75)",
|
||||||
|
"type": "table",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 34 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "histogram_quantile(0.75, sum(rate(goodgo_web_vitals_lcp_seconds_bucket[1h])) by (le, page)) > 0",
|
||||||
|
"legendFormat": "{{page}}",
|
||||||
|
"refId": "A",
|
||||||
|
"format": "table",
|
||||||
|
"instant": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "s" }
|
||||||
|
},
|
||||||
|
"transformations": [
|
||||||
|
{ "id": "organize", "options": { "excludeByName": { "Time": true, "le": true } } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Vitals Event Rate",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 34 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "sum(rate(goodgo_web_vitals_total[5m])) by (name)",
|
||||||
|
"legendFormat": "{{name}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "ops",
|
||||||
|
"custom": { "drawStyle": "line", "fillOpacity": 10 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"tags": ["web-vitals", "rum", "frontend"],
|
||||||
|
"templating": { "list": [] },
|
||||||
|
"time": { "from": "now-6h", "to": "now" },
|
||||||
|
"title": "Frontend Performance — Core Web Vitals",
|
||||||
|
"uid": "goodgo-web-vitals",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "AdminAction" AS ENUM ('LISTING_APPROVED', 'LISTING_REJECTED', 'LISTING_BULK_APPROVED', 'LISTING_BULK_REJECTED', 'USER_BANNED', 'USER_UNBANNED', 'USER_STATUS_UPDATED', 'KYC_APPROVED', 'KYC_REJECTED', 'SUBSCRIPTION_ADJUSTED');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "AuditTargetType" AS ENUM ('USER', 'LISTING', 'SUBSCRIPTION');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "AdminAuditLog" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"action" "AdminAction" NOT NULL,
|
||||||
|
"actorId" TEXT NOT NULL,
|
||||||
|
"targetId" TEXT NOT NULL,
|
||||||
|
"targetType" "AuditTargetType" NOT NULL,
|
||||||
|
"metadata" JSONB,
|
||||||
|
"ipAddress" TEXT,
|
||||||
|
"userAgent" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "AdminAuditLog_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AdminAuditLog_actorId_idx" ON "AdminAuditLog"("actorId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AdminAuditLog_targetId_targetType_idx" ON "AdminAuditLog"("targetId", "targetType");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AdminAuditLog_action_idx" ON "AdminAuditLog"("action");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AdminAuditLog_createdAt_idx" ON "AdminAuditLog"("createdAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AdminAuditLog_actorId_createdAt_idx" ON "AdminAuditLog"("actorId", "createdAt" DESC);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "AdminAuditLog_action_createdAt_idx" ON "AdminAuditLog"("action", "createdAt" DESC);
|
||||||
Reference in New Issue
Block a user