From 04b05f48a9625becfae1778024430ea6e19f735d Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 23 May 2026 01:20:58 +0000 Subject: [PATCH 01/35] fix: update go.sum to remove deprecated crypto and net dependencies --- backend/go.sum | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/go.sum b/backend/go.sum index 582fcd663..df44ff3fb 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -183,20 +183,14 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= golang.org/x/arch v0.27.0 h1:0WNVcR8u9yFz8j5FvdHpgwNp3FS5U4guYdzHwEiGjoU= golang.org/x/arch v0.27.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= From b9d852a49cc31c3df9c91b64fc7bfa75fbfafab2 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 23 May 2026 01:21:37 +0000 Subject: [PATCH 02/35] fix: update golang.org/x/sys dependency to v0.45.0 and add missing module entries in go.sum --- agent/go.mod | 2 +- agent/go.sum | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/agent/go.mod b/agent/go.mod index b2992f75f..d07d36cae 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -14,7 +14,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - golang.org/x/sys v0.44.0 // indirect + golang.org/x/sys v0.45.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/agent/go.sum b/agent/go.sum index eb90792f7..4a3eb7a2d 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -7,21 +7,25 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8 github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 1c79b42d960bba02bf3ac2991aed86484f06d470 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 23 May 2026 01:22:23 +0000 Subject: [PATCH 03/35] fix: update semver dependency to version 7.8.1 --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5702a4cfc..111b50d5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3752,9 +3752,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "dev": true, "license": "ISC", "bin": { From eefed1d4716de587c4efbfd2a0cd69d1ec756f7b Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 23 May 2026 01:23:12 +0000 Subject: [PATCH 04/35] fix: update @adobe/css-tools to version 4.5.0, @eslint/css-tree to version 4.0.4, and other dependencies --- frontend/package-lock.json | 160 ++++++++++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 29 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f4b52d607..d24562e19 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -83,9 +83,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", "dev": true, "license": "MIT" }, @@ -721,6 +721,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -732,6 +733,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -746,6 +748,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -873,13 +876,13 @@ } }, "node_modules/@eslint/css-tree": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-4.0.3.tgz", - "integrity": "sha512-lMgQJ5zg4YwsGPWtKS7Rc5Z4xqc2B9iOTtmDvhjMRa8GJ8/9zoKRtz26D7gCtWquy0J7oyeOJBwRP5M8slM/4w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-4.0.4.tgz", + "integrity": "sha512-nxMparyhqVWQvadx9x8dIfubfIPOE+X2b2waua8fzdnM9vdp9rgVtwEZlG0TmCwEUz/d/f40fzvO/eqBwdxz0A==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.28.0", + "mdn-data": "2.28.1", "source-map-js": "^1.2.1" }, "engines": { @@ -1447,6 +1450,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1464,6 +1470,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1481,6 +1490,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1498,6 +1510,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1515,6 +1530,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1532,6 +1550,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1549,6 +1570,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1566,6 +1590,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1778,6 +1805,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1792,6 +1822,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1806,6 +1839,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1820,6 +1856,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1834,6 +1873,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1848,6 +1890,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1862,6 +1907,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1876,6 +1924,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3209,6 +3260,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3226,6 +3280,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3243,6 +3300,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3260,6 +3320,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -4247,6 +4310,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4261,6 +4327,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -4309,6 +4378,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4323,6 +4395,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4337,6 +4412,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -4351,6 +4429,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4365,6 +4446,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4379,6 +4463,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5062,9 +5149,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.31", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz", - "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==", + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5873,9 +5960,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.360", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.360.tgz", - "integrity": "sha512-GkcBt6YYAw9SxFWn+xVar4cLVGlXVuswwtRLBozi2zp0GjXs4ZnOrqV4zbXzg35n7w81hCkyJNYicgXlVHAmBA==", + "version": "1.5.361", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", + "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", "dev": true, "license": "ISC" }, @@ -5887,9 +5974,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.21.5", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.5.tgz", - "integrity": "sha512-mLCNbrQli11K1ySUmuNt4ZUB3OpGIDq4q2vTBTf5cL2lpsRjI9QKqSD0ndjW8FyvcW/Jj46gMe9syyHAsvMa/A==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz", + "integrity": "sha512-xYcDWrpELkFzz9SpZ3PlI6Eu6eD93Yf0WLDRxikGhWJ3MAir2SNZTIVCVZqZ/NUyx8AdMc2gT9C0gPiw18kG+A==", "dev": true, "license": "MIT", "dependencies": { @@ -6008,9 +6095,9 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -8300,6 +8387,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8321,6 +8411,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8342,6 +8435,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8363,6 +8459,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8806,9 +8905,9 @@ } }, "node_modules/mdn-data": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.28.0.tgz", - "integrity": "sha512-uy9AS1yt+wW5eUEefgE3lOpqPghanUttycV0GXKbiXyBjwvbeE8XPj4u1C+voRfz7dEjwU4NDHTMfZ/s/JtZrQ==", + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.28.1.tgz", + "integrity": "sha512-U9w+PzSZ00Z5m9rZ5ARVFL5xOfuCHdKYi/1RRwDCJsboFgJDNT3zT6PIPD7mZQYaQLhsZM3GfDRgSMRHhSmVng==", "dev": true, "license": "CC0-1.0" }, @@ -9556,11 +9655,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.44", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", - "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/object-inspect": { "version": "1.13.4", @@ -10573,9 +10675,9 @@ } }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "dev": true, "license": "ISC", "bin": { From 71cf7f2ade29387c399656a0583182cbfb8d6141 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 23 May 2026 02:50:54 +0000 Subject: [PATCH 05/35] fix(uptime): correct false-DOWN reporting for Orthrus-managed remote servers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uptime monitoring incorrectly reported Orthrus-managed remote servers as DOWN because SyncMonitors() created TCP monitors using the ExternalProxyPort (e.g. 2375) as the target on the remote machine. That port is bound on the Charon server's loopback, not on the remote machine, so every TCP dial resulted in ECONNREFUSED and a false failure. Once the host was marked DOWN, all co-located TCP monitors (e.g. Dockhand on port 3001) were short-circuited without being checked, cascading the false DOWN state across all services at that Tailscale IP. Introduces a first-class "orthrus" monitor type that replaces TCP dials with an in-memory WebSocket session state lookup. A monitor is UP if and only if the Orthrus agent's WebSocket tunnel is connected and its local Docker proxy listener is operational. No network I/O is required for the check. Adds a pre-flight fast-path in checkHost() that exits immediately for Orthrus-only hosts, eliminating wasted retry-loop sleep time proportional to MaxRetries × 2s per poll cycle. Existing stale TCP monitors for Orthrus servers are migrated automatically to type="orthrus" on the next SyncMonitors() run — no database migration required. Fixes uptime reporting for Orthrus remote server hosts and all co-located TCP service monitors (resolves Dockhand port 3001 false-DOWN cascade). --- backend/internal/api/routes/routes.go | 1 + backend/internal/services/uptime_service.go | 74 ++ .../internal/services/uptime_service_test.go | 352 +++++++ docs/plans/current_spec.md | 883 ++++++++++-------- docs/reports/qa_report.md | 447 ++++----- tests/uptime-orthrus.spec.ts | 164 ++++ 6 files changed, 1286 insertions(+), 635 deletions(-) create mode 100644 tests/uptime-orthrus.spec.ts diff --git a/backend/internal/api/routes/routes.go b/backend/internal/api/routes/routes.go index 37415ea1c..ddb2de51d 100644 --- a/backend/internal/api/routes/routes.go +++ b/backend/internal/api/routes/routes.go @@ -510,6 +510,7 @@ func RegisterWithDeps(ctx context.Context, router *gin.Engine, db *gorm.DB, cfg dockerHandler := handlers.NewDockerHandler(dockerService, remoteServerService) if orthrusServer != nil { dockerHandler.SetOrthrusResolver(orthrusServer) + uptimeService.SetOrthrusResolver(orthrusServer) } dockerHandler.RegisterRoutes(management) diff --git a/backend/internal/services/uptime_service.go b/backend/internal/services/uptime_service.go index e0bbc2c08..687e44c26 100644 --- a/backend/internal/services/uptime_service.go +++ b/backend/internal/services/uptime_service.go @@ -21,9 +21,16 @@ import ( "gorm.io/gorm" ) +// orthrusStatusChecker allows UptimeService to query Orthrus session liveness +// without a direct dependency on the orthrus package. +type orthrusStatusChecker interface { + GetProxyAddr(agentUUID string) (string, bool) +} + type UptimeService struct { DB *gorm.DB NotificationService *NotificationService + orthrusResolver orthrusStatusChecker // nil when Orthrus feature is disabled // Batching: track pending notifications pendingNotifications map[string]*pendingHostNotification notificationMutex sync.Mutex @@ -77,6 +84,16 @@ func NewUptimeService(db *gorm.DB, ns *NotificationService) *UptimeService { } } +// SetOrthrusResolver injects the Orthrus session resolver. +// Uses the typed-nil guard pattern established in DockerHandler. +func (s *UptimeService) SetOrthrusResolver(r orthrusStatusChecker) { + if r == nil { + s.orthrusResolver = nil + return + } + s.orthrusResolver = r +} + // extractPort extracts the port from a URL or host:port string func extractPort(urlStr string) string { // Try parsing as URL first @@ -270,6 +287,16 @@ func (s *UptimeService) SyncMonitors() error { // The upstream host for grouping upstreamHost := server.Host + // Orthrus-managed servers: connectivity is measured by session liveness, not TCP. + if server.ConnectionType == models.ConnectionTypeOrthrus { + if server.OrthrusAgentUUID == nil || *server.OrthrusAgentUUID == "" { + continue // No agent linked — cannot create a meaningful monitor + } + targetType = "orthrus" + targetURL = *server.OrthrusAgentUUID // Agent UUID as the monitor identifier + // upstreamHost remains server.Host (Tailscale IP) — correct for grouping/display + } + switch err { case gorm.ErrRecordNotFound: // Find or create UptimeHost @@ -382,6 +409,8 @@ func (s *UptimeService) CheckAll() { tcpMonitors := make([]models.UptimeMonitor, 0, len(monitors)) nonTCPMonitors := make([]models.UptimeMonitor, 0, len(monitors)) + // "orthrus" type is not "tcp", so it falls into nonTCPMonitors and + // continues to run checkMonitor independently even when the host is down. for _, monitor := range monitors { normalizedType := strings.ToLower(strings.TrimSpace(monitor.Type)) if normalizedType == "tcp" { @@ -484,6 +513,22 @@ func (s *UptimeService) checkHost(ctx context.Context, host *models.UptimeHost) return } + // Fast-path: if every monitor for this host is Orthrus-type, skip the + // TCP pre-check entirely — individual checkMonitor calls determine status. + hasDialable := false + for _, m := range monitors { + if strings.ToLower(m.Type) != "orthrus" { + hasDialable = true + break + } + } + if !hasDialable { + return + } + + // Track whether any non-Orthrus monitor with a valid port was attempted. + attempted := false + // Try to connect to any of the monitor ports with retry logic success := false var msg string @@ -508,6 +553,11 @@ func (s *UptimeService) checkHost(ctx context.Context, host *models.UptimeHost) } for _, monitor := range monitors { + // Orthrus liveness is checked per-monitor via session state, not TCP pre-check. + if strings.ToLower(monitor.Type) == "orthrus" { + continue + } + var port string // Use actual backend port from ProxyHost if available @@ -522,6 +572,7 @@ func (s *UptimeService) checkHost(ctx context.Context, host *models.UptimeHost) continue } + attempted = true logger.Log().WithFields(map[string]any{ "monitor": monitor.Name, "extracted_port": extractPort(monitor.URL), @@ -554,6 +605,12 @@ func (s *UptimeService) checkHost(ctx context.Context, host *models.UptimeHost) } } + // If every monitor for this host is Orthrus-type, there are no dialable ports. + // Skip the TCP pre-check; individual checkMonitor() calls determine status. + if !attempted { + return + } + latency := time.Since(start).Milliseconds() oldStatus := host.Status var newStatus string @@ -807,6 +864,23 @@ func (s *UptimeService) checkMonitor(monitor models.UptimeMonitor) { } else { msg = err.Error() } + case "orthrus": + agentUUID := monitor.URL + if s.orthrusResolver == nil { + msg = "Orthrus subsystem unavailable" + break + } + if agentUUID == "" { + msg = "Monitor missing agent UUID" + break + } + _, ok := s.orthrusResolver.GetProxyAddr(agentUUID) + if ok { + success = true + msg = "Orthrus session active" + } else { + msg = "Orthrus agent not connected" + } default: msg = "Unknown monitor type" } diff --git a/backend/internal/services/uptime_service_test.go b/backend/internal/services/uptime_service_test.go index e3e5c2aa5..50ade0720 100644 --- a/backend/internal/services/uptime_service_test.go +++ b/backend/internal/services/uptime_service_test.go @@ -1,6 +1,7 @@ package services import ( + "context" "fmt" "net" "net/http" @@ -1890,3 +1891,354 @@ func TestCheckMonitor_TCP_AcceptsRFC1918Address(t *testing.T) { db.First(&result, "id = ?", monitor.ID) assert.Equal(t, "up", result.Status, "TCP monitor to loopback should report up") } + +// --- Orthrus uptime monitoring tests --- + +type mockOrthrusResolver struct { + addr string + ok bool +} + +func (m *mockOrthrusResolver) GetProxyAddr(_ string) (string, bool) { + return m.addr, m.ok +} + +func TestUptimeService_SetOrthrusResolver(t *testing.T) { + t.Run("normal set", func(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + mock := &mockOrthrusResolver{addr: "127.0.0.1:1234", ok: true} + us.SetOrthrusResolver(mock) + assert.Equal(t, mock, us.orthrusResolver) + }) + + t.Run("nil resolver clears field", func(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + us.SetOrthrusResolver(&mockOrthrusResolver{}) + us.SetOrthrusResolver(nil) + assert.Nil(t, us.orthrusResolver) + }) + + t.Run("resolver replacement", func(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + r1 := &mockOrthrusResolver{addr: "a", ok: true} + r2 := &mockOrthrusResolver{addr: "b", ok: false} + us.SetOrthrusResolver(r1) + us.SetOrthrusResolver(r2) + assert.Equal(t, r2, us.orthrusResolver) + }) +} + +func TestSyncMonitors_OrthrusRemoteServer_CreatesOrthrusMonitor(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + + agentUUID := "test-agent-uuid-1234" + server := models.RemoteServer{ + UUID: "remote-orthrus-1", + Name: "Orthrus Server", + Host: "100.99.23.57", + Port: 2375, + Scheme: "http", + Enabled: true, + ConnectionType: models.ConnectionTypeOrthrus, + OrthrusAgentUUID: &agentUUID, + } + require.NoError(t, db.Create(&server).Error) + + require.NoError(t, us.SyncMonitors()) + + var monitor models.UptimeMonitor + require.NoError(t, db.Where("remote_server_id = ?", server.ID).First(&monitor).Error) + assert.Equal(t, "orthrus", monitor.Type) + assert.Equal(t, agentUUID, monitor.URL) + assert.Equal(t, "100.99.23.57", monitor.UpstreamHost) +} + +func TestSyncMonitors_OrthrusRemoteServer_MigratesExistingTCPMonitor(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + + agentUUID := "migrate-agent-uuid" + server := models.RemoteServer{ + UUID: "remote-migrate-1", + Name: "Migrate Server", + Host: "100.99.23.58", + Port: 2375, + Scheme: "http", + Enabled: true, + ConnectionType: models.ConnectionTypeOrthrus, + OrthrusAgentUUID: &agentUUID, + } + require.NoError(t, db.Create(&server).Error) + + legacyMonitor := models.UptimeMonitor{ + ID: "legacy-tcp-monitor", + RemoteServerID: &server.ID, + Name: server.Name, + Type: "tcp", + URL: fmt.Sprintf("%s:%d", server.Host, server.Port), + UpstreamHost: server.Host, + Enabled: true, + Status: "up", + } + require.NoError(t, db.Create(&legacyMonitor).Error) + + require.NoError(t, us.SyncMonitors()) + + var updated models.UptimeMonitor + require.NoError(t, db.Where("remote_server_id = ?", server.ID).First(&updated).Error) + assert.Equal(t, "orthrus", updated.Type) + assert.Equal(t, agentUUID, updated.URL) +} + +func TestSyncMonitors_NonOrthrusRemoteServer_StillUsesHTTP(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + + server := models.RemoteServer{ + UUID: "remote-direct-1", + Name: "Direct Server", + Host: "192.168.1.100", + Port: 8080, + Scheme: "http", + Enabled: true, + ConnectionType: models.ConnectionTypeDirect, + } + require.NoError(t, db.Create(&server).Error) + + require.NoError(t, us.SyncMonitors()) + + var monitor models.UptimeMonitor + require.NoError(t, db.Where("remote_server_id = ?", server.ID).First(&monitor).Error) + assert.Equal(t, "http", monitor.Type) + assert.Contains(t, monitor.URL, "192.168.1.100") +} + +func TestCheckHost_OrthrusOnlyHost_SkipsTCPDial(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + + uptimeHost := models.UptimeHost{ + Host: "100.99.23.57", + Name: "Orthrus Only Host", + Status: "pending", + } + require.NoError(t, db.Create(&uptimeHost).Error) + + hostID := uptimeHost.ID + orthrusMonitor := models.UptimeMonitor{ + ID: "orthrus-only-1", + Name: "Orthrus Monitor", + Type: "orthrus", + URL: "some-agent-uuid", + Enabled: true, + Status: "pending", + UptimeHostID: &hostID, + UpstreamHost: "100.99.23.57", + } + require.NoError(t, db.Create(&orthrusMonitor).Error) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + us.checkHost(ctx, &uptimeHost) + + var refreshed models.UptimeHost + require.NoError(t, db.Where("id = ?", uptimeHost.ID).First(&refreshed).Error) + assert.Equal(t, "pending", refreshed.Status, "Orthrus-only host should not have TCP pre-check run") +} + +func TestCheckHost_MixedHost_OrthrusAndTCP_DialsTCPOnly(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + us.config.FailureThreshold = 1 + + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + port := ln.Addr().(*net.TCPAddr).Port + go func() { + conn, _ := ln.Accept() + if conn != nil { + _ = conn.Close() + } + }() + t.Cleanup(func() { _ = ln.Close() }) + + uptimeHost := models.UptimeHost{ + Host: "127.0.0.1", + Name: "Mixed Host", + Status: "pending", + } + require.NoError(t, db.Create(&uptimeHost).Error) + + hostID := uptimeHost.ID + orthrusMonitor := models.UptimeMonitor{ + ID: "mixed-orthrus-1", + Name: "Orthrus Monitor", + Type: "orthrus", + URL: "agent-uuid-xyz", + Enabled: true, + Status: "pending", + UptimeHostID: &hostID, + UpstreamHost: "127.0.0.1", + } + tcpMonitor := models.UptimeMonitor{ + ID: "mixed-tcp-1", + Name: "TCP Monitor", + Type: "tcp", + URL: fmt.Sprintf("127.0.0.1:%d", port), + Enabled: true, + Status: "pending", + UptimeHostID: &hostID, + UpstreamHost: "127.0.0.1", + } + require.NoError(t, db.Create(&orthrusMonitor).Error) + require.NoError(t, db.Create(&tcpMonitor).Error) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + us.checkHost(ctx, &uptimeHost) + + var refreshed models.UptimeHost + require.NoError(t, db.Where("id = ?", uptimeHost.ID).First(&refreshed).Error) + assert.Equal(t, "up", refreshed.Status, "Mixed host TCP port should succeed") +} + +func TestCheckMonitor_OrthrusType_AgentConnected_ReturnsUp(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + us.SetOrthrusResolver(&mockOrthrusResolver{addr: "127.0.0.1:54321", ok: true}) + + monitor := models.UptimeMonitor{ + ID: "orthrus-up-1", + Name: "Orthrus Connected", + Type: "orthrus", + URL: "connected-agent-uuid", + Enabled: true, + Status: "pending", + MaxRetries: 1, + } + require.NoError(t, db.Create(&monitor).Error) + + us.checkMonitor(monitor) + + var refreshed models.UptimeMonitor + require.NoError(t, db.Where("id = ?", monitor.ID).First(&refreshed).Error) + assert.Equal(t, "up", refreshed.Status) + + var heartbeat models.UptimeHeartbeat + require.NoError(t, db.Where("monitor_id = ?", monitor.ID).Order("created_at desc").First(&heartbeat).Error) + assert.Equal(t, "up", heartbeat.Status) + assert.Equal(t, "Orthrus session active", heartbeat.Message) +} + +func TestCheckMonitor_OrthrusType_AgentDisconnected_ReturnsDown(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + us.SetOrthrusResolver(&mockOrthrusResolver{addr: "", ok: false}) + + monitor := models.UptimeMonitor{ + ID: "orthrus-down-1", + Name: "Orthrus Disconnected", + Type: "orthrus", + URL: "disconnected-agent-uuid", + Enabled: true, + Status: "pending", + MaxRetries: 1, + FailureCount: 0, + } + require.NoError(t, db.Create(&monitor).Error) + + us.checkMonitor(monitor) + + var refreshed models.UptimeMonitor + require.NoError(t, db.Where("id = ?", monitor.ID).First(&refreshed).Error) + assert.Equal(t, "down", refreshed.Status) + + var heartbeat models.UptimeHeartbeat + require.NoError(t, db.Where("monitor_id = ?", monitor.ID).Order("created_at desc").First(&heartbeat).Error) + assert.Equal(t, "down", heartbeat.Status) + assert.Equal(t, "Orthrus agent not connected", heartbeat.Message) +} + +func TestCheckMonitor_OrthrusType_NilResolver_ReturnsDown(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + + monitor := models.UptimeMonitor{ + ID: "orthrus-nil-resolver", + Name: "Orthrus Nil Resolver", + Type: "orthrus", + URL: "some-agent-uuid", + Enabled: true, + Status: "pending", + MaxRetries: 1, + FailureCount: 0, + } + require.NoError(t, db.Create(&monitor).Error) + + us.checkMonitor(monitor) + + var heartbeat models.UptimeHeartbeat + require.NoError(t, db.Where("monitor_id = ?", monitor.ID).Order("created_at desc").First(&heartbeat).Error) + assert.Equal(t, "Orthrus subsystem unavailable", heartbeat.Message) +} + +func TestCheckAll_OrthrusMonitor_NotShortCircuitedWhenHostDown(t *testing.T) { + db := setupUptimeTestDB(t) + ns := NewNotificationService(db, nil) + us := newTestUptimeService(t, db, ns) + us.config.FailureThreshold = 1 + us.SetOrthrusResolver(&mockOrthrusResolver{addr: "", ok: false}) + + uptimeHost := models.UptimeHost{ + Host: "100.99.23.57", + Name: "Down Orthrus Host", + Status: "down", + } + require.NoError(t, db.Create(&uptimeHost).Error) + + hostID := uptimeHost.ID + orthrusMonitor := models.UptimeMonitor{ + ID: "checkall-orthrus-1", + Name: "Orthrus Not Short-Circuited", + Type: "orthrus", + URL: "agent-uuid", + Enabled: true, + Status: "pending", + UptimeHostID: &hostID, + UpstreamHost: "100.99.23.57", + MaxRetries: 1, + } + require.NoError(t, db.Create(&orthrusMonitor).Error) + + us.CheckAll() + + assert.Eventually(t, func() bool { + var refreshed models.UptimeMonitor + if db.Where("id = ?", orthrusMonitor.ID).First(&refreshed).Error != nil { + return false + } + return refreshed.Status == "down" + }, 3*time.Second, 25*time.Millisecond) + + var heartbeat models.UptimeHeartbeat + err := db.Where("monitor_id = ?", orthrusMonitor.ID).Order("created_at desc").First(&heartbeat).Error + require.NoError(t, err) + assert.Equal(t, "Orthrus agent not connected", heartbeat.Message, + "Orthrus monitor should be checked via checkMonitor, not short-circuited") + assert.NotEqual(t, "Host unreachable", heartbeat.Message) +} diff --git a/docs/plans/current_spec.md b/docs/plans/current_spec.md index 0c80be4f1..1ffe3f75b 100644 --- a/docs/plans/current_spec.md +++ b/docs/plans/current_spec.md @@ -1,7 +1,7 @@ -# Spec: Static Feedback Widget +# Uptime Monitoring Bugs: Orthrus-Managed Remote Servers Always Reported DOWN -**Status**: Draft -**Target**: Single PR — one atomic commit +**Status**: Active +**Target**: New PR (`development` → `main`) --- @@ -9,480 +9,611 @@ ### Overview -Add a persistent, accessible feedback widget to every authenticated page in the Charon frontend. The widget appears as a small floating icon button anchored to the bottom-right corner of the viewport. When activated, it expands into a compact popover panel offering two GitHub Issue links: +Two interrelated bugs cause Charon's uptime monitoring subsystem to permanently report +Orthrus-managed remote servers as DOWN, even when the Orthrus WebSocket tunnel is alive +and healthy. + +**Bug 1 — Orthrus host-check dials the wrong port on the wrong machine.** +`SyncMonitors()` creates a TCP monitor using `server.Host:server.Port` for every +`RemoteServer` regardless of `ConnectionType`. For `ConnectionTypeOrthrus`, `server.Port` +is the `ExternalProxyPort` (e.g. 2375) that Orthrus binds on `0.0.0.0` of the **Charon +server** — not on the remote machine. The host-level pre-check in `checkHost()` therefore +dials `:2375`, which is not open on the remote machine, and marks +the `UptimeHost` as DOWN after `FailureThreshold` failures. + +**Bug 2 — Downstream cascade makes every TCP monitor for the same IP report DOWN.** +Once an `UptimeHost` is marked DOWN, `CheckAll()` short-circuits all TCP monitors +associated with that host without running them. Any additional TCP monitors for services +at the same Tailscale IP (e.g. a port-3001 Dockhand monitor) are therefore always +reported DOWN even if the service is reachable, because they ride the same `UptimeHost` +that was incorrectly marked DOWN by Bug 1. -- **Report a Bug** → `https://github.com/Wikid82/Charon/issues/new?template=bug_report.md` -- **Request a Feature** → `https://github.com/Wikid82/Charon/issues/new?template=feature_request.md` +### Objectives -Both links open in a new tab. The widget is rendered inside `Layout.tsx` so it appears on all authenticated routes but is absent from `/login`, `/setup`, and `/accept-invite`. +1. Fix `SyncMonitors()` to build correct, connection-type-aware monitor records for + Orthrus remote servers. +2. Fix `checkHost()` to skip raw TCP dials for Orthrus-only hosts and report their + reachability via Orthrus session state instead. +3. Fix `checkMonitor()` to handle `Type = "orthrus"` monitors by querying the live + Orthrus session rather than dialling a TCP port. +4. Fix `CheckAll()` to never short-circuit `"orthrus"` monitors on a host-DOWN event + (analogous to the existing HTTP monitor exception). +5. Wire the Orthrus resolver into `UptimeService` without creating a circular dependency. +6. Preserve all existing behaviour for non-Orthrus remote servers and all proxy host monitors. -### Objectives +--- -- Provide a low-friction path for users to report bugs or request features directly from the app -- Match existing UI design language (semantic tokens, Tailwind, Lucide icons) -- Meet WCAG 2.2 AA accessibility requirements -- Introduce zero new runtime dependencies -- Keep the widget unobtrusive — collapsed by default, non-blocking - -The **exact Caddy rejection reason is unknown** due to two compounding observability gaps: the script discards the HTTP response body (containing the full error), and the CI debug step runs after `trap cleanup EXIT` has already removed all containers. This plan fixes the observability gap first, then addresses every confirmed defect. - -A secondary defect also exists: even if proxy host creation succeeded, `buildWAFHandler` in `config.go` returns `nil` when `secCfg.WAFMode == "disabled"` (the seeded DB value), so the WAF handler would not be present in the Caddy route during the initial proxy host creation. The security config PUT (step 5) sets `WAFMode = "block"` and triggers a second `ApplyConfig`, at which point the WAF would apply correctly. This ordering issue is a separate concern from the 500 and is documented below. - -### Architecture Summary - -| Layer | Detail | -|---|---| -| Framework | React 19.2.3, TypeScript (strict), Vite 8 | -| Styling | Tailwind CSS 4.x with semantic CSS custom properties; `darkMode: 'class'` | -| Icons | `lucide-react` — used uniformly across all components | -| Accessible overlays | `@radix-ui/react-tooltip`, `@radix-ui/react-dialog` already installed | -| Classname utility | `cn()` at `frontend/src/utils/cn.ts` | -| Component variants | `class-variance-authority` (cva) | -| i18n | `react-i18next`, keys loaded from `src/locales/{locale}/translation.json` | -| Unit tests | Vitest 4 + React Testing Library; files in `src/components/__tests__/` | -| Layout entrypoint | `frontend/src/components/Layout.tsx` | - -### Z-Index Hierarchy - -| Element | z-index | -|---|---| -| Mobile overlay (backdrop) | `z-20` | -| Sidebar (`