From 2e57e60335339bf6da040a61dbda60fcced2b1d1 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 22:41:41 -0500 Subject: [PATCH 01/19] feat: enhance WaaS Grafana dashboard with data links, backup monitoring, and layout improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add clickable links: Site Status → admin, API Reachable → endpoint, HTTP Status → Wikipedia reference, Last Backup/Records → Akeeba Manage - Fix backup status doubling with max by() aggregation - Show version as two-row display (site name + version string) using table format with labelsToFields - Map 0 values to dash on Extensions, Enabled, Disabled panels - Set minVizWidth for 3-column wrapping layout - All links use dynamic Grafana variables, no hardcoded endpoints Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- monitoring/grafana/moko-waas-dashboard.json | 172 +++++++++++++++++--- 1 file changed, 145 insertions(+), 27 deletions(-) diff --git a/monitoring/grafana/moko-waas-dashboard.json b/monitoring/grafana/moko-waas-dashboard.json index 30edeb4..784a0b2 100644 --- a/monitoring/grafana/moko-waas-dashboard.json +++ b/monitoring/grafana/moko-waas-dashboard.json @@ -36,6 +36,13 @@ "type": "value" } ], + "links": [ + { + "title": "Open Admin", + "url": "${__field.labels.instance}/administrator/", + "targetBlank": true + } + ], "thresholds": { "steps": [ { @@ -54,12 +61,14 @@ "x": 0, "y": 1, "w": 12, - "h": 4 + "h": 6 }, "id": 1, "options": { "colorMode": "background", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -120,7 +129,9 @@ "id": 2, "options": { "colorMode": "background", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -142,6 +153,13 @@ }, "fieldConfig": { "defaults": { + "links": [ + { + "title": "HTTP Status Code Reference", + "url": "https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#${__value.raw}", + "targetBlank": true + } + ], "thresholds": { "steps": [ { @@ -177,7 +195,9 @@ "id": 3, "options": { "colorMode": "background", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -228,7 +248,9 @@ "id": 4, "options": { "colorMode": "background", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -265,6 +287,13 @@ "type": "value" } ], + "links": [ + { + "title": "Open Site", + "url": "${__field.labels.exported_instance}", + "targetBlank": true + } + ], "noValue": "N/A", "thresholds": { "steps": [ @@ -289,7 +318,9 @@ "id": 5, "options": { "colorMode": "background", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -332,7 +363,9 @@ "id": 6, "options": { "colorMode": "value", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -364,8 +397,16 @@ "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "gridPos": { + "x": 0, + "y": 14, + "w": 12, + "h": 4 + }, + "id": 10, "fieldConfig": { "defaults": { + "noValue": "—", "thresholds": { "steps": [ { @@ -376,17 +417,15 @@ } } }, - "gridPos": { - "x": 0, - "y": 14, - "w": 12, - "h": 4 - }, - "id": 10, "options": { "colorMode": "background", "graphMode": "none", - "textMode": "name" + "textMode": "value_and_name", + "reduceOptions": { + "values": true, + "calcs": [], + "fields": "/^version$/" + } }, "targets": [ { @@ -395,7 +434,28 @@ "uid": "PBFA97CFB590B2093" }, "expr": "joomla_core_version_info{site=~\"$site\"}", - "legendFormat": "{{site}} {{version}}" + "instant": true, + "format": "table" + } + ], + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "__name__": true, + "client": true, + "exported_instance": true, + "instance": true, + "job": true + }, + "indexByName": { + "site": 0, + "version": 1 + } + } } ], "title": "Joomla Version", @@ -447,7 +507,9 @@ "id": 11, "options": { "colorMode": "background", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -469,7 +531,17 @@ }, "fieldConfig": { "defaults": { - "noValue": "N/A", + "mappings": [ + { + "options": { + "0": { + "text": "—" + } + }, + "type": "value" + } + ], + "noValue": "—", "thresholds": { "steps": [ { @@ -489,7 +561,9 @@ "id": 12, "options": { "colorMode": "value", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -511,7 +585,17 @@ }, "fieldConfig": { "defaults": { - "noValue": "N/A", + "mappings": [ + { + "options": { + "0": { + "text": "—" + } + }, + "type": "value" + } + ], + "noValue": "—", "thresholds": { "steps": [ { @@ -531,7 +615,9 @@ "id": 13, "options": { "colorMode": "value", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -553,7 +639,17 @@ }, "fieldConfig": { "defaults": { - "noValue": "N/A", + "mappings": [ + { + "options": { + "0": { + "text": "—" + } + }, + "type": "value" + } + ], + "noValue": "—", "thresholds": { "steps": [ { @@ -581,7 +677,9 @@ "id": 14, "options": { "colorMode": "value", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -737,6 +835,13 @@ "type": "value" } ], + "links": [ + { + "title": "Manage Backups", + "url": "${__field.labels.exported_instance}/administrator/index.php?option=com_akeebabackup&view=Manage", + "targetBlank": true + } + ], "noValue": "No Data", "thresholds": { "steps": [ @@ -761,7 +866,9 @@ "id": 40, "options": { "colorMode": "background", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -769,7 +876,7 @@ "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "joomla_backup_status{site=~\"$site\"}", + "expr": "max by (site, exported_instance, client) (joomla_backup_status{site=~\"$site\"})", "legendFormat": "{{site}}" } ], @@ -812,7 +919,9 @@ "id": 41, "options": { "colorMode": "value", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { @@ -834,6 +943,13 @@ }, "fieldConfig": { "defaults": { + "links": [ + { + "title": "Manage Backups", + "url": "${__field.labels.exported_instance}/administrator/index.php?option=com_akeebabackup&view=Manage", + "targetBlank": true + } + ], "noValue": "N/A", "thresholds": { "steps": [ @@ -858,7 +974,9 @@ "id": 42, "options": { "colorMode": "value", - "graphMode": "none" + "graphMode": "none", + "minVizWidth": 200, + "minVizHeight": 50 }, "targets": [ { -- 2.52.0 From c06133842824deb21003b03f9201ce45ebd99bee Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 22:55:50 -0500 Subject: [PATCH 02/19] feat: full-width stat layout, filter non-Joomla sites, standardize noValue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch all stat panels to w:24 full-width for natural 3-column wrapping - Filter site variable to joomla.* site_type only (excludes git, grafana) - Standardize all noValue to "—" for offline/unavailable endpoints - Joomla Version panel uses table format with labelsToFields for xx.xx.xx display - Remove wideLayout option (only applies to value_and_name textMode) Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- monitoring/grafana/moko-waas-dashboard.json | 151 ++++++++------------ 1 file changed, 63 insertions(+), 88 deletions(-) diff --git a/monitoring/grafana/moko-waas-dashboard.json b/monitoring/grafana/moko-waas-dashboard.json index 784a0b2..fc60977 100644 --- a/monitoring/grafana/moko-waas-dashboard.json +++ b/monitoring/grafana/moko-waas-dashboard.json @@ -43,6 +43,7 @@ "targetBlank": true } ], + "noValue": "—", "thresholds": { "steps": [ { @@ -60,15 +61,13 @@ "gridPos": { "x": 0, "y": 1, - "w": 12, - "h": 6 + "w": 24, + "h": 4 }, "id": 1, "options": { "colorMode": "background", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -105,7 +104,7 @@ "type": "value" } ], - "noValue": "N/A", + "noValue": "—", "thresholds": { "steps": [ { @@ -121,17 +120,15 @@ } }, "gridPos": { - "x": 12, - "y": 1, - "w": 12, + "x": 0, + "y": 5, + "w": 24, "h": 4 }, "id": 2, "options": { "colorMode": "background", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -188,16 +185,14 @@ }, "gridPos": { "x": 0, - "y": 5, - "w": 12, + "y": 9, + "w": 24, "h": 4 }, "id": 3, "options": { "colorMode": "background", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -240,17 +235,15 @@ } }, "gridPos": { - "x": 12, - "y": 5, - "w": 12, + "x": 0, + "y": 13, + "w": 24, "h": 4 }, "id": 4, "options": { "colorMode": "background", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -294,7 +287,7 @@ "targetBlank": true } ], - "noValue": "N/A", + "noValue": "—", "thresholds": { "steps": [ { @@ -311,16 +304,14 @@ }, "gridPos": { "x": 0, - "y": 9, - "w": 12, + "y": 17, + "w": 24, "h": 4 }, "id": 5, "options": { "colorMode": "background", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -342,7 +333,7 @@ }, "fieldConfig": { "defaults": { - "noValue": "Never", + "noValue": "—", "thresholds": { "steps": [ { @@ -355,17 +346,15 @@ } }, "gridPos": { - "x": 12, - "y": 9, - "w": 12, + "x": 0, + "y": 21, + "w": 24, "h": 4 }, "id": 6, "options": { "colorMode": "value", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -386,7 +375,7 @@ "h": 1, "w": 24, "x": 0, - "y": 13 + "y": 25 }, "id": 101, "title": "Joomla Core & Updates", @@ -399,8 +388,8 @@ }, "gridPos": { "x": 0, - "y": 14, - "w": 12, + "y": 26, + "w": 24, "h": 4 }, "id": 10, @@ -483,7 +472,7 @@ "type": "value" } ], - "noValue": "?", + "noValue": "—", "thresholds": { "steps": [ { @@ -499,17 +488,15 @@ } }, "gridPos": { - "x": 12, - "y": 14, - "w": 12, + "x": 0, + "y": 30, + "w": 24, "h": 4 }, "id": 11, "options": { "colorMode": "background", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -554,16 +541,14 @@ }, "gridPos": { "x": 0, - "y": 18, - "w": 12, + "y": 34, + "w": 24, "h": 4 }, "id": 12, "options": { "colorMode": "value", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -607,17 +592,15 @@ } }, "gridPos": { - "x": 12, - "y": 18, - "w": 12, + "x": 0, + "y": 38, + "w": 24, "h": 4 }, "id": 13, "options": { "colorMode": "value", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -670,16 +653,14 @@ }, "gridPos": { "x": 0, - "y": 22, - "w": 12, + "y": 42, + "w": 24, "h": 4 }, "id": 14, "options": { "colorMode": "value", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -700,7 +681,7 @@ "h": 1, "w": 24, "x": 0, - "y": 26 + "y": 46 }, "id": 102, "title": "Performance", @@ -720,7 +701,7 @@ "h": 8, "w": 16, "x": 0, - "y": 27 + "y": 47 }, "id": 20, "options": { @@ -785,7 +766,7 @@ "h": 8, "w": 8, "x": 16, - "y": 27 + "y": 47 }, "id": 22, "targets": [ @@ -807,7 +788,7 @@ "h": 1, "w": 24, "x": 0, - "y": 35 + "y": 55 }, "id": 104, "title": "Backup Status", @@ -842,7 +823,7 @@ "targetBlank": true } ], - "noValue": "No Data", + "noValue": "—", "thresholds": { "steps": [ { @@ -859,16 +840,14 @@ }, "gridPos": { "x": 0, - "y": 36, - "w": 12, + "y": 56, + "w": 24, "h": 4 }, "id": 40, "options": { "colorMode": "background", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -890,7 +869,7 @@ }, "fieldConfig": { "defaults": { - "noValue": "?", + "noValue": "—", "thresholds": { "steps": [ { @@ -911,17 +890,15 @@ } }, "gridPos": { - "x": 12, - "y": 36, - "w": 12, + "x": 0, + "y": 60, + "w": 24, "h": 4 }, "id": 41, "options": { "colorMode": "value", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -950,7 +927,7 @@ "targetBlank": true } ], - "noValue": "N/A", + "noValue": "—", "thresholds": { "steps": [ { @@ -967,16 +944,14 @@ }, "gridPos": { "x": 0, - "y": 40, - "w": 12, + "y": 64, + "w": 24, "h": 4 }, "id": 42, "options": { "colorMode": "value", - "graphMode": "none", - "minVizWidth": 200, - "minVizHeight": 50 + "graphMode": "none" }, "targets": [ { @@ -997,7 +972,7 @@ "h": 1, "w": 24, "x": 0, - "y": 44 + "y": 68 }, "id": 103, "title": "Uptime History", @@ -1039,7 +1014,7 @@ "h": 8, "w": 24, "x": 0, - "y": 45 + "y": 69 }, "id": 30, "options": { @@ -1086,7 +1061,7 @@ "label": "Site", "multi": true, "name": "site", - "query": "label_values(probe_success{job=\"blackbox-http\"}, site_name)", + "query": "label_values(probe_success{job=\"blackbox-http\", site_type=~\"joomla.*\"}, site_name)", "refresh": 2, "sort": 1, "type": "query" -- 2.52.0 From 1c7de961c60964b081720eec7849929999810d31 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 23:38:10 -0500 Subject: [PATCH 03/19] chore: migrate references from .gitea/ to .mokogitea/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update template-CONTRIBUTING.md: .github/workflows/ → .mokogitea/workflows/ - Update templates/gitea/README.md: .gitea/ → .mokogitea/ - .mokogitea/ is now the standard system folder Co-Authored-By: Claude Opus 4.6 (1M context) --- monitoring/grafana/moko-waas-dashboard.json | 1121 ++++++----------- .../docs/required/template-CONTRIBUTING.md | 4 +- templates/gitea/README.md | 18 +- 3 files changed, 387 insertions(+), 756 deletions(-) diff --git a/monitoring/grafana/moko-waas-dashboard.json b/monitoring/grafana/moko-waas-dashboard.json index fc60977..8bf3c58 100644 --- a/monitoring/grafana/moko-waas-dashboard.json +++ b/monitoring/grafana/moko-waas-dashboard.json @@ -21,356 +21,187 @@ }, "fieldConfig": { "defaults": { - "mappings": [ - { - "options": { - "0": { - "color": "red", - "text": "DOWN" - }, - "1": { - "color": "green", - "text": "UP" - } - }, - "type": "value" - } - ], - "links": [ - { - "title": "Open Admin", - "url": "${__field.labels.instance}/administrator/", - "targetBlank": true - } - ], "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "red", - "value": 0 - }, - { - "color": "green", - "value": 1 - } + "custom": { + "align": "center", + "cellOptions": { "type": "auto" } + } + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "Site" }, + "properties": [ + { "id": "custom.width", "value": 150 }, + { "id": "custom.align", "value": "left" } + ] + }, + { + "matcher": { "id": "byName", "options": "URL" }, + "properties": [ + { "id": "custom.width", "value": 300 }, + { "id": "custom.align", "value": "left" }, + { "id": "links", "value": [{ "title": "Open Site", "url": "${__value.text}", "targetBlank": true }] } + ] + }, + { + "matcher": { "id": "byName", "options": "Status" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "color": "red", "text": "DOWN" }, "1": { "color": "green", "text": "UP" } }, "type": "value" }] }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } + ] + }, + { + "matcher": { "id": "byName", "options": "HTTP" }, + "properties": [ + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 200 }, { "color": "yellow", "value": 300 }, { "color": "orange", "value": 400 }, { "color": "red", "value": 500 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } }, + { "id": "links", "value": [{ "title": "HTTP Status Code Reference", "url": "https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#${__value.raw}", "targetBlank": true }] } + ] + }, + { + "matcher": { "id": "byName", "options": "Online" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "color": "orange", "text": "OFFLINE" }, "1": { "color": "green", "text": "ONLINE" } }, "type": "value" }] }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "orange", "value": null }, { "color": "green", "value": 1 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } + ] + }, + { + "matcher": { "id": "byName", "options": "API" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "color": "red", "text": "NO" }, "1": { "color": "green", "text": "OK" } }, "type": "value" }] }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } + ] + }, + { + "matcher": { "id": "byName", "options": "SSL Days" }, + "properties": [ + { "id": "decimals", "value": 0 }, + { "id": "unit", "value": "d" }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "yellow", "value": 14 }, { "color": "green", "value": 30 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-text" } } + ] + }, + { + "matcher": { "id": "byName", "options": "Last Scrape" }, + "properties": [ + { "id": "unit", "value": "dateTimeFromNow" } ] } - } + ] }, "gridPos": { "x": 0, "y": 1, "w": 24, - "h": 4 + "h": 8 }, "id": 1, "options": { - "colorMode": "background", - "graphMode": "none" + "showHeader": true, + "cellHeight": "sm", + "footer": { "show": false } }, "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "probe_success{site_name=~\"$site\", job=\"blackbox-http\"}", - "legendFormat": "{{site_name}}" - } - ], - "title": "Site Status", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "options": { - "0": { - "color": "orange", - "text": "OFFLINE" - }, - "1": { - "color": "green", - "text": "ONLINE" - } - }, - "type": "value" - } - ], - "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "orange", - "value": 0 - }, - { - "color": "green", - "value": 1 - } - ] - } - } - }, - "gridPos": { - "x": 0, - "y": 5, - "w": 24, - "h": 4 - }, - "id": 2, - "options": { - "colorMode": "background", - "graphMode": "none" - }, - "targets": [ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "probe_success{site_name=~\"$site\", job=\"blackbox-http\"} and on(site_name) label_replace(joomla_site_online{site=~\"$site\"} == 1, \"site_name\", \"$1\", \"site\", \"(.+)\")", + "instant": true, + "format": "table", + "refId": "STATUS" + }, { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "joomla_site_online{site=~\"$site\"}", - "legendFormat": "{{site}}" - } - ], - "title": "Joomla Online", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "links": [ - { - "title": "HTTP Status Code Reference", - "url": "https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#${__value.raw}", - "targetBlank": true - } - ], - "thresholds": { - "steps": [ - { - "color": "red", - "value": 0 - }, - { - "color": "green", - "value": 200 - }, - { - "color": "yellow", - "value": 300 - }, - { - "color": "orange", - "value": 400 - }, - { - "color": "red", - "value": 500 - } - ] - } - } - }, - "gridPos": { - "x": 0, - "y": 9, - "w": 24, - "h": 4 - }, - "id": 3, - "options": { - "colorMode": "background", - "graphMode": "none" - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "probe_http_status_code{site_name=~\"$site\", job=\"blackbox-http\"}", - "legendFormat": "{{site_name}}" - } - ], - "title": "HTTP Status", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "decimals": 0, - "thresholds": { - "steps": [ - { - "color": "red", - "value": 0 - }, - { - "color": "yellow", - "value": 14 - }, - { - "color": "green", - "value": 30 - } - ] - }, - "unit": "d" - } - }, - "gridPos": { - "x": 0, - "y": 13, - "w": 24, - "h": 4 - }, - "id": 4, - "options": { - "colorMode": "background", - "graphMode": "none" - }, - "targets": [ + "instant": true, + "format": "table", + "refId": "HTTP" + }, { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "label_replace(joomla_site_online{site=~\"$site\"}, \"site_name\", \"$1\", \"site\", \"(.+)\")", + "instant": true, + "format": "table", + "refId": "ONLINE" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "label_replace(joomla_site_api_reachable{site=~\"$site\"}, \"site_name\", \"$1\", \"site\", \"(.+)\") and on(site_name) label_replace(joomla_site_online{site=~\"$site\"} == 1, \"site_name\", \"$1\", \"site\", \"(.+)\")", + "instant": true, + "format": "table", + "refId": "API" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "(probe_ssl_earliest_cert_expiry{site_name=~\"$site\"} - time()) / 86400", - "legendFormat": "{{site_name}}" + "instant": true, + "format": "table", + "refId": "SSL" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "label_replace(joomla_monitor_last_scrape{site=~\"$site\"} * 1000, \"site_name\", \"$1\", \"site\", \"(.+)\")", + "instant": true, + "format": "table", + "refId": "LASTSCRAPE" } ], - "title": "SSL Days", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "options": { - "0": { - "color": "red", - "text": "NO" - }, - "1": { - "color": "green", - "text": "OK" - } - }, - "type": "value" + "transformations": [ + { + "id": "joinByField", + "options": { "byField": "site_name", "mode": "outer" } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, "Time 1": true, "Time 2": true, "Time 3": true, "Time 4": true, "Time 5": true, "Time 6": true, + "client": true, "client 1": true, "client 2": true, "client 3": true, "client 4": true, "client 5": true, "client 6": true, + "exported_instance": true, "exported_instance 1": true, "exported_instance 2": true, "exported_instance 3": true, + "instance 1": true, "instance 2": true, "instance 3": true, "instance 4": true, "instance 5": true, "instance 6": true, + "job": true, "job 1": true, "job 2": true, "job 3": true, "job 4": true, "job 5": true, "job 6": true, + "site": true, "site 1": true, "site 2": true, "site 3": true, + "site_name 1": true, "site_name 2": true, "site_name 3": true, "site_name 4": true, + "site_type": true, "site_type 1": true, "site_type 2": true, "site_type 3": true, + "__name__": true, "__name__ 1": true, "__name__ 2": true, "__name__ 3": true, "__name__ 4": true, "__name__ 5": true, "__name__ 6": true + }, + "renameByName": { + "site_name": "Site", + "instance": "URL", + "Value #STATUS": "Status", + "Value #HTTP": "HTTP", + "Value #ONLINE": "Online", + "Value #API": "API", + "Value #SSL": "SSL Days", + "Value #LASTSCRAPE": "Last Scrape" + }, + "indexByName": { + "site_name": 0, + "instance": 1, + "Value #ONLINE": 2, + "Value #STATUS": 3, + "Value #HTTP": 4, + "Value #API": 5, + "Value #SSL": 6, + "Value #LASTSCRAPE": 7 } - ], - "links": [ - { - "title": "Open Site", - "url": "${__field.labels.exported_instance}", - "targetBlank": true - } - ], - "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "red", - "value": 0 - }, - { - "color": "green", - "value": 1 - } - ] + } + }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [{ "field": "Site", "desc": false }] } } - }, - "gridPos": { - "x": 0, - "y": 17, - "w": 24, - "h": 4 - }, - "id": 5, - "options": { - "colorMode": "background", - "graphMode": "none" - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "joomla_site_api_reachable{site=~\"$site\"}", - "legendFormat": "{{site}}" - } ], - "title": "API Reachable", - "type": "stat" + "title": "Site Health", + "type": "table" }, { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "dateTimeFromNow" - } - }, - "gridPos": { - "x": 0, - "y": 21, - "w": 24, - "h": 4 - }, - "id": 6, - "options": { - "colorMode": "value", - "graphMode": "none" - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "joomla_monitor_last_scrape{site=~\"$site\"} * 1000", - "legendFormat": "{{site}}" - } - ], - "title": "Last Check", - "type": "stat" - }, - { - "collapsed": false, + "collapsed": true, "gridPos": { "h": 1, "w": 24, @@ -379,301 +210,159 @@ }, "id": 101, "title": "Joomla Core & Updates", - "type": "row" - }, + "type": "row", + "panels": [ { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "gridPos": { - "x": 0, - "y": 26, - "w": 24, - "h": 4 - }, - "id": 10, + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "fieldConfig": { "defaults": { "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "blue", - "value": null - } + "custom": { "align": "center", "cellOptions": { "type": "auto" } } + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "Site" }, + "properties": [ + { "id": "custom.width", "value": 180 }, + { "id": "custom.align", "value": "left" } + ] + }, + { + "matcher": { "id": "byName", "options": "URL" }, + "properties": [ + { "id": "custom.width", "value": 300 }, + { "id": "custom.align", "value": "left" }, + { "id": "links", "value": [{ "title": "Open Admin", "url": "${__value.text}/administrator/", "targetBlank": true }] } + ] + }, + { + "matcher": { "id": "byName", "options": "System" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "color": "green", "text": "CURRENT" }, "1": { "color": "red", "text": "UPDATE" } }, "type": "value" }] }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } + ] + }, + { + "matcher": { "id": "byName", "options": "Ext Updates" }, + "properties": [ + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-text" } }, + { "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] } + ] + }, + { + "matcher": { "id": "byName", "options": "Total" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] } + ] + }, + { + "matcher": { "id": "byName", "options": "Enabled" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-text" } } + ] + }, + { + "matcher": { "id": "byName", "options": "Disabled" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 5 }, { "color": "orange", "value": 20 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-text" } } ] } - } + ] }, + "gridPos": { "x": 0, "y": 26, "w": 24, "h": 8 }, + "id": 10, "options": { - "colorMode": "background", - "graphMode": "none", - "textMode": "value_and_name", - "reduceOptions": { - "values": true, - "calcs": [], - "fields": "/^version$/" - } + "showHeader": true, + "cellHeight": "sm", + "footer": { "show": false } }, "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "joomla_core_version_info{site=~\"$site\"}", - "instant": true, - "format": "table" + "instant": true, "format": "table", "refId": "VERSION" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "max by (site) (joomla_core_update_available{site=~\"$site\"})", + "instant": true, "format": "table", "refId": "SYSTEM" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "joomla_extensions_update_count{site=~\"$site\"} or joomla_extensions_total{site=~\"$site\"} * 0", + "instant": true, "format": "table", "refId": "EXTUPDATES" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "joomla_extensions_total{site=~\"$site\"}", + "instant": true, "format": "table", "refId": "TOTAL" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "joomla_extensions_enabled{site=~\"$site\"}", + "instant": true, "format": "table", "refId": "ENABLED" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "joomla_extensions_disabled{site=~\"$site\"}", + "instant": true, "format": "table", "refId": "DISABLED" } ], "transformations": [ + { + "id": "joinByField", + "options": { "byField": "site", "mode": "outer" } + }, { "id": "organize", "options": { "excludeByName": { - "Time": true, - "Value": true, - "__name__": true, - "client": true, - "exported_instance": true, - "instance": true, - "job": true + "Time": true, "Time 1": true, "Time 2": true, "Time 3": true, "Time 4": true, "Time 5": true, "Time 6": true, + "Value #VERSION": true, + "client": true, "client 1": true, "client 2": true, "client 3": true, "client 4": true, "client 5": true, "client 6": true, + "exported_instance 1": true, "exported_instance 2": true, "exported_instance 3": true, "exported_instance 4": true, "exported_instance 5": true, "exported_instance 6": true, + "instance": true, "instance 1": true, "instance 2": true, "instance 3": true, "instance 4": true, "instance 5": true, "instance 6": true, + "job": true, "job 1": true, "job 2": true, "job 3": true, "job 4": true, "job 5": true, "job 6": true, + "site 1": true, "site 2": true, "site 3": true, "site 4": true, "site 5": true, + "__name__": true, "__name__ 1": true, "__name__ 2": true, "__name__ 3": true, "__name__ 4": true, "__name__ 5": true, "__name__ 6": true + }, + "renameByName": { + "site": "Site", + "exported_instance": "URL", + "version": "Version", + "Value #SYSTEM": "System", + "Value #EXTUPDATES": "Ext Updates", + "Value #TOTAL": "Total", + "Value #ENABLED": "Enabled", + "Value #DISABLED": "Disabled" }, "indexByName": { "site": 0, - "version": 1 + "exported_instance": 1, + "version": 2, + "Value #SYSTEM": 3, + "Value #EXTUPDATES": 4, + "Value #TOTAL": 5, + "Value #ENABLED": 6, + "Value #DISABLED": 7 } } - } - ], - "title": "Joomla Version", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "options": { - "0": { - "color": "green", - "text": "CURRENT" - }, - "1": { - "color": "red", - "text": "UPDATE" - } - }, - "type": "value" - } - ], - "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 1 - } - ] - } - } - }, - "gridPos": { - "x": 0, - "y": 30, - "w": 24, - "h": 4 - }, - "id": 11, - "options": { - "colorMode": "background", - "graphMode": "none" - }, - "targets": [ + }, { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "joomla_core_update_available{site=~\"$site\"}", - "legendFormat": "{{site}}" + "id": "sortBy", + "options": { "fields": {}, "sort": [{ "field": "Site", "desc": false }] } } ], - "title": "Core Update", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "options": { - "0": { - "text": "—" - } - }, - "type": "value" - } - ], - "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - } - }, - "gridPos": { - "x": 0, - "y": 34, - "w": 24, - "h": 4 - }, - "id": 12, - "options": { - "colorMode": "value", - "graphMode": "none" - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "joomla_extensions_total{site=~\"$site\"}", - "legendFormat": "{{site}}" - } - ], - "title": "Extensions", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "options": { - "0": { - "text": "—" - } - }, - "type": "value" - } - ], - "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "green", - "value": null - } - ] - } - } - }, - "gridPos": { - "x": 0, - "y": 38, - "w": 24, - "h": 4 - }, - "id": 13, - "options": { - "colorMode": "value", - "graphMode": "none" - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "joomla_extensions_enabled{site=~\"$site\"}", - "legendFormat": "{{site}}" - } - ], - "title": "Enabled", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "options": { - "0": { - "text": "—" - } - }, - "type": "value" - } - ], - "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "yellow", - "value": 5 - }, - { - "color": "orange", - "value": 20 - } - ] - } - } - }, - "gridPos": { - "x": 0, - "y": 42, - "w": 24, - "h": 4 - }, - "id": 14, - "options": { - "colorMode": "value", - "graphMode": "none" - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "joomla_extensions_disabled{site=~\"$site\"}", - "legendFormat": "{{site}}" - } - ], - "title": "Disabled", - "type": "stat" + "title": "Joomla Core & Extensions", + "type": "table" + } + ] }, { "collapsed": false, @@ -795,176 +484,118 @@ "type": "row" }, { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "fieldConfig": { "defaults": { - "mappings": [ - { - "options": { - "0": { - "color": "red", - "text": "FAILED" - }, - "1": { - "color": "green", - "text": "OK" - } - }, - "type": "value" - } - ], - "links": [ - { - "title": "Manage Backups", - "url": "${__field.labels.exported_instance}/administrator/index.php?option=com_akeebabackup&view=Manage", - "targetBlank": true - } - ], "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "red", - "value": 0 - }, - { - "color": "green", - "value": 1 - } + "custom": { "align": "center", "cellOptions": { "type": "auto" } } + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "Site" }, + "properties": [ + { "id": "custom.width", "value": 150 }, + { "id": "custom.align", "value": "left" } + ] + }, + { + "matcher": { "id": "byName", "options": "URL" }, + "properties": [ + { "id": "custom.width", "value": 300 }, + { "id": "custom.align", "value": "left" }, + { "id": "links", "value": [{ "title": "Manage Backups", "url": "${__value.text}/administrator/index.php?option=com_akeebabackup&view=Manage", "targetBlank": true }] } + ] + }, + { + "matcher": { "id": "byName", "options": "Status" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "color": "red", "text": "FAILED" }, "1": { "color": "green", "text": "OK" } }, "type": "value" }] }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } + ] + }, + { + "matcher": { "id": "byName", "options": "Age" }, + "properties": [ + { "id": "unit", "value": "s" }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 172800 }, { "color": "red", "value": 604800 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-text" } }, + { "id": "mappings", "value": [{ "options": { "-1": { "text": "—" } }, "type": "value" }] } + ] + }, + { + "matcher": { "id": "byName", "options": "Records" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "text": "—" } }, "type": "value" }] } ] } - } - }, - "gridPos": { - "x": 0, - "y": 56, - "w": 24, - "h": 4 + ] }, + "gridPos": { "x": 0, "y": 56, "w": 24, "h": 8 }, "id": 40, "options": { - "colorMode": "background", - "graphMode": "none" + "showHeader": true, + "cellHeight": "sm", + "footer": { "show": false } }, "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "max by (site, exported_instance, client) (joomla_backup_status{site=~\"$site\"})", - "legendFormat": "{{site}}" - } - ], - "title": "Last Backup", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "yellow", - "value": 172800 - }, - { - "color": "red", - "value": 604800 - } - ] - }, - "unit": "s" - } - }, - "gridPos": { - "x": 0, - "y": 60, - "w": 24, - "h": 4 - }, - "id": 41, - "options": { - "colorMode": "value", - "graphMode": "none" - }, - "targets": [ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "expr": "max by (site, exported_instance) (joomla_backup_status{site=~\"$site\"})", + "instant": true, "format": "table", "refId": "STATUS" + }, { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "joomla_backup_age_seconds{site=~\"$site\"}", - "legendFormat": "{{site}}" - } - ], - "title": "Backup Age", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "links": [ - { - "title": "Manage Backups", - "url": "${__field.labels.exported_instance}/administrator/index.php?option=com_akeebabackup&view=Manage", - "targetBlank": true - } - ], - "noValue": "—", - "thresholds": { - "steps": [ - { - "color": "red", - "value": 0 - }, - { - "color": "green", - "value": 1 - } - ] - } - } - }, - "gridPos": { - "x": 0, - "y": 64, - "w": 24, - "h": 4 - }, - "id": 42, - "options": { - "colorMode": "value", - "graphMode": "none" - }, - "targets": [ + "instant": true, "format": "table", "refId": "AGE" + }, { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "joomla_backup_records_total{site=~\"$site\"}", - "legendFormat": "{{site}}" + "instant": true, "format": "table", "refId": "RECORDS" } ], - "title": "Records", - "type": "stat" + "transformations": [ + { + "id": "joinByField", + "options": { "byField": "site", "mode": "outer" } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, "Time 1": true, "Time 2": true, "Time 3": true, + "client": true, "client 1": true, "client 2": true, "client 3": true, + "instance": true, "instance 1": true, "instance 2": true, "instance 3": true, + "job": true, "job 1": true, "job 2": true, "job 3": true, + "site 1": true, "site 2": true, + "status": true, + "exported_instance 1": true, "exported_instance 2": true, + "__name__": true, "__name__ 1": true, "__name__ 2": true, "__name__ 3": true + }, + "renameByName": { + "site": "Site", + "exported_instance": "URL", + "Value #STATUS": "Status", + "Value #AGE": "Age", + "Value #RECORDS": "Records" + }, + "indexByName": { + "site": 0, + "exported_instance": 1, + "Value #STATUS": 2, + "Value #AGE": 3, + "Value #RECORDS": 4 + } + } + }, + { + "id": "sortBy", + "options": { "fields": {}, "sort": [{ "field": "Site", "desc": false }] } + } + ], + "title": "Backup Status", + "type": "table" }, { "collapsed": false, diff --git a/templates/docs/required/template-CONTRIBUTING.md b/templates/docs/required/template-CONTRIBUTING.md index 4e4d165..5a6642e 100644 --- a/templates/docs/required/template-CONTRIBUTING.md +++ b/templates/docs/required/template-CONTRIBUTING.md @@ -109,10 +109,10 @@ All contributions must follow [MokoStandards](https://git.mokoconsulting.tech/Mo ## Custom Workflows -Place repo-specific workflows in `.github/workflows/custom/` — they are **never overwritten or deleted** by MokoStandards sync: +Place repo-specific workflows in `.mokogitea/workflows/custom/` — they are **never overwritten or deleted** by MokoStandards sync: ``` -.github/workflows/ +.mokogitea/workflows/ ├── deploy-dev.yml ← Synced from MokoStandards ├── auto-release.yml ← Synced from MokoStandards └── custom/ ← Your custom workflows (safe) diff --git a/templates/gitea/README.md b/templates/gitea/README.md index d596abe..7ad1db7 100644 --- a/templates/gitea/README.md +++ b/templates/gitea/README.md @@ -53,7 +53,7 @@ Located in `ISSUE_TEMPLATE/` directory: - **Custom Templates**: Project-specific issue types - **Configuration** (`config.yml`): Issue template configuration -**Usage**: Copy entire `ISSUE_TEMPLATE/` directory to your repository's `.gitea/` directory. +**Usage**: Copy entire `ISSUE_TEMPLATE/` directory to your repository's `.mokogitea/` directory. ### Pull Request Template @@ -61,7 +61,7 @@ Located in `ISSUE_TEMPLATE/` directory: **Purpose**: Standardize pull request descriptions and ensure all necessary information is provided before review. -**Usage**: Copy to `.gitea/PULL_REQUEST_TEMPLATE.md` in your repository. +**Usage**: Copy to `.mokogitea/PULL_REQUEST_TEMPLATE.md` in your repository. ### CODEOWNERS Template @@ -70,7 +70,7 @@ Located in `ISSUE_TEMPLATE/` directory: **Purpose**: Define code ownership for automatic review assignment. **Usage**: -1. Copy to `.gitea/CODEOWNERS` (remove `.template` suffix) +1. Copy to `.mokogitea/CODEOWNERS` (remove `.template` suffix) 2. Customize with your team and file patterns 3. Commit to repository @@ -79,7 +79,7 @@ Located in `ISSUE_TEMPLATE/` directory: ### Setup Process 1. **Choose Templates**: Identify which templates your repository needs -2. **Copy to Repository**: Copy templates to your repository's `.gitea/` directory +2. **Copy to Repository**: Copy templates to your repository's `.mokogitea/` directory 3. **Customize**: Adapt templates to your project's needs 4. **Test**: Create test issues/PRs to validate templates 5. **Document**: Update README with any project-specific requirements @@ -88,7 +88,7 @@ Located in `ISSUE_TEMPLATE/` directory: ``` your-repository/ -└── .gitea/ +└── .mokogitea/ ├── ISSUE_TEMPLATE/ │ ├── bug_report.md │ ├── feature_request.md @@ -205,7 +205,7 @@ The CODEOWNERS file: * @org/default-team /docs/ @org/docs-team /src/ @org/dev-team -/.gitea/workflows/ @org/devops-team +/.mokogitea/workflows/ @org/devops-team /scripts/ @org/automation-team /docs/policy/security-*.md @org/security-team ``` @@ -274,7 +274,7 @@ Before publishing templates: For small projects: ``` -.gitea/ +.mokogitea/ ├── ISSUE_TEMPLATE/ │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md @@ -284,7 +284,7 @@ For small projects: For large projects: ``` -.gitea/ +.mokogitea/ ├── ISSUE_TEMPLATE/ │ ├── bug_report.md │ ├── feature_request.md @@ -299,7 +299,7 @@ For large projects: For repositories with multiple components: ``` -.gitea/ +.mokogitea/ ├── ISSUE_TEMPLATE/ │ ├── bug_report.md │ ├── feature_request.md -- 2.52.0 From 787d778e91871662b727edab7d90538ea13eaade Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 23:54:37 -0500 Subject: [PATCH 04/19] feat: rewrite dashboard tables with clean filterFieldsByName pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Site Health: rewritten from scratch — site_url via label_replace on HTTP query (works for offline sites), filterFieldsByName to keep only needed fields, endpoint URL as clickable Site column - Joomla Core & Extensions: rewritten — same pattern, Site links to /administrator/ - Backup Status: rewritten — same pattern, Site links to Akeeba Manage - Combined SSL Days and Last Scrape into Site Health table - Collapsed Joomla Core section by default - Moved dashboard to Endpoints folder - Added site-uptime-alert.sh for ntfy critical alerts on site downtime - Split updates into System and Ext Updates columns Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- monitoring/grafana/moko-waas-dashboard.json | 214 ++++---------------- 1 file changed, 44 insertions(+), 170 deletions(-) diff --git a/monitoring/grafana/moko-waas-dashboard.json b/monitoring/grafana/moko-waas-dashboard.json index 8bf3c58..c6dbb00 100644 --- a/monitoring/grafana/moko-waas-dashboard.json +++ b/monitoring/grafana/moko-waas-dashboard.json @@ -15,34 +15,29 @@ "type": "row" }, { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "fieldConfig": { "defaults": { "noValue": "—", - "custom": { - "align": "center", - "cellOptions": { "type": "auto" } - } + "custom": { "align": "center", "cellOptions": { "type": "auto" } } }, "overrides": [ { "matcher": { "id": "byName", "options": "Site" }, - "properties": [ - { "id": "custom.width", "value": 150 }, - { "id": "custom.align", "value": "left" } - ] - }, - { - "matcher": { "id": "byName", "options": "URL" }, "properties": [ { "id": "custom.width", "value": 300 }, { "id": "custom.align", "value": "left" }, { "id": "links", "value": [{ "title": "Open Site", "url": "${__value.text}", "targetBlank": true }] } ] }, + { + "matcher": { "id": "byName", "options": "Online" }, + "properties": [ + { "id": "mappings", "value": [{ "options": { "0": { "color": "orange", "text": "OFFLINE" }, "1": { "color": "green", "text": "ONLINE" } }, "type": "value" }] }, + { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "orange", "value": null }, { "color": "green", "value": 1 }] } }, + { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } + ] + }, { "matcher": { "id": "byName", "options": "Status" }, "properties": [ @@ -59,14 +54,6 @@ { "id": "links", "value": [{ "title": "HTTP Status Code Reference", "url": "https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#${__value.raw}", "targetBlank": true }] } ] }, - { - "matcher": { "id": "byName", "options": "Online" }, - "properties": [ - { "id": "mappings", "value": [{ "options": { "0": { "color": "orange", "text": "OFFLINE" }, "1": { "color": "green", "text": "ONLINE" } }, "type": "value" }] }, - { "id": "thresholds", "value": { "mode": "absolute", "steps": [{ "color": "orange", "value": null }, { "color": "green", "value": 1 }] } }, - { "id": "custom.cellOptions", "value": { "type": "color-background", "mode": "basic" } } - ] - }, { "matcher": { "id": "byName", "options": "API" }, "properties": [ @@ -76,7 +63,7 @@ ] }, { - "matcher": { "id": "byName", "options": "SSL Days" }, + "matcher": { "id": "byName", "options": "SSL" }, "properties": [ { "id": "decimals", "value": 0 }, { "id": "unit", "value": "d" }, @@ -92,110 +79,64 @@ } ] }, - "gridPos": { - "x": 0, - "y": 1, - "w": 24, - "h": 8 - }, + "gridPos": { "x": 0, "y": 1, "w": 24, "h": 8 }, "id": 1, - "options": { - "showHeader": true, - "cellHeight": "sm", - "footer": { "show": false } - }, + "options": { "showHeader": true, "cellHeight": "sm", "footer": { "show": false } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "probe_success{site_name=~\"$site\", job=\"blackbox-http\"} and on(site_name) label_replace(joomla_site_online{site=~\"$site\"} == 1, \"site_name\", \"$1\", \"site\", \"(.+)\")", - "instant": true, - "format": "table", - "refId": "STATUS" + "instant": true, "format": "table", "refId": "STATUS" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "probe_http_status_code{site_name=~\"$site\", job=\"blackbox-http\"}", - "instant": true, - "format": "table", - "refId": "HTTP" + "expr": "label_replace(probe_http_status_code{site_name=~\"$site\", job=\"blackbox-http\"}, \"site_url\", \"$1\", \"instance\", \"(.+)\")", + "instant": true, "format": "table", "refId": "HTTP" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "label_replace(joomla_site_online{site=~\"$site\"}, \"site_name\", \"$1\", \"site\", \"(.+)\")", - "instant": true, - "format": "table", - "refId": "ONLINE" + "instant": true, "format": "table", "refId": "ONLINE" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "label_replace(joomla_site_api_reachable{site=~\"$site\"}, \"site_name\", \"$1\", \"site\", \"(.+)\") and on(site_name) label_replace(joomla_site_online{site=~\"$site\"} == 1, \"site_name\", \"$1\", \"site\", \"(.+)\")", - "instant": true, - "format": "table", - "refId": "API" + "instant": true, "format": "table", "refId": "API" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "(probe_ssl_earliest_cert_expiry{site_name=~\"$site\"} - time()) / 86400", - "instant": true, - "format": "table", - "refId": "SSL" + "instant": true, "format": "table", "refId": "SSL" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, "expr": "label_replace(joomla_monitor_last_scrape{site=~\"$site\"} * 1000, \"site_name\", \"$1\", \"site\", \"(.+)\")", - "instant": true, - "format": "table", - "refId": "LASTSCRAPE" + "instant": true, "format": "table", "refId": "LASTSCRAPE" } ], "transformations": [ + { "id": "joinByField", "options": { "byField": "site_name", "mode": "outer" } }, { - "id": "joinByField", - "options": { "byField": "site_name", "mode": "outer" } + "id": "filterFieldsByName", + "options": { + "include": { "pattern": "^(site_url|Value #).*" } + } }, { "id": "organize", "options": { - "excludeByName": { - "Time": true, "Time 1": true, "Time 2": true, "Time 3": true, "Time 4": true, "Time 5": true, "Time 6": true, - "client": true, "client 1": true, "client 2": true, "client 3": true, "client 4": true, "client 5": true, "client 6": true, - "exported_instance": true, "exported_instance 1": true, "exported_instance 2": true, "exported_instance 3": true, - "instance 1": true, "instance 2": true, "instance 3": true, "instance 4": true, "instance 5": true, "instance 6": true, - "job": true, "job 1": true, "job 2": true, "job 3": true, "job 4": true, "job 5": true, "job 6": true, - "site": true, "site 1": true, "site 2": true, "site 3": true, - "site_name 1": true, "site_name 2": true, "site_name 3": true, "site_name 4": true, - "site_type": true, "site_type 1": true, "site_type 2": true, "site_type 3": true, - "__name__": true, "__name__ 1": true, "__name__ 2": true, "__name__ 3": true, "__name__ 4": true, "__name__ 5": true, "__name__ 6": true - }, "renameByName": { - "site_name": "Site", - "instance": "URL", + "site_url": "Site", + "Value #ONLINE": "Online", "Value #STATUS": "Status", "Value #HTTP": "HTTP", - "Value #ONLINE": "Online", "Value #API": "API", - "Value #SSL": "SSL Days", + "Value #SSL": "SSL", "Value #LASTSCRAPE": "Last Scrape" - }, - "indexByName": { - "site_name": 0, - "instance": 1, - "Value #ONLINE": 2, - "Value #STATUS": 3, - "Value #HTTP": 4, - "Value #API": 5, - "Value #SSL": 6, - "Value #LASTSCRAPE": 7 } } }, - { - "id": "sortBy", - "options": { - "fields": {}, - "sort": [{ "field": "Site", "desc": false }] - } - } + { "id": "sortBy", "options": { "sort": [{ "field": "Site", "desc": false }] } } ], "title": "Site Health", "type": "table" @@ -222,13 +163,6 @@ "overrides": [ { "matcher": { "id": "byName", "options": "Site" }, - "properties": [ - { "id": "custom.width", "value": 180 }, - { "id": "custom.align", "value": "left" } - ] - }, - { - "matcher": { "id": "byName", "options": "URL" }, "properties": [ { "id": "custom.width", "value": 300 }, { "id": "custom.align", "value": "left" }, @@ -277,15 +211,11 @@ }, "gridPos": { "x": 0, "y": 26, "w": 24, "h": 8 }, "id": 10, - "options": { - "showHeader": true, - "cellHeight": "sm", - "footer": { "show": false } - }, + "options": { "showHeader": true, "cellHeight": "sm", "footer": { "show": false } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "joomla_core_version_info{site=~\"$site\"}", + "expr": "label_replace(joomla_core_version_info{site=~\"$site\"}, \"site_url\", \"$1\", \"exported_instance\", \"(.+)\")", "instant": true, "format": "table", "refId": "VERSION" }, { @@ -315,49 +245,27 @@ } ], "transformations": [ - { - "id": "joinByField", - "options": { "byField": "site", "mode": "outer" } - }, + { "id": "joinByField", "options": { "byField": "site", "mode": "outer" } }, + { "id": "filterFieldsByName", "options": { "include": { "pattern": "^(site_url|version|Value #).*" } } }, { "id": "organize", "options": { - "excludeByName": { - "Time": true, "Time 1": true, "Time 2": true, "Time 3": true, "Time 4": true, "Time 5": true, "Time 6": true, - "Value #VERSION": true, - "client": true, "client 1": true, "client 2": true, "client 3": true, "client 4": true, "client 5": true, "client 6": true, - "exported_instance 1": true, "exported_instance 2": true, "exported_instance 3": true, "exported_instance 4": true, "exported_instance 5": true, "exported_instance 6": true, - "instance": true, "instance 1": true, "instance 2": true, "instance 3": true, "instance 4": true, "instance 5": true, "instance 6": true, - "job": true, "job 1": true, "job 2": true, "job 3": true, "job 4": true, "job 5": true, "job 6": true, - "site 1": true, "site 2": true, "site 3": true, "site 4": true, "site 5": true, - "__name__": true, "__name__ 1": true, "__name__ 2": true, "__name__ 3": true, "__name__ 4": true, "__name__ 5": true, "__name__ 6": true - }, "renameByName": { - "site": "Site", - "exported_instance": "URL", + "site_url": "Site", "version": "Version", + "Value #VERSION": "v_hidden", "Value #SYSTEM": "System", "Value #EXTUPDATES": "Ext Updates", "Value #TOTAL": "Total", "Value #ENABLED": "Enabled", "Value #DISABLED": "Disabled" }, - "indexByName": { - "site": 0, - "exported_instance": 1, - "version": 2, - "Value #SYSTEM": 3, - "Value #EXTUPDATES": 4, - "Value #TOTAL": 5, - "Value #ENABLED": 6, - "Value #DISABLED": 7 + "excludeByName": { + "v_hidden": true } } }, - { - "id": "sortBy", - "options": { "fields": {}, "sort": [{ "field": "Site", "desc": false }] } - } + { "id": "sortBy", "options": { "sort": [{ "field": "Site", "desc": false }] } } ], "title": "Joomla Core & Extensions", "type": "table" @@ -493,13 +401,6 @@ "overrides": [ { "matcher": { "id": "byName", "options": "Site" }, - "properties": [ - { "id": "custom.width", "value": 150 }, - { "id": "custom.align", "value": "left" } - ] - }, - { - "matcher": { "id": "byName", "options": "URL" }, "properties": [ { "id": "custom.width", "value": 300 }, { "id": "custom.align", "value": "left" }, @@ -533,15 +434,11 @@ }, "gridPos": { "x": 0, "y": 56, "w": 24, "h": 8 }, "id": 40, - "options": { - "showHeader": true, - "cellHeight": "sm", - "footer": { "show": false } - }, + "options": { "showHeader": true, "cellHeight": "sm", "footer": { "show": false } }, "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "max by (site, exported_instance) (joomla_backup_status{site=~\"$site\"})", + "expr": "label_replace(max by (site, exported_instance) (joomla_backup_status{site=~\"$site\"}), \"site_url\", \"$1\", \"exported_instance\", \"(.+)\")", "instant": true, "format": "table", "refId": "STATUS" }, { @@ -556,43 +453,20 @@ } ], "transformations": [ - { - "id": "joinByField", - "options": { "byField": "site", "mode": "outer" } - }, + { "id": "joinByField", "options": { "byField": "site", "mode": "outer" } }, + { "id": "filterFieldsByName", "options": { "include": { "pattern": "^(site_url|Value #).*" } } }, { "id": "organize", "options": { - "excludeByName": { - "Time": true, "Time 1": true, "Time 2": true, "Time 3": true, - "client": true, "client 1": true, "client 2": true, "client 3": true, - "instance": true, "instance 1": true, "instance 2": true, "instance 3": true, - "job": true, "job 1": true, "job 2": true, "job 3": true, - "site 1": true, "site 2": true, - "status": true, - "exported_instance 1": true, "exported_instance 2": true, - "__name__": true, "__name__ 1": true, "__name__ 2": true, "__name__ 3": true - }, "renameByName": { - "site": "Site", - "exported_instance": "URL", + "site_url": "Site", "Value #STATUS": "Status", "Value #AGE": "Age", "Value #RECORDS": "Records" - }, - "indexByName": { - "site": 0, - "exported_instance": 1, - "Value #STATUS": 2, - "Value #AGE": 3, - "Value #RECORDS": 4 } } }, - { - "id": "sortBy", - "options": { "fields": {}, "sort": [{ "field": "Site", "desc": false }] } - } + { "id": "sortBy", "options": { "sort": [{ "field": "Site", "desc": false }] } } ], "title": "Backup Status", "type": "table" -- 2.52.0 From 63a21fe0c0f105e04fe3684575624485c5b61395 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 23:56:21 -0500 Subject: [PATCH 05/19] fix: force Site column first in Site Health table via indexByName Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- monitoring/grafana/moko-waas-dashboard.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/monitoring/grafana/moko-waas-dashboard.json b/monitoring/grafana/moko-waas-dashboard.json index c6dbb00..d6fab5e 100644 --- a/monitoring/grafana/moko-waas-dashboard.json +++ b/monitoring/grafana/moko-waas-dashboard.json @@ -133,6 +133,15 @@ "Value #API": "API", "Value #SSL": "SSL", "Value #LASTSCRAPE": "Last Scrape" + }, + "indexByName": { + "site_url": 0, + "Value #ONLINE": 1, + "Value #STATUS": 2, + "Value #HTTP": 3, + "Value #API": 4, + "Value #SSL": 5, + "Value #LASTSCRAPE": 6 } } }, -- 2.52.0 From 2c7478a6f6931d9a43468c9802b82d871dbdc716 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:43 +0000 Subject: [PATCH 06/19] chore: move .gitea/.moko-platform to .mokogitea/.moko-platform [skip ci] --- .mokogitea/.moko-platform | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .mokogitea/.moko-platform diff --git a/.mokogitea/.moko-platform b/.mokogitea/.moko-platform new file mode 100644 index 0000000..cbab803 --- /dev/null +++ b/.mokogitea/.moko-platform @@ -0,0 +1,25 @@ + + + + + moko-platform + MokoConsulting + Enterprise automation, validation, sync, and governance engine for all Moko Consulting repositories + GNU General Public License v3 + + + generic + 04.07.00 + https://git.mokoconsulting.tech/MokoConsulting/moko-platform + 2026-05-10T19:51:08+00:00 + + + HCL + generic + src/ + + -- 2.52.0 From ebb2f5891a3790f3f55d1eab661c4fdf18dcadfc Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:43 +0000 Subject: [PATCH 07/19] chore: remove .gitea/.moko-platform (moved to .mokogitea/) [skip ci] --- .gitea/.moko-platform | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .gitea/.moko-platform diff --git a/.gitea/.moko-platform b/.gitea/.moko-platform deleted file mode 100644 index cbab803..0000000 --- a/.gitea/.moko-platform +++ /dev/null @@ -1,25 +0,0 @@ - - - - - moko-platform - MokoConsulting - Enterprise automation, validation, sync, and governance engine for all Moko Consulting repositories - GNU General Public License v3 - - - generic - 04.07.00 - https://git.mokoconsulting.tech/MokoConsulting/moko-platform - 2026-05-10T19:51:08+00:00 - - - HCL - generic - src/ - - -- 2.52.0 From 63ae3bc9b09dc2b2b5d96df0ffa1807e0280f043 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:44 +0000 Subject: [PATCH 08/19] chore: move .gitea/workflows/branch-protection.yml to .mokogitea/branch-protection.yml [skip ci] --- .mokogitea/branch-protection.yml | 246 +++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 .mokogitea/branch-protection.yml diff --git a/.mokogitea/branch-protection.yml b/.mokogitea/branch-protection.yml new file mode 100644 index 0000000..35afa23 --- /dev/null +++ b/.mokogitea/branch-protection.yml @@ -0,0 +1,246 @@ +# Copyright (C) 2026 Moko Consulting +# SPDX-License-Identifier: GPL-3.0-or-later +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: MokoStandards-API.Automation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform +# PATH: /.gitea/workflows/branch-protection.yml +# BRIEF: Apply standardised branch protection rules to all governed repositories +# +# +========================================================================+ +# | BRANCH PROTECTION SETUP | +# +========================================================================+ +# | | +# | Applies protection rules for: main, dev, rc/*, beta/*, alpha/* | +# | | +# | main — Require PR, block rejected reviews, no force push | +# | dev — Allow push, no force push, no delete | +# | rc/* — Allow push, no force push, no delete | +# | beta/* — Allow push, no force push, no delete | +# | alpha/* — Allow push, no force push, no delete | +# | | +# | jmiller has override authority on all branches. | +# | | +# +========================================================================+ + +name: Branch Protection Setup + +on: + schedule: + - cron: '0 2 * * 1' # Weekly Monday 02:00 UTC + workflow_dispatch: + inputs: + dry_run: + description: 'Preview mode (no changes)' + required: false + type: boolean + default: false + repos: + description: 'Comma-separated repo names (empty = all governed repos)' + required: false + type: string + default: '' + +env: + GITEA_URL: https://git.mokoconsulting.tech + GITEA_ORG: MokoConsulting + +permissions: + contents: read + +jobs: + protect: + name: Apply Branch Protection Rules + runs-on: ubuntu-latest + + steps: + - name: Determine target repos + id: repos + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + run: | + API="${GITEA_URL}/api/v1" + + # Platform/standards/infra repos to exclude + EXCLUDE="gitea-org-config org-profile gitea-private gitea-server-setup MokoStandards MokoStandards-API MokoTesting" + EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate" + + if [ -n "${{ inputs.repos }}" ]; then + # User-specified repos + REPOS=$(echo "${{ inputs.repos }}" | tr ',' ' ') + else + # Fetch all org repos + PAGE=1 + REPOS="" + while true; do + BATCH=$(curl -sS \ + -H "Authorization: token ${GA_TOKEN}" \ + "${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \ + | jq -r '.[].name // empty') + [ -z "$BATCH" ] && break + REPOS="$REPOS $BATCH" + PAGE=$((PAGE + 1)) + done + + # Filter out excluded repos + FILTERED="" + for REPO in $REPOS; do + SKIP=false + for EX in $EXCLUDE; do + if [ "$REPO" = "$EX" ]; then + SKIP=true + break + fi + done + if [ "$SKIP" = "false" ]; then + FILTERED="$FILTERED $REPO" + fi + done + REPOS="$FILTERED" + fi + + echo "repos=$REPOS" >> "$GITHUB_OUTPUT" + COUNT=$(echo "$REPOS" | wc -w) + echo "📋 Target repos (${COUNT}): $REPOS" + + - name: Apply protection rules + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + DRY_RUN: ${{ inputs.dry_run || 'false' }} + run: | + API="${GITEA_URL}/api/v1" + REPOS="${{ steps.repos.outputs.repos }}" + + SUCCESS=0 + FAILED=0 + SKIPPED=0 + + # ── Rule definitions ────────────────────────────────────── + # Each rule: NAME|JSON_BODY + # jmiller has override (force push + push whitelist) on all branches + + RULE_MAIN='{ + "rule_name": "main", + "enable_push": true, + "enable_push_whitelist": true, + "push_whitelist_usernames": ["jmiller"], + "enable_force_push": true, + "enable_force_push_allowlist": true, + "force_push_allowlist_usernames": ["jmiller"], + "enable_merge_whitelist": false, + "required_approvals": 0, + "dismiss_stale_approvals": true, + "block_on_rejected_reviews": true, + "block_on_outdated_branch": false, + "priority": 1 + }' + + RULE_DEV='{ + "rule_name": "dev", + "enable_push": true, + "enable_push_whitelist": false, + "enable_force_push": true, + "enable_force_push_allowlist": true, + "force_push_allowlist_usernames": ["jmiller"], + "enable_merge_whitelist": false, + "required_approvals": 0, + "block_on_rejected_reviews": false, + "priority": 2 + }' + + RULE_RC='{ + "rule_name": "rc/*", + "enable_push": true, + "enable_push_whitelist": false, + "enable_force_push": true, + "enable_force_push_allowlist": true, + "force_push_allowlist_usernames": ["jmiller"], + "enable_merge_whitelist": false, + "required_approvals": 0, + "block_on_rejected_reviews": false, + "priority": 3 + }' + + RULE_BETA='{ + "rule_name": "beta/*", + "enable_push": true, + "enable_push_whitelist": false, + "enable_force_push": true, + "enable_force_push_allowlist": true, + "force_push_allowlist_usernames": ["jmiller"], + "enable_merge_whitelist": false, + "required_approvals": 0, + "block_on_rejected_reviews": false, + "priority": 4 + }' + + RULE_ALPHA='{ + "rule_name": "alpha/*", + "enable_push": true, + "enable_push_whitelist": false, + "enable_force_push": true, + "enable_force_push_allowlist": true, + "force_push_allowlist_usernames": ["jmiller"], + "enable_merge_whitelist": false, + "required_approvals": 0, + "block_on_rejected_reviews": false, + "priority": 5 + }' + + RULES=("$RULE_MAIN" "$RULE_DEV" "$RULE_RC" "$RULE_BETA" "$RULE_ALPHA") + RULE_NAMES=("main" "dev" "rc/*" "beta/*" "alpha/*") + + # ── Apply rules to each repo ────────────────────────────── + for REPO in $REPOS; do + echo "" + echo "═══ ${REPO} ═══" + + for i in "${!RULES[@]}"; do + RULE="${RULES[$i]}" + NAME="${RULE_NAMES[$i]}" + + if [ "$DRY_RUN" = "true" ]; then + echo " [DRY RUN] Would apply rule: ${NAME}" + SKIPPED=$((SKIPPED + 1)) + continue + fi + + # Delete existing rule if present (idempotent recreate) + ENCODED_NAME=$(echo "$NAME" | sed 's|/|%2F|g') + curl -sS -o /dev/null -w "" \ + -X DELETE \ + -H "Authorization: token ${GA_TOKEN}" \ + "${API}/repos/${GITEA_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true + + # Create rule + RESPONSE=$(curl -sS -w "\n%{http_code}" \ + -X POST \ + -H "Authorization: token ${GA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$RULE" \ + "${API}/repos/${GITEA_ORG}/${REPO}/branch_protections") + + HTTP=$(echo "$RESPONSE" | tail -1) + BODY=$(echo "$RESPONSE" | sed '$d') + + if [ "$HTTP" = "201" ]; then + echo " ✅ ${NAME}" + SUCCESS=$((SUCCESS + 1)) + else + echo " ❌ ${NAME} (HTTP ${HTTP}): $(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)" + FAILED=$((FAILED + 1)) + fi + done + done + + # ── Summary ─────────────────────────────────────────────── + echo "" + echo "════════════════════════════════════════" + echo " ✅ Success: ${SUCCESS}" + echo " ❌ Failed: ${FAILED}" + echo " ⏭️ Skipped: ${SKIPPED}" + echo "════════════════════════════════════════" + + if [ "$FAILED" -gt 0 ]; then + echo "::warning::${FAILED} rule(s) failed to apply" + fi -- 2.52.0 From edf09ac74432091096e52653d31f779441d3d870 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:44 +0000 Subject: [PATCH 09/19] chore: remove .gitea/workflows/branch-protection.yml (moved to .mokogitea/) [skip ci] --- .gitea/workflows/branch-protection.yml | 246 ------------------------- 1 file changed, 246 deletions(-) delete mode 100644 .gitea/workflows/branch-protection.yml diff --git a/.gitea/workflows/branch-protection.yml b/.gitea/workflows/branch-protection.yml deleted file mode 100644 index 35afa23..0000000 --- a/.gitea/workflows/branch-protection.yml +++ /dev/null @@ -1,246 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards-API.Automation -# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform -# PATH: /.gitea/workflows/branch-protection.yml -# BRIEF: Apply standardised branch protection rules to all governed repositories -# -# +========================================================================+ -# | BRANCH PROTECTION SETUP | -# +========================================================================+ -# | | -# | Applies protection rules for: main, dev, rc/*, beta/*, alpha/* | -# | | -# | main — Require PR, block rejected reviews, no force push | -# | dev — Allow push, no force push, no delete | -# | rc/* — Allow push, no force push, no delete | -# | beta/* — Allow push, no force push, no delete | -# | alpha/* — Allow push, no force push, no delete | -# | | -# | jmiller has override authority on all branches. | -# | | -# +========================================================================+ - -name: Branch Protection Setup - -on: - schedule: - - cron: '0 2 * * 1' # Weekly Monday 02:00 UTC - workflow_dispatch: - inputs: - dry_run: - description: 'Preview mode (no changes)' - required: false - type: boolean - default: false - repos: - description: 'Comma-separated repo names (empty = all governed repos)' - required: false - type: string - default: '' - -env: - GITEA_URL: https://git.mokoconsulting.tech - GITEA_ORG: MokoConsulting - -permissions: - contents: read - -jobs: - protect: - name: Apply Branch Protection Rules - runs-on: ubuntu-latest - - steps: - - name: Determine target repos - id: repos - env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} - run: | - API="${GITEA_URL}/api/v1" - - # Platform/standards/infra repos to exclude - EXCLUDE="gitea-org-config org-profile gitea-private gitea-server-setup MokoStandards MokoStandards-API MokoTesting" - EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate" - - if [ -n "${{ inputs.repos }}" ]; then - # User-specified repos - REPOS=$(echo "${{ inputs.repos }}" | tr ',' ' ') - else - # Fetch all org repos - PAGE=1 - REPOS="" - while true; do - BATCH=$(curl -sS \ - -H "Authorization: token ${GA_TOKEN}" \ - "${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \ - | jq -r '.[].name // empty') - [ -z "$BATCH" ] && break - REPOS="$REPOS $BATCH" - PAGE=$((PAGE + 1)) - done - - # Filter out excluded repos - FILTERED="" - for REPO in $REPOS; do - SKIP=false - for EX in $EXCLUDE; do - if [ "$REPO" = "$EX" ]; then - SKIP=true - break - fi - done - if [ "$SKIP" = "false" ]; then - FILTERED="$FILTERED $REPO" - fi - done - REPOS="$FILTERED" - fi - - echo "repos=$REPOS" >> "$GITHUB_OUTPUT" - COUNT=$(echo "$REPOS" | wc -w) - echo "📋 Target repos (${COUNT}): $REPOS" - - - name: Apply protection rules - env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} - DRY_RUN: ${{ inputs.dry_run || 'false' }} - run: | - API="${GITEA_URL}/api/v1" - REPOS="${{ steps.repos.outputs.repos }}" - - SUCCESS=0 - FAILED=0 - SKIPPED=0 - - # ── Rule definitions ────────────────────────────────────── - # Each rule: NAME|JSON_BODY - # jmiller has override (force push + push whitelist) on all branches - - RULE_MAIN='{ - "rule_name": "main", - "enable_push": true, - "enable_push_whitelist": true, - "push_whitelist_usernames": ["jmiller"], - "enable_force_push": true, - "enable_force_push_allowlist": true, - "force_push_allowlist_usernames": ["jmiller"], - "enable_merge_whitelist": false, - "required_approvals": 0, - "dismiss_stale_approvals": true, - "block_on_rejected_reviews": true, - "block_on_outdated_branch": false, - "priority": 1 - }' - - RULE_DEV='{ - "rule_name": "dev", - "enable_push": true, - "enable_push_whitelist": false, - "enable_force_push": true, - "enable_force_push_allowlist": true, - "force_push_allowlist_usernames": ["jmiller"], - "enable_merge_whitelist": false, - "required_approvals": 0, - "block_on_rejected_reviews": false, - "priority": 2 - }' - - RULE_RC='{ - "rule_name": "rc/*", - "enable_push": true, - "enable_push_whitelist": false, - "enable_force_push": true, - "enable_force_push_allowlist": true, - "force_push_allowlist_usernames": ["jmiller"], - "enable_merge_whitelist": false, - "required_approvals": 0, - "block_on_rejected_reviews": false, - "priority": 3 - }' - - RULE_BETA='{ - "rule_name": "beta/*", - "enable_push": true, - "enable_push_whitelist": false, - "enable_force_push": true, - "enable_force_push_allowlist": true, - "force_push_allowlist_usernames": ["jmiller"], - "enable_merge_whitelist": false, - "required_approvals": 0, - "block_on_rejected_reviews": false, - "priority": 4 - }' - - RULE_ALPHA='{ - "rule_name": "alpha/*", - "enable_push": true, - "enable_push_whitelist": false, - "enable_force_push": true, - "enable_force_push_allowlist": true, - "force_push_allowlist_usernames": ["jmiller"], - "enable_merge_whitelist": false, - "required_approvals": 0, - "block_on_rejected_reviews": false, - "priority": 5 - }' - - RULES=("$RULE_MAIN" "$RULE_DEV" "$RULE_RC" "$RULE_BETA" "$RULE_ALPHA") - RULE_NAMES=("main" "dev" "rc/*" "beta/*" "alpha/*") - - # ── Apply rules to each repo ────────────────────────────── - for REPO in $REPOS; do - echo "" - echo "═══ ${REPO} ═══" - - for i in "${!RULES[@]}"; do - RULE="${RULES[$i]}" - NAME="${RULE_NAMES[$i]}" - - if [ "$DRY_RUN" = "true" ]; then - echo " [DRY RUN] Would apply rule: ${NAME}" - SKIPPED=$((SKIPPED + 1)) - continue - fi - - # Delete existing rule if present (idempotent recreate) - ENCODED_NAME=$(echo "$NAME" | sed 's|/|%2F|g') - curl -sS -o /dev/null -w "" \ - -X DELETE \ - -H "Authorization: token ${GA_TOKEN}" \ - "${API}/repos/${GITEA_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true - - # Create rule - RESPONSE=$(curl -sS -w "\n%{http_code}" \ - -X POST \ - -H "Authorization: token ${GA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$RULE" \ - "${API}/repos/${GITEA_ORG}/${REPO}/branch_protections") - - HTTP=$(echo "$RESPONSE" | tail -1) - BODY=$(echo "$RESPONSE" | sed '$d') - - if [ "$HTTP" = "201" ]; then - echo " ✅ ${NAME}" - SUCCESS=$((SUCCESS + 1)) - else - echo " ❌ ${NAME} (HTTP ${HTTP}): $(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)" - FAILED=$((FAILED + 1)) - fi - done - done - - # ── Summary ─────────────────────────────────────────────── - echo "" - echo "════════════════════════════════════════" - echo " ✅ Success: ${SUCCESS}" - echo " ❌ Failed: ${FAILED}" - echo " ⏭️ Skipped: ${SKIPPED}" - echo "════════════════════════════════════════" - - if [ "$FAILED" -gt 0 ]; then - echo "::warning::${FAILED} rule(s) failed to apply" - fi -- 2.52.0 From ede5ae90a551807e746e515e20282107174036ac Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:44 +0000 Subject: [PATCH 10/19] chore: move .gitea/workflows/bulk-repo-sync.yml to .mokogitea/bulk-repo-sync.yml [skip ci] --- .mokogitea/bulk-repo-sync.yml | 135 ++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 .mokogitea/bulk-repo-sync.yml diff --git a/.mokogitea/bulk-repo-sync.yml b/.mokogitea/bulk-repo-sync.yml new file mode 100644 index 0000000..c81f0ad --- /dev/null +++ b/.mokogitea/bulk-repo-sync.yml @@ -0,0 +1,135 @@ +# Copyright (C) 2026 Moko Consulting +# SPDX-License-Identifier: GPL-3.0-or-later +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: MokoStandards-API.Automation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform +# PATH: /.gitea/workflows/bulk-repo-sync.yml +# BRIEF: Bulk repo sync — runs from API repo, syncs standards to all governed repos + +name: Bulk Repository Sync + +on: + schedule: + - cron: '0 0 1 * *' + workflow_dispatch: + inputs: + dry_run: + description: 'Preview mode (no changes)' + required: false + type: boolean + default: true + repos: + description: 'Comma-separated repo names (empty = all)' + required: false + type: string + default: '' + exclude: + description: 'Comma-separated repos to skip' + required: false + type: string + default: '' + force: + description: 'Force overwrite protected files' + required: false + type: boolean + default: false + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + bulk-sync: + name: Sync Standards to Repositories + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: json, mbstring, curl + tools: composer + coverage: none + + - name: Install Dependencies + run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader + + - name: Build CLI Arguments + id: args + run: | + ARGS="--org MokoConsulting" + if [ "${{ inputs.dry_run }}" = "true" ] || [ "${{ gitea.event_name }}" = "schedule" ]; then + ARGS="$ARGS --dry-run" + fi + if [ -n "${{ inputs.repos }}" ]; then + ARGS="$ARGS --repos ${{ inputs.repos }}" + fi + if [ -n "${{ inputs.exclude }}" ]; then + ARGS="$ARGS --exclude ${{ inputs.exclude }}" + fi + if [ "${{ inputs.force }}" = "true" ]; then + ARGS="$ARGS --force" + fi + ARGS="$ARGS --yes" + echo "args=$ARGS" >> $GITHUB_OUTPUT + + - name: Run Bulk Sync + run: | + echo "Running: php automation/bulk_sync.php ${{ steps.args.outputs.args }}" + php automation/bulk_sync.php ${{ steps.args.outputs.args }} 2>&1 | tee /tmp/bulk_sync.log + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + GIT_PLATFORM: gitea + GITEA_URL: https://git.mokoconsulting.tech + GITEA_ORG: MokoConsulting + + - name: Commit Updated Definitions + if: success() && inputs.dry_run != 'true' + run: | + if [ -n "$(git status --porcelain definitions/sync/)" ]; then + git config user.name "gitea-actions[bot]" + git config user.email "gitea-actions[bot]@git.mokoconsulting.tech" + git add definitions/sync/*.def.tf + git commit -m "chore: update synced repository definitions" || true + git push || true + fi + + - name: Enforce Release Channel Tags + if: success() + continue-on-error: true + run: | + echo "Enforcing standard tags on all repos..." + if [ "${{ inputs.dry_run }}" = "true" ]; then + bash automation/enforce_tags.sh --dry-run || echo "Tag enforcement skipped (non-fatal)" + else + bash automation/enforce_tags.sh || echo "Tag enforcement had errors (non-fatal)" + fi + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + GITEA_URL: https://git.mokoconsulting.tech + GITEA_ORG: MokoConsulting + + - name: Upload Sync Log + if: always() && github.server_url == 'https://github.com' + uses: actions/upload-artifact@v4 + with: + name: bulk-sync-log-${{ github.run_number }} + path: /tmp/bulk_sync.log + retention-days: 30 + + - name: Log Summary (Gitea) + if: always() && github.server_url != 'https://github.com' + run: | + if [ -f /tmp/bulk_sync.log ]; then + echo "## Sync Log (last 20 lines)" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + tail -20 /tmp/bulk_sync.log >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + fi -- 2.52.0 From 0ff2ae6f84a6f30d237182ac859dff67ad77912b Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:45 +0000 Subject: [PATCH 11/19] chore: remove .gitea/workflows/bulk-repo-sync.yml (moved to .mokogitea/) [skip ci] --- .gitea/workflows/bulk-repo-sync.yml | 135 ---------------------------- 1 file changed, 135 deletions(-) delete mode 100644 .gitea/workflows/bulk-repo-sync.yml diff --git a/.gitea/workflows/bulk-repo-sync.yml b/.gitea/workflows/bulk-repo-sync.yml deleted file mode 100644 index c81f0ad..0000000 --- a/.gitea/workflows/bulk-repo-sync.yml +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards-API.Automation -# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform -# PATH: /.gitea/workflows/bulk-repo-sync.yml -# BRIEF: Bulk repo sync — runs from API repo, syncs standards to all governed repos - -name: Bulk Repository Sync - -on: - schedule: - - cron: '0 0 1 * *' - workflow_dispatch: - inputs: - dry_run: - description: 'Preview mode (no changes)' - required: false - type: boolean - default: true - repos: - description: 'Comma-separated repo names (empty = all)' - required: false - type: string - default: '' - exclude: - description: 'Comma-separated repos to skip' - required: false - type: string - default: '' - force: - description: 'Force overwrite protected files' - required: false - type: boolean - default: false - -permissions: - contents: write - issues: write - pull-requests: write - -jobs: - bulk-sync: - name: Sync Standards to Repositories - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: json, mbstring, curl - tools: composer - coverage: none - - - name: Install Dependencies - run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader - - - name: Build CLI Arguments - id: args - run: | - ARGS="--org MokoConsulting" - if [ "${{ inputs.dry_run }}" = "true" ] || [ "${{ gitea.event_name }}" = "schedule" ]; then - ARGS="$ARGS --dry-run" - fi - if [ -n "${{ inputs.repos }}" ]; then - ARGS="$ARGS --repos ${{ inputs.repos }}" - fi - if [ -n "${{ inputs.exclude }}" ]; then - ARGS="$ARGS --exclude ${{ inputs.exclude }}" - fi - if [ "${{ inputs.force }}" = "true" ]; then - ARGS="$ARGS --force" - fi - ARGS="$ARGS --yes" - echo "args=$ARGS" >> $GITHUB_OUTPUT - - - name: Run Bulk Sync - run: | - echo "Running: php automation/bulk_sync.php ${{ steps.args.outputs.args }}" - php automation/bulk_sync.php ${{ steps.args.outputs.args }} 2>&1 | tee /tmp/bulk_sync.log - env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - GIT_PLATFORM: gitea - GITEA_URL: https://git.mokoconsulting.tech - GITEA_ORG: MokoConsulting - - - name: Commit Updated Definitions - if: success() && inputs.dry_run != 'true' - run: | - if [ -n "$(git status --porcelain definitions/sync/)" ]; then - git config user.name "gitea-actions[bot]" - git config user.email "gitea-actions[bot]@git.mokoconsulting.tech" - git add definitions/sync/*.def.tf - git commit -m "chore: update synced repository definitions" || true - git push || true - fi - - - name: Enforce Release Channel Tags - if: success() - continue-on-error: true - run: | - echo "Enforcing standard tags on all repos..." - if [ "${{ inputs.dry_run }}" = "true" ]; then - bash automation/enforce_tags.sh --dry-run || echo "Tag enforcement skipped (non-fatal)" - else - bash automation/enforce_tags.sh || echo "Tag enforcement had errors (non-fatal)" - fi - env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} - GITEA_URL: https://git.mokoconsulting.tech - GITEA_ORG: MokoConsulting - - - name: Upload Sync Log - if: always() && github.server_url == 'https://github.com' - uses: actions/upload-artifact@v4 - with: - name: bulk-sync-log-${{ github.run_number }} - path: /tmp/bulk_sync.log - retention-days: 30 - - - name: Log Summary (Gitea) - if: always() && github.server_url != 'https://github.com' - run: | - if [ -f /tmp/bulk_sync.log ]; then - echo "## Sync Log (last 20 lines)" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - tail -20 /tmp/bulk_sync.log >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi -- 2.52.0 From 5dbb5aebcb0a1f99654cf956cd4b03d0e6b98872 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:45 +0000 Subject: [PATCH 12/19] chore: move .gitea/workflows/pr-branch-check.yml to .mokogitea/pr-branch-check.yml [skip ci] --- .mokogitea/pr-branch-check.yml | 97 ++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 .mokogitea/pr-branch-check.yml diff --git a/.mokogitea/pr-branch-check.yml b/.mokogitea/pr-branch-check.yml new file mode 100644 index 0000000..5f3010e --- /dev/null +++ b/.mokogitea/pr-branch-check.yml @@ -0,0 +1,97 @@ +# Copyright (C) 2026 Moko Consulting +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: MokoStandards.CI +# INGROUP: MokoStandards +# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform +# PATH: /.gitea/workflows/pr-branch-check.yml +# BRIEF: PR branch merge policy enforcement +# +# Enforces branch merge policy: +# feature/* → dev only +# fix/* → dev only +# hotfix/* → dev or main (emergency) +# dev → main only +# alpha/* → dev only +# beta/* → dev only +# rc/* → main only + +name: Branch Policy Check + +on: + pull_request: + types: [opened, synchronize, reopened, edited] + +jobs: + check-target: + name: Verify merge target + runs-on: ubuntu-latest + steps: + - name: Check branch policy + run: | + HEAD="${{ github.head_ref }}" + BASE="${{ github.base_ref }}" + + echo "PR: ${HEAD} → ${BASE}" + + ALLOWED=true + REASON="" + + case "$HEAD" in + feature/*|feat/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Feature branches must target 'dev', not '${BASE}'" + fi + ;; + fix/*|bugfix/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Fix branches must target 'dev', not '${BASE}'" + fi + ;; + hotfix/*) + if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" + fi + ;; + alpha/*|beta/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Pre-release branches must target 'dev', not '${BASE}'" + fi + ;; + rc/*) + if [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Release candidate branches must target 'main', not '${BASE}'" + fi + ;; + dev) + if [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Dev branch can only merge into 'main', not '${BASE}'" + fi + ;; + esac + + if [ "$ALLOWED" = false ]; then + echo "::error::${REASON}" + echo "" + echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "${REASON}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY + echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + echo "Branch policy: OK (${HEAD} → ${BASE})" + echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From 61121252ca9300d6e5a36c8151a5c8e8af4d4c99 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:45 +0000 Subject: [PATCH 13/19] chore: remove .gitea/workflows/pr-branch-check.yml (moved to .mokogitea/) [skip ci] --- .gitea/workflows/pr-branch-check.yml | 97 ---------------------------- 1 file changed, 97 deletions(-) delete mode 100644 .gitea/workflows/pr-branch-check.yml diff --git a/.gitea/workflows/pr-branch-check.yml b/.gitea/workflows/pr-branch-check.yml deleted file mode 100644 index 5f3010e..0000000 --- a/.gitea/workflows/pr-branch-check.yml +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoStandards.CI -# INGROUP: MokoStandards -# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform -# PATH: /.gitea/workflows/pr-branch-check.yml -# BRIEF: PR branch merge policy enforcement -# -# Enforces branch merge policy: -# feature/* → dev only -# fix/* → dev only -# hotfix/* → dev or main (emergency) -# dev → main only -# alpha/* → dev only -# beta/* → dev only -# rc/* → main only - -name: Branch Policy Check - -on: - pull_request: - types: [opened, synchronize, reopened, edited] - -jobs: - check-target: - name: Verify merge target - runs-on: ubuntu-latest - steps: - - name: Check branch policy - run: | - HEAD="${{ github.head_ref }}" - BASE="${{ github.base_ref }}" - - echo "PR: ${HEAD} → ${BASE}" - - ALLOWED=true - REASON="" - - case "$HEAD" in - feature/*|feat/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Feature branches must target 'dev', not '${BASE}'" - fi - ;; - fix/*|bugfix/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Fix branches must target 'dev', not '${BASE}'" - fi - ;; - hotfix/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" - fi - ;; - alpha/*|beta/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Pre-release branches must target 'dev', not '${BASE}'" - fi - ;; - rc/*) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Release candidate branches must target 'main', not '${BASE}'" - fi - ;; - dev) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Dev branch can only merge into 'main', not '${BASE}'" - fi - ;; - esac - - if [ "$ALLOWED" = false ]; then - echo "::error::${REASON}" - echo "" - echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "${REASON}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY - echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - echo "Branch policy: OK (${HEAD} → ${BASE})" - echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From 624e64499acc33673a3940c78e1d83b6ca1df22e Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:46 +0000 Subject: [PATCH 14/19] chore: move .gitea/workflows/renovate.yml to .mokogitea/renovate.yml [skip ci] --- .mokogitea/renovate.yml | 128 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 .mokogitea/renovate.yml diff --git a/.mokogitea/renovate.yml b/.mokogitea/renovate.yml new file mode 100644 index 0000000..dad4bd2 --- /dev/null +++ b/.mokogitea/renovate.yml @@ -0,0 +1,128 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: MokoStandards-API.Automation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform +# PATH: /.gitea/workflows/renovate.yml +# BRIEF: Run Renovate Bot across all governed repos for dependency updates +# +# +========================================================================+ +# | RENOVATE DEPENDENCY UPDATES | +# +========================================================================+ +# | | +# | Runs Renovate CLI against all governed repos to create PRs for | +# | outdated dependencies (composer, npm). | +# | | +# | - Scheduled: weekly Wednesday 04:00 UTC | +# | - Manual: dispatch with optional repo filter | +# | - Patch updates auto-merge, minor/major require review | +# | | +# +========================================================================+ + +name: Renovate Dependency Updates + +on: + schedule: + - cron: '0 4 * * 3' # Weekly Wednesday 04:00 UTC + workflow_dispatch: + inputs: + repos: + description: 'Comma-separated repo names (empty = all governed)' + required: false + type: string + default: '' + dry_run: + description: 'Preview mode (log only, no PRs)' + required: false + type: boolean + default: false + +env: + GITEA_URL: https://git.mokoconsulting.tech + GITEA_ORG: MokoConsulting + RENOVATE_VERSION: '39' + +permissions: + contents: read + +jobs: + renovate: + name: Run Renovate + runs-on: ubuntu-latest + + steps: + - name: Determine target repos + id: repos + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + run: | + API="${GITEA_URL}/api/v1" + + EXCLUDE="gitea-org-config org-profile gitea-private gitea-server-setup MokoStandards MokoStandards-API MokoTesting" + EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate" + + if [ -n "${{ inputs.repos }}" ]; then + REPOS=$(echo "${{ inputs.repos }}" | tr ',' ' ') + else + PAGE=1 + REPOS="" + while true; do + BATCH=$(curl -sS \ + -H "Authorization: token ${GA_TOKEN}" \ + "${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \ + | jq -r '.[].name // empty') + [ -z "$BATCH" ] && break + REPOS="$REPOS $BATCH" + PAGE=$((PAGE + 1)) + done + + FILTERED="" + for REPO in $REPOS; do + SKIP=false + for EX in $EXCLUDE; do + [ "$REPO" = "$EX" ] && SKIP=true && break + done + [ "$SKIP" = "false" ] && FILTERED="$FILTERED $REPO" + done + REPOS="$FILTERED" + fi + + # Build comma-separated list for Renovate + REPO_LIST="" + for REPO in $REPOS; do + if [ -n "$REPO_LIST" ]; then + REPO_LIST="${REPO_LIST},${GITEA_ORG}/${REPO}" + else + REPO_LIST="${GITEA_ORG}/${REPO}" + fi + done + + echo "repo_list=$REPO_LIST" >> "$GITHUB_OUTPUT" + COUNT=$(echo "$REPOS" | wc -w) + echo "📋 Target repos (${COUNT})" + + - name: Run Renovate + if: steps.repos.outputs.repo_list != '' + env: + RENOVATE_TOKEN: ${{ secrets.GA_TOKEN }} + RENOVATE_PLATFORM: gitea + RENOVATE_ENDPOINT: ${{ env.GITEA_URL }}/api/v1 + RENOVATE_GIT_AUTHOR: 'Renovate Bot ' + RENOVATE_REPOSITORIES: ${{ steps.repos.outputs.repo_list }} + RENOVATE_DRY_RUN: ${{ inputs.dry_run == 'true' && 'full' || 'null' }} + LOG_LEVEL: info + run: | + npx --yes renovate@${RENOVATE_VERSION} \ + --platform=gitea \ + --endpoint="${GITEA_URL}/api/v1" \ + --token="${RENOVATE_TOKEN}" \ + --git-author="Renovate Bot " \ + --autodiscover=false \ + ${{ inputs.dry_run == 'true' && '--dry-run=full' || '' }} \ + 2>&1 | tee /tmp/renovate.log + + echo "### Renovate Summary" >> $GITHUB_STEP_SUMMARY + grep -E "(INFO|WARN|ERROR)" /tmp/renovate.log | tail -30 >> $GITHUB_STEP_SUMMARY || true -- 2.52.0 From a14002c327623d405c2827a3c239bceb18841624 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:46 +0000 Subject: [PATCH 15/19] chore: remove .gitea/workflows/renovate.yml (moved to .mokogitea/) [skip ci] --- .gitea/workflows/renovate.yml | 128 ---------------------------------- 1 file changed, 128 deletions(-) delete mode 100644 .gitea/workflows/renovate.yml diff --git a/.gitea/workflows/renovate.yml b/.gitea/workflows/renovate.yml deleted file mode 100644 index dad4bd2..0000000 --- a/.gitea/workflows/renovate.yml +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards-API.Automation -# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform -# PATH: /.gitea/workflows/renovate.yml -# BRIEF: Run Renovate Bot across all governed repos for dependency updates -# -# +========================================================================+ -# | RENOVATE DEPENDENCY UPDATES | -# +========================================================================+ -# | | -# | Runs Renovate CLI against all governed repos to create PRs for | -# | outdated dependencies (composer, npm). | -# | | -# | - Scheduled: weekly Wednesday 04:00 UTC | -# | - Manual: dispatch with optional repo filter | -# | - Patch updates auto-merge, minor/major require review | -# | | -# +========================================================================+ - -name: Renovate Dependency Updates - -on: - schedule: - - cron: '0 4 * * 3' # Weekly Wednesday 04:00 UTC - workflow_dispatch: - inputs: - repos: - description: 'Comma-separated repo names (empty = all governed)' - required: false - type: string - default: '' - dry_run: - description: 'Preview mode (log only, no PRs)' - required: false - type: boolean - default: false - -env: - GITEA_URL: https://git.mokoconsulting.tech - GITEA_ORG: MokoConsulting - RENOVATE_VERSION: '39' - -permissions: - contents: read - -jobs: - renovate: - name: Run Renovate - runs-on: ubuntu-latest - - steps: - - name: Determine target repos - id: repos - env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} - run: | - API="${GITEA_URL}/api/v1" - - EXCLUDE="gitea-org-config org-profile gitea-private gitea-server-setup MokoStandards MokoStandards-API MokoTesting" - EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate" - - if [ -n "${{ inputs.repos }}" ]; then - REPOS=$(echo "${{ inputs.repos }}" | tr ',' ' ') - else - PAGE=1 - REPOS="" - while true; do - BATCH=$(curl -sS \ - -H "Authorization: token ${GA_TOKEN}" \ - "${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \ - | jq -r '.[].name // empty') - [ -z "$BATCH" ] && break - REPOS="$REPOS $BATCH" - PAGE=$((PAGE + 1)) - done - - FILTERED="" - for REPO in $REPOS; do - SKIP=false - for EX in $EXCLUDE; do - [ "$REPO" = "$EX" ] && SKIP=true && break - done - [ "$SKIP" = "false" ] && FILTERED="$FILTERED $REPO" - done - REPOS="$FILTERED" - fi - - # Build comma-separated list for Renovate - REPO_LIST="" - for REPO in $REPOS; do - if [ -n "$REPO_LIST" ]; then - REPO_LIST="${REPO_LIST},${GITEA_ORG}/${REPO}" - else - REPO_LIST="${GITEA_ORG}/${REPO}" - fi - done - - echo "repo_list=$REPO_LIST" >> "$GITHUB_OUTPUT" - COUNT=$(echo "$REPOS" | wc -w) - echo "📋 Target repos (${COUNT})" - - - name: Run Renovate - if: steps.repos.outputs.repo_list != '' - env: - RENOVATE_TOKEN: ${{ secrets.GA_TOKEN }} - RENOVATE_PLATFORM: gitea - RENOVATE_ENDPOINT: ${{ env.GITEA_URL }}/api/v1 - RENOVATE_GIT_AUTHOR: 'Renovate Bot ' - RENOVATE_REPOSITORIES: ${{ steps.repos.outputs.repo_list }} - RENOVATE_DRY_RUN: ${{ inputs.dry_run == 'true' && 'full' || 'null' }} - LOG_LEVEL: info - run: | - npx --yes renovate@${RENOVATE_VERSION} \ - --platform=gitea \ - --endpoint="${GITEA_URL}/api/v1" \ - --token="${RENOVATE_TOKEN}" \ - --git-author="Renovate Bot " \ - --autodiscover=false \ - ${{ inputs.dry_run == 'true' && '--dry-run=full' || '' }} \ - 2>&1 | tee /tmp/renovate.log - - echo "### Renovate Summary" >> $GITHUB_STEP_SUMMARY - grep -E "(INFO|WARN|ERROR)" /tmp/renovate.log | tail -30 >> $GITHUB_STEP_SUMMARY || true -- 2.52.0 From 666e11e8dd9a2b1d27a969166e42c0a8f4ff72dc Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:47 +0000 Subject: [PATCH 16/19] chore: move .gitea/workflows/sync-wikis.yml to .mokogitea/sync-wikis.yml [skip ci] --- .mokogitea/sync-wikis.yml | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .mokogitea/sync-wikis.yml diff --git a/.mokogitea/sync-wikis.yml b/.mokogitea/sync-wikis.yml new file mode 100644 index 0000000..6c88dbb --- /dev/null +++ b/.mokogitea/sync-wikis.yml @@ -0,0 +1,41 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: MokoStandards.Maintenance +# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform +# PATH: /.gitea/workflows/sync-wikis.yml +# BRIEF: Daily sync of all Gitea wikis to consolidated GitHub wiki repo + +name: Sync Wikis to GitHub + +on: + schedule: + - cron: '0 5 * * *' # Daily at 5am UTC + workflow_dispatch: + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + sync-wikis: + name: Export wikis to GitHub + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Sync all wikis + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + run: | + if [ -z "$GH_TOKEN" ]; then + echo "::error::GH_TOKEN secret not set" + exit 1 + fi + bash scripts/sync-wikis-to-github.sh -- 2.52.0 From d954b2373e854629356f03aecef463a7f16f14b0 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 05:14:47 +0000 Subject: [PATCH 17/19] chore: remove .gitea/workflows/sync-wikis.yml (moved to .mokogitea/) [skip ci] --- .gitea/workflows/sync-wikis.yml | 41 --------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 .gitea/workflows/sync-wikis.yml diff --git a/.gitea/workflows/sync-wikis.yml b/.gitea/workflows/sync-wikis.yml deleted file mode 100644 index 6c88dbb..0000000 --- a/.gitea/workflows/sync-wikis.yml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Maintenance -# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform -# PATH: /.gitea/workflows/sync-wikis.yml -# BRIEF: Daily sync of all Gitea wikis to consolidated GitHub wiki repo - -name: Sync Wikis to GitHub - -on: - schedule: - - cron: '0 5 * * *' # Daily at 5am UTC - workflow_dispatch: - -permissions: - contents: read - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - sync-wikis: - name: Export wikis to GitHub - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Sync all wikis - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - run: | - if [ -z "$GH_TOKEN" ]; then - echo "::error::GH_TOKEN secret not set" - exit 1 - fi - bash scripts/sync-wikis-to-github.sh -- 2.52.0 From 4ecb70cdc6d8102234a06f2795e6eafc33392fba Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 00:17:07 -0500 Subject: [PATCH 18/19] feat: add 14 Grafana library panels and patch bump to v05.00.01 Add reusable library panel templates exported from Grafana: - Server: CPU, Memory, Disk, Network Traffic - Docker: Container CPU, Container Memory - Services: Nginx Request Rate, Nginx Connections, MySQL Queries/s, MySQL Connections - Monitoring: SSL Certificate Expiry, Service Health, Response Time, Uptime Availability Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- composer.json | 2 +- .../grafana/library-panels/cpu-usage.json | 85 +++++++++++++++++++ .../grafana/library-panels/disk-usage.json | 45 ++++++++++ .../library-panels/docker-container-cpu.json | 28 ++++++ .../docker-container-memory.json | 28 ++++++ .../grafana/library-panels/memory-usage.json | 44 ++++++++++ .../library-panels/mysql-connections.json | 34 ++++++++ .../mysql-queries-per-second.json | 65 ++++++++++++++ .../library-panels/network-traffic.json | 75 ++++++++++++++++ .../library-panels/nginx-connections.json | 39 +++++++++ .../library-panels/nginx-request-rate.json | 42 +++++++++ .../grafana/library-panels/response-time.json | 45 ++++++++++ .../library-panels/service-health.json | 64 ++++++++++++++ .../ssl-certificate-expiry.json | 68 +++++++++++++++ .../library-panels/uptime-availability.json | 59 +++++++++++++ 15 files changed, 722 insertions(+), 1 deletion(-) create mode 100644 monitoring/grafana/library-panels/cpu-usage.json create mode 100644 monitoring/grafana/library-panels/disk-usage.json create mode 100644 monitoring/grafana/library-panels/docker-container-cpu.json create mode 100644 monitoring/grafana/library-panels/docker-container-memory.json create mode 100644 monitoring/grafana/library-panels/memory-usage.json create mode 100644 monitoring/grafana/library-panels/mysql-connections.json create mode 100644 monitoring/grafana/library-panels/mysql-queries-per-second.json create mode 100644 monitoring/grafana/library-panels/network-traffic.json create mode 100644 monitoring/grafana/library-panels/nginx-connections.json create mode 100644 monitoring/grafana/library-panels/nginx-request-rate.json create mode 100644 monitoring/grafana/library-panels/response-time.json create mode 100644 monitoring/grafana/library-panels/service-health.json create mode 100644 monitoring/grafana/library-panels/ssl-certificate-expiry.json create mode 100644 monitoring/grafana/library-panels/uptime-availability.json diff --git a/composer.json b/composer.json index 1c373a9..c39262f 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "mokoconsulting-tech/enterprise", "description": "MokoStandards Enterprise API \u2014 PHP implementation", "type": "library", - "version": "05.00.00", + "version": "05.00.01", "license": "GPL-3.0-or-later", "authors": [ { diff --git a/monitoring/grafana/library-panels/cpu-usage.json b/monitoring/grafana/library-panels/cpu-usage.json new file mode 100644 index 0000000..c260461 --- /dev/null +++ b/monitoring/grafana/library-panels/cpu-usage.json @@ -0,0 +1,85 @@ +{ + "name": "CPU Usage", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 20, + "gradientMode": "scheme", + "lineWidth": 2 + }, + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.7 + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Yesterday CPU" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + }, + { + "id": "custom.fillOpacity", + "value": 0 + }, + { + "id": "custom.lineWidth", + "value": 1 + }, + { + "id": "color", + "value": { + "fixedColor": "rgba(255,255,255,0.35)", + "mode": "fixed" + } + } + ] + } + ] + }, + "targets": [ + { + "expr": "1 - avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m]))", + "legendFormat": "Total CPU", + "refId": "A" + }, + { + "expr": "1 - avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m] offset 1d))", + "legendFormat": "Yesterday CPU", + "refId": "B" + } + ], + "title": "CPU Usage %", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/disk-usage.json b/monitoring/grafana/library-panels/disk-usage.json new file mode 100644 index 0000000..4170794 --- /dev/null +++ b/monitoring/grafana/library-panels/disk-usage.json @@ -0,0 +1,45 @@ +{ + "name": "Disk Usage", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.75 + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + } + }, + "targets": [ + { + "expr": "1 - (node_filesystem_avail_bytes{mountpoint=\"/\",fstype!=\"tmpfs\"} / node_filesystem_size_bytes{mountpoint=\"/\",fstype!=\"tmpfs\"})", + "legendFormat": "/ (root)", + "refId": "A" + }, + { + "expr": "1 - (node_filesystem_avail_bytes{mountpoint=\"/mnt/backup\",fstype!=\"tmpfs\"} / node_filesystem_size_bytes{mountpoint=\"/mnt/backup\",fstype!=\"tmpfs\"})", + "legendFormat": "/mnt/backup", + "refId": "B" + } + ], + "title": "Filesystem Usage", + "type": "gauge" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/docker-container-cpu.json b/monitoring/grafana/library-panels/docker-container-cpu.json new file mode 100644 index 0000000..ef1c149 --- /dev/null +++ b/monitoring/grafana/library-panels/docker-container-cpu.json @@ -0,0 +1,28 @@ +{ + "name": "Docker Container CPU", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 20, + "lineWidth": 2, + "stacking": { + "mode": "normal" + } + }, + "unit": "percentunit" + } + }, + "targets": [ + { + "expr": "rate(container_cpu_usage_seconds_total{name!=\"\",name!~\".*POD.*\"}[5m])", + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "title": "CPU Usage by Container", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/docker-container-memory.json b/monitoring/grafana/library-panels/docker-container-memory.json new file mode 100644 index 0000000..dd8b411 --- /dev/null +++ b/monitoring/grafana/library-panels/docker-container-memory.json @@ -0,0 +1,28 @@ +{ + "name": "Docker Container Memory", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 20, + "lineWidth": 2, + "stacking": { + "mode": "normal" + } + }, + "unit": "bytes" + } + }, + "targets": [ + { + "expr": "container_memory_usage_bytes{name!=\"\",name!~\".*POD.*\"}", + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "title": "Memory Usage by Container", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/memory-usage.json b/monitoring/grafana/library-panels/memory-usage.json new file mode 100644 index 0000000..6f33721 --- /dev/null +++ b/monitoring/grafana/library-panels/memory-usage.json @@ -0,0 +1,44 @@ +{ + "name": "Memory Usage", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 30, + "lineWidth": 2, + "stacking": { + "group": "A", + "mode": "normal" + } + }, + "unit": "bytes" + } + }, + "targets": [ + { + "expr": "node_memory_MemTotal_bytes - node_memory_MemFree_bytes - node_memory_Buffers_bytes - node_memory_Cached_bytes - node_memory_SReclaimable_bytes", + "legendFormat": "Used", + "refId": "A" + }, + { + "expr": "node_memory_Buffers_bytes", + "legendFormat": "Buffers", + "refId": "B" + }, + { + "expr": "node_memory_Cached_bytes + node_memory_SReclaimable_bytes", + "legendFormat": "Cached", + "refId": "C" + }, + { + "expr": "node_memory_MemFree_bytes", + "legendFormat": "Free", + "refId": "D" + } + ], + "title": "Memory Usage", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/mysql-connections.json b/monitoring/grafana/library-panels/mysql-connections.json new file mode 100644 index 0000000..ae16c10 --- /dev/null +++ b/monitoring/grafana/library-panels/mysql-connections.json @@ -0,0 +1,34 @@ +{ + "name": "MySQL Connections", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 15, + "lineWidth": 2 + } + } + }, + "targets": [ + { + "expr": "mysql_global_status_threads_connected", + "legendFormat": "Connected", + "refId": "A" + }, + { + "expr": "mysql_global_status_threads_running", + "legendFormat": "Running", + "refId": "B" + }, + { + "expr": "mysql_global_variables_max_connections", + "legendFormat": "Max", + "refId": "C" + } + ], + "title": "Connections", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/mysql-queries-per-second.json b/monitoring/grafana/library-panels/mysql-queries-per-second.json new file mode 100644 index 0000000..56a97a7 --- /dev/null +++ b/monitoring/grafana/library-panels/mysql-queries-per-second.json @@ -0,0 +1,65 @@ +{ + "name": "MySQL Queries per Second", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 20, + "lineWidth": 2 + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Yesterday" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + }, + { + "id": "custom.fillOpacity", + "value": 0 + }, + { + "id": "custom.lineWidth", + "value": 1 + }, + { + "id": "color", + "value": { + "fixedColor": "rgba(255,255,255,0.35)", + "mode": "fixed" + } + } + ] + } + ] + }, + "targets": [ + { + "expr": "rate(mysql_global_status_queries[5m])", + "legendFormat": "Queries/s", + "refId": "A" + }, + { + "expr": "rate(mysql_global_status_queries[5m] offset 1d)", + "legendFormat": "Yesterday", + "refId": "B" + } + ], + "title": "Queries per Second", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/network-traffic.json b/monitoring/grafana/library-panels/network-traffic.json new file mode 100644 index 0000000..82b994e --- /dev/null +++ b/monitoring/grafana/library-panels/network-traffic.json @@ -0,0 +1,75 @@ +{ + "name": "Network Traffic", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 20, + "lineWidth": 2 + }, + "unit": "bps" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Yesterday.*/" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + }, + { + "id": "custom.fillOpacity", + "value": 0 + }, + { + "id": "custom.lineWidth", + "value": 1 + }, + { + "id": "color", + "value": { + "fixedColor": "rgba(255,255,255,0.35)", + "mode": "fixed" + } + } + ] + } + ] + }, + "targets": [ + { + "expr": "rate(node_network_receive_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m]) * 8", + "legendFormat": "RX {{device}}", + "refId": "A" + }, + { + "expr": "-rate(node_network_transmit_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m]) * 8", + "legendFormat": "TX {{device}}", + "refId": "B" + }, + { + "expr": "rate(node_network_receive_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m] offset 1d) * 8", + "legendFormat": "Yesterday RX {{device}}", + "refId": "C" + }, + { + "expr": "-rate(node_network_transmit_bytes_total{device!~\"lo|veth.*|br-.*|docker.*\"}[5m] offset 1d) * 8", + "legendFormat": "Yesterday TX {{device}}", + "refId": "D" + } + ], + "title": "Network Traffic", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/nginx-connections.json b/monitoring/grafana/library-panels/nginx-connections.json new file mode 100644 index 0000000..d6effbf --- /dev/null +++ b/monitoring/grafana/library-panels/nginx-connections.json @@ -0,0 +1,39 @@ +{ + "name": "Nginx Connections", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 20, + "lineWidth": 2 + } + } + }, + "targets": [ + { + "expr": "nginx_connections_active", + "legendFormat": "Active", + "refId": "A" + }, + { + "expr": "nginx_connections_reading", + "legendFormat": "Reading", + "refId": "B" + }, + { + "expr": "nginx_connections_writing", + "legendFormat": "Writing", + "refId": "C" + }, + { + "expr": "nginx_connections_waiting", + "legendFormat": "Waiting", + "refId": "D" + } + ], + "title": "Connections Over Time", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/nginx-request-rate.json b/monitoring/grafana/library-panels/nginx-request-rate.json new file mode 100644 index 0000000..0544ef6 --- /dev/null +++ b/monitoring/grafana/library-panels/nginx-request-rate.json @@ -0,0 +1,42 @@ +{ + "name": "Nginx Request Rate", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 25, + "gradientMode": "scheme", + "lineWidth": 2 + }, + "thresholds": { + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 200 + } + ] + }, + "unit": "reqps" + } + }, + "targets": [ + { + "expr": "rate(nginx_http_requests_total[5m])", + "legendFormat": "Requests/s", + "refId": "A" + } + ], + "title": "Request Rate", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/response-time.json b/monitoring/grafana/library-panels/response-time.json new file mode 100644 index 0000000..eded845 --- /dev/null +++ b/monitoring/grafana/library-panels/response-time.json @@ -0,0 +1,45 @@ +{ + "name": "Response Time", + "kind": 1, + "model": { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "description": "", + "fieldConfig": { + "defaults": { + "unit": "s" + } + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "probe_http_duration_seconds{site_name=~\"$site\", job=\"blackbox-http\", phase=\"transfer\"}", + "legendFormat": "{{site_name}} transfer" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "probe_http_duration_seconds{site_name=~\"$site\", job=\"blackbox-http\", phase=\"processing\"}", + "legendFormat": "{{site_name}} processing" + } + ], + "title": "Response Time", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/service-health.json b/monitoring/grafana/library-panels/service-health.json new file mode 100644 index 0000000..94f25c6 --- /dev/null +++ b/monitoring/grafana/library-panels/service-health.json @@ -0,0 +1,64 @@ +{ + "name": "Service Health", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "0": { + "color": "red", + "text": "DOWN" + }, + "1": { + "color": "green", + "text": "UP" + } + }, + "type": "value" + } + ] + } + ] + } + ] + }, + "targets": [ + { + "expr": "up", + "format": "table", + "instant": true, + "refId": "A" + } + ], + "title": "Scrape Target Health", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "__name__": true + }, + "renameByName": { + "Value": "Status", + "instance": "Instance", + "job": "Service" + } + } + } + ], + "type": "table" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/ssl-certificate-expiry.json b/monitoring/grafana/library-panels/ssl-certificate-expiry.json new file mode 100644 index 0000000..f068ba8 --- /dev/null +++ b/monitoring/grafana/library-panels/ssl-certificate-expiry.json @@ -0,0 +1,68 @@ +{ + "name": "SSL Certificate Expiry", + "kind": 1, + "model": { + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "fillOpacity": 10, + "lineWidth": 2, + "spanNulls": false, + "thresholdsStyle": { + "mode": "area" + } + }, + "decimals": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 7 + }, + { + "color": "green", + "value": 30 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "min" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "expr": "moko_ssl_cert_expiry_seconds{domain=\"git.mokoconsulting.tech\"} / 86400", + "legendFormat": "git.mokoconsulting.tech \u2014 days", + "refId": "A" + }, + { + "expr": "moko_ssl_cert_expiry_seconds{domain=\"ntfy.mokoconsulting.tech\"} / 86400", + "legendFormat": "ntfy.mokoconsulting.tech \u2014 days", + "refId": "B" + } + ], + "title": "Certificate Expiry Timeline", + "type": "timeseries" + } +} \ No newline at end of file diff --git a/monitoring/grafana/library-panels/uptime-availability.json b/monitoring/grafana/library-panels/uptime-availability.json new file mode 100644 index 0000000..292b6ed --- /dev/null +++ b/monitoring/grafana/library-panels/uptime-availability.json @@ -0,0 +1,59 @@ +{ + "name": "Uptime Availability", + "kind": 1, + "model": { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "fillOpacity": 10, + "lineWidth": 2 + }, + "max": 1, + "min": 0, + "thresholds": { + "steps": [ + { + "color": "red", + "value": 0 + }, + { + "color": "yellow", + "value": 0.95 + }, + { + "color": "green", + "value": 0.99 + } + ] + }, + "unit": "percentunit" + } + }, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "avg_over_time(probe_success{site_name=~\"$site\", job=\"blackbox-http\"}[1h])", + "legendFormat": "{{site_name}}" + } + ], + "title": "Availability (30d)", + "type": "timeseries" + } +} \ No newline at end of file -- 2.52.0 From b5d65e8aa3d7fa6be78ec63196a32a5a3467036e Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 12 May 2026 00:19:51 -0500 Subject: [PATCH 19/19] chore: rename dashboard to MokoWaaS, update description Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- monitoring/grafana/moko-waas-dashboard.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monitoring/grafana/moko-waas-dashboard.json b/monitoring/grafana/moko-waas-dashboard.json index d6fab5e..8aa703e 100644 --- a/monitoring/grafana/moko-waas-dashboard.json +++ b/monitoring/grafana/moko-waas-dashboard.json @@ -1,5 +1,5 @@ { - "description": "Unified Panopticon monitoring for all Joomla client sites", + "description": "MokoWaaS endpoint monitoring — site health, Joomla core & extensions, backups, performance, and uptime", "editable": true, "panels": [ { @@ -587,7 +587,7 @@ "to": "now" }, "timezone": "browser", - "title": "MokoWaaS -- Joomla Sites", + "title": "MokoWaaS", "uid": "moko-waas", "version": 1 } -- 2.52.0