feat: implement advanced live logs with KQL search, histogram, and live/historical modes
This commit is contained in:
4
.hypothesis/constants/0dde2bfda6648a83
Normal file
4
.hypothesis/constants/0dde2bfda6648a83
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/web/sqlite_repository.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[' AND ', ' WHERE ', ':', '[^a-zA-Z0-9_]', 'active_deckies', 'attacker', 'attacker-ip', 'attacker_ip', 'bucket_time', 'count', 'decky', 'decnet.db', 'deployed_deckies', 'event', 'event_type', 'fields', 'id > ?', 'max_id', 'msg', 'must_change_password', 'password_hash', 'raw_line', 'role', 'service', 'time', 'timestamp', 'timestamp <= ?', 'timestamp >= ?', 'total', 'total_logs', 'unique_attackers', 'username', 'uuid']
|
||||
4
.hypothesis/constants/0ff817caa72a52a4
Normal file
4
.hypothesis/constants/0ff817caa72a52a4
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/logging/file_handler.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[1024, '%(message)s', 'DECNET_LOG_FILE', 'decnet.syslog', 'utf-8']
|
||||
4
.hypothesis/constants/15d50d1e53b9b5c3
Normal file
4
.hypothesis/constants/15d50d1e53b9b5c3
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/tftp.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'restart', 'templates', 'tftp', 'unless-stopped']
|
||||
4
.hypothesis/constants/19d5adc9efd5ec68
Normal file
4
.hypothesis/constants/19d5adc9efd5ec68
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/web/ingester.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['.json', 'decnet.web.ingester', 'r', 'replace', 'utf-8']
|
||||
4
.hypothesis/constants/1f005a833d034313
Normal file
4
.hypothesis/constants/1f005a833d034313
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/logging/forwarder.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[2.0, ':']
|
||||
4
.hypothesis/constants/1f12b014d4fe2068
Normal file
4
.hypothesis/constants/1f12b014d4fe2068
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/mongodb.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[27017, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'mongodb', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/2220ccbe8a25f02d
Normal file
4
.hypothesis/constants/2220ccbe8a25f02d
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/snmp.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[161, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'restart', 'snmp', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/2f0b53ebdb35c4e1
Normal file
4
.hypothesis/constants/2f0b53ebdb35c4e1
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/sip.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[5060, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'restart', 'sip', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/3048c6da87ba838d
Normal file
4
.hypothesis/constants/3048c6da87ba838d
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/web/auth.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[1440, 'HS256', 'exp', 'iat', 'utf-8']
|
||||
4
.hypothesis/constants/30a7ffe86227f7f1
Normal file
4
.hypothesis/constants/30a7ffe86227f7f1
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/mssql.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[1433, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'mssql', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/349ec22a74b50191
Normal file
4
.hypothesis/constants/349ec22a74b50191
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/composer.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['/var/log/decnet', '3.8', 'BASE_IMAGE', 'DECNET_LOG_FILE', 'HOSTNAME', 'NET_ADMIN', 'args', 'bridge', 'build', 'cap_add', 'command', 'container_name', 'decnet_logs', 'depends_on', 'driver', 'environment', 'external', 'hostname', 'image', 'infinity', 'internal', 'ipv4_address', 'network_mode', 'networks', 'restart', 'services', 'sleep', 'sysctls', 'unless-stopped', 'version', 'volumes']
|
||||
4
.hypothesis/constants/37d6bf6c6c0b58e6
Normal file
4
.hypothesis/constants/37d6bf6c6c0b58e6
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/elasticsearch.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[9200, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'elasticsearch', 'environment', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/3cc47bb868bcb8f4
Normal file
4
.hypothesis/constants/3cc47bb868bcb8f4
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/telnet.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[':', 'COWRIE_SSH_ENABLED', 'NET_BIND_SERVICE', 'cap_add', 'container_name', 'cowrie/cowrie', 'environment', 'false', 'image', 'restart', 'telnet', 'true', 'unless-stopped']
|
||||
4
.hypothesis/constants/409656273e7d498b
Normal file
4
.hypothesis/constants/409656273e7d498b
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/web/sqlite_repository.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[' AND ', ' WHERE ', ':', '[^a-zA-Z0-9_]', 'active_deckies', 'attacker', 'attacker-ip', 'attacker_ip', 'bucket_time', 'count', 'decky', 'decnet.db', 'deployed_deckies', 'event', 'event_type', 'fields', 'id > ?', 'max_id', 'msg', 'must_change_password', 'password_hash', 'raw_line', 'role', 'service', 'time', 'timestamp', 'timestamp <= ?', 'timestamp >= ?', 'total', 'total_logs', 'unique_attackers', 'username', 'uuid']
|
||||
4
.hypothesis/constants/42a1dcb5c22b1ac1
Normal file
4
.hypothesis/constants/42a1dcb5c22b1ac1
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/ini_loader.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[100, 512, 1024, ',', '.', '1', '[', ']', 'amount', 'archetype', 'binary', 'custom-', 'exceeds maximum', 'exec', 'general', 'gw', 'interface', 'ip', 'log-target', 'log_target', 'mutate-interval', 'mutate_interval', 'net', 'nmap-os', 'nmap_os', 'ports', 'services']
|
||||
4
.hypothesis/constants/4b12b89e1879f5ab
Normal file
4
.hypothesis/constants/4b12b89e1879f5ab
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/archetypes.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[', ', 'Database Server', 'DevOps Host', 'Domain Controller', 'File Server', 'IoT Device', 'Linux Server', 'Mail Server', 'Monitoring Node', 'Network Printer', 'VoIP Server', 'Web Server', 'Windows Server', 'Windows Workstation', 'alpine', 'conpot', 'database-server', 'deaddeck', 'debian', 'devops-host', 'docker_api', 'domain-controller', 'embedded', 'fedora', 'file-server', 'ftp', 'http', 'imap', 'industrial-control', 'iot-device', 'k8s', 'ldap', 'linux', 'linux-server', 'llmnr', 'mail-server', 'monitoring-node', 'mqtt', 'mysql', 'pop3', 'postgres', 'printer', 'rdp', 'real_ssh', 'redis', 'rocky9', 'sip', 'smb', 'smtp', 'snmp', 'ssh', 'telnet', 'ubuntu20', 'ubuntu22', 'voip-server', 'web-server', 'windows', 'windows-server', 'windows-workstation']
|
||||
4
.hypothesis/constants/4efedb0b38145ee9
Normal file
4
.hypothesis/constants/4efedb0b38145ee9
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/env.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['.env', '.env.local', '0.0.0.0', '8000', '8080', 'DECNET_ADMIN_USER', 'DECNET_API_HOST', 'DECNET_API_PORT', 'DECNET_JWT_SECRET', 'DECNET_WEB_HOST', 'DECNET_WEB_PORT', 'admin']
|
||||
4
.hypothesis/constants/4f57659a52de3fc8
Normal file
4
.hypothesis/constants/4f57659a52de3fc8
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/cli.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[8000, ',', ', ', '--all', '--api', '--api-port', '--archetype', '--config', '--deckies', '--decky', '--distro', '--dry-run', '--emit-syslog', '--host', '--id', '--interface', '--ip-start', '--ipvlan', '--log-file', '--log-target', '--min-deckies', '--mode', '--mutate-interval', '--no-cache', '--output', '--port', '--randomize-distros', '--randomize-services', '--services', '--subnet', '--watch', '--web-port', '-a', '-c', '-d', '-f', '-i', '-m', '-n', '-o', '-w', '/index.html', '0.0.0.0', 'Available Services', 'Default Services', 'Description', 'Display Name', 'Docker Image', 'Image', 'Machine Archetypes', 'Name', 'Ports', 'Slug', 'archetypes', 'bold cyan', 'correlate', 'decnet', 'decnet.cli', 'decnet.log', 'decnet.web.api:app', 'decnet_web', 'dim', 'dist', 'distros', 'green', 'json', 'linux', 'mutate', 'services', 'swarm', 'syslog', 'table', 'unihost', 'uvicorn', 'web']
|
||||
4
.hypothesis/constants/507a3145954fca93
Normal file
4
.hypothesis/constants/507a3145954fca93
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/http.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[443, '/opt/html_files', 'CUSTOM_BODY', 'EXTRA_HEADERS', 'FAKE_APP', 'FILES_DIR', 'LOG_TARGET', 'NODE_NAME', 'RESPONSE_CODE', 'SERVER_HEADER', 'build', 'container_name', 'context', 'custom_body', 'environment', 'extra_headers', 'fake_app', 'files', 'http', 'response_code', 'restart', 'server_header', 'templates', 'unless-stopped', 'volumes']
|
||||
4
.hypothesis/constants/53a42446f9f19b20
Normal file
4
.hypothesis/constants/53a42446f9f19b20
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/ftp.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'ftp', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/574dbe54f9b23d3e
Normal file
4
.hypothesis/constants/574dbe54f9b23d3e
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/correlation/engine.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[3600, ',', 'Attacker IP', 'Deckies', 'Duration', 'Events', 'First Seen', 'Traversal Path', 'bold red', 'correlator', 'cyan', 'decnet-correlator', 'dim', 'events_indexed', 'lines_parsed', 'right', 'stats', 'traversal_detected', 'traversals', 'unique_ips', 'yellow']
|
||||
4
.hypothesis/constants/62e387790ed5b79f
Normal file
4
.hypothesis/constants/62e387790ed5b79f
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/vnc.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[5900, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'restart', 'templates', 'unless-stopped', 'vnc']
|
||||
4
.hypothesis/constants/66bd79275cd609e8
Normal file
4
.hypothesis/constants/66bd79275cd609e8
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/correlation/parser.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['"', '-', '\\', '\\"', '\\\\', '\\]', ']', 'client_ip', 'ip', 'remote_ip', 'src', 'src_ip']
|
||||
4
.hypothesis/constants/76302489300fdc45
Normal file
4
.hypothesis/constants/76302489300fdc45
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/config.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[0.0, ':', 'compose_path', 'config', 'debian', 'debian:bookworm-slim', 'decnet-state.json', 'linux', 'log_target', 'services', 'swarm', 'unihost']
|
||||
4
.hypothesis/constants/77b4b42ea3b9c9bf
Normal file
4
.hypothesis/constants/77b4b42ea3b9c9bf
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/llmnr.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[5353, 5355, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'llmnr', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/79661beef79449a5
Normal file
4
.hypothesis/constants/79661beef79449a5
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/registry.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['base', 'decnet.services.', 'registry']
|
||||
4
.hypothesis/constants/7f9302a54093ce41
Normal file
4
.hypothesis/constants/7f9302a54093ce41
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/correlation/__init__.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['AttackerTraversal', 'CorrelationEngine', 'LogEvent', 'TraversalHop', 'parse_line']
|
||||
4
.hypothesis/constants/8029f0494746966f
Normal file
4
.hypothesis/constants/8029f0494746966f
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/ldap.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[389, 636, 'LOG_TARGET', 'NET_BIND_SERVICE', 'NODE_NAME', 'build', 'cap_add', 'container_name', 'context', 'environment', 'ldap', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/8647241ce03e8b57
Normal file
4
.hypothesis/constants/8647241ce03e8b57
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/web/api.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[400, 404, 500, 512, 1000, 1024, '*', '/api/v1/auth/login', '/api/v1/deckies', '/api/v1/logs', '/api/v1/stats', '/api/v1/stream', '1.0.0', 'Authorization', 'Bearer', 'Bearer ', 'Decky not found', 'No active deployment', 'WWW-Authenticate', 'access_token', 'admin', 'bearer', 'data', 'decnet.web.api', 'histogram', 'id', 'lastEventId', 'limit', 'logs', 'message', 'must_change_password', 'offset', 'password_hash', 'role', 'stats', 'text/event-stream', 'token', 'token_type', 'total', 'type', 'unihost', 'username', 'uuid']
|
||||
4
.hypothesis/constants/87dce71ef389d477
Normal file
4
.hypothesis/constants/87dce71ef389d477
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/mqtt.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[1883, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'mqtt', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/8b9368be0f77a253
Normal file
4
.hypothesis/constants/8b9368be0f77a253
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/custom_service.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['-', 'LOG_TARGET', 'NODE_NAME', '_', 'command', 'container_name', 'environment', 'image', 'restart', 'unless-stopped']
|
||||
4
.hypothesis/constants/8c9335cb8231944a
Normal file
4
.hypothesis/constants/8c9335cb8231944a
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/docker_api.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[2375, 2376, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'docker_api', 'environment', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/8e330e30c399dccc
Normal file
4
.hypothesis/constants/8e330e30c399dccc
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/real_ssh.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['NET_BIND_SERVICE', 'SSH_HOSTNAME', 'SSH_ROOT_PASSWORD', 'admin', 'build', 'cap_add', 'container_name', 'context', 'environment', 'hostname', 'password', 'real_ssh', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/9193e12e937c9da2
Normal file
4
.hypothesis/constants/9193e12e937c9da2
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/logging/syslog_formatter.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[255, '"', '-', '1', '\\', '\\"', '\\\\', '\\]', ']', 'decnet@55555']
|
||||
4
.hypothesis/constants/933f6b3526e97b62
Normal file
4
.hypothesis/constants/933f6b3526e97b62
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/web/repository.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[]
|
||||
4
.hypothesis/constants/952b61539a326753
Normal file
4
.hypothesis/constants/952b61539a326753
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/ssh.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[2222, ':', 'COWRIE_HOSTNAME', 'COWRIE_SSH_VERSION', 'NET_BIND_SERVICE', 'NODE_NAME', 'build', 'cap_add', 'container_name', 'context', 'cowrie', 'environment', 'hardware_platform', 'kernel_build_string', 'kernel_version', 'restart', 'ssh', 'ssh_banner', 'templates', 'true', 'unless-stopped', 'users']
|
||||
4
.hypothesis/constants/95eb634544ca6000
Normal file
4
.hypothesis/constants/95eb634544ca6000
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/smb.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[139, 445, 'LOG_TARGET', 'NET_BIND_SERVICE', 'NODE_NAME', 'build', 'cap_add', 'container_name', 'context', 'environment', 'restart', 'smb', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/996aa9c745349122
Normal file
4
.hypothesis/constants/996aa9c745349122
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/base.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[]
|
||||
4
.hypothesis/constants/a115dde40ee13bf8
Normal file
4
.hypothesis/constants/a115dde40ee13bf8
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/mysql.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[3306, 'LOG_TARGET', 'MYSQL_VERSION', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'mysql', 'restart', 'templates', 'unless-stopped', 'version']
|
||||
4
.hypothesis/constants/a36433a7a8a46f4d
Normal file
4
.hypothesis/constants/a36433a7a8a46f4d
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/smtp.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[587, 'LOG_TARGET', 'NET_BIND_SERVICE', 'NODE_NAME', 'SMTP_BANNER', 'SMTP_MTA', 'banner', 'build', 'cap_add', 'container_name', 'context', 'environment', 'mta', 'restart', 'smtp', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/a36bdeb88e27cda2
Normal file
4
.hypothesis/constants/a36bdeb88e27cda2
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/postgres.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[5432, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'postgres', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/a4b0cd024dec37b3
Normal file
4
.hypothesis/constants/a4b0cd024dec37b3
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/pop3.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[110, 995, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'pop3', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/a92a9b5d6ef7fbda
Normal file
4
.hypothesis/constants/a92a9b5d6ef7fbda
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/os_fingerprint.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['128', '2', '255', '3', '6', '64', 'bsd', 'cisco', 'embedded', 'linux', 'windows']
|
||||
4
.hypothesis/constants/b0cdd7ca461ac3a7
Normal file
4
.hypothesis/constants/b0cdd7ca461ac3a7
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/k8s.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[6443, 8080, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'k8s', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/b3ae76f264e289ba
Normal file
4
.hypothesis/constants/b3ae76f264e289ba
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/redis.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[6379, 'LOG_TARGET', 'NODE_NAME', 'REDIS_OS', 'REDIS_VERSION', 'build', 'container_name', 'context', 'environment', 'os_string', 'redis', 'restart', 'templates', 'unless-stopped', 'version']
|
||||
4
.hypothesis/constants/b4fbfe7d71d1fde1
Normal file
4
.hypothesis/constants/b4fbfe7d71d1fde1
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/correlation/graph.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[' → ', 'attacker_ip', 'deckies', 'decky', 'decky_count', 'duration_seconds', 'event_type', 'first_seen', 'hop_count', 'hops', 'last_seen', 'path', 'service', 'timestamp']
|
||||
4
.hypothesis/constants/c1bae63b725863f0
Normal file
4
.hypothesis/constants/c1bae63b725863f0
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/imap.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[143, 993, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'imap', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/c604d77c59dde05f
Normal file
4
.hypothesis/constants/c604d77c59dde05f
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/network.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['/', 'add', 'addr', 'bridge', 'decnet_ipvlan0', 'decnet_lan', 'decnet_macvlan0', 'default', 'del', 'dev', 'inet ', 'inet6', 'ip', 'ipvlan', 'ipvlan_mode', 'l2', 'link', 'macvlan', 'mode', 'parent', 'route', 'set', 'show', 'type', 'up', 'via']
|
||||
4
.hypothesis/constants/cc88ec3582943bc7
Normal file
4
.hypothesis/constants/cc88ec3582943bc7
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/distros.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['Alpine Linux 3.19', 'Arch Linux', 'CentOS 7', 'Debian 12 (Bookworm)', 'Fedora 39', 'Kali Linux (Rolling)', 'Rocky Linux 9', 'alpha', 'alpine', 'alpine:3.19', 'arch', 'archlinux:latest', 'backup', 'bravo', 'centos7', 'centos:7', 'charlie', 'db', 'debian', 'debian:bookworm-slim', 'delta', 'dev', 'echo', 'fedora', 'fedora:39', 'files', 'foxtrot', 'generic', 'golf', 'hotel', 'india', 'juliet', 'kali', 'kilo', 'lima', 'mail', 'mike', 'minimal', 'monitor', 'nova', 'oscar', 'prod', 'proxy', 'rhel', 'rocky9', 'rockylinux:9-minimal', 'rolling', 'stage', 'ubuntu20', 'ubuntu22', 'ubuntu:20.04', 'ubuntu:22.04', 'web']
|
||||
4
.hypothesis/constants/ceb1d0465029fa83
Normal file
4
.hypothesis/constants/ceb1d0465029fa83
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/.local/bin/pytest
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
['__main__']
|
||||
4
.hypothesis/constants/da39a3ee5e6b4b0d
Normal file
4
.hypothesis/constants/da39a3ee5e6b4b0d
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/__init__.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[]
|
||||
4
.hypothesis/constants/e04c4b026eeb7e26
Normal file
4
.hypothesis/constants/e04c4b026eeb7e26
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/rdp.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[3389, 'LOG_TARGET', 'NODE_NAME', 'build', 'container_name', 'context', 'environment', 'rdp', 'restart', 'templates', 'unless-stopped']
|
||||
4
.hypothesis/constants/f61b7d1d118bca37
Normal file
4
.hypothesis/constants/f61b7d1d118bca37
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/deployer.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[5.0, ', ', '--build', '--no-cache', '--watch', '-d', '-f', 'DECNET Deckies', 'Decky', 'Deployed Deckies', 'Hostname', 'IP', 'IPvlan', 'IPvlan L2', 'MACVLAN', 'Services', 'Status', '[green]up[/]', '[red]degraded[/]', 'absent', 'bold', 'build', 'cmdline', 'compose', 'decnet-compose.yml', 'decnet.cli', 'decnet.web.api:app', 'docker', 'down', 'green', 'manifest for', 'manifest unknown', 'mutate', 'name', 'not found', 'pid', 'pull access denied', 'red', 'rm', 'running', 'stop', 'up', 'uvicorn']
|
||||
4
.hypothesis/constants/fb7b3bbd8bd7b0f3
Normal file
4
.hypothesis/constants/fb7b3bbd8bd7b0f3
Normal file
@@ -0,0 +1,4 @@
|
||||
# file: /home/anti/Tools/DECNET/decnet/services/conpot.py
|
||||
# hypothesis_version: 6.151.11
|
||||
|
||||
[161, 502, 'CONPOT_TEMPLATE', 'conpot', 'container_name', 'default', 'environment', 'honeynet/conpot', 'image', 'restart', 'unless-stopped']
|
||||
BIN
.hypothesis/unicode_data/16.0.0/charmap.json.gz
Normal file
BIN
.hypothesis/unicode_data/16.0.0/charmap.json.gz
Normal file
Binary file not shown.
BIN
.hypothesis/unicode_data/16.0.0/codec-utf-8.json.gz
Normal file
BIN
.hypothesis/unicode_data/16.0.0/codec-utf-8.json.gz
Normal file
Binary file not shown.
@@ -226,6 +226,8 @@ async def stream_events(
|
||||
request: Request,
|
||||
last_event_id: int = Query(0, alias="lastEventId"),
|
||||
search: Optional[str] = None,
|
||||
start_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None,
|
||||
current_user: str = Depends(get_current_user)
|
||||
) -> StreamingResponse:
|
||||
import json
|
||||
@@ -245,7 +247,7 @@ async def stream_events(
|
||||
break
|
||||
|
||||
# Poll for new logs
|
||||
new_logs = await repo.get_logs_after_id(last_id, limit=50, search=search)
|
||||
new_logs = await repo.get_logs_after_id(last_id, limit=50, search=search, start_time=start_time, end_time=end_time)
|
||||
if new_logs:
|
||||
# Update last_id to the max id in the fetched batch
|
||||
last_id = max(log["id"] for log in new_logs)
|
||||
@@ -260,6 +262,12 @@ async def stream_events(
|
||||
stats = await repo.get_stats_summary()
|
||||
payload = json.dumps({"type": "stats", "data": stats})
|
||||
yield f"event: message\ndata: {payload}\n\n"
|
||||
|
||||
# Also yield histogram
|
||||
histogram = await repo.get_log_histogram(search=search, start_time=start_time, end_time=end_time, interval_minutes=15)
|
||||
hist_payload = json.dumps({"type": "histogram", "data": histogram})
|
||||
yield f"event: message\ndata: {hist_payload}\n\n"
|
||||
|
||||
loops_since_stats = 0
|
||||
|
||||
loops_since_stats += 1
|
||||
|
||||
@@ -12,6 +12,7 @@ class SQLiteRepository(BaseRepository):
|
||||
|
||||
async def initialize(self) -> None:
|
||||
async with aiosqlite.connect(self.db_path) as _db:
|
||||
await _db.execute("PRAGMA journal_mode=WAL")
|
||||
# Logs table
|
||||
await _db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS logs (
|
||||
@@ -82,20 +83,76 @@ class SQLiteRepository(BaseRepository):
|
||||
)
|
||||
await _db.commit()
|
||||
|
||||
def _build_where_clause(
|
||||
self,
|
||||
search: Optional[str] = None,
|
||||
start_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None,
|
||||
base_where: Optional[str] = None,
|
||||
base_params: Optional[list[Any]] = None
|
||||
) -> tuple[str, list[Any]]:
|
||||
import shlex
|
||||
import re
|
||||
|
||||
where_clauses = []
|
||||
params = []
|
||||
|
||||
if base_where:
|
||||
where_clauses.append(base_where)
|
||||
if base_params:
|
||||
params.extend(base_params)
|
||||
|
||||
if start_time:
|
||||
where_clauses.append("timestamp >= ?")
|
||||
params.append(start_time)
|
||||
if end_time:
|
||||
where_clauses.append("timestamp <= ?")
|
||||
params.append(end_time)
|
||||
|
||||
if search:
|
||||
try:
|
||||
tokens = shlex.split(search)
|
||||
except ValueError:
|
||||
tokens = search.split(" ")
|
||||
|
||||
core_fields = {
|
||||
"decky": "decky",
|
||||
"service": "service",
|
||||
"event": "event_type",
|
||||
"attacker": "attacker_ip",
|
||||
"attacker-ip": "attacker_ip",
|
||||
"attacker_ip": "attacker_ip"
|
||||
}
|
||||
|
||||
for token in tokens:
|
||||
if ":" in token:
|
||||
key, val = token.split(":", 1)
|
||||
if key in core_fields:
|
||||
where_clauses.append(f"{core_fields[key]} = ?")
|
||||
params.append(val)
|
||||
else:
|
||||
key_safe = re.sub(r'[^a-zA-Z0-9_]', '', key)
|
||||
where_clauses.append(f"json_extract(fields, '$.{key_safe}') = ?")
|
||||
params.append(val)
|
||||
else:
|
||||
where_clauses.append("(raw_line LIKE ? OR decky LIKE ? OR service LIKE ? OR attacker_ip LIKE ?)")
|
||||
like_val = f"%{token}%"
|
||||
params.extend([like_val, like_val, like_val, like_val])
|
||||
|
||||
if where_clauses:
|
||||
return " WHERE " + " AND ".join(where_clauses), params
|
||||
return "", []
|
||||
|
||||
async def get_logs(
|
||||
self,
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
search: Optional[str] = None
|
||||
search: Optional[str] = None,
|
||||
start_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None
|
||||
) -> list[dict[str, Any]]:
|
||||
_query: str = "SELECT * FROM logs"
|
||||
_params: list[Any] = []
|
||||
if search:
|
||||
_query += " WHERE raw_line LIKE ? OR decky LIKE ? OR service LIKE ? OR attacker_ip LIKE ?"
|
||||
_like_val: str = f"%{search}%"
|
||||
_params.extend([_like_val, _like_val, _like_val, _like_val])
|
||||
|
||||
_query += " ORDER BY timestamp DESC LIMIT ? OFFSET ?"
|
||||
_where, _params = self._build_where_clause(search, start_time, end_time)
|
||||
_query = f"SELECT * FROM logs{_where} ORDER BY timestamp DESC LIMIT ? OFFSET ?"
|
||||
_params.extend([limit, offset])
|
||||
|
||||
async with aiosqlite.connect(self.db_path) as _db:
|
||||
@@ -112,16 +169,16 @@ class SQLiteRepository(BaseRepository):
|
||||
_row: aiosqlite.Row | None = await _cursor.fetchone()
|
||||
return _row["max_id"] if _row and _row["max_id"] is not None else 0
|
||||
|
||||
async def get_logs_after_id(self, last_id: int, limit: int = 50, search: Optional[str] = None) -> list[dict[str, Any]]:
|
||||
_query: str = "SELECT * FROM logs WHERE id > ?"
|
||||
_params: list[Any] = [last_id]
|
||||
|
||||
if search:
|
||||
_query += " AND (raw_line LIKE ? OR decky LIKE ? OR service LIKE ? OR attacker_ip LIKE ?)"
|
||||
_like_val: str = f"%{search}%"
|
||||
_params.extend([_like_val, _like_val, _like_val, _like_val])
|
||||
|
||||
_query += " ORDER BY id ASC LIMIT ?"
|
||||
async def get_logs_after_id(
|
||||
self,
|
||||
last_id: int,
|
||||
limit: int = 50,
|
||||
search: Optional[str] = None,
|
||||
start_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None
|
||||
) -> list[dict[str, Any]]:
|
||||
_where, _params = self._build_where_clause(search, start_time, end_time, base_where="id > ?", base_params=[last_id])
|
||||
_query = f"SELECT * FROM logs{_where} ORDER BY id ASC LIMIT ?"
|
||||
_params.append(limit)
|
||||
|
||||
async with aiosqlite.connect(self.db_path) as _db:
|
||||
@@ -130,13 +187,14 @@ class SQLiteRepository(BaseRepository):
|
||||
_rows: list[aiosqlite.Row] = await _cursor.fetchall()
|
||||
return [dict(_row) for _row in _rows]
|
||||
|
||||
async def get_total_logs(self, search: Optional[str] = None) -> int:
|
||||
_query: str = "SELECT COUNT(*) as total FROM logs"
|
||||
_params: list[Any] = []
|
||||
if search:
|
||||
_query += " WHERE raw_line LIKE ? OR decky LIKE ? OR service LIKE ? OR attacker_ip LIKE ?"
|
||||
_like_val: str = f"%{search}%"
|
||||
_params.extend([_like_val, _like_val, _like_val, _like_val])
|
||||
async def get_total_logs(
|
||||
self,
|
||||
search: Optional[str] = None,
|
||||
start_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None
|
||||
) -> int:
|
||||
_where, _params = self._build_where_clause(search, start_time, end_time)
|
||||
_query = f"SELECT COUNT(*) as total FROM logs{_where}"
|
||||
|
||||
async with aiosqlite.connect(self.db_path) as _db:
|
||||
_db.row_factory = aiosqlite.Row
|
||||
@@ -144,6 +202,36 @@ class SQLiteRepository(BaseRepository):
|
||||
_row: Optional[aiosqlite.Row] = await _cursor.fetchone()
|
||||
return _row["total"] if _row else 0
|
||||
|
||||
async def get_log_histogram(
|
||||
self,
|
||||
search: Optional[str] = None,
|
||||
start_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None,
|
||||
interval_minutes: int = 15
|
||||
) -> list[dict[str, Any]]:
|
||||
# Map interval to sqlite strftime modifiers
|
||||
# Since SQLite doesn't have an easy "bucket by X minutes" natively,
|
||||
# we can do it by grouping by (strftime('%s', timestamp) / (interval_minutes * 60))
|
||||
# and then multiplying back to get the bucket start time.
|
||||
|
||||
_where, _params = self._build_where_clause(search, start_time, end_time)
|
||||
|
||||
_query = f"""
|
||||
SELECT
|
||||
datetime((strftime('%s', timestamp) / {interval_minutes * 60}) * {interval_minutes * 60}, 'unixepoch') as bucket_time,
|
||||
COUNT(*) as count
|
||||
FROM logs
|
||||
{_where}
|
||||
GROUP BY bucket_time
|
||||
ORDER BY bucket_time ASC
|
||||
"""
|
||||
|
||||
async with aiosqlite.connect(self.db_path) as _db:
|
||||
_db.row_factory = aiosqlite.Row
|
||||
async with _db.execute(_query, _params) as _cursor:
|
||||
_rows: list[aiosqlite.Row] = await _cursor.fetchall()
|
||||
return [{"time": _row["bucket_time"], "count": _row["count"]} for _row in _rows]
|
||||
|
||||
async def get_stats_summary(self) -> dict[str, Any]:
|
||||
async with aiosqlite.connect(self.db_path) as _db:
|
||||
_db.row_factory = aiosqlite.Row
|
||||
|
||||
413
decnet_web/package-lock.json
generated
413
decnet_web/package-lock.json
generated
@@ -12,7 +12,8 @@
|
||||
"lucide-react": "^1.7.0",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-router-dom": "^7.14.0"
|
||||
"react-router-dom": "^7.14.0",
|
||||
"recharts": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
@@ -591,6 +592,42 @@
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
|
||||
"integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@standard-schema/utils": "^0.3.0",
|
||||
"immer": "^11.0.0",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"reselect": "^5.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||
"version": "11.1.4",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
|
||||
"integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0-rc.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.13.tgz",
|
||||
@@ -855,6 +892,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@standard-schema/utils": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
@@ -866,6 +915,69 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-ease": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
|
||||
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
||||
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
|
||||
"integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-path": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-timer": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -894,7 +1006,7 @@
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
@@ -910,6 +1022,12 @@
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz",
|
||||
@@ -1437,6 +1555,15 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -1515,9 +1642,130 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
|
||||
"integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-shape": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-path": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-array": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-time": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
@@ -1536,6 +1784,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
@@ -1628,6 +1882,16 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-toolkit": {
|
||||
"version": "1.45.1",
|
||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
|
||||
"integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"docs",
|
||||
"benchmarks"
|
||||
]
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
@@ -1835,6 +2099,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -2146,6 +2416,16 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
|
||||
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
@@ -2173,6 +2453,15 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -2862,6 +3151,36 @@
|
||||
"react": "^19.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
|
||||
"integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.25 || ^19",
|
||||
"react": "^18.0 || ^19",
|
||||
"redux": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz",
|
||||
@@ -2900,6 +3219,57 @@
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz",
|
||||
"integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"www"
|
||||
],
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.9.0 || 2.x.x",
|
||||
"clsx": "^2.1.1",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"es-toolkit": "^1.39.3",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"immer": "^10.1.1",
|
||||
"react-redux": "8.x.x || 9.x.x",
|
||||
"reselect": "5.1.1",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"use-sync-external-store": "^1.2.2",
|
||||
"victory-vendor": "^37.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"redux": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@@ -3032,6 +3402,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
@@ -3169,6 +3545,37 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/victory-vendor": {
|
||||
"version": "37.3.6",
|
||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
|
||||
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
|
||||
"license": "MIT AND ISC",
|
||||
"dependencies": {
|
||||
"@types/d3-array": "^3.0.3",
|
||||
"@types/d3-ease": "^3.0.0",
|
||||
"@types/d3-interpolate": "^3.0.1",
|
||||
"@types/d3-scale": "^4.0.2",
|
||||
"@types/d3-shape": "^3.1.0",
|
||||
"@types/d3-time": "^3.0.0",
|
||||
"@types/d3-timer": "^3.0.0",
|
||||
"d3-array": "^3.1.6",
|
||||
"d3-ease": "^3.0.1",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.1.0",
|
||||
"d3-time": "^3.0.0",
|
||||
"d3-timer": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.7.tgz",
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"lucide-react": "^1.7.0",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-router-dom": "^7.14.0"
|
||||
"react-router-dom": "^7.14.0",
|
||||
"recharts": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
|
||||
@@ -1,17 +1,340 @@
|
||||
import React from 'react';
|
||||
import { Terminal } from 'lucide-react';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
Terminal, Search, Activity,
|
||||
ChevronLeft, ChevronRight, Play, Pause
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell
|
||||
} from 'recharts';
|
||||
import api from '../utils/api';
|
||||
import './Dashboard.css';
|
||||
|
||||
interface LogEntry {
|
||||
id: number;
|
||||
timestamp: string;
|
||||
decky: string;
|
||||
service: string;
|
||||
event_type: string;
|
||||
attacker_ip: string;
|
||||
raw_line: string;
|
||||
fields: string;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
interface HistogramData {
|
||||
time: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
const LiveLogs: React.FC = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
// URL-synced state
|
||||
const query = searchParams.get('q') || '';
|
||||
const timeRange = searchParams.get('time') || '1h';
|
||||
const isLive = searchParams.get('live') !== 'false';
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
|
||||
// Local state
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
const [histogram, setHistogram] = useState<HistogramData[]>([]);
|
||||
const [totalLogs, setTotalLogs] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [streaming, setStreaming] = useState(isLive);
|
||||
const [searchInput, setSearchInput] = useState(query);
|
||||
|
||||
const eventSourceRef = useRef<EventSource | null>(null);
|
||||
const limit = 50;
|
||||
|
||||
// Sync search input if URL changes (e.g. back button)
|
||||
useEffect(() => {
|
||||
setSearchInput(query);
|
||||
}, [query]);
|
||||
|
||||
const fetchData = async () => {
|
||||
if (streaming) return; // Don't fetch historical if streaming
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const offset = (page - 1) * limit;
|
||||
let url = `/logs?limit=${limit}&offset=${offset}&search=${encodeURIComponent(query)}`;
|
||||
|
||||
// Calculate time bounds for historical fetch
|
||||
const now = new Date();
|
||||
let startTime: string | null = null;
|
||||
if (timeRange !== 'all') {
|
||||
const minutes = timeRange === '15m' ? 15 : timeRange === '1h' ? 60 : timeRange === '24h' ? 1440 : 0;
|
||||
if (minutes > 0) {
|
||||
startTime = new Date(now.getTime() - minutes * 60000).toISOString();
|
||||
url += `&start_time=${startTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await api.get(url);
|
||||
setLogs(res.data.data);
|
||||
setTotalLogs(res.data.total);
|
||||
|
||||
// Fetch histogram for historical view
|
||||
const histUrl = `/logs/histogram?search=${encodeURIComponent(query)}` + (startTime ? `&start_time=${startTime}` : '');
|
||||
const histRes = await api.get(histUrl);
|
||||
setHistogram(histRes.data);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch historical logs', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const setupSSE = () => {
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close();
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000/api/v1';
|
||||
let url = `${baseUrl}/stream?token=${token}&search=${encodeURIComponent(query)}`;
|
||||
|
||||
if (timeRange !== 'all') {
|
||||
const minutes = timeRange === '15m' ? 15 : timeRange === '1h' ? 60 : timeRange === '24h' ? 1440 : 0;
|
||||
if (minutes > 0) {
|
||||
const startTime = new Date(Date.now() - minutes * 60000).toISOString();
|
||||
url += `&start_time=${startTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
const es = new EventSource(url);
|
||||
eventSourceRef.current = es;
|
||||
|
||||
es.onmessage = (event) => {
|
||||
try {
|
||||
const payload = JSON.parse(event.data);
|
||||
if (payload.type === 'logs') {
|
||||
setLogs(prev => [...payload.data, ...prev].slice(0, 500));
|
||||
} else if (payload.type === 'histogram') {
|
||||
setHistogram(payload.data);
|
||||
} else if (payload.type === 'stats') {
|
||||
setTotalLogs(payload.data.total_logs);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to parse SSE payload', err);
|
||||
}
|
||||
};
|
||||
|
||||
es.onerror = () => {
|
||||
console.error('SSE connection lost, reconnecting...');
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (streaming) {
|
||||
setupSSE();
|
||||
setLoading(false);
|
||||
} else {
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close();
|
||||
eventSourceRef.current = null;
|
||||
}
|
||||
fetchData();
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close();
|
||||
}
|
||||
};
|
||||
}, [query, timeRange, streaming, page]);
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSearchParams({ q: searchInput, time: timeRange, live: streaming.toString(), page: '1' });
|
||||
};
|
||||
|
||||
const handleToggleLive = () => {
|
||||
const newStreaming = !streaming;
|
||||
setStreaming(newStreaming);
|
||||
setSearchParams({ q: query, time: timeRange, live: newStreaming.toString(), page: '1' });
|
||||
};
|
||||
|
||||
const handleTimeChange = (newTime: string) => {
|
||||
setSearchParams({ q: query, time: newTime, live: streaming.toString(), page: '1' });
|
||||
};
|
||||
|
||||
const changePage = (newPage: number) => {
|
||||
setSearchParams({ q: query, time: timeRange, live: 'false', page: newPage.toString() });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="logs-section">
|
||||
<div className="section-header">
|
||||
<Terminal size={20} />
|
||||
<h2>FULL LIVE LOG STREAM</h2>
|
||||
<div className="dashboard">
|
||||
{/* Control Bar */}
|
||||
<div className="logs-section" style={{ border: 'none', background: 'transparent', padding: 0 }}>
|
||||
<form onSubmit={handleSearch} style={{ display: 'flex', gap: '16px', marginBottom: '24px' }}>
|
||||
<div className="search-container" style={{ flexGrow: 1, maxWidth: 'none' }}>
|
||||
<Search className="search-icon" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Query logs (e.g. decky:decky-01 service:http attacker:192.168.1.5 status:failed)"
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
value={timeRange}
|
||||
onChange={(e) => handleTimeChange(e.target.value)}
|
||||
className="search-container"
|
||||
style={{ width: 'auto', color: 'var(--text-color)', cursor: 'pointer' }}
|
||||
>
|
||||
<option value="15m">LAST 15 MIN</option>
|
||||
<option value="1h">LAST 1 HOUR</option>
|
||||
<option value="24h">LAST 24 HOURS</option>
|
||||
<option value="all">ALL TIME</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggleLive}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: '8px',
|
||||
border: `1px solid ${streaming ? 'var(--text-color)' : 'var(--border-color)'}`,
|
||||
color: streaming ? 'var(--text-color)' : 'var(--dim-color)',
|
||||
minWidth: '120px', justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
{streaming ? <><Play size={14} className="neon-blink" /> LIVE</> : <><Pause size={14} /> PAUSED</>}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div style={{ padding: '40px', textAlign: 'center', opacity: 0.5 }}>
|
||||
<p>STREAM ESTABLISHED. WAITING FOR INCOMING DATA...</p>
|
||||
<p style={{ marginTop: '10px', fontSize: '0.8rem' }}>(Dedicated Live Logs view placeholder)</p>
|
||||
|
||||
{/* Histogram Chart */}
|
||||
<div className="logs-section" style={{ height: '200px', padding: '20px', marginBottom: '24px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.7rem', color: 'var(--dim-color)' }}>
|
||||
<Activity size={12} /> ATTACK VOLUME OVER TIME
|
||||
</div>
|
||||
<div style={{ fontSize: '0.7rem', color: 'var(--text-color)' }}>
|
||||
MATCHES: {totalLogs.toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={histogram}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#30363d" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
hide
|
||||
/>
|
||||
<YAxis
|
||||
stroke="#30363d"
|
||||
fontSize={10}
|
||||
tickFormatter={(val) => Math.floor(val).toString()}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{ backgroundColor: '#0d1117', border: '1px solid #30363d', fontSize: '0.8rem' }}
|
||||
itemStyle={{ color: 'var(--text-color)' }}
|
||||
labelStyle={{ color: 'var(--dim-color)', marginBottom: '4px' }}
|
||||
cursor={{ fill: 'rgba(0, 255, 65, 0.05)' }}
|
||||
/>
|
||||
<Bar dataKey="count" fill="var(--text-color)" radius={[2, 2, 0, 0]}>
|
||||
{histogram.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fillOpacity={0.6 + (entry.count / (Math.max(...histogram.map(h => h.count)) || 1)) * 0.4} />
|
||||
))}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
{/* Logs Table */}
|
||||
<div className="logs-section">
|
||||
<div className="section-header" style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Terminal size={20} />
|
||||
<h2>LOG EXPLORER</h2>
|
||||
</div>
|
||||
{!streaming && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||
<span className="dim" style={{ fontSize: '0.8rem' }}>
|
||||
Page {page} of {Math.ceil(totalLogs / limit)}
|
||||
</span>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button
|
||||
disabled={page === 1}
|
||||
onClick={() => changePage(page - 1)}
|
||||
style={{ padding: '4px', border: '1px solid var(--border-color)', opacity: page === 1 ? 0.3 : 1 }}
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
</button>
|
||||
<button
|
||||
disabled={page >= Math.ceil(totalLogs / limit)}
|
||||
onClick={() => changePage(page + 1)}
|
||||
style={{ padding: '4px', border: '1px solid var(--border-color)', opacity: page >= Math.ceil(totalLogs / limit) ? 0.3 : 1 }}
|
||||
>
|
||||
<ChevronRight size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="logs-table-container">
|
||||
<table className="logs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>TIMESTAMP</th>
|
||||
<th>DECKY</th>
|
||||
<th>SERVICE</th>
|
||||
<th>ATTACKER</th>
|
||||
<th>EVENT</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{logs.length > 0 ? logs.map(log => {
|
||||
let parsedFields: Record<string, any> = {};
|
||||
if (log.fields) {
|
||||
try {
|
||||
parsedFields = JSON.parse(log.fields);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={log.id}>
|
||||
<td className="dim" style={{ fontSize: '0.75rem', whiteSpace: 'nowrap' }}>{new Date(log.timestamp).toLocaleString()}</td>
|
||||
<td className="violet-accent">{log.decky}</td>
|
||||
<td className="matrix-text">{log.service}</td>
|
||||
<td>{log.attacker_ip}</td>
|
||||
<td>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
<div style={{ fontWeight: 'bold', color: 'var(--text-color)', fontSize: '0.9rem' }}>
|
||||
{log.event_type} {log.msg && log.msg !== '-' && <span style={{ fontWeight: 'normal', opacity: 0.8 }}>— {log.msg}</span>}
|
||||
</div>
|
||||
{Object.keys(parsedFields).length > 0 && (
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||
{Object.entries(parsedFields).map(([k, v]) => (
|
||||
<span key={k} style={{
|
||||
fontSize: '0.7rem',
|
||||
backgroundColor: 'rgba(0, 255, 65, 0.1)',
|
||||
padding: '2px 8px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid rgba(0, 255, 65, 0.3)',
|
||||
wordBreak: 'break-all'
|
||||
}}>
|
||||
<span style={{ opacity: 0.6 }}>{k}:</span> {typeof v === 'object' ? JSON.stringify(v) : v}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}) : (
|
||||
<tr>
|
||||
<td colSpan={5} style={{ textAlign: 'center', padding: '40px', opacity: 0.5 }}>
|
||||
{loading ? 'RETRIEVING DATA...' : 'NO LOGS MATCHING CRITERIA'}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user