From 686fe113c518503b7c2913daa75adeb4e89f359b Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Wed, 4 Mar 2026 11:06:15 +0100 Subject: [PATCH] feat(vhosts-checker): Add KISS UI dashboard for HAProxy vhosts status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed from routes-status to vhosts-checker to avoid conflict with OpenWrt's default network routes page. - KISS UI theme with header chips and status cards - Shows HAProxy vhosts with mitmproxy route status (OUT/IN) - SSL certificate status indicators - WAF bypass detection - Sync routes and add missing route actions - Accessible at Status → VHosts Checker and KISS UI Network → VHosts Checker Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 4 +- dist/sbom/checksums.sha256 | 2 + dist/sbom/sbom-warnings.txt | 0 dist/sbom/secubox-0.20-cra-summary.txt | 28 ++ dist/sbom/secubox-0.20-cve-report.json | 1 + dist/sbom/secubox-0.20-cve-table.txt | 1 + dist/sbom/secubox-0.20.cdx.json | 57 ++++ dist/sbom/secubox-0.20.spdx.json | 14 + .../resources/view/routes-status/overview.js | 295 +++++++++--------- .../luci/menu.d/luci-app-routes-status.json | 4 +- .../resources/secubox/kiss-theme.js | 1 + 11 files changed, 248 insertions(+), 159 deletions(-) create mode 100644 dist/sbom/checksums.sha256 create mode 100644 dist/sbom/sbom-warnings.txt create mode 100644 dist/sbom/secubox-0.20-cra-summary.txt create mode 100644 dist/sbom/secubox-0.20-cve-report.json create mode 100644 dist/sbom/secubox-0.20-cve-table.txt create mode 100644 dist/sbom/secubox-0.20.cdx.json create mode 100644 dist/sbom/secubox-0.20.spdx.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json index aec19c6d..86b1f355 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -511,7 +511,9 @@ "WebFetch(domain:enhance-app.gk2.secubox.in)", "Bash(openssl dgst:*)", "Bash(# Check the exact field names returned by stats ssh root@192.168.255.1 \"\"ubus call luci.cdn-cache stats\"\")", - "Bash(arping:*)" + "Bash(arping:*)", + "Bash(./scripts/check-sbom-prereqs.sh:*)", + "Bash(git revert:*)" ] } } diff --git a/dist/sbom/checksums.sha256 b/dist/sbom/checksums.sha256 new file mode 100644 index 00000000..9728eef9 --- /dev/null +++ b/dist/sbom/checksums.sha256 @@ -0,0 +1,2 @@ +1c32d5dcf933dd9ad6f96e6cbabcb14ef0327a048917450d4667599677270c49 secubox-0.20.cdx.json +541e440338f5c499e8232c92cb1f251eec684757c77deccfc495c1b83a85af16 secubox-0.20.spdx.json diff --git a/dist/sbom/sbom-warnings.txt b/dist/sbom/sbom-warnings.txt new file mode 100644 index 00000000..e69de29b diff --git a/dist/sbom/secubox-0.20-cra-summary.txt b/dist/sbom/secubox-0.20-cra-summary.txt new file mode 100644 index 00000000..9dd979d4 --- /dev/null +++ b/dist/sbom/secubox-0.20-cra-summary.txt @@ -0,0 +1,28 @@ +=== SecuBox CRA Compliance Summary === +Version : 0.20 +Generated : 2026-03-03T16:38:30+01:00 +SOURCE_DATE : 1772552310 + +--- Software Bill of Materials --- +SBOM format : CycloneDX 1.6 + SPDX 2.3 +Total components : 0 +SBOM SHA256 : 1c32d5dcf933dd9ad6f96e6cbabcb14ef0327a048917450d4667599677270c49 + +--- Licenses --- +No licenses detected + +--- CVE Summary --- +CRITICAL : 0 +HIGH : 0 +MEDIUM : 0 +LOW : 0 +(See secubox-0.20-cve-report.json for details) + +--- Missing Metadata --- +None + +--- CRA Annex I Checklist --- +[x] SBOM published in machine-readable format +[x] Vulnerability disclosure contact: security@cybermind.fr +[ ] CSPN certification : IN PROGRESS (target Q3 2026) +[x] VCS traceability : https://github.com/cybermind/secubox diff --git a/dist/sbom/secubox-0.20-cve-report.json b/dist/sbom/secubox-0.20-cve-report.json new file mode 100644 index 00000000..9000bed3 --- /dev/null +++ b/dist/sbom/secubox-0.20-cve-report.json @@ -0,0 +1 @@ +{"matches":[],"source":{"type":"sbom-file","target":"/home/reepost/CyberMindStudio/secubox-openwrt/scripts/../dist/sbom/secubox-0.20.cdx.json"},"distro":{"name":"","version":"","idLike":null},"descriptor":{"name":"grype","version":"0.109.0","configuration":{"output":["json"],"file":"/home/reepost/CyberMindStudio/secubox-openwrt/scripts/../dist/sbom/secubox-0.20-cve-report.json","pretty":false,"distro":"","add-cpes-if-none":false,"output-template-file":"","check-for-app-update":true,"only-fixed":false,"only-notfixed":false,"ignore-wontfix":"","platform":"","search":{"scope":"squashed","unindexed-archives":false,"indexed-archives":true},"ignore":[{"vulnerability":"","include-aliases":false,"reason":"","namespace":"","fix-state":"","package":{"name":"kernel-headers","version":"","language":"","type":"rpm","location":"","upstream-name":"kernel"},"vex-status":"","vex-justification":"","match-type":"exact-indirect-match"},{"vulnerability":"","include-aliases":false,"reason":"","namespace":"","fix-state":"","package":{"name":"linux(-.*)?-headers-.*","version":"","language":"","type":"deb","location":"","upstream-name":"linux.*"},"vex-status":"","vex-justification":"","match-type":"exact-indirect-match"},{"vulnerability":"","include-aliases":false,"reason":"","namespace":"","fix-state":"","package":{"name":"linux-libc-dev","version":"","language":"","type":"deb","location":"","upstream-name":"linux"},"vex-status":"","vex-justification":"","match-type":"exact-indirect-match"}],"exclude":[],"externalSources":{"enable":false,"maven":{"searchUpstreamBySha1":true,"baseUrl":"https://search.maven.org/solrsearch/select","rateLimit":300000000}},"match":{"java":{"using-cpes":false},"jvm":{"using-cpes":true},"dotnet":{"using-cpes":false},"golang":{"using-cpes":false,"always-use-cpe-for-stdlib":true,"allow-main-module-pseudo-version-comparison":false},"javascript":{"using-cpes":false},"python":{"using-cpes":false},"ruby":{"using-cpes":false},"rust":{"using-cpes":false},"hex":{"using-cpes":false},"stock":{"using-cpes":true},"dpkg":{"using-cpes":false,"missing-epoch-strategy":"zero","use-cpes-for-eol":false},"rpm":{"using-cpes":false,"missing-epoch-strategy":"auto","use-cpes-for-eol":false}},"fail-on-severity":"","registry":{"insecure-skip-tls-verify":false,"insecure-use-http":false,"ca-cert":""},"show-suppressed":false,"by-cve":false,"SortBy":{"sort-by":"risk"},"name":"","default-image-pull-source":"","from":null,"vex-documents":[],"vex-add":[],"match-upstream-kernel-headers":false,"fix-channel":{"redhat-eus":{"apply":"auto","versions":">= 8.0"}},"timestamp":true,"alerts":{"enable-eol-distro-warnings":true},"db":{"cache-dir":"/home/reepost/.cache/grype/db","update-url":"https://grype.anchore.io/databases","ca-cert":"","auto-update":true,"validate-by-hash-on-start":true,"validate-age":true,"max-allowed-built-age":432000000000000,"require-update-check":false,"update-available-timeout":30000000000,"update-download-timeout":300000000000,"max-update-check-frequency":7200000000000},"exp":{},"dev":{"db":{"debug":false}}},"db":{"status":{"schemaVersion":"v6.1.4","from":"https://grype.anchore.io/databases/v6/vulnerability-db_v6.1.4_2026-03-03T00:31:00Z_1772605503.tar.zst?checksum=sha256%3Afcd52485b156cb02a749d13a639c59e8d0dfe7e8e1c204816c2f477d7ee0d521","built":"2026-03-04T06:25:03Z","path":"/home/reepost/.cache/grype/db/6/vulnerability.db","valid":true},"providers":{"alma":{"captured":"2026-03-04T00:29:07Z","input":"xxh64:a1027418f269feb8"},"alpine":{"captured":"2026-03-04T00:29:06Z","input":"xxh64:5b206de31619c612"},"amazon":{"captured":"2026-03-04T00:29:22Z","input":"xxh64:5d7162b3fb08ded1"},"arch":{"captured":"2026-03-04T00:29:06Z","input":"xxh64:dc45bfc5fdf8575e"},"bitnami":{"captured":"2026-03-04T00:29:27Z","input":"xxh64:9735c581000cc3d4"},"chainguard":{"captured":"2026-03-04T00:33:32Z","input":"xxh64:582e3250297b3831"},"chainguard-libraries":{"captured":"2026-03-04T00:29:04Z","input":"xxh64:3d50a5bec88c05c8"},"debian":{"captured":"2026-03-04T00:29:14Z","input":"xxh64:352bd576d67ce220"},"echo":{"captured":"2026-03-04T00:29:18Z","input":"xxh64:dab56e9c49d11f00"},"eol":{"captured":"2026-03-04T00:30:32Z","input":"xxh64:b4133ab1408c567f"},"epss":{"captured":"2026-03-04T00:29:05Z","input":"xxh64:5ed89a5c5912ad86"},"fedora":{"captured":"2026-03-04T00:29:10Z","input":"xxh64:7950de3da9871fbb"},"github":{"captured":"2026-03-04T00:29:11Z","input":"xxh64:e37460e6ba69f030"},"kev":{"captured":"2026-03-04T00:29:14Z","input":"xxh64:c9e3d0a7e0a1150f"},"mariner":{"captured":"2026-03-04T00:29:07Z","input":"xxh64:d317e83671b5ec0c"},"minimos":{"captured":"2026-03-04T00:30:31Z","input":"xxh64:1bd040f80b238540"},"nvd":{"captured":"2026-03-04T00:29:45Z","input":"xxh64:95714c5be148a462"},"oracle":{"captured":"2026-03-04T00:29:17Z","input":"xxh64:840c55f96b22e7d7"},"photon":{"captured":"2026-03-03T00:31:00Z","input":"xxh64:2d1107d24940a482"},"rhel":{"captured":"2026-03-04T00:30:11Z","input":"xxh64:5695eff91f74d566"},"secureos":{"captured":"2026-03-04T00:29:16Z","input":"xxh64:60918aec14714cff"},"sles":{"captured":"2026-03-04T00:29:37Z","input":"xxh64:9179abdbd64c364e"},"ubuntu":{"captured":"2026-03-04T00:31:27Z","input":"xxh64:a976743caa951a4f"},"wolfi":{"captured":"2026-03-04T00:29:14Z","input":"xxh64:bc8a82d403646735"}}},"timestamp":"2026-03-04T07:59:57.895160886+01:00"}} diff --git a/dist/sbom/secubox-0.20-cve-table.txt b/dist/sbom/secubox-0.20-cve-table.txt new file mode 100644 index 00000000..8900c02c --- /dev/null +++ b/dist/sbom/secubox-0.20-cve-table.txt @@ -0,0 +1 @@ +No vulnerabilities found diff --git a/dist/sbom/secubox-0.20.cdx.json b/dist/sbom/secubox-0.20.cdx.json new file mode 100644 index 00000000..c467b245 --- /dev/null +++ b/dist/sbom/secubox-0.20.cdx.json @@ -0,0 +1,57 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:7a140c3e-ba05-44e1-aff2-39a028a5b7f1", + "version": 1, + "metadata": { + "timestamp": "2026-03-03T16:38:30+01:00", + "component": { + "type": "firmware", + "name": "SecuBox", + "version": "0.20", + "supplier": { + "name": "CyberMind Produits SASU", + "contact": [ + { + "email": "secubox@cybermind.fr" + } + ] + }, + "manufacturer": { + "name": "CyberMind Produits SASU", + "url": [ + "https://cybermind.fr" + ] + }, + "licenses": [ + { + "license": { + "id": "GPL-2.0-only" + } + } + ], + "externalReferences": [ + { + "type": "website", + "url": "https://secubox.in" + }, + { + "type": "vcs", + "url": "https://github.com/cybermind/secubox" + }, + { + "type": "documentation", + "url": "https://secubox.in/docs/cra" + } + ] + }, + "tools": [ + { + "vendor": "Anchore", + "name": "syft", + "version": "1.42.1" + } + ] + }, + "components": [] +} diff --git a/dist/sbom/secubox-0.20.spdx.json b/dist/sbom/secubox-0.20.spdx.json new file mode 100644 index 00000000..dd3dfe94 --- /dev/null +++ b/dist/sbom/secubox-0.20.spdx.json @@ -0,0 +1,14 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "spdxVersion": "SPDX-2.3", + "creationInfo": { + "comment": "This SPDX document has been converted from CycloneDX format.", + "created": "2026-03-03T15:38:30Z", + "creators": [ + "Tool: syft-1.42.1" + ] + }, + "name": "SecuBox-0.20", + "dataLicense": "CC0-1.0", + "documentNamespace": "http://spdx.org/spdxdocs/SecuBox-0.20-7a140c3e-ba05-44e1-aff2-39a028a5b7f1" +} \ No newline at end of file diff --git a/package/secubox/luci-app-routes-status/htdocs/luci-static/resources/view/routes-status/overview.js b/package/secubox/luci-app-routes-status/htdocs/luci-static/resources/view/routes-status/overview.js index 0ede95c9..96577925 100644 --- a/package/secubox/luci-app-routes-status/htdocs/luci-static/resources/view/routes-status/overview.js +++ b/package/secubox/luci-app-routes-status/htdocs/luci-static/resources/view/routes-status/overview.js @@ -3,6 +3,7 @@ 'require dom'; 'require ui'; 'require rpc'; +'require secubox/kiss-theme'; var callStatus = rpc.declare({ object: 'luci.routes-status', @@ -28,66 +29,31 @@ return view.extend({ return callStatus(); }, - renderStatusBadge: function(running, label) { - var color = running ? '#4CAF50' : '#f44336'; - return E('span', { - 'style': 'display:inline-block;padding:4px 12px;margin:4px;border-radius:4px;color:#fff;background:' + color + ';font-size:0.9em;font-weight:500;' - }, label + ': ' + (running ? 'Running' : 'Stopped')); + renderHeaderChip: function(icon, label, value, tone) { + var display = (value == null ? '—' : value).toString(); + return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [ + E('span', { 'class': 'sh-chip-icon' }, icon), + E('div', { 'class': 'sh-chip-text' }, [ + E('span', { 'class': 'sh-chip-label' }, label), + E('strong', {}, display) + ]) + ]); }, - renderRouteBadge: function(hasRoute, type) { - if (hasRoute) { - return E('span', { - 'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:3px;color:#fff;background:#4CAF50;font-size:0.8em;' - }, type); - } else { - return E('span', { - 'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:3px;color:#fff;background:#ff9800;font-size:0.8em;' - }, type + ' (missing)'); - } - }, - - renderSslBadge: function(status) { - var color, text; - if (status === 'missing') { - color = '#9e9e9e'; - text = 'No SSL'; - } else if (status === 'expired') { - color = '#f44336'; - text = 'Expired'; - } else if (status && status.indexOf('expiring:') === 0) { - var days = status.split(':')[1]; - color = '#ff9800'; - text = 'Expires in ' + days + 'd'; - } else if (status && status.indexOf('valid:') === 0) { - var days = status.split(':')[1]; - color = '#4CAF50'; - text = 'Valid (' + days + 'd)'; - } else { - color = '#4CAF50'; - text = 'Valid'; - } - + renderPill: function(text, type) { + var colors = { + success: '#4CAF50', + warning: '#ff9800', + danger: '#f44336', + info: '#2196F3', + muted: '#9e9e9e' + }; return E('span', { - 'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:3px;color:#fff;background:' + color + ';font-size:0.8em;' + 'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:4px;color:#fff;background:' + (colors[type] || colors.muted) + ';font-size:0.8em;font-weight:500;' }, text); }, - renderWafBadge: function(bypass) { - if (bypass) { - return E('span', { - 'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:3px;color:#fff;background:#f44336;font-size:0.8em;' - }, 'WAF Bypass'); - } else { - return E('span', { - 'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:3px;color:#fff;background:#2196F3;font-size:0.8em;' - }, 'WAF Protected'); - } - }, - handleSync: function() { - var self = this; - ui.showModal(_('Syncing Routes...'), [ E('p', { 'class': 'spinning' }, _('Please wait...')) ]); @@ -106,36 +72,31 @@ return view.extend({ handleAddRoute: function(domain, port) { var self = this; - if (!port) { - // Ask for port - ui.showModal(_('Add Route'), [ - E('div', { 'class': 'cbi-section' }, [ - E('p', {}, _('Add mitmproxy route for: %s').format(domain)), - E('div', { 'class': 'cbi-value' }, [ - E('label', { 'class': 'cbi-value-title' }, _('Backend Port')), - E('div', { 'class': 'cbi-value-field' }, [ - E('input', { 'type': 'number', 'id': 'route-port', 'value': '443', 'style': 'width:100px;' }) - ]) + ui.showModal(_('Add Route'), [ + E('div', { 'class': 'cbi-section' }, [ + E('p', {}, _('Add mitmproxy route for: %s').format(domain)), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, _('Backend Port')), + E('div', { 'class': 'cbi-value-field' }, [ + E('input', { 'type': 'number', 'id': 'route-port', 'value': port || '443', 'style': 'width:100px;' }) ]) - ]), - E('div', { 'class': 'right' }, [ - E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')), - E('button', { - 'class': 'btn cbi-button-positive', - 'click': function() { - var port = parseInt(document.getElementById('route-port').value, 10); - if (port > 0) { - ui.hideModal(); - self.doAddRoute(domain, port); - } - }, - 'style': 'margin-left:10px;' - }, _('Add Route')) ]) - ]); - } else { - this.doAddRoute(domain, parseInt(port, 10)); - } + ]), + E('div', { 'class': 'right' }, [ + E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')), + E('button', { + 'class': 'btn cbi-button-positive', + 'click': function() { + var p = parseInt(document.getElementById('route-port').value, 10); + if (p > 0) { + ui.hideModal(); + self.doAddRoute(domain, p); + } + }, + 'style': 'margin-left:10px;' + }, _('Add Route')) + ]) + ]); }, doAddRoute: function(domain, port) { @@ -158,31 +119,35 @@ return view.extend({ var self = this; var missingRoutes = !vhost.has_route_out || !vhost.has_route_in; - return E('tr', { 'class': vhost.active ? '' : 'inactive' }, [ + return E('tr', {}, [ E('td', {}, [ E('a', { 'href': 'https://' + vhost.domain, 'target': '_blank', - 'style': 'color:#1976D2;text-decoration:none;' + 'style': 'color:#1976D2;text-decoration:none;font-weight:500;' }, vhost.domain) ]), E('td', {}, vhost.backend || '-'), - E('td', { 'style': 'text-align:center;' }, vhost.backend_port || '-'), E('td', {}, [ - this.renderRouteBadge(vhost.has_route_out, 'OUT'), - this.renderRouteBadge(vhost.has_route_in, 'IN') + vhost.has_route_out ? this.renderPill('OUT', 'success') : this.renderPill('OUT', 'warning'), + vhost.has_route_in ? this.renderPill('IN', 'success') : this.renderPill('IN', 'warning') ]), - E('td', {}, this.renderSslBadge(vhost.ssl_status)), - E('td', {}, this.renderWafBadge(vhost.waf_bypass)), E('td', {}, [ - vhost.active ? - E('span', { 'style': 'color:#4CAF50;font-weight:bold;' }, 'Active') : - E('span', { 'style': 'color:#9e9e9e;' }, 'Inactive'), + vhost.ssl_status === 'valid' ? this.renderPill('SSL', 'success') : + vhost.ssl_status === 'expiring' ? this.renderPill('Expiring', 'warning') : + vhost.ssl_status === 'expired' ? this.renderPill('Expired', 'danger') : + this.renderPill('No SSL', 'muted') + ]), + E('td', {}, [ + vhost.waf_bypass ? this.renderPill('Bypass', 'danger') : this.renderPill('WAF', 'info') + ]), + E('td', {}, [ + vhost.active ? this.renderPill('Active', 'success') : this.renderPill('Inactive', 'muted'), missingRoutes ? E('button', { - 'class': 'btn cbi-button', + 'class': 'cbi-button cbi-button-action', 'click': function() { self.handleAddRoute(vhost.domain, vhost.backend_port); }, - 'style': 'margin-left:10px;font-size:0.8em;padding:2px 8px;' - }, _('Add Route')) : null + 'style': 'margin-left:8px;padding:2px 8px;font-size:0.8em;' + }, _('+ Route')) : null ]) ]); }, @@ -196,76 +161,94 @@ return view.extend({ return a.domain.localeCompare(b.domain); }); - // Count stats + // Stats var totalVhosts = vhosts.length; var activeVhosts = vhosts.filter(function(v) { return v.active; }).length; var missingRoutes = vhosts.filter(function(v) { return !v.has_route_out || !v.has_route_in; }).length; var wafBypassed = vhosts.filter(function(v) { return v.waf_bypass; }).length; + var sslValid = vhosts.filter(function(v) { return v.ssl_status === 'valid'; }).length; - var content = []; - - // Header - content.push(E('h2', {}, _('Routes Status Dashboard'))); - - // Service Status - content.push(E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Service Status')), - E('div', { 'style': 'margin:10px 0;' }, [ - this.renderStatusBadge(data.haproxy_running, 'HAProxy'), - this.renderStatusBadge(data.mitmproxy_running, 'mitmproxy') - ]), - E('p', { 'style': 'color:#666;' }, _('Host IP: %s').format(data.host_ip || '192.168.255.1')) - ])); - - // Statistics - content.push(E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Statistics')), - E('div', { 'style': 'display:flex;gap:30px;margin:15px 0;' }, [ - E('div', { 'style': 'text-align:center;' }, [ - E('div', { 'style': 'font-size:2em;font-weight:bold;color:#1976D2;' }, String(totalVhosts)), - E('div', { 'style': 'color:#666;' }, _('Total Vhosts')) - ]), - E('div', { 'style': 'text-align:center;' }, [ - E('div', { 'style': 'font-size:2em;font-weight:bold;color:#4CAF50;' }, String(activeVhosts)), - E('div', { 'style': 'color:#666;' }, _('Active')) - ]), - E('div', { 'style': 'text-align:center;' }, [ - E('div', { 'style': 'font-size:2em;font-weight:bold;color:' + (missingRoutes > 0 ? '#ff9800' : '#4CAF50') + ';' }, String(missingRoutes)), - E('div', { 'style': 'color:#666;' }, _('Missing Routes')) - ]), - E('div', { 'style': 'text-align:center;' }, [ - E('div', { 'style': 'font-size:2em;font-weight:bold;color:' + (wafBypassed > 0 ? '#f44336' : '#4CAF50') + ';' }, String(wafBypassed)), - E('div', { 'style': 'color:#666;' }, _('WAF Bypassed')) - ]) - ]) - ])); - - // Vhosts Table var vhostRows = vhosts.map(function(v) { return self.renderVhostRow(v); }); - content.push(E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Virtual Hosts')), - E('div', { 'class': 'cbi-page-actions', 'style': 'margin-bottom:15px;' }, [ - E('button', { - 'class': 'btn cbi-button-action', - 'click': function() { self.handleSync(); } - }, _('Sync Routes from HAProxy')) + var content = E('div', { 'class': 'routes-status-page' }, [ + // KISS Header + E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ + E('div', {}, [ + E('h2', { 'class': 'sh-page-title' }, [ + E('span', { 'class': 'sh-page-title-icon' }, '🔀'), + _('Routes Status') + ]), + E('p', { 'class': 'sh-page-subtitle' }, + _('HAProxy vhosts and mitmproxy route configuration overview.')) + ]), + E('div', { 'class': 'sh-header-meta' }, [ + this.renderHeaderChip('🌐', _('Vhosts'), totalVhosts), + this.renderHeaderChip('✅', _('Active'), activeVhosts), + this.renderHeaderChip('⚠️', _('Missing Routes'), missingRoutes, missingRoutes > 0 ? 'warn' : ''), + this.renderHeaderChip('🛡️', _('WAF Bypass'), wafBypassed, wafBypassed > 0 ? 'warn' : ''), + this.renderHeaderChip('🔒', _('SSL Valid'), sslValid) + ]) ]), - vhosts.length > 0 ? - E('table', { 'class': 'table cbi-section-table' }, [ - E('tr', { 'class': 'tr table-titles' }, [ - E('th', { 'class': 'th' }, _('Domain')), - E('th', { 'class': 'th' }, _('Backend')), - E('th', { 'class': 'th', 'style': 'text-align:center;' }, _('Port')), - E('th', { 'class': 'th' }, _('Routes')), - E('th', { 'class': 'th' }, _('SSL')), - E('th', { 'class': 'th' }, _('WAF')), - E('th', { 'class': 'th' }, _('Status')) - ]) - ].concat(vhostRows)) : - E('p', { 'style': 'color:#666;' }, _('No virtual hosts configured.')) - ])); - return E('div', { 'class': 'cbi-map' }, content); + // Service Status Cards + E('div', { 'class': 'sh-card-grid', 'style': 'display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin:20px 0;' }, [ + E('div', { 'class': 'sh-card', 'style': 'background:#fff;border-radius:8px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);' }, [ + E('div', { 'style': 'display:flex;align-items:center;gap:8px;margin-bottom:8px;' }, [ + E('span', { 'style': 'font-size:1.5em;' }, '⚖️'), + E('strong', {}, 'HAProxy') + ]), + data.haproxy_running ? + this.renderPill('Running', 'success') : + this.renderPill('Stopped', 'danger') + ]), + E('div', { 'class': 'sh-card', 'style': 'background:#fff;border-radius:8px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);' }, [ + E('div', { 'style': 'display:flex;align-items:center;gap:8px;margin-bottom:8px;' }, [ + E('span', { 'style': 'font-size:1.5em;' }, '🔍'), + E('strong', {}, 'mitmproxy') + ]), + data.mitmproxy_running ? + this.renderPill('Running', 'success') : + this.renderPill('Stopped', 'danger') + ]), + E('div', { 'class': 'sh-card', 'style': 'background:#fff;border-radius:8px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);' }, [ + E('div', { 'style': 'display:flex;align-items:center;gap:8px;margin-bottom:8px;' }, [ + E('span', { 'style': 'font-size:1.5em;' }, '🖥️'), + E('strong', {}, _('Host IP')) + ]), + E('code', { 'style': 'background:#f5f5f5;padding:4px 8px;border-radius:4px;' }, data.host_ip || '192.168.255.1') + ]) + ]), + + // Actions + E('div', { 'style': 'margin:20px 0;' }, [ + E('button', { + 'class': 'cbi-button cbi-button-action', + 'click': function() { self.handleSync(); }, + 'style': 'margin-right:10px;' + }, '🔄 ' + _('Sync Routes from HAProxy')) + ]), + + // Vhosts Table + E('div', { 'class': 'sh-card', 'style': 'background:#fff;border-radius:8px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);overflow-x:auto;' }, [ + E('h3', { 'style': 'margin:0 0 16px 0;' }, '🌐 ' + _('Virtual Hosts (%d)').format(totalVhosts)), + vhosts.length > 0 ? + E('table', { 'class': 'table', 'style': 'width:100%;border-collapse:collapse;' }, [ + E('thead', {}, [ + E('tr', { 'style': 'background:#f5f5f5;' }, [ + E('th', { 'style': 'padding:10px;text-align:left;' }, _('Domain')), + E('th', { 'style': 'padding:10px;text-align:left;' }, _('Backend')), + E('th', { 'style': 'padding:10px;text-align:left;' }, _('Routes')), + E('th', { 'style': 'padding:10px;text-align:left;' }, _('SSL')), + E('th', { 'style': 'padding:10px;text-align:left;' }, _('WAF')), + E('th', { 'style': 'padding:10px;text-align:left;' }, _('Status')) + ]) + ]), + E('tbody', {}, vhostRows) + ]) : + E('p', { 'style': 'color:#666;text-align:center;padding:20px;' }, _('No virtual hosts configured.')) + ]) + ]); + + return KissTheme.wrap([content], 'admin/status/vhosts-checker'); } }); diff --git a/package/secubox/luci-app-routes-status/root/usr/share/luci/menu.d/luci-app-routes-status.json b/package/secubox/luci-app-routes-status/root/usr/share/luci/menu.d/luci-app-routes-status.json index dc960942..104c24ac 100644 --- a/package/secubox/luci-app-routes-status/root/usr/share/luci/menu.d/luci-app-routes-status.json +++ b/package/secubox/luci-app-routes-status/root/usr/share/luci/menu.d/luci-app-routes-status.json @@ -1,6 +1,6 @@ { - "admin/status/routes": { - "title": "Routes Status", + "admin/status/vhosts-checker": { + "title": "VHosts Checker", "order": 15, "action": { "type": "view", diff --git a/package/secubox/luci-theme-secubox/htdocs/luci-static/resources/secubox/kiss-theme.js b/package/secubox/luci-theme-secubox/htdocs/luci-static/resources/secubox/kiss-theme.js index 0fc6df29..18c0796e 100644 --- a/package/secubox/luci-theme-secubox/htdocs/luci-static/resources/secubox/kiss-theme.js +++ b/package/secubox/luci-theme-secubox/htdocs/luci-static/resources/secubox/kiss-theme.js @@ -68,6 +68,7 @@ var KissThemeClass = baseclass.extend({ { name: 'Stats', path: 'admin/services/haproxy/stats' }, { name: 'Settings', path: 'admin/services/haproxy/settings' } ]}, + { icon: '🔀', name: 'VHosts Checker', path: 'admin/status/vhosts-checker' }, { icon: '🔒', name: 'WireGuard', path: 'admin/services/wireguard', tabs: [ { name: 'Wizard', path: 'admin/services/wireguard/wizard' }, { name: 'Overview', path: 'admin/services/wireguard/overview' },