feat(ops/monitoring): add in-memory log buffer, metrics time-series and ops UI integration
This commit is contained in:
11
changelog.md
11
changelog.md
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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)
|
## 2026-02-19 - 7.0.1 - fix(monitoring)
|
||||||
Use smartMetrics cpuPercentage for cpuUsage.user and update smartmetrics and smartproxy dependencies
|
Use smartMetrics cpuPercentage for cpuUsage.user and update smartmetrics and smartproxy dependencies
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"@push.rocks/smartfile": "^13.1.2",
|
"@push.rocks/smartfile": "^13.1.2",
|
||||||
"@push.rocks/smartguard": "^3.1.0",
|
"@push.rocks/smartguard": "^3.1.0",
|
||||||
"@push.rocks/smartjwt": "^2.2.1",
|
"@push.rocks/smartjwt": "^2.2.1",
|
||||||
"@push.rocks/smartlog": "^3.1.11",
|
"@push.rocks/smartlog": "^3.2.0",
|
||||||
"@push.rocks/smartmetrics": "^3.0.1",
|
"@push.rocks/smartmetrics": "^3.0.1",
|
||||||
"@push.rocks/smartmongo": "^5.1.0",
|
"@push.rocks/smartmongo": "^5.1.0",
|
||||||
"@push.rocks/smartmta": "^5.2.2",
|
"@push.rocks/smartmta": "^5.2.2",
|
||||||
|
|||||||
69
pnpm-lock.yaml
generated
69
pnpm-lock.yaml
generated
@@ -54,8 +54,8 @@ importers:
|
|||||||
specifier: ^2.2.1
|
specifier: ^2.2.1
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
'@push.rocks/smartlog':
|
'@push.rocks/smartlog':
|
||||||
specifier: ^3.1.11
|
specifier: ^3.2.0
|
||||||
version: 3.1.11
|
version: 3.2.0
|
||||||
'@push.rocks/smartmetrics':
|
'@push.rocks/smartmetrics':
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
@@ -960,8 +960,8 @@ packages:
|
|||||||
'@push.rocks/smartlog-interfaces@3.0.2':
|
'@push.rocks/smartlog-interfaces@3.0.2':
|
||||||
resolution: {integrity: sha512-8hGRTJehbsFSJxLhCQkA018mZtXVPxPTblbg9VaE/EqISRzUw+eosJ2EJV7M4Qu0eiTJZjnWnNLn8CkD77ziWw==}
|
resolution: {integrity: sha512-8hGRTJehbsFSJxLhCQkA018mZtXVPxPTblbg9VaE/EqISRzUw+eosJ2EJV7M4Qu0eiTJZjnWnNLn8CkD77ziWw==}
|
||||||
|
|
||||||
'@push.rocks/smartlog@3.1.11':
|
'@push.rocks/smartlog@3.2.0':
|
||||||
resolution: {integrity: sha512-zyLH8pQD2UD7l76wJBESEWXU1FSTBLOuRI0/DN139EYyMkwMq1+pdQKptTkJhhVL/OIj56oMg9SpJb4bJB7uKg==}
|
resolution: {integrity: sha512-d6IzsSG8HTmgxr8c9BVzZWpn8m3c17b5O+orHdwrlgHqPVa0+WXMe1ezItuPVZH5q6i8h+OfCv382PTloNiweg==}
|
||||||
|
|
||||||
'@push.rocks/smartmail@2.2.0':
|
'@push.rocks/smartmail@2.2.0':
|
||||||
resolution: {integrity: sha512-28K4HAcda7ODUUpFCgbS/uA+eqwVRcmLJERIdM9AvLHXaHAPLHH97HmwPPcAu9Sp3z05Um0inmDF51X6yVVkcw==}
|
resolution: {integrity: sha512-28K4HAcda7ODUUpFCgbS/uA+eqwVRcmLJERIdM9AvLHXaHAPLHH97HmwPPcAu9Sp3z05Um0inmDF51X6yVVkcw==}
|
||||||
@@ -1127,9 +1127,6 @@ packages:
|
|||||||
'@push.rocks/webrequest@3.0.37':
|
'@push.rocks/webrequest@3.0.37':
|
||||||
resolution: {integrity: sha512-fLN7kP6GeHFxE4UH4r9C9pjcQb0QkJxHeAMwXvbOqB9hh0MFNKhtGU7GoaTn8SVRGRMPc9UqZVNwo6u5l8Wn0A==}
|
resolution: {integrity: sha512-fLN7kP6GeHFxE4UH4r9C9pjcQb0QkJxHeAMwXvbOqB9hh0MFNKhtGU7GoaTn8SVRGRMPc9UqZVNwo6u5l8Wn0A==}
|
||||||
|
|
||||||
'@push.rocks/webrequest@4.0.1':
|
|
||||||
resolution: {integrity: sha512-I60XZZLVf8W5I7YdmUVVu4G92teE3rg3/aKaV00BRg8vJ3VXx3wc59Qj4em7zxQ5o0HvL8m1Aezw3RFMDPyVgA==}
|
|
||||||
|
|
||||||
'@push.rocks/webrequest@4.0.2':
|
'@push.rocks/webrequest@4.0.2':
|
||||||
resolution: {integrity: sha512-rowzty+Q2papFBcnNYPcy+8CQJukSn/FGfQG8ap0bUgQUsx882u8kEyLM0Q+GlGHS5OiZ+Z0z5TZqLKlk3XHxA==}
|
resolution: {integrity: sha512-rowzty+Q2papFBcnNYPcy+8CQJukSn/FGfQG8ap0bUgQUsx882u8kEyLM0Q+GlGHS5OiZ+Z0z5TZqLKlk3XHxA==}
|
||||||
|
|
||||||
@@ -4303,7 +4300,7 @@ snapshots:
|
|||||||
'@push.rocks/smartfeed': 1.4.0
|
'@push.rocks/smartfeed': 1.4.0
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartjson': 5.2.0
|
'@push.rocks/smartjson': 5.2.0
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartlog-destination-devtools': 1.0.12
|
'@push.rocks/smartlog-destination-devtools': 1.0.12
|
||||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||||
'@push.rocks/smartmanifest': 2.0.2
|
'@push.rocks/smartmanifest': 2.0.2
|
||||||
@@ -4352,7 +4349,7 @@ snapshots:
|
|||||||
'@push.rocks/smartfile': 13.1.2
|
'@push.rocks/smartfile': 13.1.2
|
||||||
'@push.rocks/smartfs': 1.3.1
|
'@push.rocks/smartfs': 1.3.1
|
||||||
'@push.rocks/smartjson': 5.2.0
|
'@push.rocks/smartjson': 5.2.0
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartlog-destination-devtools': 1.0.12
|
'@push.rocks/smartlog-destination-devtools': 1.0.12
|
||||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||||
'@push.rocks/smartmanifest': 2.0.2
|
'@push.rocks/smartmanifest': 2.0.2
|
||||||
@@ -4419,7 +4416,7 @@ snapshots:
|
|||||||
'@apiclient.xyz/cloudflare@7.1.0':
|
'@apiclient.xyz/cloudflare@7.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrequest': 5.0.1
|
'@push.rocks/smartrequest': 5.0.1
|
||||||
'@push.rocks/smartstring': 4.1.0
|
'@push.rocks/smartstring': 4.1.0
|
||||||
@@ -5153,7 +5150,7 @@ snapshots:
|
|||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartfile': 13.1.2
|
'@push.rocks/smartfile': 13.1.2
|
||||||
'@push.rocks/smartfs': 1.3.1
|
'@push.rocks/smartfs': 1.3.1
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
@@ -5174,7 +5171,7 @@ snapshots:
|
|||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartfs': 1.3.1
|
'@push.rocks/smartfs': 1.3.1
|
||||||
'@push.rocks/smartinteract': 2.0.16
|
'@push.rocks/smartinteract': 2.0.16
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartlog-destination-local': 9.0.2
|
'@push.rocks/smartlog-destination-local': 9.0.2
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
@@ -5200,7 +5197,7 @@ snapshots:
|
|||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartfile': 13.1.2
|
'@push.rocks/smartfile': 13.1.2
|
||||||
'@push.rocks/smartfs': 1.3.1
|
'@push.rocks/smartfs': 1.3.1
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartnpm': 2.0.6
|
'@push.rocks/smartnpm': 2.0.6
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartrequest': 5.0.1
|
'@push.rocks/smartrequest': 5.0.1
|
||||||
@@ -5235,7 +5232,7 @@ snapshots:
|
|||||||
'@push.rocks/smartexpect': 2.5.0
|
'@push.rocks/smartexpect': 2.5.0
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartjson': 5.2.0
|
'@push.rocks/smartjson': 5.2.0
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
||||||
'@push.rocks/smartnetwork': 4.4.0
|
'@push.rocks/smartnetwork': 4.4.0
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
@@ -5281,7 +5278,7 @@ snapshots:
|
|||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartfs': 1.3.1
|
'@push.rocks/smartfs': 1.3.1
|
||||||
'@push.rocks/smartinteract': 2.0.16
|
'@push.rocks/smartinteract': 2.0.16
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartlog-destination-local': 9.0.2
|
'@push.rocks/smartlog-destination-local': 9.0.2
|
||||||
'@push.rocks/smartshell': 3.3.0
|
'@push.rocks/smartshell': 3.3.0
|
||||||
'@push.rocks/smartwatch': 6.3.0
|
'@push.rocks/smartwatch': 6.3.0
|
||||||
@@ -5714,7 +5711,7 @@ snapshots:
|
|||||||
'@push.rocks/qenv': 6.1.3
|
'@push.rocks/qenv': 6.1.3
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartjson': 5.2.0
|
'@push.rocks/smartjson': 5.2.0
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
@@ -5738,7 +5735,7 @@ snapshots:
|
|||||||
'@api.global/typedrequest': 3.2.6
|
'@api.global/typedrequest': 3.2.6
|
||||||
'@configvault.io/interfaces': 1.0.17
|
'@configvault.io/interfaces': 1.0.17
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
|
|
||||||
'@push.rocks/smartacme@9.1.3(socks@2.8.7)':
|
'@push.rocks/smartacme@9.1.3(socks@2.8.7)':
|
||||||
@@ -5749,7 +5746,7 @@ snapshots:
|
|||||||
'@push.rocks/smartdata': 7.0.15(socks@2.8.7)
|
'@push.rocks/smartdata': 7.0.15(socks@2.8.7)
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartdns': 7.8.1
|
'@push.rocks/smartdns': 7.8.1
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartnetwork': 4.4.0
|
'@push.rocks/smartnetwork': 4.4.0
|
||||||
'@push.rocks/smartstring': 4.1.0
|
'@push.rocks/smartstring': 4.1.0
|
||||||
'@push.rocks/smarttime': 4.2.3
|
'@push.rocks/smarttime': 4.2.3
|
||||||
@@ -5859,7 +5856,7 @@ snapshots:
|
|||||||
'@push.rocks/smartcli@4.0.20':
|
'@push.rocks/smartcli@4.0.20':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartobject': 1.0.12
|
'@push.rocks/smartobject': 1.0.12
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
@@ -5884,7 +5881,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
@@ -5913,7 +5910,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
@@ -6105,7 +6102,7 @@ snapshots:
|
|||||||
'@api.global/typedrequest-interfaces': 2.0.2
|
'@api.global/typedrequest-interfaces': 2.0.2
|
||||||
'@tsclass/tsclass': 4.4.4
|
'@tsclass/tsclass': 4.4.4
|
||||||
|
|
||||||
'@push.rocks/smartlog@3.1.11':
|
'@push.rocks/smartlog@3.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
'@api.global/typedrequest-interfaces': 3.0.19
|
||||||
'@push.rocks/consolecolor': 2.0.3
|
'@push.rocks/consolecolor': 2.0.3
|
||||||
@@ -6114,8 +6111,8 @@ snapshots:
|
|||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smarthash': 3.2.6
|
'@push.rocks/smarthash': 3.2.6
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@push.rocks/webrequest': 4.0.1
|
'@push.rocks/webrequest': 4.0.2
|
||||||
'@tsclass/tsclass': 9.3.0
|
'@tsclass/tsclass': 9.3.0
|
||||||
|
|
||||||
'@push.rocks/smartmail@2.2.0':
|
'@push.rocks/smartmail@2.2.0':
|
||||||
@@ -6152,7 +6149,7 @@ snapshots:
|
|||||||
'@push.rocks/smartmetrics@3.0.1':
|
'@push.rocks/smartmetrics@3.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
|
|
||||||
'@push.rocks/smartmime@1.0.6':
|
'@push.rocks/smartmime@1.0.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6221,7 +6218,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartfile': 13.1.2
|
'@push.rocks/smartfile': 13.1.2
|
||||||
'@push.rocks/smartfs': 1.3.1
|
'@push.rocks/smartfs': 1.3.1
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartmail': 2.2.0
|
'@push.rocks/smartmail': 2.2.0
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartrust': 1.2.1
|
'@push.rocks/smartrust': 1.2.1
|
||||||
@@ -6327,7 +6324,7 @@ snapshots:
|
|||||||
'@push.rocks/smartproxy@25.7.8':
|
'@push.rocks/smartproxy@25.7.8':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartcrypto': 2.0.4
|
'@push.rocks/smartcrypto': 2.0.4
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartrust': 1.2.1
|
'@push.rocks/smartrust': 1.2.1
|
||||||
'@tsclass/tsclass': 9.3.0
|
'@tsclass/tsclass': 9.3.0
|
||||||
minimatch: 10.2.1
|
minimatch: 10.2.1
|
||||||
@@ -6408,7 +6405,7 @@ snapshots:
|
|||||||
'@cfworker/json-schema': 4.1.1
|
'@cfworker/json-schema': 4.1.1
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartenv': 6.0.0
|
'@push.rocks/smartenv': 6.0.0
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
ws: 8.19.0
|
ws: 8.19.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -6443,7 +6440,7 @@ snapshots:
|
|||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartenv': 5.0.13
|
'@push.rocks/smartenv': 5.0.13
|
||||||
'@push.rocks/smartjson': 5.2.0
|
'@push.rocks/smartjson': 5.2.0
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.1.1
|
||||||
@@ -6558,7 +6555,7 @@ snapshots:
|
|||||||
'@design.estate/dees-element': 2.1.6
|
'@design.estate/dees-element': 2.1.6
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.1.1
|
||||||
@@ -6574,7 +6571,7 @@ snapshots:
|
|||||||
'@design.estate/dees-element': 2.1.6
|
'@design.estate/dees-element': 2.1.6
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.1.1
|
||||||
@@ -6590,7 +6587,7 @@ snapshots:
|
|||||||
'@design.estate/dees-element': 2.1.6
|
'@design.estate/dees-element': 2.1.6
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.2.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smarttime': 4.2.3
|
'@push.rocks/smarttime': 4.2.3
|
||||||
@@ -6609,14 +6606,6 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/webstore': 2.0.20
|
'@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':
|
'@push.rocks/webrequest@4.0.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '7.0.1',
|
version: '7.1.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1055,7 +1055,25 @@ export class DcRouter {
|
|||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
await this.emailServer.start();
|
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(', ')}`);
|
logger.log('info', `Email server started on ports: ${emailConfig.ports.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import { SmartlogDestinationBuffer } from '@push.rocks/smartlog/destination-buffer';
|
||||||
|
|
||||||
// Map NODE_ENV to valid TEnvironment
|
// Map NODE_ENV to valid TEnvironment
|
||||||
const nodeEnv = process.env.NODE_ENV || 'production';
|
const nodeEnv = process.env.NODE_ENV || 'production';
|
||||||
@@ -10,6 +11,9 @@ const envMap: Record<string, 'local' | 'test' | 'staging' | 'production'> = {
|
|||||||
'production': 'production'
|
'production': 'production'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// In-memory log buffer for the OpsServer UI
|
||||||
|
export const logBuffer = new SmartlogDestinationBuffer({ maxEntries: 2000 });
|
||||||
|
|
||||||
// Default Smartlog instance
|
// Default Smartlog instance
|
||||||
const baseLogger = new plugins.smartlog.Smartlog({
|
const baseLogger = new plugins.smartlog.Smartlog({
|
||||||
logContext: {
|
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
|
// Extended logger compatible with the original enhanced logger API
|
||||||
class StandardLogger {
|
class StandardLogger {
|
||||||
private defaultContext: Record<string, any> = {};
|
private defaultContext: Record<string, any> = {};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { DcRouter } from '../classes.dcrouter.js';
|
import { DcRouter } from '../classes.dcrouter.js';
|
||||||
import { MetricsCache } from './classes.metricscache.js';
|
import { MetricsCache } from './classes.metricscache.js';
|
||||||
|
import { SecurityLogger, SecurityEventType } from '../security/classes.securitylogger.js';
|
||||||
|
|
||||||
export class MetricsManager {
|
export class MetricsManager {
|
||||||
private logger: plugins.smartlog.Smartlog;
|
private logger: plugins.smartlog.Smartlog;
|
||||||
@@ -37,6 +38,10 @@ export class MetricsManager {
|
|||||||
responseTimes: [] as number[], // Track response times in ms
|
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
|
// Track security-specific metrics
|
||||||
private securityMetrics = {
|
private securityMetrics = {
|
||||||
blockedIPs: 0,
|
blockedIPs: 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
|
// Get security metrics
|
||||||
public async getSecurityStats() {
|
public async getSecurityStats() {
|
||||||
return this.metricsCache.get('securityStats', () => {
|
return this.metricsCache.get('securityStats', () => {
|
||||||
|
// Sync counters from the real SecurityLogger events
|
||||||
|
this.syncFromSecurityLogger();
|
||||||
|
|
||||||
// Get recent incidents (last 20)
|
// Get recent incidents (last 20)
|
||||||
const recentIncidents = this.securityMetrics.incidents.slice(-20);
|
const recentIncidents = this.securityMetrics.incidents.slice(-20);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
blockedIPs: this.securityMetrics.blockedIPs,
|
blockedIPs: this.securityMetrics.blockedIPs,
|
||||||
authFailures: this.securityMetrics.authFailures,
|
authFailures: this.securityMetrics.authFailures,
|
||||||
spamDetected: this.securityMetrics.spamDetected,
|
spamDetected: this.securityMetrics.spamDetected,
|
||||||
malwareDetected: this.securityMetrics.malwareDetected,
|
malwareDetected: this.securityMetrics.malwareDetected,
|
||||||
phishingDetected: this.securityMetrics.phishingDetected,
|
phishingDetected: this.securityMetrics.phishingDetected,
|
||||||
totalThreatsBlocked: this.securityMetrics.spamDetected +
|
totalThreatsBlocked: this.securityMetrics.spamDetected +
|
||||||
this.securityMetrics.malwareDetected +
|
this.securityMetrics.malwareDetected +
|
||||||
this.securityMetrics.phishingDetected,
|
this.securityMetrics.phishingDetected,
|
||||||
recentIncidents,
|
recentIncidents,
|
||||||
};
|
};
|
||||||
@@ -275,6 +305,7 @@ export class MetricsManager {
|
|||||||
// Email event tracking methods
|
// Email event tracking methods
|
||||||
public trackEmailSent(recipient?: string, deliveryTimeMs?: number): void {
|
public trackEmailSent(recipient?: string, deliveryTimeMs?: number): void {
|
||||||
this.emailMetrics.sentToday++;
|
this.emailMetrics.sentToday++;
|
||||||
|
this.incrementEmailBucket('sent');
|
||||||
|
|
||||||
if (recipient) {
|
if (recipient) {
|
||||||
const count = this.emailMetrics.recipients.get(recipient) || 0;
|
const count = this.emailMetrics.recipients.get(recipient) || 0;
|
||||||
@@ -311,6 +342,7 @@ export class MetricsManager {
|
|||||||
|
|
||||||
public trackEmailReceived(sender?: string): void {
|
public trackEmailReceived(sender?: string): void {
|
||||||
this.emailMetrics.receivedToday++;
|
this.emailMetrics.receivedToday++;
|
||||||
|
this.incrementEmailBucket('received');
|
||||||
|
|
||||||
this.emailMetrics.recentActivity.push({
|
this.emailMetrics.recentActivity.push({
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@@ -326,6 +358,7 @@ export class MetricsManager {
|
|||||||
|
|
||||||
public trackEmailFailed(recipient?: string, reason?: string): void {
|
public trackEmailFailed(recipient?: string, reason?: string): void {
|
||||||
this.emailMetrics.failedToday++;
|
this.emailMetrics.failedToday++;
|
||||||
|
this.incrementEmailBucket('failed');
|
||||||
|
|
||||||
this.emailMetrics.recentActivity.push({
|
this.emailMetrics.recentActivity.push({
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
@@ -361,6 +394,7 @@ export class MetricsManager {
|
|||||||
// DNS event tracking methods
|
// DNS event tracking methods
|
||||||
public trackDnsQuery(queryType: string, domain: string, cacheHit: boolean, responseTimeMs?: number): void {
|
public trackDnsQuery(queryType: string, domain: string, cacheHit: boolean, responseTimeMs?: number): void {
|
||||||
this.dnsMetrics.totalQueries++;
|
this.dnsMetrics.totalQueries++;
|
||||||
|
this.incrementDnsBucket();
|
||||||
|
|
||||||
if (cacheHit) {
|
if (cacheHit) {
|
||||||
this.dnsMetrics.cacheHits++;
|
this.dnsMetrics.cacheHits++;
|
||||||
@@ -547,4 +581,90 @@ export class MetricsManager {
|
|||||||
};
|
};
|
||||||
}, 200); // Use 200ms cache for more frequent updates
|
}, 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 * as plugins from '../../plugins.js';
|
||||||
import type { OpsServer } from '../classes.opsserver.js';
|
import type { OpsServer } from '../classes.opsserver.js';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||||
|
import { logBuffer } from '../../logger.js';
|
||||||
|
|
||||||
export class LogsHandler {
|
export class LogsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
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(
|
private async getRecentLogs(
|
||||||
level?: 'error' | 'warn' | 'info' | 'debug',
|
level?: 'error' | 'warn' | 'info' | 'debug',
|
||||||
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email',
|
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email',
|
||||||
@@ -78,42 +105,64 @@ export class LogsHandler {
|
|||||||
message: string;
|
message: string;
|
||||||
metadata?: any;
|
metadata?: any;
|
||||||
}>> {
|
}>> {
|
||||||
// TODO: Implement actual log retrieval from storage or logger
|
// Compute a timestamp cutoff from timeRange
|
||||||
// For now, return mock data
|
let since: number | undefined;
|
||||||
const mockLogs: Array<{
|
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;
|
timestamp: number;
|
||||||
level: 'debug' | 'info' | 'warn' | 'error';
|
level: 'debug' | 'info' | 'warn' | 'error';
|
||||||
category: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
category: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
||||||
message: string;
|
message: string;
|
||||||
metadata?: any;
|
metadata?: any;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
const categories: Array<'smtp' | 'dns' | 'security' | 'system' | 'email'> = ['smtp', 'dns', 'security', 'system', 'email'];
|
for (const pkg of rawEntries) {
|
||||||
const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['info', 'warn', 'error', 'debug'];
|
const uiLevel = LogsHandler.mapLogLevel(pkg.level);
|
||||||
const now = Date.now();
|
const uiCategory = LogsHandler.deriveCategory(pkg.context?.zone, pkg.message);
|
||||||
|
|
||||||
// Generate some mock log entries
|
if (category && uiCategory !== category) continue;
|
||||||
for (let i = 0; i < 50; i++) {
|
|
||||||
const mockCategory = categories[Math.floor(Math.random() * categories.length)];
|
mapped.push({
|
||||||
const mockLevel = levels[Math.floor(Math.random() * levels.length)];
|
timestamp: pkg.timestamp,
|
||||||
|
level: uiLevel,
|
||||||
// Filter by requested criteria
|
category: uiCategory,
|
||||||
if (level && mockLevel !== level) continue;
|
message: pkg.message,
|
||||||
if (category && mockCategory !== category) continue;
|
metadata: pkg.data,
|
||||||
|
|
||||||
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(),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (mapped.length >= limit) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply pagination
|
return mapped;
|
||||||
return mockLogs.slice(offset, offset + limit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupLogStream(
|
private setupLogStream(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
|
|||||||
import type { OpsServer } from '../classes.opsserver.js';
|
import type { OpsServer } from '../classes.opsserver.js';
|
||||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||||
import { MetricsManager } from '../../monitoring/index.js';
|
import { MetricsManager } from '../../monitoring/index.js';
|
||||||
|
import { SecurityLogger } from '../../security/classes.securitylogger.js';
|
||||||
|
|
||||||
export class StatsHandler {
|
export class StatsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
@@ -203,6 +204,11 @@ export class StatsHandler {
|
|||||||
if (sections.email) {
|
if (sections.email) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.collectEmailStats().then(stats => {
|
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 = {
|
metrics.email = {
|
||||||
sent: stats.sentToday,
|
sent: stats.sentToday,
|
||||||
received: stats.receivedToday,
|
received: stats.receivedToday,
|
||||||
@@ -212,6 +218,7 @@ export class StatsHandler {
|
|||||||
averageDeliveryTime: 0,
|
averageDeliveryTime: 0,
|
||||||
deliveryRate: stats.deliveryRate,
|
deliveryRate: stats.deliveryRate,
|
||||||
bounceRate: stats.bounceRate,
|
bounceRate: stats.bounceRate,
|
||||||
|
timeSeries,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -220,6 +227,11 @@ export class StatsHandler {
|
|||||||
if (sections.dns) {
|
if (sections.dns) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.collectDnsStats().then(stats => {
|
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 = {
|
metrics.dns = {
|
||||||
totalQueries: stats.totalQueries,
|
totalQueries: stats.totalQueries,
|
||||||
cacheHits: stats.cacheHits,
|
cacheHits: stats.cacheHits,
|
||||||
@@ -228,6 +240,7 @@ export class StatsHandler {
|
|||||||
activeDomains: stats.topDomains.length,
|
activeDomains: stats.topDomains.length,
|
||||||
averageResponseTime: 0,
|
averageResponseTime: 0,
|
||||||
queryTypes: stats.queryTypes,
|
queryTypes: stats.queryTypes,
|
||||||
|
timeSeries,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -236,6 +249,19 @@ export class StatsHandler {
|
|||||||
if (sections.security && this.opsServerRef.dcRouterRef.metricsManager) {
|
if (sections.security && this.opsServerRef.dcRouterRef.metricsManager) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.opsServerRef.dcRouterRef.metricsManager.getSecurityStats().then(stats => {
|
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 = {
|
metrics.security = {
|
||||||
blockedIPs: stats.blockedIPs,
|
blockedIPs: stats.blockedIPs,
|
||||||
reputationScores: {},
|
reputationScores: {},
|
||||||
@@ -244,6 +270,7 @@ export class StatsHandler {
|
|||||||
phishingDetected: stats.phishingDetected,
|
phishingDetected: stats.phishingDetected,
|
||||||
authenticationFailures: stats.authFailures,
|
authenticationFailures: stats.authFailures,
|
||||||
suspiciousActivities: stats.totalThreatsBlocked,
|
suspiciousActivities: stats.totalThreatsBlocked,
|
||||||
|
recentEvents,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ export interface IServerStats {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ITimeSeriesPoint {
|
||||||
|
timestamp: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IEmailStats {
|
export interface IEmailStats {
|
||||||
sent: number;
|
sent: number;
|
||||||
received: number;
|
received: number;
|
||||||
@@ -35,6 +40,11 @@ export interface IEmailStats {
|
|||||||
averageDeliveryTime: number;
|
averageDeliveryTime: number;
|
||||||
deliveryRate: number;
|
deliveryRate: number;
|
||||||
bounceRate: number;
|
bounceRate: number;
|
||||||
|
timeSeries?: {
|
||||||
|
sent: ITimeSeriesPoint[];
|
||||||
|
received: ITimeSeriesPoint[];
|
||||||
|
failed: ITimeSeriesPoint[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDnsStats {
|
export interface IDnsStats {
|
||||||
@@ -47,6 +57,9 @@ export interface IDnsStats {
|
|||||||
queryTypes: {
|
queryTypes: {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
};
|
};
|
||||||
|
timeSeries?: {
|
||||||
|
queries: ITimeSeriesPoint[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRateLimitInfo {
|
export interface IRateLimitInfo {
|
||||||
@@ -58,6 +71,17 @@ export interface IRateLimitInfo {
|
|||||||
blocked: boolean;
|
blocked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISecurityEvent {
|
||||||
|
timestamp: number;
|
||||||
|
level: string;
|
||||||
|
type: string;
|
||||||
|
message: string;
|
||||||
|
details?: any;
|
||||||
|
ipAddress?: string;
|
||||||
|
domain?: string;
|
||||||
|
success?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ISecurityMetrics {
|
export interface ISecurityMetrics {
|
||||||
blockedIPs: string[];
|
blockedIPs: string[];
|
||||||
reputationScores: {
|
reputationScores: {
|
||||||
@@ -68,6 +92,7 @@ export interface ISecurityMetrics {
|
|||||||
phishingDetected: number;
|
phishingDetected: number;
|
||||||
authenticationFailures: number;
|
authenticationFailures: number;
|
||||||
suspiciousActivities: number;
|
suspiciousActivities: number;
|
||||||
|
recentEvents?: ISecurityEvent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILogEntry {
|
export interface ILogEntry {
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '7.0.1',
|
version: '7.1.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ export class OpsViewLogs extends DeesElement {
|
|||||||
filters: {},
|
filters: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor filterLevel: string | undefined;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor filterCategory: string | undefined;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor filterLimit: number = 100;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
const subscription = appstate.logStatePart
|
const subscription = appstate.logStatePart
|
||||||
@@ -174,29 +183,43 @@ export class OpsViewLogs extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
// Auto-fetch logs when the view mounts
|
||||||
|
this.fetchLogs();
|
||||||
|
}
|
||||||
|
|
||||||
private async fetchLogs() {
|
private async fetchLogs() {
|
||||||
const filters = this.getActiveFilters();
|
|
||||||
await appstate.logStatePart.dispatchAction(appstate.fetchRecentLogsAction, {
|
await appstate.logStatePart.dispatchAction(appstate.fetchRecentLogsAction, {
|
||||||
limit: filters.limit || 100,
|
limit: this.filterLimit,
|
||||||
level: filters.level as 'debug' | 'info' | 'warn' | 'error' | undefined,
|
level: this.filterLevel as 'debug' | 'info' | 'warn' | 'error' | undefined,
|
||||||
category: filters.category as 'smtp' | 'dns' | 'security' | 'system' | 'email' | undefined,
|
category: this.filterCategory as 'smtp' | 'dns' | 'security' | 'system' | 'email' | undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateFilter(type: string, value: string) {
|
private updateFilter(type: string, value: string) {
|
||||||
if (value === 'all') {
|
const resolved = value === 'all' ? undefined : value;
|
||||||
value = undefined;
|
|
||||||
|
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();
|
this.fetchLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getActiveFilters() {
|
private getActiveFilters() {
|
||||||
return {
|
return {
|
||||||
level: this.logState.filters.level?.[0],
|
level: this.filterLevel,
|
||||||
category: this.logState.filters.category?.[0],
|
category: this.filterCategory,
|
||||||
limit: 100,
|
limit: this.filterLimit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,14 +26,36 @@ export class OpsViewOverview extends DeesElement {
|
|||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor logState: appstate.ILogState = {
|
||||||
|
recentLogs: [],
|
||||||
|
isStreaming: false,
|
||||||
|
filters: {},
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
const subscription = appstate.statsStatePart
|
const statsSub = appstate.statsStatePart
|
||||||
.select((stateArg) => stateArg)
|
.select((stateArg) => stateArg)
|
||||||
.subscribe((statsState) => {
|
.subscribe((statsState) => {
|
||||||
this.statsState = 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 = [
|
public static styles = [
|
||||||
@@ -96,10 +118,24 @@ export class OpsViewOverview extends DeesElement {
|
|||||||
${this.renderDnsStats()}
|
${this.renderDnsStats()}
|
||||||
|
|
||||||
<div class="chartGrid">
|
<div class="chartGrid">
|
||||||
<dees-chart-area .label=${'Email Traffic (24h)'} .data=${[]}></dees-chart-area>
|
<dees-chart-area
|
||||||
<dees-chart-area .label=${'DNS Queries (24h)'} .data=${[]}></dees-chart-area>
|
.label=${'Email Traffic (24h)'}
|
||||||
<dees-chart-log .label=${'Recent Events'} .data=${[]}></dees-chart-log>
|
.series=${this.getEmailTrafficSeries()}
|
||||||
<dees-chart-log .label=${'Security Alerts'} .data=${[]}></dees-chart-log>
|
.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>
|
</div>
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
@@ -337,4 +373,42 @@ export class OpsViewOverview extends DeesElement {
|
|||||||
<dees-statsgrid .tiles=${tiles}></dees-statsgrid>
|
<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) {
|
private renderOverview(metrics: any) {
|
||||||
const threatLevel = this.calculateThreatLevel(metrics);
|
const threatLevel = this.calculateThreatLevel(metrics);
|
||||||
const threatScore = this.getThreatScore(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[] = [
|
const tiles: IStatsTile[] = [
|
||||||
{
|
{
|
||||||
id: 'threatLevel',
|
id: 'threatLevel',
|
||||||
@@ -271,7 +278,7 @@ export class OpsViewSecurity extends DeesElement {
|
|||||||
{
|
{
|
||||||
id: 'blockedThreats',
|
id: 'blockedThreats',
|
||||||
title: 'Blocked Threats',
|
title: 'Blocked Threats',
|
||||||
value: metrics.blockedIPs.length + metrics.spamDetected,
|
value: (metrics.blockedIPs?.length || 0) + metrics.spamDetected,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
icon: 'lucide:ShieldCheck',
|
icon: 'lucide:ShieldCheck',
|
||||||
color: '#ef4444',
|
color: '#ef4444',
|
||||||
@@ -280,11 +287,11 @@ export class OpsViewSecurity extends DeesElement {
|
|||||||
{
|
{
|
||||||
id: 'activeSessions',
|
id: 'activeSessions',
|
||||||
title: 'Active Sessions',
|
title: 'Active Sessions',
|
||||||
value: 0,
|
value: recentAuthSuccesses,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
icon: 'lucide:Users',
|
icon: 'lucide:Users',
|
||||||
color: '#22c55e',
|
color: '#22c55e',
|
||||||
description: 'Current authenticated sessions',
|
description: 'Authenticated in last hour',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'authFailures',
|
id: 'authFailures',
|
||||||
@@ -349,6 +356,11 @@ export class OpsViewSecurity extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderAuthentication(metrics: any) {
|
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[] = [
|
const tiles: IStatsTile[] = [
|
||||||
{
|
{
|
||||||
id: 'authFailures',
|
id: 'authFailures',
|
||||||
@@ -362,7 +374,7 @@ export class OpsViewSecurity extends DeesElement {
|
|||||||
{
|
{
|
||||||
id: 'successfulLogins',
|
id: 'successfulLogins',
|
||||||
title: 'Successful Logins',
|
title: 'Successful Logins',
|
||||||
value: 0,
|
value: successfulLogins,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
icon: 'lucide:Lock',
|
icon: 'lucide:Lock',
|
||||||
color: '#22c55e',
|
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`
|
return html`
|
||||||
<dees-statsgrid
|
<dees-statsgrid
|
||||||
.tiles=${tiles}
|
.tiles=${tiles}
|
||||||
@@ -380,7 +401,7 @@ export class OpsViewSecurity extends DeesElement {
|
|||||||
<dees-table
|
<dees-table
|
||||||
.heading1=${'Login History'}
|
.heading1=${'Login History'}
|
||||||
.heading2=${'Recent authentication attempts'}
|
.heading2=${'Recent authentication attempts'}
|
||||||
.data=${[]}
|
.data=${loginHistory}
|
||||||
.displayFunction=${(item) => ({
|
.displayFunction=${(item) => ({
|
||||||
'Time': new Date(item.timestamp).toLocaleString(),
|
'Time': new Date(item.timestamp).toLocaleString(),
|
||||||
'Username': item.username,
|
'Username': item.username,
|
||||||
@@ -483,48 +504,38 @@ export class OpsViewSecurity extends DeesElement {
|
|||||||
private getThreatScore(metrics: any): number {
|
private getThreatScore(metrics: any): number {
|
||||||
// Simple scoring algorithm
|
// Simple scoring algorithm
|
||||||
let score = 100;
|
let score = 100;
|
||||||
score -= metrics.blockedIPs.length * 2;
|
const blockedCount = Array.isArray(metrics.blockedIPs) ? metrics.blockedIPs.length : (metrics.blockedIPs || 0);
|
||||||
score -= metrics.authenticationFailures * 1;
|
score -= blockedCount * 2;
|
||||||
score -= metrics.spamDetected * 0.5;
|
score -= (metrics.authenticationFailures || 0) * 1;
|
||||||
score -= metrics.malwareDetected * 3;
|
score -= (metrics.spamDetected || 0) * 0.5;
|
||||||
score -= metrics.phishingDetected * 3;
|
score -= (metrics.malwareDetected || 0) * 3;
|
||||||
score -= metrics.suspiciousActivities * 2;
|
score -= (metrics.phishingDetected || 0) * 3;
|
||||||
|
score -= (metrics.suspiciousActivities || 0) * 2;
|
||||||
return Math.max(0, Math.min(100, Math.round(score)));
|
return Math.max(0, Math.min(100, Math.round(score)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSecurityEvents(metrics: any): any[] {
|
private getSecurityEvents(metrics: any): any[] {
|
||||||
// Mock data - in real implementation, this would come from the server
|
const events: any[] = metrics.recentEvents || [];
|
||||||
return [
|
return events.map((evt: any) => ({
|
||||||
{
|
timestamp: evt.timestamp,
|
||||||
timestamp: Date.now() - 1000 * 60 * 5,
|
event: evt.message,
|
||||||
event: 'Multiple failed login attempts',
|
severity: evt.level === 'critical' ? 'critical' : evt.level === 'error' ? 'high' : evt.level === 'warn' ? 'warning' : 'info',
|
||||||
severity: 'warning',
|
details: evt.ipAddress ? `IP: ${evt.ipAddress}` : evt.domain ? `Domain: ${evt.domain}` : evt.type,
|
||||||
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',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async clearBlockedIPs() {
|
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) {
|
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() {
|
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