diff --git a/.hypothesis/constants/0dde2bfda6648a83 b/.hypothesis/constants/0dde2bfda6648a83 new file mode 100644 index 0000000..70b9640 --- /dev/null +++ b/.hypothesis/constants/0dde2bfda6648a83 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/0ff817caa72a52a4 b/.hypothesis/constants/0ff817caa72a52a4 new file mode 100644 index 0000000..5254387 --- /dev/null +++ b/.hypothesis/constants/0ff817caa72a52a4 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/15d50d1e53b9b5c3 b/.hypothesis/constants/15d50d1e53b9b5c3 new file mode 100644 index 0000000..ea03552 --- /dev/null +++ b/.hypothesis/constants/15d50d1e53b9b5c3 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/19d5adc9efd5ec68 b/.hypothesis/constants/19d5adc9efd5ec68 new file mode 100644 index 0000000..372bfa2 --- /dev/null +++ b/.hypothesis/constants/19d5adc9efd5ec68 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/1f005a833d034313 b/.hypothesis/constants/1f005a833d034313 new file mode 100644 index 0000000..4df4a4b --- /dev/null +++ b/.hypothesis/constants/1f005a833d034313 @@ -0,0 +1,4 @@ +# file: /home/anti/Tools/DECNET/decnet/logging/forwarder.py +# hypothesis_version: 6.151.11 + +[2.0, ':'] \ No newline at end of file diff --git a/.hypothesis/constants/1f12b014d4fe2068 b/.hypothesis/constants/1f12b014d4fe2068 new file mode 100644 index 0000000..4ae687e --- /dev/null +++ b/.hypothesis/constants/1f12b014d4fe2068 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/2220ccbe8a25f02d b/.hypothesis/constants/2220ccbe8a25f02d new file mode 100644 index 0000000..aa9c846 --- /dev/null +++ b/.hypothesis/constants/2220ccbe8a25f02d @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/2f0b53ebdb35c4e1 b/.hypothesis/constants/2f0b53ebdb35c4e1 new file mode 100644 index 0000000..6fc4732 --- /dev/null +++ b/.hypothesis/constants/2f0b53ebdb35c4e1 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/3048c6da87ba838d b/.hypothesis/constants/3048c6da87ba838d new file mode 100644 index 0000000..18ba47e --- /dev/null +++ b/.hypothesis/constants/3048c6da87ba838d @@ -0,0 +1,4 @@ +# file: /home/anti/Tools/DECNET/decnet/web/auth.py +# hypothesis_version: 6.151.11 + +[1440, 'HS256', 'exp', 'iat', 'utf-8'] \ No newline at end of file diff --git a/.hypothesis/constants/30a7ffe86227f7f1 b/.hypothesis/constants/30a7ffe86227f7f1 new file mode 100644 index 0000000..fa23399 --- /dev/null +++ b/.hypothesis/constants/30a7ffe86227f7f1 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/349ec22a74b50191 b/.hypothesis/constants/349ec22a74b50191 new file mode 100644 index 0000000..8399e32 --- /dev/null +++ b/.hypothesis/constants/349ec22a74b50191 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/37d6bf6c6c0b58e6 b/.hypothesis/constants/37d6bf6c6c0b58e6 new file mode 100644 index 0000000..f20a315 --- /dev/null +++ b/.hypothesis/constants/37d6bf6c6c0b58e6 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/3cc47bb868bcb8f4 b/.hypothesis/constants/3cc47bb868bcb8f4 new file mode 100644 index 0000000..7cdc792 --- /dev/null +++ b/.hypothesis/constants/3cc47bb868bcb8f4 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/409656273e7d498b b/.hypothesis/constants/409656273e7d498b new file mode 100644 index 0000000..70b9640 --- /dev/null +++ b/.hypothesis/constants/409656273e7d498b @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/42a1dcb5c22b1ac1 b/.hypothesis/constants/42a1dcb5c22b1ac1 new file mode 100644 index 0000000..3aba509 --- /dev/null +++ b/.hypothesis/constants/42a1dcb5c22b1ac1 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/4b12b89e1879f5ab b/.hypothesis/constants/4b12b89e1879f5ab new file mode 100644 index 0000000..16932ef --- /dev/null +++ b/.hypothesis/constants/4b12b89e1879f5ab @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/4efedb0b38145ee9 b/.hypothesis/constants/4efedb0b38145ee9 new file mode 100644 index 0000000..b1c7a57 --- /dev/null +++ b/.hypothesis/constants/4efedb0b38145ee9 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/4f57659a52de3fc8 b/.hypothesis/constants/4f57659a52de3fc8 new file mode 100644 index 0000000..9583d59 --- /dev/null +++ b/.hypothesis/constants/4f57659a52de3fc8 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/507a3145954fca93 b/.hypothesis/constants/507a3145954fca93 new file mode 100644 index 0000000..59e91a6 --- /dev/null +++ b/.hypothesis/constants/507a3145954fca93 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/53a42446f9f19b20 b/.hypothesis/constants/53a42446f9f19b20 new file mode 100644 index 0000000..d8c1242 --- /dev/null +++ b/.hypothesis/constants/53a42446f9f19b20 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/574dbe54f9b23d3e b/.hypothesis/constants/574dbe54f9b23d3e new file mode 100644 index 0000000..0d41063 --- /dev/null +++ b/.hypothesis/constants/574dbe54f9b23d3e @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/62e387790ed5b79f b/.hypothesis/constants/62e387790ed5b79f new file mode 100644 index 0000000..09cb0cb --- /dev/null +++ b/.hypothesis/constants/62e387790ed5b79f @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/66bd79275cd609e8 b/.hypothesis/constants/66bd79275cd609e8 new file mode 100644 index 0000000..21e2f78 --- /dev/null +++ b/.hypothesis/constants/66bd79275cd609e8 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/76302489300fdc45 b/.hypothesis/constants/76302489300fdc45 new file mode 100644 index 0000000..68be2a1 --- /dev/null +++ b/.hypothesis/constants/76302489300fdc45 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/77b4b42ea3b9c9bf b/.hypothesis/constants/77b4b42ea3b9c9bf new file mode 100644 index 0000000..dbaf534 --- /dev/null +++ b/.hypothesis/constants/77b4b42ea3b9c9bf @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/79661beef79449a5 b/.hypothesis/constants/79661beef79449a5 new file mode 100644 index 0000000..427d19f --- /dev/null +++ b/.hypothesis/constants/79661beef79449a5 @@ -0,0 +1,4 @@ +# file: /home/anti/Tools/DECNET/decnet/services/registry.py +# hypothesis_version: 6.151.11 + +['base', 'decnet.services.', 'registry'] \ No newline at end of file diff --git a/.hypothesis/constants/7f9302a54093ce41 b/.hypothesis/constants/7f9302a54093ce41 new file mode 100644 index 0000000..18fa665 --- /dev/null +++ b/.hypothesis/constants/7f9302a54093ce41 @@ -0,0 +1,4 @@ +# file: /home/anti/Tools/DECNET/decnet/correlation/__init__.py +# hypothesis_version: 6.151.11 + +['AttackerTraversal', 'CorrelationEngine', 'LogEvent', 'TraversalHop', 'parse_line'] \ No newline at end of file diff --git a/.hypothesis/constants/8029f0494746966f b/.hypothesis/constants/8029f0494746966f new file mode 100644 index 0000000..f4746df --- /dev/null +++ b/.hypothesis/constants/8029f0494746966f @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/8647241ce03e8b57 b/.hypothesis/constants/8647241ce03e8b57 new file mode 100644 index 0000000..0add520 --- /dev/null +++ b/.hypothesis/constants/8647241ce03e8b57 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/87dce71ef389d477 b/.hypothesis/constants/87dce71ef389d477 new file mode 100644 index 0000000..795c72e --- /dev/null +++ b/.hypothesis/constants/87dce71ef389d477 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/8b9368be0f77a253 b/.hypothesis/constants/8b9368be0f77a253 new file mode 100644 index 0000000..c01b6c8 --- /dev/null +++ b/.hypothesis/constants/8b9368be0f77a253 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/8c9335cb8231944a b/.hypothesis/constants/8c9335cb8231944a new file mode 100644 index 0000000..40e2302 --- /dev/null +++ b/.hypothesis/constants/8c9335cb8231944a @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/8e330e30c399dccc b/.hypothesis/constants/8e330e30c399dccc new file mode 100644 index 0000000..7b9b106 --- /dev/null +++ b/.hypothesis/constants/8e330e30c399dccc @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/9193e12e937c9da2 b/.hypothesis/constants/9193e12e937c9da2 new file mode 100644 index 0000000..3481ac4 --- /dev/null +++ b/.hypothesis/constants/9193e12e937c9da2 @@ -0,0 +1,4 @@ +# file: /home/anti/Tools/DECNET/decnet/logging/syslog_formatter.py +# hypothesis_version: 6.151.11 + +[255, '"', '-', '1', '\\', '\\"', '\\\\', '\\]', ']', 'decnet@55555'] \ No newline at end of file diff --git a/.hypothesis/constants/933f6b3526e97b62 b/.hypothesis/constants/933f6b3526e97b62 new file mode 100644 index 0000000..275bac0 --- /dev/null +++ b/.hypothesis/constants/933f6b3526e97b62 @@ -0,0 +1,4 @@ +# file: /home/anti/Tools/DECNET/decnet/web/repository.py +# hypothesis_version: 6.151.11 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/952b61539a326753 b/.hypothesis/constants/952b61539a326753 new file mode 100644 index 0000000..8eeb44a --- /dev/null +++ b/.hypothesis/constants/952b61539a326753 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/95eb634544ca6000 b/.hypothesis/constants/95eb634544ca6000 new file mode 100644 index 0000000..7b49590 --- /dev/null +++ b/.hypothesis/constants/95eb634544ca6000 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/996aa9c745349122 b/.hypothesis/constants/996aa9c745349122 new file mode 100644 index 0000000..5f2dc31 --- /dev/null +++ b/.hypothesis/constants/996aa9c745349122 @@ -0,0 +1,4 @@ +# file: /home/anti/Tools/DECNET/decnet/services/base.py +# hypothesis_version: 6.151.11 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/a115dde40ee13bf8 b/.hypothesis/constants/a115dde40ee13bf8 new file mode 100644 index 0000000..6029563 --- /dev/null +++ b/.hypothesis/constants/a115dde40ee13bf8 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/a36433a7a8a46f4d b/.hypothesis/constants/a36433a7a8a46f4d new file mode 100644 index 0000000..18ab98e --- /dev/null +++ b/.hypothesis/constants/a36433a7a8a46f4d @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/a36bdeb88e27cda2 b/.hypothesis/constants/a36bdeb88e27cda2 new file mode 100644 index 0000000..632b00a --- /dev/null +++ b/.hypothesis/constants/a36bdeb88e27cda2 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/a4b0cd024dec37b3 b/.hypothesis/constants/a4b0cd024dec37b3 new file mode 100644 index 0000000..33a906a --- /dev/null +++ b/.hypothesis/constants/a4b0cd024dec37b3 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/a92a9b5d6ef7fbda b/.hypothesis/constants/a92a9b5d6ef7fbda new file mode 100644 index 0000000..11589ce --- /dev/null +++ b/.hypothesis/constants/a92a9b5d6ef7fbda @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/b0cdd7ca461ac3a7 b/.hypothesis/constants/b0cdd7ca461ac3a7 new file mode 100644 index 0000000..a12233e --- /dev/null +++ b/.hypothesis/constants/b0cdd7ca461ac3a7 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/b3ae76f264e289ba b/.hypothesis/constants/b3ae76f264e289ba new file mode 100644 index 0000000..be51ca8 --- /dev/null +++ b/.hypothesis/constants/b3ae76f264e289ba @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/b4fbfe7d71d1fde1 b/.hypothesis/constants/b4fbfe7d71d1fde1 new file mode 100644 index 0000000..2250f94 --- /dev/null +++ b/.hypothesis/constants/b4fbfe7d71d1fde1 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/c1bae63b725863f0 b/.hypothesis/constants/c1bae63b725863f0 new file mode 100644 index 0000000..2a9531f --- /dev/null +++ b/.hypothesis/constants/c1bae63b725863f0 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/c604d77c59dde05f b/.hypothesis/constants/c604d77c59dde05f new file mode 100644 index 0000000..5f7d4fe --- /dev/null +++ b/.hypothesis/constants/c604d77c59dde05f @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/cc88ec3582943bc7 b/.hypothesis/constants/cc88ec3582943bc7 new file mode 100644 index 0000000..a0c1c88 --- /dev/null +++ b/.hypothesis/constants/cc88ec3582943bc7 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/ceb1d0465029fa83 b/.hypothesis/constants/ceb1d0465029fa83 new file mode 100644 index 0000000..962a59c --- /dev/null +++ b/.hypothesis/constants/ceb1d0465029fa83 @@ -0,0 +1,4 @@ +# file: /home/anti/.local/bin/pytest +# hypothesis_version: 6.151.11 + +['__main__'] \ No newline at end of file diff --git a/.hypothesis/constants/da39a3ee5e6b4b0d b/.hypothesis/constants/da39a3ee5e6b4b0d new file mode 100644 index 0000000..62b7279 --- /dev/null +++ b/.hypothesis/constants/da39a3ee5e6b4b0d @@ -0,0 +1,4 @@ +# file: /home/anti/Tools/DECNET/decnet/__init__.py +# hypothesis_version: 6.151.11 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/e04c4b026eeb7e26 b/.hypothesis/constants/e04c4b026eeb7e26 new file mode 100644 index 0000000..dfad0cb --- /dev/null +++ b/.hypothesis/constants/e04c4b026eeb7e26 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/f61b7d1d118bca37 b/.hypothesis/constants/f61b7d1d118bca37 new file mode 100644 index 0000000..caadac3 --- /dev/null +++ b/.hypothesis/constants/f61b7d1d118bca37 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/constants/fb7b3bbd8bd7b0f3 b/.hypothesis/constants/fb7b3bbd8bd7b0f3 new file mode 100644 index 0000000..3749001 --- /dev/null +++ b/.hypothesis/constants/fb7b3bbd8bd7b0f3 @@ -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'] \ No newline at end of file diff --git a/.hypothesis/unicode_data/16.0.0/charmap.json.gz b/.hypothesis/unicode_data/16.0.0/charmap.json.gz new file mode 100644 index 0000000..1bfee5c Binary files /dev/null and b/.hypothesis/unicode_data/16.0.0/charmap.json.gz differ diff --git a/.hypothesis/unicode_data/16.0.0/codec-utf-8.json.gz b/.hypothesis/unicode_data/16.0.0/codec-utf-8.json.gz new file mode 100644 index 0000000..a73d774 Binary files /dev/null and b/.hypothesis/unicode_data/16.0.0/codec-utf-8.json.gz differ diff --git a/decnet/web/api.py b/decnet/web/api.py index 4c4a155..4195feb 100644 --- a/decnet/web/api.py +++ b/decnet/web/api.py @@ -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 diff --git a/decnet/web/sqlite_repository.py b/decnet/web/sqlite_repository.py index 45dc3c7..8cfc65c 100644 --- a/decnet/web/sqlite_repository.py +++ b/decnet/web/sqlite_repository.py @@ -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 diff --git a/decnet_web/package-lock.json b/decnet_web/package-lock.json index 1f29ab1..913bd2a 100644 --- a/decnet_web/package-lock.json +++ b/decnet_web/package-lock.json @@ -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", diff --git a/decnet_web/package.json b/decnet_web/package.json index 5fdd23f..4e82f89 100644 --- a/decnet_web/package.json +++ b/decnet_web/package.json @@ -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", diff --git a/decnet_web/src/components/LiveLogs.tsx b/decnet_web/src/components/LiveLogs.tsx index 0df96f5..7dd44e1 100644 --- a/decnet_web/src/components/LiveLogs.tsx +++ b/decnet_web/src/components/LiveLogs.tsx @@ -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([]); + const [histogram, setHistogram] = useState([]); + const [totalLogs, setTotalLogs] = useState(0); + const [loading, setLoading] = useState(true); + const [streaming, setStreaming] = useState(isLive); + const [searchInput, setSearchInput] = useState(query); + + const eventSourceRef = useRef(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 ( -
-
- -

FULL LIVE LOG STREAM

+
+ {/* Control Bar */} +
+
+
+ + setSearchInput(e.target.value)} + /> +
+ + +
-
-

STREAM ESTABLISHED. WAITING FOR INCOMING DATA...

-

(Dedicated Live Logs view placeholder)

+ + {/* Histogram Chart */} +
+
+
+ ATTACK VOLUME OVER TIME +
+
+ MATCHES: {totalLogs.toLocaleString()} +
+
+ + + + + Math.floor(val).toString()} + /> + + + {histogram.map((entry, index) => ( + h.count)) || 1)) * 0.4} /> + ))} + + + +
+ + {/* Logs Table */} +
+
+
+ +

LOG EXPLORER

+
+ {!streaming && ( +
+ + Page {page} of {Math.ceil(totalLogs / limit)} + +
+ + +
+
+ )} +
+ +
+ + + + + + + + + + + + {logs.length > 0 ? logs.map(log => { + let parsedFields: Record = {}; + if (log.fields) { + try { + parsedFields = JSON.parse(log.fields); + } catch (e) {} + } + + return ( + + + + + + + + ); + }) : ( + + + + )} + +
TIMESTAMPDECKYSERVICEATTACKEREVENT
{new Date(log.timestamp).toLocaleString()}{log.decky}{log.service}{log.attacker_ip} +
+
+ {log.event_type} {log.msg && log.msg !== '-' && — {log.msg}} +
+ {Object.keys(parsedFields).length > 0 && ( +
+ {Object.entries(parsedFields).map(([k, v]) => ( + + {k}: {typeof v === 'object' ? JSON.stringify(v) : v} + + ))} +
+ )} +
+
+ {loading ? 'RETRIEVING DATA...' : 'NO LOGS MATCHING CRITERIA'} +
+
);