Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1337a4905a | |||
| c7418d9e1a | |||
| 2a94ffd4c9 | |||
| b2fe6caf33 | |||
| 822bbc1957 | |||
| eacddc7ce1 | |||
| dc6ce341bd | |||
| 1aadc93f92 | |||
| 8fdcd479d6 | |||
| d24dde8eff | |||
| 40a34073e9 | |||
| 9ac297c197 |
52
changelog.md
52
changelog.md
@@ -1,5 +1,57 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-02-20 - 7.3.0 - feat(dcrouter)
|
||||
Wire DNS server 'query' events to MetricsManager for time-series tracking and bump @push.rocks/smartdns to ^7.9.0
|
||||
|
||||
- Add dnsServer 'query' event listener that iterates event.questions and calls metricsManager.trackDnsQuery(question.type, question.name, false, event.responseTimeMs).
|
||||
- Listener is guarded by a metricsManager existence check to avoid runtime errors when metrics are not configured.
|
||||
- Bump dependency @push.rocks/smartdns from ^7.8.1 to ^7.9.0 in package.json.
|
||||
|
||||
## 2026-02-20 - 7.2.0 - feat(logs)
|
||||
replace custom logs list with dees-chart-log component and push logs to chart, add log mapping and lifecycle sync, and bump smartlog dependency
|
||||
|
||||
- Replaced the legacy in-component log list and styling with a dees-chart-log element to render application logs.
|
||||
- Added updated() lifecycle handler to push new logs to the chart and new helper methods pushLogsToChart() and getMappedLogEntries() to map log entries to the chart's expected format.
|
||||
- Removed the streaming toggle, getActiveFilters(), legacy CSS for the log list, and the old per-entry rendering markup.
|
||||
- Added explicit typing for dropdown @selectedOption handlers (e: any).
|
||||
- Bumped dependency @push.rocks/smartlog from ^3.2.0 to ^3.2.1 in package.json.
|
||||
|
||||
## 2026-02-19 - 7.1.0 - feat(ops/monitoring)
|
||||
add in-memory log buffer, metrics time-series and ops UI integration
|
||||
|
||||
- bump @push.rocks/smartlog to ^3.2.0
|
||||
- introduce SmartlogDestinationBuffer (logBuffer) and wire it into the base logger to provide an in-memory log store for the Ops UI
|
||||
- implement minute-resolution time-series buckets in MetricsManager with increment/prune helpers and new APIs getEmailTimeSeries and getDnsTimeSeries
|
||||
- sync security counters from SecurityLogger and expose recent security events via StatsHandler
|
||||
- wire email delivery lifecycle events and bounce processing to MetricsManager for tracking sent/received/failed metrics
|
||||
- LogsHandler now queries the in-memory log buffer, maps smartlog levels/categories, supports search/level/time-range filtering and pagination
|
||||
- UI updates: ops-view-overview, ops-view-logs and ops-view-security consume time-series and recent events to render charts, tables and filters
|
||||
|
||||
## 2026-02-19 - 7.0.1 - fix(monitoring)
|
||||
Use smartMetrics cpuPercentage for cpuUsage.user and update smartmetrics and smartproxy dependencies
|
||||
|
||||
- Switch cpuUsage.user from parseFloat(smartMetricsData.cpuUsageText) to smartMetricsData.cpuPercentage to align with smartmetrics v3 API
|
||||
- Bump @push.rocks/smartmetrics from ^2.0.10 to ^3.0.1
|
||||
- Bump @push.rocks/smartproxy from ^25.7.6 to ^25.7.8
|
||||
|
||||
## 2026-02-19 - 7.0.0 - BREAKING CHANGE(deps)
|
||||
bump dependencies: @serve.zone/remoteingress to ^4.0.0 (breaking), @push.rocks/smartproxy to ^25.7.6, @types/node to ^25.3.0
|
||||
|
||||
- Updated @serve.zone/remoteingress from ^3.3.0 to ^4.0.0 — major breaking change; may require code changes to adapt to new API.
|
||||
- Updated @push.rocks/smartproxy from ^25.7.3 to ^25.7.6 — patch update (non-breaking).
|
||||
- Updated @types/node from ^25.2.3 to ^25.3.0 — patch update (non-breaking).
|
||||
- Current package version is 6.13.2; recommend bumping to 7.0.0 due to the breaking dependency upgrade.
|
||||
|
||||
## 2026-02-19 - 6.13.2 - fix(runtime)
|
||||
prevent memory leaks and improve shutdown/stream handling across services
|
||||
|
||||
- Add CertProvisionScheduler.clear() to reset in-memory backoff cache and call it during DcRouter shutdown
|
||||
- Stop any existing SmartAcme instance before creating a new one (await stop and log errors) to avoid duplicate running instances
|
||||
- Null out many DcRouter service references and clear certificateStatusMap on shutdown to allow GC of stopped services
|
||||
- Cap emailMetrics.recipients map size and trim to ~80% of MAX_TOP_DOMAINS to prevent unbounded growth
|
||||
- Await virtualStream.sendData in logs follow handler and clear the interval if the stream errors/closes to avoid interval leaks
|
||||
- Limit normalizedMacCache size and evict oldest entries when it exceeds 10000 to prevent unbounded cache growth
|
||||
|
||||
## 2026-02-18 - 6.13.1 - fix(dcrouter)
|
||||
enable PROXY protocol v1 handling for SmartProxy when remoteIngress is enabled to preserve client IPs
|
||||
|
||||
|
||||
14
package.json
14
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@serve.zone/dcrouter",
|
||||
"private": false,
|
||||
"version": "6.13.1",
|
||||
"version": "7.3.0",
|
||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -24,7 +24,7 @@
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.8",
|
||||
"@git.zone/tswatch": "^3.1.0",
|
||||
"@types/node": "^25.2.3"
|
||||
"@types/node": "^25.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@api.global/typedrequest": "^3.2.6",
|
||||
@@ -38,25 +38,25 @@
|
||||
"@push.rocks/qenv": "^6.1.3",
|
||||
"@push.rocks/smartacme": "^9.1.3",
|
||||
"@push.rocks/smartdata": "^7.0.15",
|
||||
"@push.rocks/smartdns": "^7.8.1",
|
||||
"@push.rocks/smartdns": "^7.9.0",
|
||||
"@push.rocks/smartfile": "^13.1.2",
|
||||
"@push.rocks/smartguard": "^3.1.0",
|
||||
"@push.rocks/smartjwt": "^2.2.1",
|
||||
"@push.rocks/smartlog": "^3.1.11",
|
||||
"@push.rocks/smartmetrics": "^2.0.10",
|
||||
"@push.rocks/smartlog": "^3.2.1",
|
||||
"@push.rocks/smartmetrics": "^3.0.1",
|
||||
"@push.rocks/smartmongo": "^5.1.0",
|
||||
"@push.rocks/smartmta": "^5.2.2",
|
||||
"@push.rocks/smartnetwork": "^4.4.0",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartproxy": "^25.7.3",
|
||||
"@push.rocks/smartproxy": "^25.7.8",
|
||||
"@push.rocks/smartradius": "^1.1.1",
|
||||
"@push.rocks/smartrequest": "^5.0.1",
|
||||
"@push.rocks/smartrx": "^3.0.10",
|
||||
"@push.rocks/smartstate": "^2.0.30",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@serve.zone/interfaces": "^5.3.0",
|
||||
"@serve.zone/remoteingress": "^3.3.0",
|
||||
"@serve.zone/remoteingress": "^4.0.0",
|
||||
"@tsclass/tsclass": "^9.3.0",
|
||||
"lru-cache": "^11.2.6",
|
||||
"uuid": "^13.0.0"
|
||||
|
||||
234
pnpm-lock.yaml
generated
234
pnpm-lock.yaml
generated
@@ -42,8 +42,8 @@ importers:
|
||||
specifier: ^7.0.15
|
||||
version: 7.0.15(socks@2.8.7)
|
||||
'@push.rocks/smartdns':
|
||||
specifier: ^7.8.1
|
||||
version: 7.8.1
|
||||
specifier: ^7.9.0
|
||||
version: 7.9.0
|
||||
'@push.rocks/smartfile':
|
||||
specifier: ^13.1.2
|
||||
version: 13.1.2
|
||||
@@ -54,11 +54,11 @@ importers:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
'@push.rocks/smartlog':
|
||||
specifier: ^3.1.11
|
||||
version: 3.1.11
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1
|
||||
'@push.rocks/smartmetrics':
|
||||
specifier: ^2.0.10
|
||||
version: 2.0.10
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
'@push.rocks/smartmongo':
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0(socks@2.8.7)
|
||||
@@ -75,8 +75,8 @@ importers:
|
||||
specifier: ^4.2.3
|
||||
version: 4.2.3
|
||||
'@push.rocks/smartproxy':
|
||||
specifier: ^25.7.3
|
||||
version: 25.7.3
|
||||
specifier: ^25.7.8
|
||||
version: 25.7.8
|
||||
'@push.rocks/smartradius':
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
@@ -96,8 +96,8 @@ importers:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@serve.zone/remoteingress':
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
'@tsclass/tsclass':
|
||||
specifier: ^9.3.0
|
||||
version: 9.3.0
|
||||
@@ -124,8 +124,8 @@ importers:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(@tiptap/pm@2.27.2)
|
||||
'@types/node':
|
||||
specifier: ^25.2.3
|
||||
version: 25.2.3
|
||||
specifier: ^25.3.0
|
||||
version: 25.3.0
|
||||
|
||||
packages:
|
||||
|
||||
@@ -757,10 +757,6 @@ packages:
|
||||
'@napi-rs/wasm-runtime@1.1.1':
|
||||
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
|
||||
|
||||
'@opentelemetry/api@1.9.0':
|
||||
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
'@oxc-project/types@0.99.0':
|
||||
resolution: {integrity: sha512-LLDEhXB7g1m5J+woRSgfKsFPS3LhR9xRhTeIoEBm5WrkwMxn6eZ0Ld0c0K5eHB57ChZX6I3uSmmLjZ8pcjlRcw==}
|
||||
|
||||
@@ -898,8 +894,8 @@ packages:
|
||||
'@push.rocks/smartdelay@3.0.5':
|
||||
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
|
||||
|
||||
'@push.rocks/smartdns@7.8.1':
|
||||
resolution: {integrity: sha512-qEizM9dFzhq4XGICDC8Im7JLjwdokHdDZ6wLufBInaEOupq+8XOa9bC6EGlBQVsCXFUyrKzsFk6eBa9BSZMKPw==}
|
||||
'@push.rocks/smartdns@7.9.0':
|
||||
resolution: {integrity: sha512-1nDUfyXQo6j9HTUfcjE+BLeAv9QZ7WtAsM1V28zIoFdUpjNg/5g382L024H73PHsxh6lSYNhYYmFvWqzFQhXKg==}
|
||||
|
||||
'@push.rocks/smartenv@5.0.13':
|
||||
resolution: {integrity: sha512-ACXmUcHZHl2CF2jnVuRw9saRRrZvJblCRs2d+K5aLR1DfkYFX3eA21kcMlKeLisI3aGNbIj9vz/rowN5qkRkfA==}
|
||||
@@ -964,8 +960,8 @@ packages:
|
||||
'@push.rocks/smartlog-interfaces@3.0.2':
|
||||
resolution: {integrity: sha512-8hGRTJehbsFSJxLhCQkA018mZtXVPxPTblbg9VaE/EqISRzUw+eosJ2EJV7M4Qu0eiTJZjnWnNLn8CkD77ziWw==}
|
||||
|
||||
'@push.rocks/smartlog@3.1.11':
|
||||
resolution: {integrity: sha512-zyLH8pQD2UD7l76wJBESEWXU1FSTBLOuRI0/DN139EYyMkwMq1+pdQKptTkJhhVL/OIj56oMg9SpJb4bJB7uKg==}
|
||||
'@push.rocks/smartlog@3.2.1':
|
||||
resolution: {integrity: sha512-x9/P59pfzY6HOGYmYrhqmoRl/pliTVx44g2Vbb8dIr/0zA39cAJHlPze1+UGncn37XKGmutK2iLSsJLEsexD0A==}
|
||||
|
||||
'@push.rocks/smartmail@2.2.0':
|
||||
resolution: {integrity: sha512-28K4HAcda7ODUUpFCgbS/uA+eqwVRcmLJERIdM9AvLHXaHAPLHH97HmwPPcAu9Sp3z05Um0inmDF51X6yVVkcw==}
|
||||
@@ -979,8 +975,8 @@ packages:
|
||||
'@push.rocks/smartmatch@2.0.0':
|
||||
resolution: {integrity: sha512-MBzP++1yNIBeox71X6VxpIgZ8m4bXnJpZJ4nWVH6IWpmO38MXTu4X0QF8tQnyT4LFcwvc9iiWaD15cstHa7Mmw==}
|
||||
|
||||
'@push.rocks/smartmetrics@2.0.10':
|
||||
resolution: {integrity: sha512-Fr4ZzJWFqTR67ThmPsj+uUfPoWZ+p87n7wItpXVjlQ2mf4pboWnsfmrrC1gqIXca1Dwa2i7L+GPoJ3hOZ+Qhfw==}
|
||||
'@push.rocks/smartmetrics@3.0.1':
|
||||
resolution: {integrity: sha512-wdc9v8F0dQXwraAVCZjGvt2lnGpbVYHWzER9OFR+u+ultMq7aLjUevxSpkS8BkhBMLTuNMCCWIqyAzaCqHzFgQ==}
|
||||
|
||||
'@push.rocks/smartmime@1.0.6':
|
||||
resolution: {integrity: sha512-PHd+I4UcsnOATNg8wjDsSAmmJ4CwQFrQCNzd0HSJMs4ZpiK3Ya91almd6GLpDPU370U4HFh4FaPF4eEAI6vkJQ==}
|
||||
@@ -1034,8 +1030,8 @@ packages:
|
||||
'@push.rocks/smartpromise@4.2.3':
|
||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||
|
||||
'@push.rocks/smartproxy@25.7.3':
|
||||
resolution: {integrity: sha512-9b5dwsLAhuDqnJptGBum4qBHlZwZPqPG3CJKxAwE3uFKjCmcE8qGDwodI0CjrQ7KW2PJ1BMq/Lk4ghs3Da6PWw==}
|
||||
'@push.rocks/smartproxy@25.7.8':
|
||||
resolution: {integrity: sha512-rKuC/5DgCBQmk1iCY2mZd+ZdH2mBOfcP1hWMARTP4Je4KqnNTJ2STM1tJmc9FmKVXxtEQCxWJnEnq1wNqwQFRA==}
|
||||
|
||||
'@push.rocks/smartpuppeteer@2.0.5':
|
||||
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
||||
@@ -1131,9 +1127,6 @@ packages:
|
||||
'@push.rocks/webrequest@3.0.37':
|
||||
resolution: {integrity: sha512-fLN7kP6GeHFxE4UH4r9C9pjcQb0QkJxHeAMwXvbOqB9hh0MFNKhtGU7GoaTn8SVRGRMPc9UqZVNwo6u5l8Wn0A==}
|
||||
|
||||
'@push.rocks/webrequest@4.0.1':
|
||||
resolution: {integrity: sha512-I60XZZLVf8W5I7YdmUVVu4G92teE3rg3/aKaV00BRg8vJ3VXx3wc59Qj4em7zxQ5o0HvL8m1Aezw3RFMDPyVgA==}
|
||||
|
||||
'@push.rocks/webrequest@4.0.2':
|
||||
resolution: {integrity: sha512-rowzty+Q2papFBcnNYPcy+8CQJukSn/FGfQG8ap0bUgQUsx882u8kEyLM0Q+GlGHS5OiZ+Z0z5TZqLKlk3XHxA==}
|
||||
|
||||
@@ -1340,8 +1333,8 @@ packages:
|
||||
'@serve.zone/interfaces@5.3.0':
|
||||
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
||||
|
||||
'@serve.zone/remoteingress@3.3.0':
|
||||
resolution: {integrity: sha512-nmw0F+Otrg78Xai9G3qLcP3NP4VkGPGm/6IGJmrXEgx3Z+ewh5Rhs1/rtN0mJFNXP77LZz1HuEBgR8aWbSHFQw==}
|
||||
'@serve.zone/remoteingress@4.0.0':
|
||||
resolution: {integrity: sha512-kcC9pnQdS5Mw0ypmwT3GIYwrjVQJOjWw5TJ+j46t6Y98S4Mb7wEAAPsU+YRXozpkqUxWUDBeDK1fQHLvoF9PmQ==}
|
||||
|
||||
'@sindresorhus/is@5.6.0':
|
||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||
@@ -1834,11 +1827,8 @@ packages:
|
||||
'@types/node@22.19.11':
|
||||
resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==}
|
||||
|
||||
'@types/node@25.2.3':
|
||||
resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==}
|
||||
|
||||
'@types/pidusage@2.0.5':
|
||||
resolution: {integrity: sha512-MIiyZI4/MK9UGUXWt0jJcCZhVw7YdhBuTOuqP/BjuLDLZ2PmmViMIQgZiWxtaMicQfAz/kMrZ5T7PKxFSkTeUA==}
|
||||
'@types/node@25.3.0':
|
||||
resolution: {integrity: sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==}
|
||||
|
||||
'@types/ping@0.4.4':
|
||||
resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==}
|
||||
@@ -2059,9 +2049,6 @@ packages:
|
||||
resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
bintrees@1.0.2:
|
||||
resolution: {integrity: sha1-SfiW1uhYpKSZ34XDj7OZua/4QPg=}
|
||||
|
||||
body-parser@2.2.2:
|
||||
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -3234,8 +3221,8 @@ packages:
|
||||
resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
minimatch@10.2.0:
|
||||
resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==}
|
||||
minimatch@10.2.1:
|
||||
resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
minimatch@3.1.2:
|
||||
@@ -3548,15 +3535,6 @@ packages:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pidtree@0.6.0:
|
||||
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
|
||||
pidusage@4.0.1:
|
||||
resolution: {integrity: sha512-yCH2dtLHfEBnzlHUJymR/Z1nN2ePG3m392Mv8TFlTP1B0xkpMQNHAnfkY0n2tAi6ceKO6YWhxYfZ96V4vVkh/g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ping@0.4.4:
|
||||
resolution: {integrity: sha512-56ZMC0j7SCsMMLdOoUg12VZCfj/+ZO+yfOSjaNCRrmZZr6GLbN2X/Ui56T15dI8NhiHckaw5X2pvyfAomanwqQ==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
@@ -3576,10 +3554,6 @@ packages:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
prom-client@15.1.3:
|
||||
resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==}
|
||||
engines: {node: ^16 || ^18 || >=20}
|
||||
|
||||
property-information@7.1.0:
|
||||
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
||||
|
||||
@@ -3978,9 +3952,6 @@ packages:
|
||||
tar-stream@3.1.7:
|
||||
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
|
||||
|
||||
tdigest@0.1.2:
|
||||
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
|
||||
|
||||
text-decoder@1.2.3:
|
||||
resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==}
|
||||
|
||||
@@ -4089,8 +4060,8 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
undici-types@7.18.2:
|
||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||
|
||||
unified@11.0.5:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
@@ -4325,7 +4296,7 @@ snapshots:
|
||||
'@push.rocks/smartfeed': 1.4.0
|
||||
'@push.rocks/smartfile': 11.2.7
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartlog-destination-devtools': 1.0.12
|
||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||
'@push.rocks/smartmanifest': 2.0.2
|
||||
@@ -4374,7 +4345,7 @@ snapshots:
|
||||
'@push.rocks/smartfile': 13.1.2
|
||||
'@push.rocks/smartfs': 1.3.1
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartlog-destination-devtools': 1.0.12
|
||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||
'@push.rocks/smartmanifest': 2.0.2
|
||||
@@ -4441,7 +4412,7 @@ snapshots:
|
||||
'@apiclient.xyz/cloudflare@7.1.0':
|
||||
dependencies:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrequest': 5.0.1
|
||||
'@push.rocks/smartstring': 4.1.0
|
||||
@@ -5175,7 +5146,7 @@ snapshots:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartfile': 13.1.2
|
||||
'@push.rocks/smartfs': 1.3.1
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
typescript: 5.9.3
|
||||
@@ -5196,7 +5167,7 @@ snapshots:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartfs': 1.3.1
|
||||
'@push.rocks/smartinteract': 2.0.16
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartlog-destination-local': 9.0.2
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
@@ -5222,7 +5193,7 @@ snapshots:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartfile': 13.1.2
|
||||
'@push.rocks/smartfs': 1.3.1
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartnpm': 2.0.6
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartrequest': 5.0.1
|
||||
@@ -5257,7 +5228,7 @@ snapshots:
|
||||
'@push.rocks/smartexpect': 2.5.0
|
||||
'@push.rocks/smartfile': 11.2.7
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
||||
'@push.rocks/smartnetwork': 4.4.0
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
@@ -5303,7 +5274,7 @@ snapshots:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartfs': 1.3.1
|
||||
'@push.rocks/smartinteract': 2.0.16
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartlog-destination-local': 9.0.2
|
||||
'@push.rocks/smartshell': 3.3.0
|
||||
'@push.rocks/smartwatch': 6.3.0
|
||||
@@ -5534,8 +5505,6 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@opentelemetry/api@1.9.0': {}
|
||||
|
||||
'@oxc-project/types@0.99.0': {}
|
||||
|
||||
'@pdf-lib/standard-fonts@1.0.0':
|
||||
@@ -5738,7 +5707,7 @@ snapshots:
|
||||
'@push.rocks/qenv': 6.1.3
|
||||
'@push.rocks/smartfile': 11.2.7
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
@@ -5762,7 +5731,7 @@ snapshots:
|
||||
'@api.global/typedrequest': 3.2.6
|
||||
'@configvault.io/interfaces': 1.0.17
|
||||
'@push.rocks/smartfile': 11.2.7
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
|
||||
'@push.rocks/smartacme@9.1.3(socks@2.8.7)':
|
||||
@@ -5772,8 +5741,8 @@ snapshots:
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdata': 7.0.15(socks@2.8.7)
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartdns': 7.8.1
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartdns': 7.9.0
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartnetwork': 4.4.0
|
||||
'@push.rocks/smartstring': 4.1.0
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
@@ -5883,7 +5852,7 @@ snapshots:
|
||||
'@push.rocks/smartcli@4.0.20':
|
||||
dependencies:
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartobject': 1.0.12
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
@@ -5908,7 +5877,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
@@ -5937,7 +5906,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
@@ -5966,7 +5935,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
|
||||
'@push.rocks/smartdns@7.8.1':
|
||||
'@push.rocks/smartdns@7.9.0':
|
||||
dependencies:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartenv': 6.0.0
|
||||
@@ -5974,7 +5943,7 @@ snapshots:
|
||||
'@push.rocks/smartrust': 1.2.1
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
acme-client: 5.4.0
|
||||
minimatch: 10.2.0
|
||||
minimatch: 10.2.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -6129,7 +6098,7 @@ snapshots:
|
||||
'@api.global/typedrequest-interfaces': 2.0.2
|
||||
'@tsclass/tsclass': 4.4.4
|
||||
|
||||
'@push.rocks/smartlog@3.1.11':
|
||||
'@push.rocks/smartlog@3.2.1':
|
||||
dependencies:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@push.rocks/consolecolor': 2.0.3
|
||||
@@ -6138,13 +6107,13 @@ snapshots:
|
||||
'@push.rocks/smartfile': 11.2.7
|
||||
'@push.rocks/smarthash': 3.2.6
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
'@push.rocks/webrequest': 4.0.1
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
'@push.rocks/webrequest': 4.0.2
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
|
||||
'@push.rocks/smartmail@2.2.0':
|
||||
dependencies:
|
||||
'@push.rocks/smartdns': 7.8.1
|
||||
'@push.rocks/smartdns': 7.9.0
|
||||
'@push.rocks/smartfile': 13.1.2
|
||||
'@push.rocks/smartmustache': 3.0.2
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
@@ -6173,14 +6142,10 @@ snapshots:
|
||||
dependencies:
|
||||
matcher: 5.0.0
|
||||
|
||||
'@push.rocks/smartmetrics@2.0.10':
|
||||
'@push.rocks/smartmetrics@3.0.1':
|
||||
dependencies:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@types/pidusage': 2.0.5
|
||||
pidtree: 0.6.0
|
||||
pidusage: 4.0.1
|
||||
prom-client: 15.1.3
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
|
||||
'@push.rocks/smartmime@1.0.6':
|
||||
dependencies:
|
||||
@@ -6249,7 +6214,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@push.rocks/smartfile': 13.1.2
|
||||
'@push.rocks/smartfs': 1.3.1
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartmail': 2.2.0
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
'@push.rocks/smartrust': 1.2.1
|
||||
@@ -6266,7 +6231,7 @@ snapshots:
|
||||
|
||||
'@push.rocks/smartnetwork@4.4.0':
|
||||
dependencies:
|
||||
'@push.rocks/smartdns': 7.8.1
|
||||
'@push.rocks/smartdns': 7.9.0
|
||||
'@push.rocks/smartping': 1.0.8
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartstring': 4.1.0
|
||||
@@ -6352,13 +6317,13 @@ snapshots:
|
||||
|
||||
'@push.rocks/smartpromise@4.2.3': {}
|
||||
|
||||
'@push.rocks/smartproxy@25.7.3':
|
||||
'@push.rocks/smartproxy@25.7.8':
|
||||
dependencies:
|
||||
'@push.rocks/smartcrypto': 2.0.4
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartrust': 1.2.1
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
minimatch: 10.2.0
|
||||
minimatch: 10.2.1
|
||||
|
||||
'@push.rocks/smartpuppeteer@2.0.5(typescript@5.9.3)':
|
||||
dependencies:
|
||||
@@ -6436,7 +6401,7 @@ snapshots:
|
||||
'@cfworker/json-schema': 4.1.1
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartenv': 6.0.0
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
ws: 8.19.0
|
||||
transitivePeerDependencies:
|
||||
@@ -6471,7 +6436,7 @@ snapshots:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartenv': 5.0.13
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
@@ -6586,7 +6551,7 @@ snapshots:
|
||||
'@design.estate/dees-element': 2.1.6
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
@@ -6602,7 +6567,7 @@ snapshots:
|
||||
'@design.estate/dees-element': 2.1.6
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smarttime': 4.1.1
|
||||
@@ -6618,7 +6583,7 @@ snapshots:
|
||||
'@design.estate/dees-element': 2.1.6
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartlog': 3.1.11
|
||||
'@push.rocks/smartlog': 3.2.1
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smarttime': 4.2.3
|
||||
@@ -6637,14 +6602,6 @@ snapshots:
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/webstore': 2.0.20
|
||||
|
||||
'@push.rocks/webrequest@4.0.1':
|
||||
dependencies:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
'@push.rocks/smartenv': 5.0.13
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/webstore': 2.0.20
|
||||
|
||||
'@push.rocks/webrequest@4.0.2':
|
||||
dependencies:
|
||||
'@push.rocks/smartdelay': 3.0.5
|
||||
@@ -6830,7 +6787,7 @@ snapshots:
|
||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||
'@tsclass/tsclass': 9.3.0
|
||||
|
||||
'@serve.zone/remoteingress@3.3.0':
|
||||
'@serve.zone/remoteingress@4.0.0':
|
||||
dependencies:
|
||||
'@push.rocks/qenv': 6.1.3
|
||||
'@push.rocks/smartrust': 1.2.1
|
||||
@@ -7362,22 +7319,22 @@ snapshots:
|
||||
'@types/body-parser@1.19.6':
|
||||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/buffer-json@2.0.3': {}
|
||||
|
||||
'@types/clean-css@4.2.11':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
source-map: 0.6.1
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/cors@2.8.19':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
dependencies:
|
||||
@@ -7385,7 +7342,7 @@ snapshots:
|
||||
|
||||
'@types/express-serve-static-core@5.1.1':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
'@types/qs': 6.14.0
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 1.2.1
|
||||
@@ -7398,17 +7355,17 @@ snapshots:
|
||||
|
||||
'@types/from2@2.3.6':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/fs-extra@11.0.4':
|
||||
dependencies:
|
||||
'@types/jsonfile': 6.1.4
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/glob@8.1.0':
|
||||
dependencies:
|
||||
'@types/minimatch': 5.1.2
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
dependencies:
|
||||
@@ -7430,12 +7387,12 @@ snapshots:
|
||||
|
||||
'@types/jsonfile@6.1.4':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/jsonwebtoken@9.0.10':
|
||||
dependencies:
|
||||
'@types/ms': 2.1.0
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/linkify-it@5.0.0': {}
|
||||
|
||||
@@ -7458,16 +7415,16 @@ snapshots:
|
||||
|
||||
'@types/mute-stream@0.0.4':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/node-fetch@2.6.13':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
form-data: 4.0.5
|
||||
|
||||
'@types/node-forge@1.3.14':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/node@18.19.130':
|
||||
dependencies:
|
||||
@@ -7477,11 +7434,9 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/node@25.2.3':
|
||||
'@types/node@25.3.0':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/pidusage@2.0.5': {}
|
||||
undici-types: 7.18.2
|
||||
|
||||
'@types/ping@0.4.4': {}
|
||||
|
||||
@@ -7497,22 +7452,22 @@ snapshots:
|
||||
|
||||
'@types/send@1.2.1':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/serve-static@2.2.0':
|
||||
dependencies:
|
||||
'@types/http-errors': 2.0.5
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/symbol-tree@3.2.5': {}
|
||||
|
||||
'@types/tar-stream@3.1.4':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/through2@2.0.41':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/trusted-types@2.0.7': {}
|
||||
|
||||
@@ -7542,11 +7497,11 @@ snapshots:
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
optional: true
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
@@ -7692,8 +7647,6 @@ snapshots:
|
||||
|
||||
basic-ftp@5.1.0: {}
|
||||
|
||||
bintrees@1.0.2: {}
|
||||
|
||||
body-parser@2.2.2:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
@@ -8019,7 +7972,7 @@ snapshots:
|
||||
engine.io@6.6.4:
|
||||
dependencies:
|
||||
'@types/cors': 2.8.19
|
||||
'@types/node': 25.2.3
|
||||
'@types/node': 25.3.0
|
||||
accepts: 1.3.8
|
||||
base64id: 2.0.0
|
||||
cookie: 0.7.2
|
||||
@@ -9163,7 +9116,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@isaacs/brace-expansion': 5.0.1
|
||||
|
||||
minimatch@10.2.0:
|
||||
minimatch@10.2.1:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.2
|
||||
|
||||
@@ -9439,12 +9392,6 @@ snapshots:
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pidtree@0.6.0: {}
|
||||
|
||||
pidusage@4.0.1:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
ping@0.4.4: {}
|
||||
|
||||
pkg-dir@4.2.0:
|
||||
@@ -9459,11 +9406,6 @@ snapshots:
|
||||
|
||||
progress@2.0.3: {}
|
||||
|
||||
prom-client@15.1.3:
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
tdigest: 0.1.2
|
||||
|
||||
property-information@7.1.0: {}
|
||||
|
||||
prosemirror-changeset@2.4.0:
|
||||
@@ -10050,10 +9992,6 @@ snapshots:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
tdigest@0.1.2:
|
||||
dependencies:
|
||||
bintrees: 1.0.2
|
||||
|
||||
text-decoder@1.2.3:
|
||||
dependencies:
|
||||
b4a: 1.7.3
|
||||
@@ -10152,7 +10090,7 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
undici-types@7.18.2: {}
|
||||
|
||||
unified@11.0.5:
|
||||
dependencies:
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '6.13.1',
|
||||
version: '7.3.0',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -106,6 +106,13 @@ export class CertProvisionScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all in-memory backoff cache entries
|
||||
*/
|
||||
public clear(): void {
|
||||
this.backoffCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backoff info for UI display
|
||||
*/
|
||||
|
||||
@@ -534,6 +534,12 @@ export class DcRouter {
|
||||
|
||||
// If we have DNS challenge handlers, create SmartAcme and wire to certProvisionFunction
|
||||
if (challengeHandlers.length > 0) {
|
||||
// Stop old SmartAcme if it exists (e.g., during updateSmartProxyConfig)
|
||||
if (this.smartAcme) {
|
||||
await this.smartAcme.stop().catch(err =>
|
||||
console.error('[DcRouter] Error stopping old SmartAcme:', err)
|
||||
);
|
||||
}
|
||||
this.smartAcme = new plugins.smartacme.SmartAcme({
|
||||
accountEmail: acmeConfig?.accountEmail || this.options.tls?.contactEmail || 'admin@example.com',
|
||||
certManager: new StorageBackedCertManager(this.storageManager),
|
||||
@@ -944,6 +950,25 @@ export class DcRouter {
|
||||
await this.cacheDb.stop().catch(err => console.error('Error stopping CacheDb:', err));
|
||||
}
|
||||
|
||||
// Clear backoff cache in cert scheduler
|
||||
if (this.certProvisionScheduler) {
|
||||
this.certProvisionScheduler.clear();
|
||||
}
|
||||
|
||||
// Allow GC of stopped services by nulling references
|
||||
this.smartProxy = undefined;
|
||||
this.emailServer = undefined;
|
||||
this.dnsServer = undefined;
|
||||
this.metricsManager = undefined;
|
||||
this.cacheCleaner = undefined;
|
||||
this.cacheDb = undefined;
|
||||
this.tunnelManager = undefined;
|
||||
this.radiusServer = undefined;
|
||||
this.smartAcme = undefined;
|
||||
this.certProvisionScheduler = undefined;
|
||||
this.remoteIngressManager = undefined;
|
||||
this.certificateStatusMap.clear();
|
||||
|
||||
console.log('All DcRouter services stopped');
|
||||
} catch (error) {
|
||||
console.error('Error during DcRouter shutdown:', error);
|
||||
@@ -1030,7 +1055,25 @@ export class DcRouter {
|
||||
|
||||
// Start the server
|
||||
await this.emailServer.start();
|
||||
|
||||
|
||||
// Wire delivery events to MetricsManager for time-series tracking
|
||||
if (this.metricsManager && this.emailServer.deliverySystem) {
|
||||
this.emailServer.deliverySystem.on('deliveryStart', (item: any) => {
|
||||
this.metricsManager.trackEmailReceived(item?.from);
|
||||
});
|
||||
this.emailServer.deliverySystem.on('deliverySuccess', (item: any) => {
|
||||
this.metricsManager.trackEmailSent(item?.to);
|
||||
});
|
||||
this.emailServer.deliverySystem.on('deliveryFailed', (item: any, error: any) => {
|
||||
this.metricsManager.trackEmailFailed(item?.to, error?.message);
|
||||
});
|
||||
}
|
||||
if (this.metricsManager && this.emailServer) {
|
||||
this.emailServer.on('bounceProcessed', () => {
|
||||
this.metricsManager.trackEmailBounced();
|
||||
});
|
||||
}
|
||||
|
||||
logger.log('info', `Email server started on ports: ${emailConfig.ports.join(', ')}`);
|
||||
}
|
||||
|
||||
@@ -1213,6 +1256,20 @@ export class DcRouter {
|
||||
// Start the DNS server (UDP only)
|
||||
await this.dnsServer.start();
|
||||
logger.log('info', `DNS server started on UDP ${vmIpAddress}:53`);
|
||||
|
||||
// Wire DNS query events to MetricsManager for time-series tracking
|
||||
if (this.metricsManager && this.dnsServer) {
|
||||
this.dnsServer.on('query', (event: plugins.smartdns.dnsServerMod.IDnsQueryCompletedEvent) => {
|
||||
for (const question of event.questions) {
|
||||
this.metricsManager.trackDnsQuery(
|
||||
question.type,
|
||||
question.name,
|
||||
false,
|
||||
event.responseTimeMs,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Validate DNS configuration
|
||||
await this.validateDnsConfiguration();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { SmartlogDestinationBuffer } from '@push.rocks/smartlog/destination-buffer';
|
||||
|
||||
// Map NODE_ENV to valid TEnvironment
|
||||
const nodeEnv = process.env.NODE_ENV || 'production';
|
||||
@@ -10,6 +11,9 @@ const envMap: Record<string, 'local' | 'test' | 'staging' | 'production'> = {
|
||||
'production': 'production'
|
||||
};
|
||||
|
||||
// In-memory log buffer for the OpsServer UI
|
||||
export const logBuffer = new SmartlogDestinationBuffer({ maxEntries: 2000 });
|
||||
|
||||
// Default Smartlog instance
|
||||
const baseLogger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
@@ -19,6 +23,9 @@ const baseLogger = new plugins.smartlog.Smartlog({
|
||||
}
|
||||
});
|
||||
|
||||
// Wire the buffer destination so all logs are captured
|
||||
baseLogger.addLogDestination(logBuffer);
|
||||
|
||||
// Extended logger compatible with the original enhanced logger API
|
||||
class StandardLogger {
|
||||
private defaultContext: Record<string, any> = {};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { DcRouter } from '../classes.dcrouter.js';
|
||||
import { MetricsCache } from './classes.metricscache.js';
|
||||
import { SecurityLogger, SecurityEventType } from '../security/classes.securitylogger.js';
|
||||
|
||||
export class MetricsManager {
|
||||
private logger: plugins.smartlog.Smartlog;
|
||||
@@ -37,6 +38,10 @@ export class MetricsManager {
|
||||
responseTimes: [] as number[], // Track response times in ms
|
||||
};
|
||||
|
||||
// Per-minute time-series buckets for charts
|
||||
private emailMinuteBuckets = new Map<number, { sent: number; received: number; failed: number }>();
|
||||
private dnsMinuteBuckets = new Map<number, { queries: number }>();
|
||||
|
||||
// Track security-specific metrics
|
||||
private securityMetrics = {
|
||||
blockedIPs: 0,
|
||||
@@ -139,8 +144,8 @@ export class MetricsManager {
|
||||
actualUsagePercentage: smartMetricsData.memoryPercentage,
|
||||
},
|
||||
cpuUsage: {
|
||||
user: parseFloat(smartMetricsData.cpuUsageText || '0'),
|
||||
system: 0, // SmartMetrics doesn't separate user/system
|
||||
user: smartMetricsData.cpuPercentage,
|
||||
system: 0,
|
||||
},
|
||||
activeConnections: proxyStats ? proxyStats.activeConnections : 0,
|
||||
totalConnections: proxyMetrics ? proxyMetrics.totals.connections() : 0,
|
||||
@@ -227,20 +232,45 @@ export class MetricsManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync security metrics from the SecurityLogger singleton (last 24h).
|
||||
* Called before returning security stats so counters reflect real events.
|
||||
*/
|
||||
private syncFromSecurityLogger(): void {
|
||||
try {
|
||||
const securityLogger = SecurityLogger.getInstance();
|
||||
const summary = securityLogger.getEventsSummary(86400000); // last 24h
|
||||
|
||||
this.securityMetrics.spamDetected = summary.byType[SecurityEventType.SPAM] || 0;
|
||||
this.securityMetrics.malwareDetected = summary.byType[SecurityEventType.MALWARE] || 0;
|
||||
this.securityMetrics.phishingDetected = summary.byType[SecurityEventType.DMARC] || 0; // phishing via DMARC
|
||||
this.securityMetrics.authFailures =
|
||||
summary.byType[SecurityEventType.AUTHENTICATION] || 0;
|
||||
this.securityMetrics.blockedIPs =
|
||||
(summary.byType[SecurityEventType.IP_REPUTATION] || 0) +
|
||||
(summary.byType[SecurityEventType.REJECTED_CONNECTION] || 0);
|
||||
} catch {
|
||||
// SecurityLogger may not be initialized yet — ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Get security metrics
|
||||
public async getSecurityStats() {
|
||||
return this.metricsCache.get('securityStats', () => {
|
||||
// Sync counters from the real SecurityLogger events
|
||||
this.syncFromSecurityLogger();
|
||||
|
||||
// Get recent incidents (last 20)
|
||||
const recentIncidents = this.securityMetrics.incidents.slice(-20);
|
||||
|
||||
|
||||
return {
|
||||
blockedIPs: this.securityMetrics.blockedIPs,
|
||||
authFailures: this.securityMetrics.authFailures,
|
||||
spamDetected: this.securityMetrics.spamDetected,
|
||||
malwareDetected: this.securityMetrics.malwareDetected,
|
||||
phishingDetected: this.securityMetrics.phishingDetected,
|
||||
totalThreatsBlocked: this.securityMetrics.spamDetected +
|
||||
this.securityMetrics.malwareDetected +
|
||||
totalThreatsBlocked: this.securityMetrics.spamDetected +
|
||||
this.securityMetrics.malwareDetected +
|
||||
this.securityMetrics.phishingDetected,
|
||||
recentIncidents,
|
||||
};
|
||||
@@ -275,10 +305,19 @@ export class MetricsManager {
|
||||
// Email event tracking methods
|
||||
public trackEmailSent(recipient?: string, deliveryTimeMs?: number): void {
|
||||
this.emailMetrics.sentToday++;
|
||||
this.incrementEmailBucket('sent');
|
||||
|
||||
if (recipient) {
|
||||
const count = this.emailMetrics.recipients.get(recipient) || 0;
|
||||
this.emailMetrics.recipients.set(recipient, count + 1);
|
||||
|
||||
// Cap recipients map to prevent unbounded growth within a day
|
||||
if (this.emailMetrics.recipients.size > this.MAX_TOP_DOMAINS) {
|
||||
const sorted = Array.from(this.emailMetrics.recipients.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, Math.floor(this.MAX_TOP_DOMAINS * 0.8));
|
||||
this.emailMetrics.recipients = new Map(sorted);
|
||||
}
|
||||
}
|
||||
|
||||
if (deliveryTimeMs) {
|
||||
@@ -303,6 +342,7 @@ export class MetricsManager {
|
||||
|
||||
public trackEmailReceived(sender?: string): void {
|
||||
this.emailMetrics.receivedToday++;
|
||||
this.incrementEmailBucket('received');
|
||||
|
||||
this.emailMetrics.recentActivity.push({
|
||||
timestamp: Date.now(),
|
||||
@@ -318,6 +358,7 @@ export class MetricsManager {
|
||||
|
||||
public trackEmailFailed(recipient?: string, reason?: string): void {
|
||||
this.emailMetrics.failedToday++;
|
||||
this.incrementEmailBucket('failed');
|
||||
|
||||
this.emailMetrics.recentActivity.push({
|
||||
timestamp: Date.now(),
|
||||
@@ -353,6 +394,7 @@ export class MetricsManager {
|
||||
// DNS event tracking methods
|
||||
public trackDnsQuery(queryType: string, domain: string, cacheHit: boolean, responseTimeMs?: number): void {
|
||||
this.dnsMetrics.totalQueries++;
|
||||
this.incrementDnsBucket();
|
||||
|
||||
if (cacheHit) {
|
||||
this.dnsMetrics.cacheHits++;
|
||||
@@ -539,4 +581,90 @@ export class MetricsManager {
|
||||
};
|
||||
}, 200); // Use 200ms cache for more frequent updates
|
||||
}
|
||||
|
||||
// --- Time-series helpers ---
|
||||
|
||||
private static minuteKey(ts: number = Date.now()): number {
|
||||
return Math.floor(ts / 60000) * 60000;
|
||||
}
|
||||
|
||||
private incrementEmailBucket(field: 'sent' | 'received' | 'failed'): void {
|
||||
const key = MetricsManager.minuteKey();
|
||||
let bucket = this.emailMinuteBuckets.get(key);
|
||||
if (!bucket) {
|
||||
bucket = { sent: 0, received: 0, failed: 0 };
|
||||
this.emailMinuteBuckets.set(key, bucket);
|
||||
}
|
||||
bucket[field]++;
|
||||
}
|
||||
|
||||
private incrementDnsBucket(): void {
|
||||
const key = MetricsManager.minuteKey();
|
||||
let bucket = this.dnsMinuteBuckets.get(key);
|
||||
if (!bucket) {
|
||||
bucket = { queries: 0 };
|
||||
this.dnsMinuteBuckets.set(key, bucket);
|
||||
}
|
||||
bucket.queries++;
|
||||
}
|
||||
|
||||
private pruneOldBuckets(): void {
|
||||
const cutoff = Date.now() - 86400000; // 24h
|
||||
for (const key of this.emailMinuteBuckets.keys()) {
|
||||
if (key < cutoff) this.emailMinuteBuckets.delete(key);
|
||||
}
|
||||
for (const key of this.dnsMinuteBuckets.keys()) {
|
||||
if (key < cutoff) this.dnsMinuteBuckets.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email time-series data for the last N hours, aggregated per minute.
|
||||
*/
|
||||
public getEmailTimeSeries(hours: number = 24): {
|
||||
sent: Array<{ timestamp: number; value: number }>;
|
||||
received: Array<{ timestamp: number; value: number }>;
|
||||
failed: Array<{ timestamp: number; value: number }>;
|
||||
} {
|
||||
this.pruneOldBuckets();
|
||||
const cutoff = Date.now() - hours * 3600000;
|
||||
const sent: Array<{ timestamp: number; value: number }> = [];
|
||||
const received: Array<{ timestamp: number; value: number }> = [];
|
||||
const failed: Array<{ timestamp: number; value: number }> = [];
|
||||
|
||||
const sortedKeys = Array.from(this.emailMinuteBuckets.keys())
|
||||
.filter((k) => k >= cutoff)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
for (const key of sortedKeys) {
|
||||
const bucket = this.emailMinuteBuckets.get(key)!;
|
||||
sent.push({ timestamp: key, value: bucket.sent });
|
||||
received.push({ timestamp: key, value: bucket.received });
|
||||
failed.push({ timestamp: key, value: bucket.failed });
|
||||
}
|
||||
|
||||
return { sent, received, failed };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DNS time-series data for the last N hours, aggregated per minute.
|
||||
*/
|
||||
public getDnsTimeSeries(hours: number = 24): {
|
||||
queries: Array<{ timestamp: number; value: number }>;
|
||||
} {
|
||||
this.pruneOldBuckets();
|
||||
const cutoff = Date.now() - hours * 3600000;
|
||||
const queries: Array<{ timestamp: number; value: number }> = [];
|
||||
|
||||
const sortedKeys = Array.from(this.dnsMinuteBuckets.keys())
|
||||
.filter((k) => k >= cutoff)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
for (const key of sortedKeys) {
|
||||
const bucket = this.dnsMinuteBuckets.get(key)!;
|
||||
queries.push({ timestamp: key, value: bucket.queries });
|
||||
}
|
||||
|
||||
return { queries };
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
import { logBuffer } from '../../logger.js';
|
||||
|
||||
export class LogsHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
@@ -64,6 +65,32 @@ export class LogsHandler {
|
||||
);
|
||||
}
|
||||
|
||||
private static mapLogLevel(smartlogLevel: string): 'debug' | 'info' | 'warn' | 'error' {
|
||||
switch (smartlogLevel) {
|
||||
case 'silly':
|
||||
case 'debug':
|
||||
return 'debug';
|
||||
case 'warn':
|
||||
return 'warn';
|
||||
case 'error':
|
||||
return 'error';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
private static deriveCategory(
|
||||
zone?: string,
|
||||
message?: string
|
||||
): 'smtp' | 'dns' | 'security' | 'system' | 'email' {
|
||||
const msg = (message || '').toLowerCase();
|
||||
if (msg.includes('[security:') || msg.includes('security')) return 'security';
|
||||
if (zone === 'email' || msg.includes('email') || msg.includes('smtp') || msg.includes('mta')) return 'email';
|
||||
if (zone === 'dns' || msg.includes('dns')) return 'dns';
|
||||
if (msg.includes('smtp')) return 'smtp';
|
||||
return 'system';
|
||||
}
|
||||
|
||||
private async getRecentLogs(
|
||||
level?: 'error' | 'warn' | 'info' | 'debug',
|
||||
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email',
|
||||
@@ -78,42 +105,64 @@ export class LogsHandler {
|
||||
message: string;
|
||||
metadata?: any;
|
||||
}>> {
|
||||
// TODO: Implement actual log retrieval from storage or logger
|
||||
// For now, return mock data
|
||||
const mockLogs: Array<{
|
||||
// Compute a timestamp cutoff from timeRange
|
||||
let since: number | undefined;
|
||||
if (timeRange) {
|
||||
const rangeMs: Record<string, number> = {
|
||||
'1h': 3600000,
|
||||
'6h': 21600000,
|
||||
'24h': 86400000,
|
||||
'7d': 604800000,
|
||||
'30d': 2592000000,
|
||||
};
|
||||
since = Date.now() - (rangeMs[timeRange] || 86400000);
|
||||
}
|
||||
|
||||
// Map the UI level to smartlog levels for filtering
|
||||
const smartlogLevels: string[] | undefined = level
|
||||
? level === 'debug'
|
||||
? ['debug', 'silly']
|
||||
: level === 'info'
|
||||
? ['info', 'ok', 'success', 'note', 'lifecycle']
|
||||
: [level]
|
||||
: undefined;
|
||||
|
||||
// Fetch a larger batch from buffer, then apply category filter client-side
|
||||
const rawEntries = logBuffer.getEntries({
|
||||
level: smartlogLevels as any,
|
||||
search,
|
||||
since,
|
||||
limit: limit * 3, // over-fetch to compensate for category filtering
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
// Map ILogPackage → UI log format and apply category filter
|
||||
const mapped: Array<{
|
||||
timestamp: number;
|
||||
level: 'debug' | 'info' | 'warn' | 'error';
|
||||
category: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
||||
message: string;
|
||||
metadata?: any;
|
||||
}> = [];
|
||||
|
||||
const categories: Array<'smtp' | 'dns' | 'security' | 'system' | 'email'> = ['smtp', 'dns', 'security', 'system', 'email'];
|
||||
const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['info', 'warn', 'error', 'debug'];
|
||||
const now = Date.now();
|
||||
|
||||
// Generate some mock log entries
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const mockCategory = categories[Math.floor(Math.random() * categories.length)];
|
||||
const mockLevel = levels[Math.floor(Math.random() * levels.length)];
|
||||
|
||||
// Filter by requested criteria
|
||||
if (level && mockLevel !== level) continue;
|
||||
if (category && mockCategory !== category) continue;
|
||||
|
||||
mockLogs.push({
|
||||
timestamp: now - (i * 60000), // 1 minute apart
|
||||
level: mockLevel,
|
||||
category: mockCategory,
|
||||
message: `Sample log message ${i} from ${mockCategory}`,
|
||||
metadata: {
|
||||
requestId: plugins.uuid.v4(),
|
||||
},
|
||||
|
||||
for (const pkg of rawEntries) {
|
||||
const uiLevel = LogsHandler.mapLogLevel(pkg.level);
|
||||
const uiCategory = LogsHandler.deriveCategory(pkg.context?.zone, pkg.message);
|
||||
|
||||
if (category && uiCategory !== category) continue;
|
||||
|
||||
mapped.push({
|
||||
timestamp: pkg.timestamp,
|
||||
level: uiLevel,
|
||||
category: uiCategory,
|
||||
message: pkg.message,
|
||||
metadata: pkg.data,
|
||||
});
|
||||
|
||||
if (mapped.length >= limit) break;
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
return mockLogs.slice(offset, offset + limit);
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
private setupLogStream(
|
||||
@@ -148,17 +197,17 @@ export class LogsHandler {
|
||||
}
|
||||
|
||||
// For follow mode, simulate real-time log streaming
|
||||
intervalId = setInterval(() => {
|
||||
intervalId = setInterval(async () => {
|
||||
const categories: Array<'smtp' | 'dns' | 'security' | 'system' | 'email'> = ['smtp', 'dns', 'security', 'system', 'email'];
|
||||
const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['info', 'warn', 'error', 'debug'];
|
||||
|
||||
|
||||
const mockCategory = categories[Math.floor(Math.random() * categories.length)];
|
||||
const mockLevel = levels[Math.floor(Math.random() * levels.length)];
|
||||
|
||||
|
||||
// Filter by requested criteria
|
||||
if (levelFilter && !levelFilter.includes(mockLevel)) return;
|
||||
if (categoryFilter && !categoryFilter.includes(mockCategory)) return;
|
||||
|
||||
|
||||
const logEntry = {
|
||||
timestamp: Date.now(),
|
||||
level: mockLevel,
|
||||
@@ -168,10 +217,16 @@ export class LogsHandler {
|
||||
requestId: plugins.uuid.v4(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const logData = JSON.stringify(logEntry);
|
||||
const encoder = new TextEncoder();
|
||||
virtualStream.sendData(encoder.encode(logData));
|
||||
try {
|
||||
await virtualStream.sendData(encoder.encode(logData));
|
||||
} catch {
|
||||
// Stream closed or errored — clean up to prevent interval leak
|
||||
clearInterval(intervalId!);
|
||||
intervalId = null;
|
||||
}
|
||||
}, 2000); // Send a log every 2 seconds
|
||||
|
||||
// TODO: Hook into actual logger events
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
|
||||
import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
import { MetricsManager } from '../../monitoring/index.js';
|
||||
import { SecurityLogger } from '../../security/classes.securitylogger.js';
|
||||
|
||||
export class StatsHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
@@ -203,6 +204,11 @@ export class StatsHandler {
|
||||
if (sections.email) {
|
||||
promises.push(
|
||||
this.collectEmailStats().then(stats => {
|
||||
// Get time-series data from MetricsManager
|
||||
const timeSeries = this.opsServerRef.dcRouterRef.metricsManager
|
||||
? this.opsServerRef.dcRouterRef.metricsManager.getEmailTimeSeries(24)
|
||||
: undefined;
|
||||
|
||||
metrics.email = {
|
||||
sent: stats.sentToday,
|
||||
received: stats.receivedToday,
|
||||
@@ -212,6 +218,7 @@ export class StatsHandler {
|
||||
averageDeliveryTime: 0,
|
||||
deliveryRate: stats.deliveryRate,
|
||||
bounceRate: stats.bounceRate,
|
||||
timeSeries,
|
||||
};
|
||||
})
|
||||
);
|
||||
@@ -220,6 +227,11 @@ export class StatsHandler {
|
||||
if (sections.dns) {
|
||||
promises.push(
|
||||
this.collectDnsStats().then(stats => {
|
||||
// Get time-series data from MetricsManager
|
||||
const timeSeries = this.opsServerRef.dcRouterRef.metricsManager
|
||||
? this.opsServerRef.dcRouterRef.metricsManager.getDnsTimeSeries(24)
|
||||
: undefined;
|
||||
|
||||
metrics.dns = {
|
||||
totalQueries: stats.totalQueries,
|
||||
cacheHits: stats.cacheHits,
|
||||
@@ -228,6 +240,7 @@ export class StatsHandler {
|
||||
activeDomains: stats.topDomains.length,
|
||||
averageResponseTime: 0,
|
||||
queryTypes: stats.queryTypes,
|
||||
timeSeries,
|
||||
};
|
||||
})
|
||||
);
|
||||
@@ -236,6 +249,19 @@ export class StatsHandler {
|
||||
if (sections.security && this.opsServerRef.dcRouterRef.metricsManager) {
|
||||
promises.push(
|
||||
this.opsServerRef.dcRouterRef.metricsManager.getSecurityStats().then(stats => {
|
||||
// Get recent events from the SecurityLogger singleton
|
||||
const securityLogger = SecurityLogger.getInstance();
|
||||
const recentEvents = securityLogger.getRecentEvents(50).map((evt) => ({
|
||||
timestamp: evt.timestamp,
|
||||
level: evt.level,
|
||||
type: evt.type,
|
||||
message: evt.message,
|
||||
details: evt.details,
|
||||
ipAddress: evt.ipAddress,
|
||||
domain: evt.domain,
|
||||
success: evt.success,
|
||||
}));
|
||||
|
||||
metrics.security = {
|
||||
blockedIPs: stats.blockedIPs,
|
||||
reputationScores: {},
|
||||
@@ -244,6 +270,7 @@ export class StatsHandler {
|
||||
phishingDetected: stats.phishingDetected,
|
||||
authenticationFailures: stats.authFailures,
|
||||
suspiciousActivities: stats.totalThreatsBlocked,
|
||||
recentEvents,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
@@ -100,6 +100,14 @@ export class VlanManager {
|
||||
// Cache the result
|
||||
this.normalizedMacCache.set(mac, normalized);
|
||||
|
||||
// Prevent unbounded cache growth
|
||||
if (this.normalizedMacCache.size > 10000) {
|
||||
const iterator = this.normalizedMacCache.keys();
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
this.normalizedMacCache.delete(iterator.next().value);
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ export interface IServerStats {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITimeSeriesPoint {
|
||||
timestamp: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface IEmailStats {
|
||||
sent: number;
|
||||
received: number;
|
||||
@@ -35,6 +40,11 @@ export interface IEmailStats {
|
||||
averageDeliveryTime: number;
|
||||
deliveryRate: number;
|
||||
bounceRate: number;
|
||||
timeSeries?: {
|
||||
sent: ITimeSeriesPoint[];
|
||||
received: ITimeSeriesPoint[];
|
||||
failed: ITimeSeriesPoint[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IDnsStats {
|
||||
@@ -47,6 +57,9 @@ export interface IDnsStats {
|
||||
queryTypes: {
|
||||
[key: string]: number;
|
||||
};
|
||||
timeSeries?: {
|
||||
queries: ITimeSeriesPoint[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IRateLimitInfo {
|
||||
@@ -58,6 +71,17 @@ export interface IRateLimitInfo {
|
||||
blocked: boolean;
|
||||
}
|
||||
|
||||
export interface ISecurityEvent {
|
||||
timestamp: number;
|
||||
level: string;
|
||||
type: string;
|
||||
message: string;
|
||||
details?: any;
|
||||
ipAddress?: string;
|
||||
domain?: string;
|
||||
success?: boolean;
|
||||
}
|
||||
|
||||
export interface ISecurityMetrics {
|
||||
blockedIPs: string[];
|
||||
reputationScores: {
|
||||
@@ -68,6 +92,7 @@ export interface ISecurityMetrics {
|
||||
phishingDetected: number;
|
||||
authenticationFailures: number;
|
||||
suspiciousActivities: number;
|
||||
recentEvents?: ISecurityEvent[];
|
||||
}
|
||||
|
||||
export interface ILogEntry {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '6.13.1',
|
||||
version: '7.3.0',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -20,6 +20,15 @@ export class OpsViewLogs extends DeesElement {
|
||||
filters: {},
|
||||
};
|
||||
|
||||
@state()
|
||||
accessor filterLevel: string | undefined;
|
||||
|
||||
@state()
|
||||
accessor filterCategory: string | undefined;
|
||||
|
||||
@state()
|
||||
accessor filterLimit: number = 100;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subscription = appstate.logStatePart
|
||||
@@ -46,87 +55,20 @@ export class OpsViewLogs extends DeesElement {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.logContainer {
|
||||
background: ${cssManager.bdTheme('#f8f9fa', '#1e1e1e')};
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.logEntry {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logTimestamp {
|
||||
color: ${cssManager.bdTheme('#7a7a7a', '#7a7a7a')};
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.logLevel {
|
||||
font-weight: bold;
|
||||
margin-right: 8px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.logLevel.debug {
|
||||
color: ${cssManager.bdTheme('#6a9955', '#6a9955')};
|
||||
background: ${cssManager.bdTheme('rgba(106, 153, 85, 0.1)', 'rgba(106, 153, 85, 0.1)')};
|
||||
}
|
||||
.logLevel.info {
|
||||
color: ${cssManager.bdTheme('#569cd6', '#569cd6')};
|
||||
background: ${cssManager.bdTheme('rgba(86, 156, 214, 0.1)', 'rgba(86, 156, 214, 0.1)')};
|
||||
}
|
||||
.logLevel.warn {
|
||||
color: ${cssManager.bdTheme('#ce9178', '#ce9178')};
|
||||
background: ${cssManager.bdTheme('rgba(206, 145, 120, 0.1)', 'rgba(206, 145, 120, 0.1)')};
|
||||
}
|
||||
.logLevel.error {
|
||||
color: ${cssManager.bdTheme('#f44747', '#f44747')};
|
||||
background: ${cssManager.bdTheme('rgba(244, 71, 71, 0.1)', 'rgba(244, 71, 71, 0.1)')};
|
||||
}
|
||||
|
||||
.logCategory {
|
||||
color: ${cssManager.bdTheme('#c586c0', '#c586c0')};
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.logMessage {
|
||||
color: ${cssManager.bdTheme('#333', '#d4d4d4')};
|
||||
}
|
||||
|
||||
.noLogs {
|
||||
color: ${cssManager.bdTheme('#7a7a7a', '#7a7a7a')};
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<ops-sectionheading>Logs</ops-sectionheading>
|
||||
|
||||
|
||||
<div class="controls">
|
||||
<div class="filterGroup">
|
||||
<dees-button
|
||||
<dees-button
|
||||
@click=${() => this.fetchLogs()}
|
||||
>
|
||||
Refresh Logs
|
||||
</dees-button>
|
||||
|
||||
<dees-button
|
||||
@click=${() => this.toggleStreaming()}
|
||||
.type=${this.logState.isStreaming ? 'highlighted' : 'normal'}
|
||||
>
|
||||
${this.logState.isStreaming ? 'Stop Streaming' : 'Start Streaming'}
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
<div class="filterGroup">
|
||||
@@ -134,7 +76,7 @@ export class OpsViewLogs extends DeesElement {
|
||||
<dees-input-dropdown
|
||||
.options=${['all', 'debug', 'info', 'warn', 'error']}
|
||||
.selectedOption=${'all'}
|
||||
@selectedOption=${(e) => this.updateFilter('level', e.detail)}
|
||||
@selectedOption=${(e: any) => this.updateFilter('level', e.detail)}
|
||||
></dees-input-dropdown>
|
||||
</div>
|
||||
|
||||
@@ -143,7 +85,7 @@ export class OpsViewLogs extends DeesElement {
|
||||
<dees-input-dropdown
|
||||
.options=${['all', 'smtp', 'dns', 'security', 'system', 'email']}
|
||||
.selectedOption=${'all'}
|
||||
@selectedOption=${(e) => this.updateFilter('category', e.detail)}
|
||||
@selectedOption=${(e: any) => this.updateFilter('category', e.detail)}
|
||||
></dees-input-dropdown>
|
||||
</div>
|
||||
|
||||
@@ -152,56 +94,78 @@ export class OpsViewLogs extends DeesElement {
|
||||
<dees-input-dropdown
|
||||
.options=${['50', '100', '200', '500']}
|
||||
.selectedOption=${'100'}
|
||||
@selectedOption=${(e) => this.updateFilter('limit', e.detail)}
|
||||
@selectedOption=${(e: any) => this.updateFilter('limit', e.detail)}
|
||||
></dees-input-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logContainer">
|
||||
${this.logState.recentLogs.length > 0 ?
|
||||
this.logState.recentLogs.map(log => html`
|
||||
<div class="logEntry">
|
||||
<span class="logTimestamp">${new Date(log.timestamp).toLocaleTimeString()}</span>
|
||||
<span class="logLevel ${log.level}">${log.level.toUpperCase()}</span>
|
||||
<span class="logCategory">[${log.category}]</span>
|
||||
<span class="logMessage">${log.message}</span>
|
||||
</div>
|
||||
`) : html`
|
||||
<div class="noLogs">No logs to display</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
<dees-chart-log
|
||||
.label=${'Application Logs'}
|
||||
.autoScroll=${true}
|
||||
.maxEntries=${2000}
|
||||
.showMetrics=${true}
|
||||
></dees-chart-log>
|
||||
`;
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.fetchLogs();
|
||||
}
|
||||
|
||||
async updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('logState')) {
|
||||
this.pushLogsToChart();
|
||||
}
|
||||
}
|
||||
|
||||
private async pushLogsToChart() {
|
||||
const chartLog = this.shadowRoot?.querySelector('dees-chart-log') as any;
|
||||
if (!chartLog) return;
|
||||
|
||||
// Ensure the chart element has finished its own initialization
|
||||
await chartLog.updateComplete;
|
||||
|
||||
chartLog.clearLogs();
|
||||
const entries = this.getMappedLogEntries();
|
||||
if (entries.length > 0) {
|
||||
chartLog.updateLog(entries);
|
||||
}
|
||||
}
|
||||
|
||||
private getMappedLogEntries() {
|
||||
return this.logState.recentLogs.map((log) => ({
|
||||
timestamp: new Date(log.timestamp).toISOString(),
|
||||
level: log.level as 'debug' | 'info' | 'warn' | 'error',
|
||||
message: log.message,
|
||||
source: log.category,
|
||||
}));
|
||||
}
|
||||
|
||||
private async fetchLogs() {
|
||||
const filters = this.getActiveFilters();
|
||||
await appstate.logStatePart.dispatchAction(appstate.fetchRecentLogsAction, {
|
||||
limit: filters.limit || 100,
|
||||
level: filters.level as 'debug' | 'info' | 'warn' | 'error' | undefined,
|
||||
category: filters.category as 'smtp' | 'dns' | 'security' | 'system' | 'email' | undefined,
|
||||
limit: this.filterLimit,
|
||||
level: this.filterLevel as 'debug' | 'info' | 'warn' | 'error' | undefined,
|
||||
category: this.filterCategory as 'smtp' | 'dns' | 'security' | 'system' | 'email' | undefined,
|
||||
});
|
||||
}
|
||||
|
||||
private updateFilter(type: string, value: string) {
|
||||
if (value === 'all') {
|
||||
value = undefined;
|
||||
const resolved = value === 'all' ? undefined : value;
|
||||
|
||||
switch (type) {
|
||||
case 'level':
|
||||
this.filterLevel = resolved;
|
||||
break;
|
||||
case 'category':
|
||||
this.filterCategory = resolved;
|
||||
break;
|
||||
case 'limit':
|
||||
this.filterLimit = resolved ? parseInt(resolved, 10) : 100;
|
||||
break;
|
||||
}
|
||||
|
||||
// Update filters then fetch logs
|
||||
|
||||
this.fetchLogs();
|
||||
}
|
||||
|
||||
private getActiveFilters() {
|
||||
return {
|
||||
level: this.logState.filters.level?.[0],
|
||||
category: this.logState.filters.category?.[0],
|
||||
limit: 100,
|
||||
};
|
||||
}
|
||||
|
||||
private toggleStreaming() {
|
||||
// TODO: Implement log streaming with VirtualStream
|
||||
console.log('Streaming toggle not yet implemented');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,14 +26,36 @@ export class OpsViewOverview extends DeesElement {
|
||||
error: null,
|
||||
};
|
||||
|
||||
@state()
|
||||
accessor logState: appstate.ILogState = {
|
||||
recentLogs: [],
|
||||
isStreaming: false,
|
||||
filters: {},
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subscription = appstate.statsStatePart
|
||||
const statsSub = appstate.statsStatePart
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((statsState) => {
|
||||
this.statsState = statsState;
|
||||
});
|
||||
this.rxSubscriptions.push(subscription);
|
||||
this.rxSubscriptions.push(statsSub);
|
||||
|
||||
const logSub = appstate.logStatePart
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((logState) => {
|
||||
this.logState = logState;
|
||||
});
|
||||
this.rxSubscriptions.push(logSub);
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Ensure logs are fetched for the overview charts
|
||||
if (this.logState.recentLogs.length === 0) {
|
||||
appstate.logStatePart.dispatchAction(appstate.fetchRecentLogsAction, { limit: 100 });
|
||||
}
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
@@ -96,10 +118,24 @@ export class OpsViewOverview extends DeesElement {
|
||||
${this.renderDnsStats()}
|
||||
|
||||
<div class="chartGrid">
|
||||
<dees-chart-area .label=${'Email Traffic (24h)'} .data=${[]}></dees-chart-area>
|
||||
<dees-chart-area .label=${'DNS Queries (24h)'} .data=${[]}></dees-chart-area>
|
||||
<dees-chart-log .label=${'Recent Events'} .data=${[]}></dees-chart-log>
|
||||
<dees-chart-log .label=${'Security Alerts'} .data=${[]}></dees-chart-log>
|
||||
<dees-chart-area
|
||||
.label=${'Email Traffic (24h)'}
|
||||
.series=${this.getEmailTrafficSeries()}
|
||||
.yAxisFormatter=${(val: number) => `${val}`}
|
||||
></dees-chart-area>
|
||||
<dees-chart-area
|
||||
.label=${'DNS Queries (24h)'}
|
||||
.series=${this.getDnsQuerySeries()}
|
||||
.yAxisFormatter=${(val: number) => `${val}`}
|
||||
></dees-chart-area>
|
||||
<dees-chart-log
|
||||
.label=${'Recent Events'}
|
||||
.logEntries=${this.getRecentEventEntries()}
|
||||
></dees-chart-log>
|
||||
<dees-chart-log
|
||||
.label=${'Security Alerts'}
|
||||
.logEntries=${this.getSecurityAlertEntries()}
|
||||
></dees-chart-log>
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
@@ -337,4 +373,42 @@ export class OpsViewOverview extends DeesElement {
|
||||
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
|
||||
`;
|
||||
}
|
||||
|
||||
// --- Chart data helpers ---
|
||||
|
||||
private getRecentEventEntries(): Array<{ timestamp: string; level: 'debug' | 'info' | 'warn' | 'error' | 'success'; message: string; source?: string }> {
|
||||
return this.logState.recentLogs.map((log) => ({
|
||||
timestamp: new Date(log.timestamp).toISOString(),
|
||||
level: log.level as 'debug' | 'info' | 'warn' | 'error',
|
||||
message: log.message,
|
||||
source: log.category,
|
||||
}));
|
||||
}
|
||||
|
||||
private getSecurityAlertEntries(): Array<{ timestamp: string; level: 'debug' | 'info' | 'warn' | 'error' | 'success'; message: string; source?: string }> {
|
||||
const events: any[] = this.statsState.securityMetrics?.recentEvents || [];
|
||||
return events.map((evt: any) => ({
|
||||
timestamp: new Date(evt.timestamp).toISOString(),
|
||||
level: evt.level === 'critical' || evt.level === 'error' ? 'error' as const : evt.level === 'warn' ? 'warn' as const : 'info' as const,
|
||||
message: evt.message,
|
||||
source: evt.type,
|
||||
}));
|
||||
}
|
||||
|
||||
private getEmailTrafficSeries(): Array<{ name: string; color: string; data: Array<{ x: number; y: number }> }> {
|
||||
const ts = this.statsState.emailStats?.timeSeries;
|
||||
if (!ts) return [];
|
||||
return [
|
||||
{ name: 'Sent', color: '#22c55e', data: (ts.sent || []).map((p: any) => ({ x: p.timestamp, y: p.value })) },
|
||||
{ name: 'Received', color: '#3b82f6', data: (ts.received || []).map((p: any) => ({ x: p.timestamp, y: p.value })) },
|
||||
];
|
||||
}
|
||||
|
||||
private getDnsQuerySeries(): Array<{ name: string; color: string; data: Array<{ x: number; y: number }> }> {
|
||||
const ts = this.statsState.dnsStats?.timeSeries;
|
||||
if (!ts) return [];
|
||||
return [
|
||||
{ name: 'Queries', color: '#8b5cf6', data: (ts.queries || []).map((p: any) => ({ x: p.timestamp, y: p.value })) },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,14 @@ export class OpsViewSecurity extends DeesElement {
|
||||
private renderOverview(metrics: any) {
|
||||
const threatLevel = this.calculateThreatLevel(metrics);
|
||||
const threatScore = this.getThreatScore(metrics);
|
||||
|
||||
|
||||
// Derive active sessions from recent successful auth events (last hour)
|
||||
const allEvents: any[] = metrics.recentEvents || [];
|
||||
const oneHourAgo = Date.now() - 3600000;
|
||||
const recentAuthSuccesses = allEvents.filter(
|
||||
(evt: any) => evt.type === 'authentication' && evt.success === true && evt.timestamp >= oneHourAgo
|
||||
).length;
|
||||
|
||||
const tiles: IStatsTile[] = [
|
||||
{
|
||||
id: 'threatLevel',
|
||||
@@ -271,7 +278,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
{
|
||||
id: 'blockedThreats',
|
||||
title: 'Blocked Threats',
|
||||
value: metrics.blockedIPs.length + metrics.spamDetected,
|
||||
value: (metrics.blockedIPs?.length || 0) + metrics.spamDetected,
|
||||
type: 'number',
|
||||
icon: 'lucide:ShieldCheck',
|
||||
color: '#ef4444',
|
||||
@@ -280,11 +287,11 @@ export class OpsViewSecurity extends DeesElement {
|
||||
{
|
||||
id: 'activeSessions',
|
||||
title: 'Active Sessions',
|
||||
value: 0,
|
||||
value: recentAuthSuccesses,
|
||||
type: 'number',
|
||||
icon: 'lucide:Users',
|
||||
color: '#22c55e',
|
||||
description: 'Current authenticated sessions',
|
||||
description: 'Authenticated in last hour',
|
||||
},
|
||||
{
|
||||
id: 'authFailures',
|
||||
@@ -349,6 +356,11 @@ export class OpsViewSecurity extends DeesElement {
|
||||
}
|
||||
|
||||
private renderAuthentication(metrics: any) {
|
||||
// Derive auth events from recentEvents
|
||||
const allEvents: any[] = metrics.recentEvents || [];
|
||||
const authEvents = allEvents.filter((evt: any) => evt.type === 'authentication');
|
||||
const successfulLogins = authEvents.filter((evt: any) => evt.success === true).length;
|
||||
|
||||
const tiles: IStatsTile[] = [
|
||||
{
|
||||
id: 'authFailures',
|
||||
@@ -362,7 +374,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
{
|
||||
id: 'successfulLogins',
|
||||
title: 'Successful Logins',
|
||||
value: 0,
|
||||
value: successfulLogins,
|
||||
type: 'number',
|
||||
icon: 'lucide:Lock',
|
||||
color: '#22c55e',
|
||||
@@ -370,6 +382,15 @@ export class OpsViewSecurity extends DeesElement {
|
||||
},
|
||||
];
|
||||
|
||||
// Map auth events to login history table data
|
||||
const loginHistory = authEvents.map((evt: any) => ({
|
||||
timestamp: evt.timestamp,
|
||||
username: evt.details?.username || 'unknown',
|
||||
ipAddress: evt.ipAddress || 'unknown',
|
||||
success: evt.success ?? false,
|
||||
reason: evt.success ? '' : evt.message || 'Authentication failed',
|
||||
}));
|
||||
|
||||
return html`
|
||||
<dees-statsgrid
|
||||
.tiles=${tiles}
|
||||
@@ -380,7 +401,7 @@ export class OpsViewSecurity extends DeesElement {
|
||||
<dees-table
|
||||
.heading1=${'Login History'}
|
||||
.heading2=${'Recent authentication attempts'}
|
||||
.data=${[]}
|
||||
.data=${loginHistory}
|
||||
.displayFunction=${(item) => ({
|
||||
'Time': new Date(item.timestamp).toLocaleString(),
|
||||
'Username': item.username,
|
||||
@@ -483,48 +504,38 @@ export class OpsViewSecurity extends DeesElement {
|
||||
private getThreatScore(metrics: any): number {
|
||||
// Simple scoring algorithm
|
||||
let score = 100;
|
||||
score -= metrics.blockedIPs.length * 2;
|
||||
score -= metrics.authenticationFailures * 1;
|
||||
score -= metrics.spamDetected * 0.5;
|
||||
score -= metrics.malwareDetected * 3;
|
||||
score -= metrics.phishingDetected * 3;
|
||||
score -= metrics.suspiciousActivities * 2;
|
||||
const blockedCount = Array.isArray(metrics.blockedIPs) ? metrics.blockedIPs.length : (metrics.blockedIPs || 0);
|
||||
score -= blockedCount * 2;
|
||||
score -= (metrics.authenticationFailures || 0) * 1;
|
||||
score -= (metrics.spamDetected || 0) * 0.5;
|
||||
score -= (metrics.malwareDetected || 0) * 3;
|
||||
score -= (metrics.phishingDetected || 0) * 3;
|
||||
score -= (metrics.suspiciousActivities || 0) * 2;
|
||||
return Math.max(0, Math.min(100, Math.round(score)));
|
||||
}
|
||||
|
||||
private getSecurityEvents(metrics: any): any[] {
|
||||
// Mock data - in real implementation, this would come from the server
|
||||
return [
|
||||
{
|
||||
timestamp: Date.now() - 1000 * 60 * 5,
|
||||
event: 'Multiple failed login attempts',
|
||||
severity: 'warning',
|
||||
details: 'IP: 192.168.1.100',
|
||||
},
|
||||
{
|
||||
timestamp: Date.now() - 1000 * 60 * 15,
|
||||
event: 'SPF check failed',
|
||||
severity: 'medium',
|
||||
details: 'Domain: example.com',
|
||||
},
|
||||
{
|
||||
timestamp: Date.now() - 1000 * 60 * 30,
|
||||
event: 'IP blocked due to spam',
|
||||
severity: 'high',
|
||||
details: 'IP: 10.0.0.1',
|
||||
},
|
||||
];
|
||||
const events: any[] = metrics.recentEvents || [];
|
||||
return events.map((evt: any) => ({
|
||||
timestamp: evt.timestamp,
|
||||
event: evt.message,
|
||||
severity: evt.level === 'critical' ? 'critical' : evt.level === 'error' ? 'high' : evt.level === 'warn' ? 'warning' : 'info',
|
||||
details: evt.ipAddress ? `IP: ${evt.ipAddress}` : evt.domain ? `Domain: ${evt.domain}` : evt.type,
|
||||
}));
|
||||
}
|
||||
|
||||
private async clearBlockedIPs() {
|
||||
console.log('Clear blocked IPs');
|
||||
// SmartProxy manages IP blocking — not yet exposed via API
|
||||
alert('Clearing blocked IPs is not yet supported from the UI.');
|
||||
}
|
||||
|
||||
private async unblockIP(ip: string) {
|
||||
console.log('Unblock IP:', ip);
|
||||
// SmartProxy manages IP blocking — not yet exposed via API
|
||||
alert(`Unblocking IP ${ip} is not yet supported from the UI.`);
|
||||
}
|
||||
|
||||
private async saveEmailSecuritySettings() {
|
||||
console.log('Save email security settings');
|
||||
// Config is read-only from the UI for now
|
||||
alert('Email security settings are read-only. Update the dcrouter configuration file to change these settings.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user