Hitan2004 commited on
Commit
9aed943
·
verified ·
1 Parent(s): d3e88ae

Delete frontend/index.html

Browse files
Files changed (1) hide show
  1. frontend/index.html +0 -1176
frontend/index.html DELETED
@@ -1,1176 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8"/>
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
- <title>SentinelNet — Live Threat Monitor</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
- <style>
10
- *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
11
- :root{
12
- --bg:#04080d;--bg2:#080e16;--bg3:#0d1520;
13
- --surface:#101d2e;--surface2:#162438;
14
- --border:rgba(0,210,130,0.08);--border2:rgba(0,210,130,0.18);--border3:rgba(0,210,130,0.35);
15
- --accent:#00e87a;--cyan:#00c8e8;--red:#ff3d5a;--amber:#ffaa00;--purple:#b06fff;
16
- --text:#d8eeff;--muted:#4a6a88;--muted2:#7a9ab8;
17
- --mono:'IBM Plex Mono',monospace;--sans:'Space Grotesk',sans-serif;
18
- --glow-green:0 0 20px rgba(0,232,122,0.25);
19
- }
20
- html{scroll-behavior:smooth}
21
- body{background:var(--bg);color:var(--text);font-family:var(--sans);min-height:100vh;overflow-x:hidden}
22
- body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:0;background:radial-gradient(ellipse 60% 40% at 10% 20%,rgba(0,232,122,0.04) 0%,transparent 60%),radial-gradient(ellipse 50% 50% at 90% 80%,rgba(0,200,232,0.03) 0%,transparent 60%),repeating-linear-gradient(0deg,transparent,transparent 40px,rgba(0,210,130,0.015) 40px,rgba(0,210,130,0.015) 41px),repeating-linear-gradient(90deg,transparent,transparent 40px,rgba(0,210,130,0.015) 40px,rgba(0,210,130,0.015) 41px)}
23
- .app{position:relative;z-index:1;max-width:1500px;margin:0 auto;padding:0 24px 80px}
24
- header{display:flex;align-items:center;justify-content:space-between;padding:20px 0;border-bottom:1px solid var(--border2);margin-bottom:0;position:relative}
25
- header::after{content:'';position:absolute;bottom:-1px;left:0;width:200px;height:1px;background:linear-gradient(90deg,var(--accent),transparent)}
26
- .logo{display:flex;align-items:center;gap:14px}
27
- .logo-shield{width:44px;height:44px;border-radius:12px;background:linear-gradient(135deg,#001a0d,#003020);border:1px solid var(--border3);display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:var(--glow-green)}
28
- .logo-name{font-family:var(--mono);font-size:20px;font-weight:700;color:var(--accent);letter-spacing:-0.5px}
29
- .logo-tag{font-size:10px;color:var(--muted);letter-spacing:3px;text-transform:uppercase;margin-top:3px;font-family:var(--mono)}
30
- .header-right{display:flex;align-items:center;gap:14px}
31
- .live-badge{display:flex;align-items:center;gap:8px;padding:7px 16px;border-radius:999px;border:1px solid var(--border2);background:rgba(0,232,122,0.05);font-family:var(--mono);font-size:11px;color:var(--accent)}
32
- .dot{width:7px;height:7px;border-radius:50%;background:var(--accent);animation:blink 1.4s ease-in-out infinite;flex-shrink:0}
33
- .dot.red{background:var(--red);animation:none;box-shadow:0 0 6px var(--red)}
34
- .dot.amber{background:var(--amber);box-shadow:0 0 6px var(--amber)}
35
- .dot.green{background:var(--accent);box-shadow:0 0 8px var(--accent)}
36
- @keyframes blink{0%,100%{opacity:1}50%{opacity:0.2}}
37
- #sessionClock{font-family:var(--mono);font-size:11px;color:var(--muted2);background:var(--surface);padding:6px 12px;border-radius:6px;border:1px solid var(--border)}
38
- .tab-nav{display:flex;border-bottom:1px solid var(--border2);margin-bottom:28px}
39
- .tab-btn{padding:16px 32px;font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:2px;text-transform:uppercase;color:var(--muted);background:transparent;border:none;cursor:pointer;transition:color .2s;border-bottom:2px solid transparent;margin-bottom:-1px}
40
- .tab-btn:hover{color:var(--muted2)}
41
- .tab-btn.active{color:var(--accent);border-bottom:2px solid var(--accent)}
42
- .tab-btn .tab-icon{margin-right:8px}
43
- .tab-pane{display:none}
44
- .tab-pane.active{display:block;animation:fadeIn .25s ease}
45
- @keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
46
- .panel{background:var(--bg2);border:1px solid var(--border);border-radius:14px;overflow:hidden}
47
- .panel-head{padding:12px 18px;font-family:var(--mono);font-size:10px;color:var(--muted);border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;letter-spacing:1.5px;text-transform:uppercase;background:rgba(0,0,0,0.2)}
48
- .panel-head .accent{color:var(--accent)}.panel-head .red{color:var(--red)}.panel-head .cyan{color:var(--cyan)}.panel-head .amber{color:var(--amber)}
49
- .config-strip{display:flex;gap:10px;align-items:center;background:var(--bg2);border:1px solid var(--border2);border-radius:12px;padding:14px 18px;margin-bottom:22px;flex-wrap:wrap}
50
- .speed-wrap{display:flex;align-items:center;gap:8px;font-family:var(--mono);font-size:11px;color:var(--muted)}
51
- .speed-wrap select{background:var(--surface);border:1px solid var(--border2);border-radius:7px;color:var(--cyan);font-family:var(--mono);font-size:11px;padding:7px 12px;outline:none;cursor:pointer}
52
- .btn{padding:8px 18px;border-radius:8px;font-family:var(--mono);font-size:11px;font-weight:700;cursor:pointer;border:none;transition:all .2s;white-space:nowrap;letter-spacing:.5px}
53
- .btn-start{background:rgba(0,232,122,0.12);color:var(--accent);border:1px solid rgba(0,232,122,0.3)}
54
- .btn-start:hover:not(:disabled){background:rgba(0,232,122,0.22);box-shadow:var(--glow-green)}
55
- .btn-start:disabled{opacity:.4;cursor:not-allowed}
56
- .btn-stop{background:rgba(255,61,90,0.1);color:var(--red);border:1px solid rgba(255,61,90,0.25)}
57
- .btn-stop:hover:not(:disabled){background:rgba(255,61,90,0.2)}
58
- .btn-stop:disabled{opacity:.4;cursor:not-allowed}
59
- .btn-clear{background:var(--surface);color:var(--muted2);border:1px solid var(--border)}
60
- .btn-clear:hover{color:var(--text);border-color:var(--border2)}
61
- #connBadge{font-family:var(--mono);font-size:11px;padding:6px 14px;border-radius:7px;white-space:nowrap;transition:all .3s}
62
- #connBadge.real{background:rgba(0,232,122,0.1);color:var(--accent);border:1px solid rgba(0,232,122,0.2)}
63
- #connBadge.local{background:rgba(255,170,0,0.1);color:var(--amber);border:1px solid rgba(255,170,0,0.2)}
64
- #connBadge.idle{background:var(--surface);color:var(--muted);border:1px solid var(--border)}
65
- .metrics{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:22px}
66
- @media(max-width:1000px){.metrics{grid-template-columns:repeat(3,1fr)}}
67
- .mc{background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:18px 16px;position:relative;overflow:hidden;transition:border-color .3s}
68
- .mc.flash{border-color:var(--accent);animation:mcflash .6s ease-out}
69
- @keyframes mcflash{0%{box-shadow:0 0 16px rgba(0,232,122,0.5)}100%{box-shadow:none}}
70
- .mc-label{font-family:var(--mono);font-size:9px;letter-spacing:2px;text-transform:uppercase;color:var(--muted);margin-bottom:10px}
71
- .mc-val{font-family:var(--mono);font-size:28px;font-weight:700;line-height:1}
72
- .mc-val.green{color:var(--accent)}.mc-val.red{color:var(--red)}.mc-val.amber{color:var(--amber)}.mc-val.blue{color:var(--cyan)}.mc-val.purple{color:var(--purple)}
73
- .mc-sub{font-size:10px;color:var(--muted);margin-top:6px;font-family:var(--mono)}
74
- .mc-bar{position:absolute;bottom:0;left:0;height:2px;transition:width .6s ease}
75
- .mc-bar.green{background:linear-gradient(90deg,var(--accent),transparent)}.mc-bar.red{background:linear-gradient(90deg,var(--red),transparent)}.mc-bar.blue{background:linear-gradient(90deg,var(--cyan),transparent)}.mc-bar.purple{background:linear-gradient(90deg,var(--purple),transparent)}
76
- .main-grid{display:grid;grid-template-columns:1fr 360px;gap:18px;margin-bottom:18px}
77
- @media(max-width:1100px){.main-grid{grid-template-columns:1fr}}
78
- .feed-wrap{max-height:480px;overflow-y:auto}
79
- .feed-table{width:100%;border-collapse:collapse;font-size:11px}
80
- .feed-table th{padding:9px 14px;text-align:left;font-family:var(--mono);font-size:9px;color:var(--muted);background:var(--surface);position:sticky;top:0;letter-spacing:1.5px;z-index:2}
81
- .feed-table td{padding:8px 14px;border-top:1px solid var(--border);font-family:var(--mono)}
82
- .feed-table tr.new-row{animation:rowIn .4s ease-out}
83
- @keyframes rowIn{from{opacity:0;background:rgba(0,232,122,0.07)}to{opacity:1;background:transparent}}
84
- .cls-badge{display:inline-flex;padding:3px 9px;border-radius:5px;font-size:9px;font-weight:700;letter-spacing:.5px;font-family:var(--mono)}
85
- .cls-normal{background:rgba(0,232,122,0.12);color:var(--accent);border:1px solid rgba(0,232,122,0.2)}
86
- .cls-DoS{background:rgba(255,61,90,0.14);color:var(--red);border:1px solid rgba(255,61,90,0.2)}
87
- .cls-Probe{background:rgba(0,200,232,0.12);color:var(--cyan);border:1px solid rgba(0,200,232,0.2)}
88
- .cls-R2L{background:rgba(255,170,0,0.12);color:var(--amber);border:1px solid rgba(255,170,0,0.2)}
89
- .cls-U2R{background:rgba(176,111,255,0.13);color:var(--purple);border:1px solid rgba(176,111,255,0.2)}
90
- .side-panels{display:flex;flex-direction:column;gap:18px}
91
- .bar-row{display:flex;align-items:center;gap:10px;margin-bottom:10px}
92
- .bar-lbl{font-size:10px;font-family:var(--mono);color:var(--muted2);width:58px;flex-shrink:0}
93
- .bar-track{flex:1;background:var(--surface);border-radius:3px;height:5px;overflow:hidden}
94
- .bar-fill{height:100%;border-radius:3px;transition:width .8s cubic-bezier(.4,0,.2,1)}
95
- .bar-cnt{font-size:10px;font-family:var(--mono);min-width:34px;text-align:right;color:var(--text)}
96
- .timeline-wrap{padding:14px 16px}
97
- .tl-canvas{width:100%;height:80px;display:block}
98
- .term-wrap{max-height:180px;overflow-y:auto;padding:12px 14px;background:rgba(0,0,0,0.4)}
99
- .term-line{font-family:var(--mono);font-size:10px;line-height:1.9;white-space:nowrap}
100
- .term-line .ts{color:var(--muted)}.term-line .ok{color:var(--accent)}.term-line .warn{color:var(--amber)}.term-line .crit{color:var(--red)}.term-line .info{color:var(--cyan)}
101
- .empty-state{padding:70px 20px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px}
102
- .empty-icon{font-size:40px;margin-bottom:14px;opacity:.3;display:block}
103
- .bottom-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}
104
- @media(max-width:800px){.bottom-grid{grid-template-columns:1fr}}
105
- .heatmap-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:3px;padding:14px}
106
- .hm-cell{aspect-ratio:1;border-radius:3px;background:var(--surface)}
107
- .summary-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;padding:14px}
108
- .sum-item{background:var(--surface);border-radius:10px;padding:14px;border:1px solid var(--border)}
109
- .sum-label{font-family:var(--mono);font-size:9px;color:var(--muted);letter-spacing:1.5px;text-transform:uppercase;margin-bottom:6px}
110
- .sum-val{font-family:var(--mono);font-size:20px;font-weight:700}
111
- /* CSV */
112
- .upload-zone{border:2px dashed var(--border2);border-radius:20px;padding:70px 40px;text-align:center;cursor:pointer;transition:all .3s;background:var(--bg2);position:relative;overflow:hidden}
113
- .upload-zone:hover,.upload-zone.drag-over{border-color:var(--accent);background:rgba(0,232,122,0.03)}
114
- .upload-zone input{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%}
115
- .upload-icon{font-size:56px;margin-bottom:18px;display:block;opacity:.5}
116
- .upload-title{font-family:var(--mono);font-size:18px;color:var(--accent);margin-bottom:10px;font-weight:700}
117
- .upload-sub{font-size:13px;color:var(--muted2)}
118
- .upload-hint{display:inline-block;margin-top:18px;padding:8px 20px;border-radius:8px;border:1px solid var(--border2);font-family:var(--mono);font-size:10px;color:var(--muted);letter-spacing:1px;background:var(--surface)}
119
- .format-banner{display:flex;align-items:center;gap:12px;padding:12px 18px;border-radius:10px;font-family:var(--mono);font-size:11px;margin-bottom:16px;border:1px solid}
120
- .format-banner.ok{background:rgba(0,232,122,0.06);border-color:rgba(0,232,122,0.2);color:var(--accent)}
121
- .file-card{background:var(--bg2);border:1px solid var(--border2);border-radius:14px;padding:18px 22px;margin-bottom:18px;display:flex;align-items:center;gap:18px}
122
- .file-icon{font-size:32px;flex-shrink:0}
123
- .file-details{flex:1}
124
- .file-name{font-family:var(--mono);font-size:14px;color:var(--accent);font-weight:700}
125
- .file-meta{font-family:var(--mono);font-size:10px;color:var(--muted);margin-top:5px;line-height:1.8}
126
- .file-actions{display:flex;gap:10px}
127
- .progress-block{background:var(--bg2);border:1px solid var(--border2);border-radius:14px;padding:24px;margin-bottom:22px}
128
- .scan-line{height:1px;background:linear-gradient(90deg,transparent,var(--accent),transparent);animation:scanAnim 1.5s ease-in-out infinite;margin:0 0 16px}
129
- @keyframes scanAnim{0%,100%{opacity:0;transform:scaleX(0.2)}50%{opacity:1;transform:scaleX(1)}}
130
- .progress-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}
131
- .progress-title{font-family:var(--mono);font-size:11px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}
132
- .progress-stats{font-family:var(--mono);font-size:13px;color:var(--cyan);font-weight:600}
133
- .progress-track{background:var(--surface);border-radius:6px;height:10px;overflow:hidden;margin-bottom:14px}
134
- .progress-fill{height:100%;background:linear-gradient(90deg,var(--accent),var(--cyan));border-radius:6px;transition:width .4s ease;width:0%}
135
- .progress-fill.warning{background:linear-gradient(90deg,var(--amber),var(--red))}
136
- .progress-row{display:flex;justify-content:space-between;font-family:var(--mono);font-size:10px;color:var(--muted);margin-bottom:12px;flex-wrap:wrap;gap:6px}
137
- .current-row-display{font-family:var(--mono);font-size:10px;background:rgba(0,0,0,0.4);border-radius:8px;padding:10px 14px;color:var(--cyan);word-break:break-all;border-left:2px solid var(--accent);min-height:36px;line-height:1.6}
138
- .batch-status{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}
139
- .batch-chip{font-family:var(--mono);font-size:10px;padding:4px 10px;border-radius:5px;background:var(--surface);color:var(--muted);border:1px solid var(--border)}
140
- .batch-chip.done{color:var(--accent);border-color:rgba(0,232,122,0.3);background:rgba(0,232,122,0.07)}
141
- .batch-chip.active{color:var(--cyan);border-color:rgba(0,200,232,0.3);background:rgba(0,200,232,0.07);animation:blink 1s infinite}
142
- .csv-results-grid{display:grid;grid-template-columns:1fr 340px;gap:18px;margin-bottom:20px}
143
- @media(max-width:1100px){.csv-results-grid{grid-template-columns:1fr}}
144
- .csv-feed-wrap{max-height:520px;overflow-y:auto}
145
- .csv-feed-table{width:100%;border-collapse:collapse;font-size:11px}
146
- .csv-feed-table th{padding:9px 14px;text-align:left;font-family:var(--mono);font-size:9px;color:var(--muted);background:var(--surface);position:sticky;top:0;letter-spacing:1.5px;z-index:2}
147
- .csv-feed-table td{padding:8px 14px;border-top:1px solid var(--border);font-family:var(--mono)}
148
- .csv-feed-table tr.csv-new-row{animation:rowIn .3s ease-out}
149
- .mini-stat-panel{display:flex;flex-direction:column;gap:14px}
150
- .mini-card{background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:16px}
151
- .mini-card-title{font-family:var(--mono);font-size:9px;color:var(--muted);letter-spacing:2px;text-transform:uppercase;margin-bottom:12px}
152
- .sev-bar-row{display:flex;align-items:center;gap:10px;margin-bottom:9px}
153
- .sev-bar-lbl{font-family:var(--mono);font-size:10px;width:62px;color:var(--muted2)}
154
- .sev-bar-track{flex:1;background:var(--surface);border-radius:3px;height:7px;overflow:hidden}
155
- .sev-bar-fill{height:100%;border-radius:3px;transition:width .8s ease}
156
- .sev-bar-cnt{font-family:var(--mono);font-size:10px;min-width:36px;text-align:right}
157
- /* REPORT */
158
- .processing-area{display:none}
159
- .processing-area.visible{display:block}
160
- .report-section{display:none}
161
- .report-section.visible{display:block;animation:fadeIn .4s ease}
162
- .completion-banner{background:linear-gradient(135deg,rgba(0,232,122,0.08),rgba(0,200,232,0.05));border:1px solid rgba(0,232,122,0.3);border-radius:14px;padding:20px 24px;display:flex;align-items:center;gap:18px;margin-bottom:22px}
163
- .banner-title{font-family:var(--mono);font-size:15px;font-weight:700;color:var(--accent)}
164
- .banner-sub{font-family:var(--mono);font-size:10px;color:var(--muted2);margin-top:4px;line-height:1.8}
165
- .export-bar{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:24px}
166
- .btn-export{padding:10px 22px;border-radius:9px;font-family:var(--mono);font-size:11px;font-weight:700;cursor:pointer;border:none;transition:all .2s;letter-spacing:.5px}
167
- .btn-export-csv{background:rgba(0,232,122,0.1);color:var(--accent);border:1px solid rgba(0,232,122,0.25)}
168
- .btn-export-csv:hover{background:rgba(0,232,122,0.2)}
169
- .btn-export-pdf{background:rgba(0,200,232,0.1);color:var(--cyan);border:1px solid rgba(0,200,232,0.25)}
170
- .btn-export-pdf:hover{background:rgba(0,200,232,0.2)}
171
- .btn-export-json{background:rgba(176,111,255,0.1);color:var(--purple);border:1px solid rgba(176,111,255,0.25)}
172
- .btn-export-json:hover{background:rgba(176,111,255,0.2)}
173
- .btn-new-scan{background:rgba(255,170,0,0.1);color:var(--amber);border:1px solid rgba(255,170,0,0.25)}
174
- .btn-new-scan:hover{background:rgba(255,170,0,0.2)}
175
- .report-header{background:linear-gradient(135deg,rgba(0,232,122,0.05),rgba(0,200,232,0.03));border:1px solid var(--border2);border-radius:18px;padding:32px 36px;margin-bottom:22px}
176
- .report-title{font-family:var(--mono);font-size:24px;font-weight:700;color:var(--accent);margin-bottom:6px}
177
- .report-subtitle{font-family:var(--mono);font-size:10px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}
178
- .report-meta{display:flex;gap:28px;margin-top:22px;flex-wrap:wrap}
179
- .report-meta-item{font-family:var(--mono);font-size:11px;color:var(--muted)}
180
- .report-meta-item span{color:var(--text)}
181
- .report-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin-bottom:22px}
182
- @media(max-width:1000px){.report-grid{grid-template-columns:repeat(3,1fr)}}
183
- .report-stat{background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:20px 16px;text-align:center;position:relative;overflow:hidden}
184
- .report-stat::before{content:'';position:absolute;top:0;left:0;right:0;height:2px}
185
- .report-stat.green::before{background:var(--accent)}.report-stat.red::before{background:var(--red)}.report-stat.amber::before{background:var(--amber)}.report-stat.cyan::before{background:var(--cyan)}.report-stat.purple::before{background:var(--purple)}
186
- .report-stat-val{font-family:var(--mono);font-size:30px;font-weight:700;margin-bottom:6px;line-height:1}
187
- .report-stat-lbl{font-family:var(--mono);font-size:9px;color:var(--muted);letter-spacing:2px;text-transform:uppercase}
188
- .report-charts{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px;margin-bottom:22px}
189
- @media(max-width:1000px){.report-charts{grid-template-columns:1fr 1fr}}
190
- .chart-canvas-wrap{padding:16px}
191
- .chart-canvas{width:100%;height:160px;display:block}
192
- .cluster-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;padding:16px}
193
- @media(max-width:900px){.cluster-grid{grid-template-columns:repeat(2,1fr)}}
194
- .cluster-card{background:var(--surface);border-radius:10px;padding:16px;border:1px solid var(--border);border-left:3px solid}
195
- .cluster-card.DoS{border-left-color:var(--red)}.cluster-card.Probe{border-left-color:var(--cyan)}.cluster-card.R2L{border-left-color:var(--amber)}.cluster-card.U2R{border-left-color:var(--purple)}.cluster-card.normal{border-left-color:var(--accent)}
196
- .cluster-title{font-family:var(--mono);font-size:11px;font-weight:700;margin-bottom:6px}
197
- .cluster-count{font-family:var(--mono);font-size:24px;font-weight:700;margin-bottom:4px}
198
- .cluster-sub{font-family:var(--mono);font-size:9px;color:var(--muted);line-height:1.8}
199
- /* VIRTUAL TABLE */
200
- .vt-wrap{position:relative;overflow:hidden}
201
- .vt-scroll{overflow-y:auto;max-height:420px}
202
- .vt-spacer{width:100%}
203
- .threat-table{width:100%;border-collapse:collapse;font-size:11px}
204
- .threat-table th{padding:10px 16px;text-align:left;font-family:var(--mono);font-size:9px;color:var(--muted);background:var(--surface);position:sticky;top:0;letter-spacing:1.5px;z-index:2;border-bottom:1px solid var(--border2)}
205
- .threat-table td{padding:9px 16px;border-top:1px solid var(--border);font-family:var(--mono);font-size:11px}
206
- .threat-table tr.row-intrusion td:first-child{border-left:2px solid var(--red)}
207
- .proto-bars{padding:14px 18px}
208
- .proto-row{display:flex;align-items:center;gap:10px;margin-bottom:10px}
209
- .proto-lbl{font-family:var(--mono);font-size:10px;color:var(--muted2);width:50px;flex-shrink:0}
210
- .proto-track{flex:1;background:var(--surface);border-radius:3px;height:6px;overflow:hidden}
211
- .proto-fill{height:100%;border-radius:3px;transition:width .9s ease}
212
- .proto-cnt{font-family:var(--mono);font-size:10px;min-width:90px;text-align:right;color:var(--muted2)}
213
- .services-list{padding:14px 18px}
214
- .svc-row{display:flex;align-items:center;padding:6px 0;border-top:1px solid var(--border);gap:8px}
215
- .svc-name{font-family:var(--mono);font-size:11px;min-width:70px}
216
- .risk-gauge-wrap{padding:16px;display:flex;flex-direction:column;align-items:center;gap:8px}
217
- .risk-gauge-canvas{width:160px;height:90px;display:block}
218
- .risk-label{font-family:var(--mono);font-size:11px;color:var(--muted2);text-align:center}
219
- /* pagination for report table */
220
- .pager{display:flex;align-items:center;gap:8px;padding:12px 16px;font-family:var(--mono);font-size:11px;color:var(--muted);border-top:1px solid var(--border)}
221
- .pager-btn{padding:5px 14px;border-radius:6px;background:var(--surface);border:1px solid var(--border2);color:var(--cyan);font-family:var(--mono);font-size:11px;cursor:pointer}
222
- .pager-btn:disabled{opacity:.4;cursor:not-allowed}
223
- ::-webkit-scrollbar{width:4px;height:4px}
224
- ::-webkit-scrollbar-track{background:transparent}
225
- ::-webkit-scrollbar-thumb{background:var(--surface2);border-radius:2px}
226
- </style>
227
- </head>
228
- <body>
229
- <div class="app">
230
- <header>
231
- <div class="logo">
232
- <div class="logo-shield">🛡</div>
233
- <div><div class="logo-name">SentinelNet</div><div class="logo-tag">Intrusion Detection System · NSL-KDD</div></div>
234
- </div>
235
- <div class="header-right">
236
- <div id="sessionClock">Session: 00:00</div>
237
- <div class="live-badge"><div class="dot" id="liveDot"></div><span id="liveStatus">IDLE</span></div>
238
- </div>
239
- </header>
240
-
241
- <div class="tab-nav">
242
- <button class="tab-btn active" onclick="switchTab('live',this)"><span class="tab-icon">📡</span>LIVE MONITOR</button>
243
- <button class="tab-btn" onclick="switchTab('csv',this)"><span class="tab-icon">📂</span>CSV ANALYSIS</button>
244
- </div>
245
-
246
- <!-- TAB 1: LIVE MONITOR -->
247
- <div class="tab-pane active" id="tab-live">
248
- <div class="config-strip">
249
- <div class="speed-wrap"><span>Speed</span>
250
- <select id="speedSel">
251
- <option value="2000">Slow (0.5/s)</option>
252
- <option value="1000" selected>Normal (1/s)</option>
253
- <option value="500">Fast (2/s)</option>
254
- <option value="200">Turbo (5/s)</option>
255
- </select>
256
- </div>
257
- <button class="btn btn-start" id="startBtn" onclick="startMonitor()">▶ START MONITOR</button>
258
- <button class="btn btn-stop" id="stopBtn" onclick="stopMonitor()" disabled>■ STOP</button>
259
- <button class="btn btn-clear" onclick="clearAll()">↺ RESET</button>
260
- <div id="connBadge" class="idle">— IDLE</div>
261
- </div>
262
- <div class="metrics">
263
- <div class="mc" id="mc-total"><div class="mc-label">Packets Analyzed</div><div class="mc-val blue" id="m-total">0</div><div class="mc-sub" id="m-rate">0 /sec</div><div class="mc-bar blue" id="mb-total" style="width:0%"></div></div>
264
- <div class="mc" id="mc-normal"><div class="mc-label">Normal Traffic</div><div class="mc-val green" id="m-normal">0</div><div class="mc-sub" id="m-normal-pct">—</div><div class="mc-bar green" id="mb-normal" style="width:0%"></div></div>
265
- <div class="mc" id="mc-intrusions"><div class="mc-label">Intrusions</div><div class="mc-val red" id="m-intrusions">0</div><div class="mc-sub" id="m-intrusions-pct">—</div><div class="mc-bar red" id="mb-intrusions" style="width:0%"></div></div>
266
- <div class="mc" id="mc-dos"><div class="mc-label">DoS Attacks</div><div class="mc-val red" id="m-dos">0</div><div class="mc-sub">Denial of service</div><div class="mc-bar red" id="mb-dos" style="width:0%"></div></div>
267
- <div class="mc" id="mc-probe"><div class="mc-label">Probes</div><div class="mc-val blue" id="m-probe">0</div><div class="mc-sub">Reconnaissance</div><div class="mc-bar blue" id="mb-probe" style="width:0%"></div></div>
268
- <div class="mc" id="mc-u2r"><div class="mc-label">Critical (U2R)</div><div class="mc-val purple" id="m-u2r">0</div><div class="mc-sub">Privilege escalation</div><div class="mc-bar purple" id="mb-u2r" style="width:0%"></div></div>
269
- </div>
270
- <div class="main-grid">
271
- <div class="panel">
272
- <div class="panel-head"><span>// LIVE DETECTION FEED</span><span class="red" id="alertCount">0 ALERTS</span></div>
273
- <div class="feed-wrap"><div class="empty-state" id="emptyState"><div class="empty-icon">📡</div>Press START MONITOR to begin</div>
274
- <table class="feed-table" id="feedTable" style="display:none">
275
- <thead><tr><th>#</th><th>TIME</th><th>PROTOCOL</th><th>SRC BYTES</th><th>PREDICTION</th><th>CONFIDENCE</th><th>SEVERITY</th></tr></thead>
276
- <tbody id="feedBody"></tbody>
277
- </table>
278
- </div>
279
- </div>
280
- <div class="side-panels">
281
- <div class="panel">
282
- <div class="panel-head"><span>// ATTACK DISTRIBUTION</span><span id="distTotal" class="accent">0 total</span></div>
283
- <div style="padding:14px 18px">
284
- <div class="bar-row"><div class="bar-lbl">normal</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--accent)" id="bar-normal"></div></div><div class="bar-cnt" id="bc-normal">0</div></div>
285
- <div class="bar-row"><div class="bar-lbl">DoS</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--red)" id="bar-DoS"></div></div><div class="bar-cnt" id="bc-DoS">0</div></div>
286
- <div class="bar-row"><div class="bar-lbl">Probe</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--cyan)" id="bar-Probe"></div></div><div class="bar-cnt" id="bc-Probe">0</div></div>
287
- <div class="bar-row"><div class="bar-lbl">R2L</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--amber)" id="bar-R2L"></div></div><div class="bar-cnt" id="bc-R2L">0</div></div>
288
- <div class="bar-row"><div class="bar-lbl">U2R</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--purple)" id="bar-U2R"></div></div><div class="bar-cnt" id="bc-U2R">0</div></div>
289
- </div>
290
- </div>
291
- <div class="panel">
292
- <div class="panel-head"><span>// THREAT TIMELINE</span><span class="accent" id="tlWindow">last 60s</span></div>
293
- <div class="timeline-wrap"><canvas class="tl-canvas" id="tlCanvas"></canvas></div>
294
- </div>
295
- <div class="panel">
296
- <div class="panel-head"><span>// SYSTEM LOG</span></div>
297
- <div class="term-wrap" id="termWrap"><div class="term-line"><span class="ts">[--:--:--]</span> <span class="info">SentinelNet initialized.</span></div></div>
298
- </div>
299
- </div>
300
- </div>
301
- <div class="bottom-grid">
302
- <div class="panel">
303
- <div class="panel-head"><span>// ACTIVITY HEATMAP</span><span class="accent" style="font-size:9px">last 60 packets</span></div>
304
- <div class="heatmap-grid" id="heatmap"></div>
305
- </div>
306
- <div class="panel">
307
- <div class="panel-head"><span>// CONFIDENCE DISTRIBUTION</span><span id="avgConf" class="accent">avg: —</span></div>
308
- <div style="padding:14px 18px">
309
- <div class="bar-row"><div class="bar-lbl">90-100%</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--accent)" id="conf-90"></div></div><div class="bar-cnt" id="cbc-90">0</div></div>
310
- <div class="bar-row"><div class="bar-lbl">80-90%</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--cyan)" id="conf-80"></div></div><div class="bar-cnt" id="cbc-80">0</div></div>
311
- <div class="bar-row"><div class="bar-lbl">70-80%</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--amber)" id="conf-70"></div></div><div class="bar-cnt" id="cbc-70">0</div></div>
312
- <div class="bar-row"><div class="bar-lbl">&lt;70%</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--red)" id="conf-low"></div></div><div class="bar-cnt" id="cbc-low">0</div></div>
313
- </div>
314
- </div>
315
- <div class="panel">
316
- <div class="panel-head"><span>// SESSION SUMMARY</span></div>
317
- <div class="summary-grid">
318
- <div class="sum-item"><div class="sum-label">Detection Rate</div><div class="sum-val" id="sum-rate" style="color:var(--accent)">—</div></div>
319
- <div class="sum-item"><div class="sum-label">Avg Confidence</div><div class="sum-val" id="sum-conf" style="color:var(--cyan)">—</div></div>
320
- <div class="sum-item"><div class="sum-label">Peak Threat</div><div class="sum-val" id="sum-peak" style="color:var(--red)">—</div></div>
321
- <div class="sum-item"><div class="sum-label">Model Source</div><div class="sum-val" id="sum-model" style="color:var(--amber);font-size:13px">LOCAL</div></div>
322
- </div>
323
- </div>
324
- </div>
325
- </div>
326
-
327
- <!-- TAB 2: CSV ANALYSIS -->
328
- <div class="tab-pane" id="tab-csv">
329
- <div id="csvUploadSection">
330
- <div class="upload-zone" id="uploadZone">
331
- <input type="file" id="csvFileInput" accept=".csv" onchange="handleFileSelect(event)"/>
332
- <span class="upload-icon">📁</span>
333
- <div class="upload-title">Drop your NSL-KDD CSV here</div>
334
- <div class="upload-sub">Auto-detects headers · Batch processed · Instant report</div>
335
- <div class="upload-hint">ACCEPTED: .csv · NSL-KDD format · 42 or 43 columns · Any size</div>
336
- </div>
337
- <div style="margin-top:22px;display:grid;grid-template-columns:repeat(3,1fr);gap:16px">
338
- <div class="panel"><div class="panel-head"><span>// SUPPORTED FORMATS</span></div><div style="padding:16px;font-family:var(--mono);font-size:10px;color:var(--muted2);line-height:2.4">✓ NSL-KDD <span style="color:var(--accent)">with headers</span><br>✓ NSL-KDD <span style="color:var(--accent)">without headers</span><br>✓ KDDTrain+.txt / KDDTest+.txt<br>✓ 42 or 43 columns</div></div>
339
- <div class="panel"><div class="panel-head"><span>// BATCH PROCESSING</span></div><div style="padding:16px;font-family:var(--mono);font-size:10px;color:var(--muted2);line-height:2.4">✓ <span style="color:var(--cyan)">100 rows/batch</span> — fast<br>✓ Live feed while processing<br>✓ Auto-downloads CSV on finish<br>✓ Full report: 6 charts + risk gauge</div></div>
340
- <div class="panel"><div class="panel-head"><span>// THREAT CLASSES</span></div><div style="padding:16px"><div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span class="cls-badge cls-normal">normal</span><span style="font-family:var(--mono);font-size:10px;color:var(--muted2)">Clean traffic</span></div><div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span class="cls-badge cls-DoS">DoS</span><span style="font-family:var(--mono);font-size:10px;color:var(--muted2)">Denial of service</span></div><div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span class="cls-badge cls-Probe">Probe</span><span style="font-family:var(--mono);font-size:10px;color:var(--muted2)">Reconnaissance</span></div><div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span class="cls-badge cls-R2L">R2L</span><span style="font-family:var(--mono);font-size:10px;color:var(--muted2)">Remote to local</span></div><div style="display:flex;align-items:center;gap:8px"><span class="cls-badge cls-U2R">U2R</span><span style="font-family:var(--mono);font-size:10px;color:var(--muted2)">Privilege escalation</span></div></div></div>
341
- </div>
342
- </div>
343
-
344
- <div class="processing-area" id="csvProcessingArea">
345
- <div id="formatBanner" class="format-banner ok" style="display:none"></div>
346
- <div class="file-card">
347
- <div class="file-icon">📊</div>
348
- <div class="file-details"><div class="file-name" id="csvFileName">—</div><div class="file-meta" id="csvFileMeta">—</div></div>
349
- <div class="file-actions">
350
- <button class="btn btn-start" id="csvStartBtn" onclick="startCsvAnalysis()">▶ ANALYZE</button>
351
- <button class="btn btn-stop" id="csvStopBtn" onclick="stopCsvAnalysis()" disabled>■ PAUSE</button>
352
- <button class="btn btn-clear" onclick="resetCsvTab()">✕ CLEAR</button>
353
- </div>
354
- </div>
355
- <div class="progress-block" id="csvProgressBlock" style="display:none">
356
- <div class="scan-line"></div>
357
- <div class="progress-header"><span class="progress-title">// BATCH PROCESSING</span><span class="progress-stats" id="csvProgressStats">0 / 0 rows</span></div>
358
- <div class="progress-track"><div class="progress-fill" id="csvProgressFill"></div></div>
359
- <div class="progress-row">
360
- <span id="csvProgressPct">0%</span>
361
- <span id="csvProgressEta">ETA: —</span>
362
- <span id="csvThreatRate" style="color:var(--red)">Threats: 0</span>
363
- <span id="csvSpeedStat" style="color:var(--cyan)">— rows/s</span>
364
- </div>
365
- <div class="current-row-display" id="csvCurrentRow">Initializing…</div>
366
- <div class="batch-status" id="batchStatus"></div>
367
- </div>
368
- <div class="csv-results-grid" id="csvLiveGrid" style="display:none">
369
- <div class="panel">
370
- <div class="panel-head"><span>// ROW ANALYSIS FEED</span><span id="csvAlertCount" class="red">0 THREATS</span></div>
371
- <div class="csv-feed-wrap"><table class="csv-feed-table">
372
- <thead><tr><th>ROW</th><th>PROTO</th><th>SERVICE</th><th>SRC BYTES</th><th>CLASS</th><th>CONFIDENCE</th><th>SEVERITY</th></tr></thead>
373
- <tbody id="csvFeedBody"></tbody>
374
- </table></div>
375
- </div>
376
- <div class="mini-stat-panel">
377
- <div class="mini-card">
378
- <div class="mini-card-title">Distribution</div>
379
- <div class="bar-row"><div class="bar-lbl">normal</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--accent)" id="csvbar-normal"></div></div><div class="bar-cnt" id="csvbc-normal">0</div></div>
380
- <div class="bar-row"><div class="bar-lbl">DoS</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--red)" id="csvbar-DoS"></div></div><div class="bar-cnt" id="csvbc-DoS">0</div></div>
381
- <div class="bar-row"><div class="bar-lbl">Probe</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--cyan)" id="csvbar-Probe"></div></div><div class="bar-cnt" id="csvbc-Probe">0</div></div>
382
- <div class="bar-row"><div class="bar-lbl">R2L</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--amber)" id="csvbar-R2L"></div></div><div class="bar-cnt" id="csvbc-R2L">0</div></div>
383
- <div class="bar-row"><div class="bar-lbl">U2R</div><div class="bar-track"><div class="bar-fill" style="width:0%;background:var(--purple)" id="csvbar-U2R"></div></div><div class="bar-cnt" id="csvbc-U2R">0</div></div>
384
- </div>
385
- <div class="mini-card"><div class="mini-card-title">Avg Confidence</div><div style="font-family:var(--mono);font-size:30px;font-weight:700;color:var(--cyan)" id="csvAvgConf">—</div><div style="font-family:var(--mono);font-size:9px;color:var(--muted);margin-top:4px">across all predictions</div></div>
386
- <div class="mini-card">
387
- <div class="mini-card-title">Severity Breakdown</div>
388
- <div class="sev-bar-row"><div class="sev-bar-lbl">Critical</div><div class="sev-bar-track"><div class="sev-bar-fill" style="width:0%;background:var(--red)" id="sevbar-Critical"></div></div><div class="sev-bar-cnt" id="sevbc-Critical" style="color:var(--red)">0</div></div>
389
- <div class="sev-bar-row"><div class="sev-bar-lbl">High</div><div class="sev-bar-track"><div class="sev-bar-fill" style="width:0%;background:var(--amber)" id="sevbar-High"></div></div><div class="sev-bar-cnt" id="sevbc-High" style="color:var(--amber)">0</div></div>
390
- <div class="sev-bar-row"><div class="sev-bar-lbl">Medium</div><div class="sev-bar-track"><div class="sev-bar-fill" style="width:0%;background:var(--cyan)" id="sevbar-Medium"></div></div><div class="sev-bar-cnt" id="sevbc-Medium" style="color:var(--cyan)">0</div></div>
391
- <div class="sev-bar-row"><div class="sev-bar-lbl">None</div><div class="sev-bar-track"><div class="sev-bar-fill" style="width:0%;background:var(--accent)" id="sevbar-None"></div></div><div class="sev-bar-cnt" id="sevbc-None" style="color:var(--accent)">0</div></div>
392
- </div>
393
- <div class="mini-card"><div class="mini-card-title">Processing Speed</div><div style="font-family:var(--mono);font-size:24px;font-weight:700;color:var(--accent)" id="csvProcRate">—</div><div style="font-family:var(--mono);font-size:9px;color:var(--muted);margin-top:4px">rows / second</div></div>
394
- </div>
395
- </div>
396
-
397
- <!-- FULL REPORT -->
398
- <div class="report-section" id="reportSection">
399
- <div class="completion-banner">
400
- <div style="font-size:32px">✅</div>
401
- <div><div class="banner-title">Analysis Complete — Threat Report Ready</div><div class="banner-sub" id="bannerSub">—</div></div>
402
- </div>
403
- <div class="export-bar">
404
- <button class="btn-export btn-export-csv" onclick="exportAnnotatedCSV()">⬇ ANNOTATED CSV</button>
405
- <button class="btn-export btn-export-pdf" onclick="exportPDFReport()">⬇ PDF REPORT</button>
406
- <button class="btn-export btn-export-json" onclick="exportJSON()">⬇ JSON EXPORT</button>
407
- <button class="btn-export btn-new-scan" onclick="resetCsvTab()">⟳ NEW SCAN</button>
408
- </div>
409
- <div class="report-header">
410
- <div class="report-title">🛡 SentinelNet Threat Analysis Report</div>
411
- <div class="report-subtitle" id="reportSubtitle">—</div>
412
- <div class="report-meta">
413
- <div class="report-meta-item">File: <span id="rmFile">—</span></div>
414
- <div class="report-meta-item">Rows: <span id="rmRows">—</span></div>
415
- <div class="report-meta-item">Date: <span id="rmDate">—</span></div>
416
- <div class="report-meta-item">Model: <span id="rmModel">—</span></div>
417
- <div class="report-meta-item">Duration: <span id="rmDuration">—</span></div>
418
- <div class="report-meta-item">Format: <span id="rmFormat">—</span></div>
419
- </div>
420
- </div>
421
- <div class="report-grid">
422
- <div class="report-stat cyan"><div class="report-stat-val" id="rs-total" style="color:var(--cyan)">—</div><div class="report-stat-lbl">Total Rows</div></div>
423
- <div class="report-stat red"><div class="report-stat-val" id="rs-threats" style="color:var(--red)">—</div><div class="report-stat-lbl">Threats Found</div></div>
424
- <div class="report-stat amber"><div class="report-stat-val" id="rs-rate" style="color:var(--amber)">—</div><div class="report-stat-lbl">Threat Rate</div></div>
425
- <div class="report-stat green"><div class="report-stat-val" id="rs-conf" style="color:var(--accent)">—</div><div class="report-stat-lbl">Avg Confidence</div></div>
426
- <div class="report-stat purple"><div class="report-stat-val" id="rs-risk" style="color:var(--purple)">—</div><div class="report-stat-lbl">Risk Score</div></div>
427
- </div>
428
- <div class="report-charts">
429
- <div class="panel"><div class="panel-head"><span>// CLASS DISTRIBUTION</span></div><div class="chart-canvas-wrap"><canvas class="chart-canvas" id="reportBarCanvas"></canvas></div></div>
430
- <div class="panel"><div class="panel-head"><span>// CONFIDENCE WAVEFORM</span><span class="cyan">over dataset</span></div><div class="chart-canvas-wrap"><canvas class="chart-canvas" id="reportConfCanvas"></canvas></div></div>
431
- <div class="panel"><div class="panel-head"><span>// THREAT INTENSITY</span><span class="red">rolling density</span></div><div class="chart-canvas-wrap"><canvas class="chart-canvas" id="reportIntensityCanvas"></canvas></div></div>
432
- </div>
433
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px;margin-bottom:22px">
434
- <div class="panel"><div class="panel-head"><span>// PROTOCOL BREAKDOWN</span></div><div class="proto-bars" id="protoBreakdown"></div></div>
435
- <div class="panel"><div class="panel-head"><span>// TOP TARGETED SERVICES</span></div><div class="services-list" id="servicesList"></div></div>
436
- <div class="panel">
437
- <div class="panel-head"><span>// RISK GAUGE</span></div>
438
- <div class="risk-gauge-wrap"><canvas class="risk-gauge-canvas" id="riskGaugeCanvas"></canvas><div class="risk-label" id="riskLabel">—</div></div>
439
- <div style="padding:0 18px 14px">
440
- <div class="sev-bar-row"><div class="sev-bar-lbl">Critical</div><div class="sev-bar-track"><div class="sev-bar-fill" style="width:0%;background:var(--red)" id="rsevbar-Critical"></div></div><div class="sev-bar-cnt" id="rsevbc-Critical" style="color:var(--red)">0</div></div>
441
- <div class="sev-bar-row"><div class="sev-bar-lbl">High</div><div class="sev-bar-track"><div class="sev-bar-fill" style="width:0%;background:var(--amber)" id="rsevbar-High"></div></div><div class="sev-bar-cnt" id="rsevbc-High" style="color:var(--amber)">0</div></div>
442
- <div class="sev-bar-row"><div class="sev-bar-lbl">Medium</div><div class="sev-bar-track"><div class="sev-bar-fill" style="width:0%;background:var(--cyan)" id="rsevbar-Medium"></div></div><div class="sev-bar-cnt" id="rsevbc-Medium" style="color:var(--cyan)">0</div></div>
443
- <div class="sev-bar-row"><div class="sev-bar-lbl">None</div><div class="sev-bar-track"><div class="sev-bar-fill" style="width:0%;background:var(--accent)" id="rsevbar-None"></div></div><div class="sev-bar-cnt" id="rsevbc-None" style="color:var(--accent)">0</div></div>
444
- </div>
445
- </div>
446
- </div>
447
- <div class="panel" style="margin-bottom:22px">
448
- <div class="panel-head"><span>// ATTACK PATTERN CLUSTERS</span><span class="accent" id="clusterCount">0 clusters</span></div>
449
- <div class="cluster-grid" id="clusterGrid"></div>
450
- </div>
451
- <!-- PAGINATED TABLE — replaces the old all-rows table -->
452
- <div class="panel">
453
- <div class="panel-head"><span>// COMPLETE ANALYSIS</span><span id="reportRowCount" class="accent">0 rows</span></div>
454
- <div style="overflow-x:auto">
455
- <table class="threat-table">
456
- <thead><tr><th>ROW</th><th>PROTOCOL</th><th>SERVICE</th><th>SRC BYTES</th><th>DST BYTES</th><th>CLASS</th><th>CONFIDENCE</th><th>SEVERITY</th><th>TRUE LABEL</th></tr></thead>
457
- <tbody id="reportTableBody"></tbody>
458
- </table>
459
- </div>
460
- <div class="pager">
461
- <button class="pager-btn" id="pgPrev" onclick="changePage(-1)" disabled>◀ PREV</button>
462
- <span id="pgInfo" style="flex:1;text-align:center">Page 1 of 1</span>
463
- <button class="pager-btn" id="pgNext" onclick="changePage(1)" disabled>NEXT ▶</button>
464
- </div>
465
- </div>
466
- </div>
467
- </div>
468
- </div>
469
- </div>
470
-
471
- <script>
472
- // ── CONFIG ──────────────────────────────────────
473
- const BACKEND_URL = '';
474
- const BATCH_SIZE = 100;
475
- const PAGE_SIZE = 100; // rows per page in report table — KEY PERF FIX
476
-
477
- // ── CONSTANTS ───────────────────────────────────
478
- const NSL_KDD_COLUMNS=['duration','protocol_type','service','flag','src_bytes','dst_bytes','land','wrong_fragment','urgent','hot','num_failed_logins','logged_in','num_compromised','root_shell','su_attempted','num_root','num_file_creations','num_shells','num_access_files','num_outbound_cmds','is_host_login','is_guest_login','count','srv_count','serror_rate','srv_serror_rate','rerror_rate','srv_rerror_rate','same_srv_rate','diff_srv_rate','srv_diff_host_rate','dst_host_count','dst_host_srv_count','dst_host_same_srv_rate','dst_host_diff_srv_rate','dst_host_same_src_port_rate','dst_host_srv_diff_host_rate','dst_host_serror_rate','dst_host_srv_serror_rate','dst_host_rerror_rate','dst_host_srv_rerror_rate','label','difficulty_level'];
479
- const STRING_COLS=new Set(['protocol_type','service','flag','label']);
480
- const ATTACK_MAP={normal:'normal',back:'DoS',land:'DoS',neptune:'DoS',pod:'DoS',smurf:'DoS',teardrop:'DoS',mailbomb:'DoS',apache2:'DoS',processtable:'DoS',udpstorm:'DoS',satan:'Probe',ipsweep:'Probe',nmap:'Probe',portsweep:'Probe',mscan:'Probe',saint:'Probe',guess_passwd:'R2L',ftp_write:'R2L',imap:'R2L',phf:'R2L',multihop:'R2L',warezmaster:'R2L',warezclient:'R2L',spy:'R2L',xlock:'R2L',xsnoop:'R2L',snmpguess:'R2L',snmpgetattack:'R2L',httptunnel:'R2L',sendmail:'R2L',named:'R2L',buffer_overflow:'U2R',loadmodule:'U2R',perl:'U2R',rootkit:'U2R',ps:'U2R',xterm:'U2R',sqlattack:'U2R'};
481
- const SEV_MAP={normal:'None',DoS:'Critical',Probe:'Medium',R2L:'High',U2R:'Critical'};
482
- const SEV_COLOR={None:'#00e87a',Medium:'#00c8e8',High:'#ffaa00',Critical:'#ff3d5a'};
483
- const CLASS_COLOR={normal:'#00e87a',DoS:'#ff3d5a',Probe:'#00c8e8',R2L:'#ffaa00',U2R:'#b06fff'};
484
- const PROTOCOLS=['tcp','udp','icmp'];
485
- const SERVICES=['http','ftp','smtp','ssh','dns','telnet','pop3','imap4','finger','auth'];
486
- const LABEL_POOL=[...Array(60).fill('normal'),...Array(12).fill('neptune'),...Array(6).fill('smurf'),...Array(4).fill('back'),...Array(5).fill('ipsweep'),...Array(4).fill('satan'),...Array(3).fill('portsweep'),...Array(2).fill('guess_passwd'),...Array(1).fill('buffer_overflow'),...Array(1).fill('rootkit')];
487
-
488
- // ── LIVE STATE ──────────────────────────────────
489
- let monitorInterval=null,sessionInterval=null,sessionSeconds=0,isRunning=false,usingRealModel=false,packetId=0;
490
- const counts={normal:0,DoS:0,Probe:0,R2L:0,U2R:0};
491
- let totalPackets=0,totalIntrusions=0,confSum=0,peakClass=null;
492
- let confBuckets={90:0,80:0,70:0,low:0};
493
- let timelineBuckets=Array(60).fill(0),heatmapCells=Array(60).fill(null);
494
- // throttle timeline redraws
495
- let tlDirty=false;
496
-
497
- // ── CSV STATE ───────────────────────────────────
498
- let csvRows=[],csvResults=[],csvIndex=0,csvRunning=false,csvStartTime=null;
499
- let csvCounts={normal:0,DoS:0,Probe:0,R2L:0,U2R:0};
500
- let csvSevCounts={Critical:0,High:0,Medium:0,None:0};
501
- let csvConfSum=0,csvIntrusionCount=0,csvConfHistory=[],csvUsingReal=false,csvFormatInfo='';
502
- let batchNum=0,totalBatches=0;
503
- // pagination
504
- let reportPage=0;
505
-
506
- // ── TAB ──────────────────────────────────────────
507
- function switchTab(name,btn){
508
- document.querySelectorAll('.tab-pane').forEach(p=>p.classList.remove('active'));
509
- document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
510
- document.getElementById('tab-'+name).classList.add('active');
511
- btn.classList.add('active');
512
- }
513
-
514
- // ── CSV PARSER ───────────────────────────────────
515
- function parseCSV(text){
516
- const lines=text.trim().split('\n').filter(l=>l.trim());
517
- if(!lines.length)return{rows:[],hasHeader:false,cols:0};
518
- function splitLine(line){const vals=[];let cur='',inQ=false;for(const c of line){if(c==='"')inQ=!inQ;else if(c===','&&!inQ){vals.push(cur.trim());cur='';}else cur+=c;}vals.push(cur.trim());return vals;}
519
- const firstVals=splitLine(lines[0]);
520
- const knownCols=new Set(NSL_KDD_COLUMNS);
521
- const looksLikeHeader=firstVals.some(v=>knownCols.has(v.toLowerCase().replace(/^"|"$/g,'')));
522
- let headers,dataLines;
523
- if(looksLikeHeader){headers=firstVals.map(h=>h.toLowerCase().replace(/^"|"$/g,'').trim());dataLines=lines.slice(1);}
524
- else{headers=NSL_KDD_COLUMNS.slice(0,firstVals.length);dataLines=lines;}
525
- const rows=[];
526
- for(const line of dataLines){
527
- const vals=splitLine(line);if(vals.length<2)continue;
528
- const obj={};headers.forEach((h,i)=>{let v=(vals[i]||'').trim().replace(/^"|"$/g,'');obj[h]=STRING_COLS.has(h)?v:(v===''?0:(isNaN(v)?v:parseFloat(v)));});
529
- rows.push(obj);
530
- }
531
- return{rows,hasHeader:looksLikeHeader,cols:headers.length};
532
- }
533
-
534
- // ── LOCAL CLASSIFIER ─────────────────────────────
535
- function classifyLocal(row){
536
- const rawLabel=(row.label||'').toString().toLowerCase().trim().replace(/\.$/,'');
537
- if(rawLabel&&rawLabel!=='unknown'){
538
- const mc=ATTACK_MAP[rawLabel];
539
- if(mc){const base={normal:0.88,DoS:0.91,Probe:0.84,R2L:0.79,U2R:0.82}[mc]||0.80;const conf=Math.min(0.99,base+(Math.random()*0.08-0.04));return{predicted_class:mc,severity:SEV_MAP[mc],confidence:+conf.toFixed(4),is_intrusion:mc!=='normal'};}
540
- }
541
- const srcBytes=parseFloat(row.src_bytes)||0,flag=(row.flag||'').toUpperCase();
542
- const serrorRate=parseFloat(row.serror_rate)||0,rerrorRate=parseFloat(row.rerror_rate)||0;
543
- const srvCount=parseFloat(row.srv_count)||0;
544
- const loggedIn=parseFloat(row.logged_in)||0,numRoot=parseFloat(row.num_root)||0,rootShell=parseFloat(row.root_shell)||0;
545
- const count=parseFloat(row.count)||0;
546
- let cls='normal',conf=0.75+Math.random()*0.15;
547
- if(['S0','S1','S2','S3','REJ','RSTO','RSTR'].includes(flag)&&count>100){cls='DoS';conf=0.85+Math.random()*0.1;}
548
- else if(srcBytes>50000&&(parseFloat(row.duration)||0)<5){cls='DoS';conf=0.80+Math.random()*0.12;}
549
- else if(serrorRate>0.7||rerrorRate>0.7){cls='DoS';conf=0.78+Math.random()*0.1;}
550
- else if(srvCount>100&&srcBytes<500&&loggedIn===0){cls='Probe';conf=0.80+Math.random()*0.12;}
551
- else if(loggedIn===1&&(row.num_failed_logins||0)>0&&srcBytes<10000){cls='R2L';conf=0.75+Math.random()*0.12;}
552
- else if(rootShell>0||numRoot>0){cls='U2R';conf=0.82+Math.random()*0.12;}
553
- return{predicted_class:cls,severity:SEV_MAP[cls],confidence:+Math.min(0.99,conf).toFixed(4),is_intrusion:cls!=='normal'};
554
- }
555
-
556
- // ── API ──────────────────────────────────────────
557
- async function predictBatch(rows){
558
- try{
559
- const r=await fetch(BACKEND_URL+'/predict',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({rows}),signal:AbortSignal.timeout(20000)});
560
- if(!r.ok)return null;
561
- const d=await r.json();
562
- return(d.status==='ok'&&Array.isArray(d.results))?d.results:null;
563
- }catch{return null;}
564
- }
565
- async function predictSingle(row){
566
- try{
567
- const r=await fetch(BACKEND_URL+'/predict',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({rows:[row]}),signal:AbortSignal.timeout(5000)});
568
- if(!r.ok)return null;
569
- const d=await r.json();
570
- return(d.status==='ok'&&d.results?.[0])?d.results[0]:null;
571
- }catch{return null;}
572
- }
573
-
574
- // ── FILE UPLOAD ──────────────────────────────────
575
- const uploadZone=document.getElementById('uploadZone');
576
- uploadZone.addEventListener('dragover',e=>{e.preventDefault();uploadZone.classList.add('drag-over');});
577
- uploadZone.addEventListener('dragleave',()=>uploadZone.classList.remove('drag-over'));
578
- uploadZone.addEventListener('drop',e=>{e.preventDefault();uploadZone.classList.remove('drag-over');const f=e.dataTransfer.files[0];if(f&&f.name.endsWith('.csv'))processFileUpload(f);});
579
- function handleFileSelect(e){const f=e.target.files[0];if(f)processFileUpload(f);}
580
-
581
- function processFileUpload(file){
582
- const reader=new FileReader();
583
- reader.onload=e=>{
584
- const{rows,hasHeader,cols}=parseCSV(e.target.result);
585
- if(!rows.length){alert('Could not parse CSV.');return;}
586
- csvRows=rows;
587
- csvFormatInfo=hasHeader?`With headers · ${cols} columns`:`Headerless — auto-mapped · ${cols} columns`;
588
- totalBatches=Math.ceil(rows.length/BATCH_SIZE);
589
- const banner=document.getElementById('formatBanner');
590
- banner.style.display='flex';banner.className='format-banner ok';
591
- banner.innerHTML=`✓ ${hasHeader?'Headers detected':'Headerless — NSL-KDD auto-mapped'} · ${cols} columns · ${rows.length.toLocaleString()} rows · ${totalBatches} batches`;
592
- document.getElementById('csvUploadSection').style.display='none';
593
- document.getElementById('csvProcessingArea').classList.add('visible');
594
- setText('csvFileName',file.name);
595
- setText('csvFileMeta',`${rows.length.toLocaleString()} rows · ${(file.size/1024).toFixed(1)} KB · ${csvFormatInfo}`);
596
- csvResults=[];csvIndex=0;csvConfSum=0;csvIntrusionCount=0;csvConfHistory=[];batchNum=0;
597
- Object.keys(csvCounts).forEach(k=>csvCounts[k]=0);
598
- Object.keys(csvSevCounts).forEach(k=>csvSevCounts[k]=0);
599
- csvUsingReal=false;csvStartTime=null;
600
- };
601
- reader.readAsText(file);
602
- }
603
-
604
- // ── CSV ENGINE ───────────────────────────────────
605
- async function startCsvAnalysis(){
606
- if(csvRunning||csvIndex>=csvRows.length)return;
607
- csvRunning=true;csvStartTime=csvStartTime||Date.now();
608
- document.getElementById('csvStartBtn').disabled=true;
609
- document.getElementById('csvStopBtn').disabled=false;
610
- document.getElementById('csvProgressBlock').style.display='block';
611
- document.getElementById('csvLiveGrid').style.display='grid';
612
- document.getElementById('reportSection').classList.remove('visible');
613
- document.getElementById('liveDot').className='dot amber';
614
- setText('liveStatus','SCANNING');
615
- await processBatches();
616
- }
617
-
618
- async function processBatches(){
619
- while(csvRunning&&csvIndex<csvRows.length){
620
- const bStart=csvIndex,bEnd=Math.min(csvIndex+BATCH_SIZE,csvRows.length);
621
- const batch=csvRows.slice(bStart,bEnd);
622
- batchNum++;
623
-
624
- // Only update chips every 5 batches — PERF FIX
625
- if(batchNum===1||batchNum%5===0||batchNum===totalBatches)
626
- updateBatchChips(batchNum,totalBatches);
627
-
628
- setText('csvCurrentRow',`Batch ${batchNum}/${totalBatches} — rows ${(bStart+1).toLocaleString()}–${bEnd.toLocaleString()}`);
629
-
630
- let results=await predictBatch(batch);
631
- if(results){
632
- if(!csvUsingReal){csvUsingReal=true;setConnBadge('real');}
633
- }else{
634
- if(csvUsingReal||batchNum===1){csvUsingReal=false;setConnBadge('local');}
635
- results=batch.map(r=>classifyLocal(r));
636
- }
637
-
638
- for(let i=0;i<batch.length;i++){
639
- const row=batch[i],res=results[i];
640
- const{predicted_class:cls,confidence:conf,severity:sev,is_intrusion:isI}=res;
641
- csvResults.push({rowNum:bStart+i+1,row,cls,conf,sev,isI});
642
- // Only store sampled conf history to save memory — PERF FIX
643
- if(csvResults.length%5===0)csvConfHistory.push(conf);
644
- csvCounts[cls]=(csvCounts[cls]||0)+1;
645
- csvSevCounts[sev]=(csvSevCounts[sev]||0)+1;
646
- csvConfSum+=conf;
647
- if(isI)csvIntrusionCount++;
648
- }
649
-
650
- // Show only last 5 rows in feed — PERF FIX (was 15)
651
- const feedSlice=batch.slice(-5);
652
- feedSlice.forEach((row,i)=>{
653
- const ri=bEnd-feedSlice.length+i;
654
- addCsvFeedRow(ri+1,row,results[batch.length-feedSlice.length+i].predicted_class,results[batch.length-feedSlice.length+i].confidence,results[batch.length-feedSlice.length+i].severity);
655
- });
656
-
657
- csvIndex=bEnd;
658
- const pct=(csvIndex/csvRows.length*100).toFixed(1);
659
- const elapsed=(Date.now()-csvStartTime)/1000;
660
- const rate=csvIndex/Math.max(elapsed,0.01);
661
- const remaining=(csvRows.length-csvIndex)/Math.max(rate,0.1);
662
-
663
- setText('csvProgressStats',`${csvIndex.toLocaleString()} / ${csvRows.length.toLocaleString()} rows`);
664
- setText('csvProgressPct',pct+'%');
665
- setText('csvThreatRate',`Threats: ${csvIntrusionCount.toLocaleString()}`);
666
- setText('csvProgressEta',csvIndex<csvRows.length?`ETA: ${formatETA(remaining)}`:'Done!');
667
- setText('csvSpeedStat',Math.round(rate)+' rows/s');
668
- document.getElementById('csvProgressFill').style.width=pct+'%';
669
- if(csvIntrusionCount/Math.max(csvIndex,1)>0.5)
670
- document.getElementById('csvProgressFill').classList.add('warning');
671
-
672
- // Only update sidebar every 3 batches — PERF FIX
673
- if(batchNum%3===0||batchNum===totalBatches)updateCsvSidebar(rate);
674
- setText('csvAlertCount',csvIntrusionCount.toLocaleString()+' THREATS');
675
-
676
- await new Promise(r=>setTimeout(r,60)); // more breathing room — PERF FIX
677
- }
678
- if(csvIndex>=csvRows.length)finishCsvAnalysis();
679
- }
680
-
681
- function setConnBadge(type){
682
- const el=document.getElementById('connBadge');
683
- if(type==='real'){el.textContent='✓ REAL MODEL';el.className='real';}
684
- else{el.textContent='⚠ LOCAL SIM';el.className='local';}
685
- }
686
-
687
- function updateBatchChips(current,total){
688
- const el=document.getElementById('batchStatus');el.innerHTML='';
689
- const show=Math.min(total,12);
690
- for(let i=1;i<=show;i++){
691
- const chip=document.createElement('div');
692
- chip.className='batch-chip'+(i<current?' done':i===current?' active':'');
693
- chip.textContent=i<current?`✓${i}`:i===current?`⟳${i}`:`${i}`;
694
- el.appendChild(chip);
695
- }
696
- if(total>show){const chip=document.createElement('div');chip.className='batch-chip';chip.textContent=`+${total-show} more`;el.appendChild(chip);}
697
- }
698
-
699
- function addCsvFeedRow(rowNum,row,cls,conf,sev){
700
- const tbody=document.getElementById('csvFeedBody');
701
- const tr=document.createElement('tr');tr.className='csv-new-row';
702
- if(cls!=='normal')tr.style.background='rgba(255,61,90,0.025)';
703
- tr.innerHTML=`<td style="color:var(--muted)">${rowNum}</td><td style="color:var(--cyan)">${row.protocol_type||'—'}</td><td>${row.service||'—'}</td><td>${(row.src_bytes||0).toLocaleString()}</td><td><span class="cls-badge cls-${cls}">${cls}</span></td><td style="color:${conf>0.9?'var(--accent)':conf>0.8?'var(--cyan)':'var(--amber)'}">${(conf*100).toFixed(1)}%</td><td style="color:${SEV_COLOR[sev]}">● ${sev}</td>`;
704
- tbody.insertBefore(tr,tbody.firstChild);
705
- // Cap at 100 DOM rows — PERF FIX (was 500)
706
- while(tbody.children.length>100)tbody.removeChild(tbody.lastChild);
707
- }
708
-
709
- function updateCsvSidebar(rate){
710
- const classes=['normal','DoS','Probe','R2L','U2R'];
711
- const mx=Math.max(...classes.map(c=>csvCounts[c]||0),1);
712
- classes.forEach(c=>{setWidth('csvbar-'+c,(csvCounts[c]||0)/mx*100);setText('csvbc-'+c,(csvCounts[c]||0).toLocaleString());});
713
- setText('csvAvgConf',csvIndex>0?(csvConfSum/csvIndex*100).toFixed(1)+'%':'—');
714
- const sevs=['Critical','High','Medium','None'];
715
- const smx=Math.max(...sevs.map(s=>csvSevCounts[s]||0),1);
716
- sevs.forEach(s=>{setWidth('sevbar-'+s,(csvSevCounts[s]||0)/smx*100);setText('sevbc-'+s,(csvSevCounts[s]||0).toLocaleString());});
717
- setText('csvProcRate',Math.round(rate).toLocaleString());
718
- }
719
-
720
- function stopCsvAnalysis(){
721
- csvRunning=false;
722
- document.getElementById('csvStartBtn').disabled=false;
723
- document.getElementById('csvStopBtn').disabled=true;
724
- document.getElementById('liveDot').className='dot red';
725
- setText('liveStatus','PAUSED');
726
- }
727
-
728
- function finishCsvAnalysis(){
729
- csvRunning=false;
730
- document.getElementById('csvStartBtn').disabled=true;
731
- document.getElementById('csvStopBtn').disabled=true;
732
- document.getElementById('liveDot').className='dot green';
733
- setText('liveStatus','DONE');
734
- setText('csvProgressEta','Done!');
735
- setText('csvCurrentRow',`✓ All ${csvRows.length.toLocaleString()} rows processed. Building report…`);
736
- setTimeout(()=>{exportAnnotatedCSV();buildReport();},300);
737
- }
738
-
739
- // ── REPORT BUILDER ───────────────────────────────
740
- function buildReport(){
741
- document.getElementById('reportSection').classList.add('visible');
742
- const elapsed=(Date.now()-csvStartTime)/1000;
743
- const fileName=document.getElementById('csvFileName').textContent;
744
- const total=csvResults.length,threats=csvIntrusionCount;
745
- const avgConf=(csvConfSum/total*100).toFixed(1)+'%';
746
- const rate=(threats/total*100).toFixed(1)+'%';
747
- const riskScore=Math.min(100,Math.round(((csvCounts.DoS||0)*0.4+(csvCounts.U2R||0)*0.35+(csvCounts.R2L||0)*0.15+(csvCounts.Probe||0)*0.1)/Math.max(total,1)*100*6));
748
-
749
- setText('bannerSub',`${total.toLocaleString()} rows · ${formatETA(elapsed)} · ${threats.toLocaleString()} threats detected`);
750
- setText('rmFile',fileName);setText('rmRows',total.toLocaleString());setText('rmDate',new Date().toLocaleString());
751
- setText('rmModel',csvUsingReal?'Real Random Forest':'Local Simulation');
752
- setText('rmDuration',formatETA(elapsed));setText('rmFormat',csvFormatInfo);
753
- setText('reportSubtitle',`Generated ${new Date().toUTCString()}`);
754
- setText('rs-total',total.toLocaleString());setText('rs-threats',threats.toLocaleString());
755
- setText('rs-rate',rate);setText('rs-conf',avgConf);setText('rs-risk',riskScore+'/100');
756
-
757
- const sevs=['Critical','High','Medium','None'];
758
- const smx=Math.max(...sevs.map(s=>csvSevCounts[s]||0),1);
759
- sevs.forEach(s=>{setWidth('rsevbar-'+s,(csvSevCounts[s]||0)/smx*100);setText('rsevbc-'+s,(csvSevCounts[s]||0).toLocaleString());});
760
-
761
- // Draw charts using requestAnimationFrame — PERF FIX
762
- requestAnimationFrame(()=>{
763
- drawBarChart();drawConfWave();drawIntensity();
764
- drawProto();drawServices();drawGauge(riskScore);
765
- buildClusters();
766
- reportPage=0;renderReportPage(); // paginated table — PERF FIX
767
- });
768
-
769
- setTimeout(()=>document.getElementById('reportSection').scrollIntoView({behavior:'smooth',block:'start'}),300);
770
- }
771
-
772
- // ── PAGINATED TABLE — renders only PAGE_SIZE rows at a time ─────────────────
773
- function renderReportPage(){
774
- const tbody=document.getElementById('reportTableBody');
775
- const total=csvResults.length;
776
- const totalPages=Math.ceil(total/PAGE_SIZE);
777
- const start=reportPage*PAGE_SIZE;
778
- const end=Math.min(start+PAGE_SIZE,total);
779
-
780
- // Build fragment — only PAGE_SIZE rows, not all rows — KEY PERF FIX
781
- const frag=document.createDocumentFragment();
782
- for(let i=start;i<end;i++){
783
- const{rowNum,row,cls,conf,sev,isI}=csvResults[i];
784
- const tr=document.createElement('tr');
785
- if(isI)tr.className='row-intrusion';
786
- tr.innerHTML=`<td style="color:var(--muted)">${rowNum}</td><td style="color:var(--cyan)">${row.protocol_type||'—'}</td><td>${row.service||'—'}</td><td>${(row.src_bytes||0).toLocaleString()}</td><td>${(row.dst_bytes||0).toLocaleString()}</td><td><span class="cls-badge cls-${cls}">${cls}</span></td><td style="color:${conf>0.9?'var(--accent)':conf>0.8?'var(--cyan)':'var(--amber)'}">${(conf*100).toFixed(1)}%</td><td style="color:${SEV_COLOR[sev]}">● ${sev}</td><td style="color:var(--muted);font-size:10px">${row.label||'—'}</td>`;
787
- frag.appendChild(tr);
788
- }
789
- tbody.innerHTML='';
790
- tbody.appendChild(frag);
791
-
792
- setText('reportRowCount',`${total.toLocaleString()} rows`);
793
- setText('pgInfo',`Page ${reportPage+1} of ${totalPages} · rows ${start+1}–${end}`);
794
- document.getElementById('pgPrev').disabled=reportPage===0;
795
- document.getElementById('pgNext').disabled=reportPage>=totalPages-1;
796
- }
797
-
798
- function changePage(dir){
799
- reportPage+=dir;
800
- renderReportPage();
801
- document.getElementById('reportSection').scrollIntoView({behavior:'smooth',block:'start'});
802
- }
803
-
804
- // ── CHARTS ───────────────────────────────────────
805
- function drawBarChart(){
806
- const c=document.getElementById('reportBarCanvas'),ctx=c.getContext('2d');
807
- const W=c.offsetWidth||300,H=160;c.width=W*devicePixelRatio;c.height=H*devicePixelRatio;
808
- ctx.scale(devicePixelRatio,devicePixelRatio);ctx.clearRect(0,0,W,H);
809
- const classes=['normal','DoS','Probe','R2L','U2R'],colors=['#00e87a','#ff3d5a','#00c8e8','#ffaa00','#b06fff'];
810
- const vals=classes.map(c=>csvCounts[c]||0),mx=Math.max(...vals,1);
811
- const bw=(W-40)/classes.length,pad=bw*0.18;
812
- classes.forEach((cls,i)=>{
813
- const x=20+i*bw+pad,bW=bw-pad*2,bH=(vals[i]/mx)*(H-30),y=H-10-bH;
814
- const g=ctx.createLinearGradient(0,y,0,H-10);g.addColorStop(0,colors[i]);g.addColorStop(1,colors[i]+'33');
815
- ctx.fillStyle=g;ctx.beginPath();ctx.roundRect(x,y,bW,Math.max(bH,1),4);ctx.fill();
816
- ctx.fillStyle='rgba(90,122,153,.9)';ctx.font='9px IBM Plex Mono';ctx.textAlign='center';
817
- ctx.fillText(cls,x+bW/2,H-1);
818
- if(vals[i]>0){ctx.fillStyle=colors[i];ctx.fillText(vals[i].toLocaleString(),x+bW/2,y-4);}
819
- });
820
- }
821
-
822
- function drawConfWave(){
823
- const c=document.getElementById('reportConfCanvas'),ctx=c.getContext('2d');
824
- const W=c.offsetWidth||300,H=160;c.width=W*devicePixelRatio;c.height=H*devicePixelRatio;
825
- ctx.scale(devicePixelRatio,devicePixelRatio);ctx.clearRect(0,0,W,H);
826
- const data=csvConfHistory;if(data.length<2)return;
827
- const xStep=(W-20)/Math.max(data.length-1,1),mn=0.5,mx=1;
828
- ctx.strokeStyle='rgba(0,200,120,.06)';ctx.lineWidth=1;
829
- [.25,.5,.75,1].forEach(f=>{const y=10+(1-f)*(H-20);ctx.beginPath();ctx.moveTo(10,y);ctx.lineTo(W-10,y);ctx.stroke();});
830
- ctx.beginPath();data.forEach((v,i)=>{const x=10+i*xStep,y=10+(1-(v-mn)/(mx-mn))*(H-20);i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);});
831
- ctx.lineTo(10+(data.length-1)*xStep,H-10);ctx.lineTo(10,H-10);ctx.closePath();
832
- const g=ctx.createLinearGradient(0,0,0,H);g.addColorStop(0,'rgba(0,200,232,.15)');g.addColorStop(1,'rgba(0,200,232,.01)');
833
- ctx.fillStyle=g;ctx.fill();
834
- ctx.beginPath();ctx.strokeStyle='#00c8e8';ctx.lineWidth=1.5;
835
- data.forEach((v,i)=>{const x=10+i*xStep,y=10+(1-(v-mn)/(mx-mn))*(H-20);i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);});
836
- ctx.stroke();
837
- const avg=csvConfSum/csvResults.length,avgY=10+(1-(avg-mn)/(mx-mn))*(H-20);
838
- ctx.beginPath();ctx.setLineDash([4,3]);ctx.strokeStyle='rgba(0,232,122,.6)';ctx.lineWidth=1;
839
- ctx.moveTo(10,avgY);ctx.lineTo(W-10,avgY);ctx.stroke();ctx.setLineDash([]);
840
- ctx.fillStyle='rgba(0,232,122,.8)';ctx.font='9px IBM Plex Mono';ctx.textAlign='left';
841
- ctx.fillText(`avg ${(avg*100).toFixed(1)}%`,14,avgY-5);
842
- }
843
-
844
- function drawIntensity(){
845
- const c=document.getElementById('reportIntensityCanvas'),ctx=c.getContext('2d');
846
- const W=c.offsetWidth||300,H=160;c.width=W*devicePixelRatio;c.height=H*devicePixelRatio;
847
- ctx.scale(devicePixelRatio,devicePixelRatio);ctx.clearRect(0,0,W,H);
848
- const wSize=Math.max(5,Math.floor(csvResults.length/60));
849
- const windows=[];
850
- for(let i=0;i<csvResults.length;i+=wSize){const sl=csvResults.slice(i,i+wSize);windows.push(sl.filter(r=>r.isI).length/sl.length);}
851
- if(windows.length<2){ctx.fillStyle='#4a6a88';ctx.font='11px IBM Plex Mono';ctx.textAlign='center';ctx.fillText('Not enough data',W/2,H/2);return;}
852
- const mx=Math.max(...windows,.01),xStep=(W-20)/Math.max(windows.length-1,1);
853
- ctx.strokeStyle='rgba(255,61,90,.05)';ctx.lineWidth=1;
854
- [.25,.5,.75,1].forEach(f=>{const y=10+(1-f)*(H-20);ctx.beginPath();ctx.moveTo(10,y);ctx.lineTo(W-10,y);ctx.stroke();});
855
- ctx.beginPath();windows.forEach((v,i)=>{const x=10+i*xStep,y=10+(1-v/mx)*(H-20);i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);});
856
- ctx.lineTo(10+(windows.length-1)*xStep,H-10);ctx.lineTo(10,H-10);ctx.closePath();
857
- const g=ctx.createLinearGradient(0,0,0,H);g.addColorStop(0,'rgba(255,61,90,.28)');g.addColorStop(1,'rgba(255,61,90,.02)');
858
- ctx.fillStyle=g;ctx.fill();
859
- ctx.beginPath();ctx.strokeStyle='#ff3d5a';ctx.lineWidth=2;
860
- windows.forEach((v,i)=>{const x=10+i*xStep,y=10+(1-v/mx)*(H-20);i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);});ctx.stroke();
861
- }
862
-
863
- function drawProto(){
864
- const el=document.getElementById('protoBreakdown');el.innerHTML='';
865
- const pc={},pt={};
866
- csvResults.forEach(({row,isI})=>{const p=row.protocol_type||'unknown';pc[p]=(pc[p]||0)+1;if(isI)pt[p]=(pt[p]||0)+1;});
867
- const sorted=Object.entries(pc).sort((a,b)=>b[1]-a[1]);
868
- const mx=Math.max(...sorted.map(([,v])=>v),1);
869
- const frag=document.createDocumentFragment();
870
- sorted.forEach(([proto,cnt])=>{
871
- const tc=pt[proto]||0,tp=cnt>0?Math.round(tc/cnt*100):0;
872
- const div=document.createElement('div');div.className='proto-row';
873
- div.innerHTML=`<div class="proto-lbl">${proto}</div><div class="proto-track"><div class="proto-fill" style="width:${cnt/mx*100}%;background:${tc>0?'var(--red)':'var(--accent)'}"></div></div><div class="proto-cnt">${cnt.toLocaleString()} <span style="color:${tc>0?'var(--red)':'var(--muted)'}">${tp}% threat</span></div>`;
874
- frag.appendChild(div);
875
- });
876
- el.appendChild(frag);
877
- }
878
-
879
- function drawServices(){
880
- const el=document.getElementById('servicesList');el.innerHTML='';
881
- const sc={},st={};
882
- csvResults.forEach(({row,isI})=>{const s=row.service||'unknown';sc[s]=(sc[s]||0)+1;if(isI)st[s]=(st[s]||0)+1;});
883
- const sorted=Object.entries(sc).sort((a,b)=>b[1]-a[1]).slice(0,8);
884
- const mx=Math.max(...sorted.map(([,v])=>v),1);
885
- const frag=document.createDocumentFragment();
886
- sorted.forEach(([svc,cnt])=>{
887
- const hot=(st[svc]||0)>cnt*0.3;
888
- const div=document.createElement('div');div.className='svc-row';
889
- div.innerHTML=`<span class="svc-name" style="color:${hot?'var(--red)':'var(--cyan)'}">${svc}</span><span style="flex:1;margin:0 8px;background:var(--surface);border-radius:2px;height:4px;display:block;overflow:hidden"><span style="display:block;height:100%;width:${Math.round(cnt/mx*100)}%;background:${hot?'var(--red)':'var(--cyan)'};border-radius:2px"></span></span><span style="font-family:var(--mono);font-size:10px;color:var(--muted2)">${cnt.toLocaleString()}</span>`;
890
- frag.appendChild(div);
891
- });
892
- el.appendChild(frag);
893
- }
894
-
895
- function drawGauge(score){
896
- const c=document.getElementById('riskGaugeCanvas'),ctx=c.getContext('2d');
897
- const W=160,H=90;c.width=W*devicePixelRatio;c.height=H*devicePixelRatio;
898
- ctx.scale(devicePixelRatio,devicePixelRatio);ctx.clearRect(0,0,W,H);
899
- const cx=W/2,cy=H-8,r=66;
900
- ctx.beginPath();ctx.arc(cx,cy,r,Math.PI,2*Math.PI);ctx.strokeStyle='rgba(255,255,255,.05)';ctx.lineWidth=14;ctx.lineCap='round';ctx.stroke();
901
- [[0,.33,'#00e87a'],[.33,.66,'#ffaa00'],[.66,1,'#ff3d5a']].forEach(([from,to,col])=>{ctx.beginPath();ctx.arc(cx,cy,r,Math.PI+from*Math.PI,Math.PI+to*Math.PI);ctx.strokeStyle=col+'55';ctx.lineWidth=14;ctx.stroke();});
902
- const sc=score<33?'#00e87a':score<66?'#ffaa00':'#ff3d5a';
903
- ctx.beginPath();ctx.arc(cx,cy,r,Math.PI,Math.PI+(score/100)*Math.PI);ctx.strokeStyle=sc;ctx.lineWidth=14;ctx.lineCap='round';ctx.stroke();
904
- ctx.fillStyle=sc;ctx.font='bold 22px IBM Plex Mono';ctx.textAlign='center';ctx.textBaseline='middle';ctx.fillText(score,cx,cy-16);
905
- ctx.fillStyle='rgba(90,122,153,.8)';ctx.font='9px IBM Plex Mono';ctx.fillText('/100',cx,cy-2);
906
- const lbl=score<20?'LOW':score<40?'MODERATE':score<60?'ELEVATED':score<80?'HIGH':'CRITICAL';
907
- setText('riskLabel',`${lbl} RISK · Score ${score}/100`);
908
- }
909
-
910
- function buildClusters(){
911
- const grid=document.getElementById('clusterGrid');grid.innerHTML='';let cnt=0;
912
- const frag=document.createDocumentFragment();
913
- ['DoS','Probe','R2L','U2R','normal'].forEach(cls=>{
914
- const c=csvCounts[cls]||0;if(!c)return;cnt++;
915
- const col=CLASS_COLOR[cls],res=csvResults.filter(r=>r.cls===cls);
916
- const avgConf=res.reduce((s,r)=>s+r.conf,0)/c;
917
- const div=document.createElement('div');div.className=`cluster-card ${cls}`;
918
- div.innerHTML=`<div class="cluster-title" style="color:${col}">${cls} Traffic</div><div class="cluster-count" style="color:${col}">${c.toLocaleString()}</div><div class="cluster-sub">Severity: ${SEV_MAP[cls]}<br>Avg confidence: ${(avgConf*100).toFixed(1)}%<br>${(c/csvResults.length*100).toFixed(1)}% of dataset</div>`;
919
- frag.appendChild(div);
920
- });
921
- grid.appendChild(frag);
922
- if(!cnt)grid.innerHTML='<div style="padding:20px;font-family:var(--mono);font-size:11px;color:var(--accent);grid-column:span 4">✓ No attack clusters — clean dataset</div>';
923
- setText('clusterCount',cnt+' clusters');
924
- }
925
-
926
- // ── EXPORTS ──────────────────────────────────────
927
- function exportAnnotatedCSV(){
928
- if(!csvResults.length)return;
929
- const headers=Object.keys(csvResults[0].row).concat(['predicted_class','severity','confidence','is_intrusion']);
930
- const lines=[headers.join(',')];
931
- csvResults.forEach(({row,cls,sev,conf,isI})=>{
932
- const vals=Object.values(row).map(v=>typeof v==='string'&&v.includes(',')?`"${v}"`:v);
933
- vals.push(cls,sev,conf,isI?1:0);lines.push(vals.join(','));
934
- });
935
- downloadFile('sentinelnet_annotated.csv',lines.join('\n'),'text/csv');
936
- }
937
-
938
- function exportJSON(){
939
- if(!csvResults.length){alert('No results.');return;}
940
- const data={meta:{file:document.getElementById('csvFileName').textContent,date:new Date().toISOString(),total:csvResults.length,threats:csvIntrusionCount,threatRate:(csvIntrusionCount/csvResults.length*100).toFixed(1)+'%',model:csvUsingReal?'Real RF':'Local Sim',batchSize:BATCH_SIZE},distribution:csvCounts,severity:csvSevCounts,avgConf:(csvConfSum/csvResults.length*100).toFixed(2)+'%',results:csvResults.map(({rowNum,cls,conf,sev,isI})=>({rowNum,cls,conf,sev,isI}))};
941
- downloadFile('sentinelnet_results.json',JSON.stringify(data,null,2),'application/json');
942
- }
943
-
944
- function exportPDFReport(){
945
- if(!csvResults.length){alert('No results.');return;}
946
- const total=csvResults.length,threats=csvIntrusionCount;
947
- const rate=(threats/total*100).toFixed(1),avgConf=(csvConfSum/total*100).toFixed(1);
948
- const fileName=document.getElementById('csvFileName').textContent;
949
- const dateStr=new Date().toLocaleString();
950
- const elapsed=(Date.now()-csvStartTime)/1000;
951
- const modelSrc=csvUsingReal?'Real Random Forest Model':'Local Simulation';
952
- const riskScore=Math.min(100,Math.round(((csvCounts.DoS||0)*0.4+(csvCounts.U2R||0)*0.35+(csvCounts.R2L||0)*0.15+(csvCounts.Probe||0)*0.1)/Math.max(total,1)*100*6));
953
- const riskLbl=riskScore<20?'LOW':riskScore<40?'MODERATE':riskScore<60?'ELEVATED':riskScore<80?'HIGH':'CRITICAL';
954
- const colors={normal:'#00e87a',DoS:'#ff3d5a',Probe:'#00c8e8',R2L:'#ffaa00',U2R:'#b06fff'};
955
- const sevColors={None:'#00e87a',Medium:'#00c8e8',High:'#ffaa00',Critical:'#ff3d5a'};
956
- const distRows=['normal','DoS','Probe','R2L','U2R'].map(c=>`<tr><td style="color:${colors[c]}">${c}</td><td>${(csvCounts[c]||0).toLocaleString()}</td><td>${total>0?((csvCounts[c]||0)/total*100).toFixed(1):'0'}%</td><td style="color:${sevColors[SEV_MAP[c]]}">${SEV_MAP[c]}</td></tr>`).join('');
957
- const pc={};csvResults.forEach(({row})=>{const p=row.protocol_type||'unknown';pc[p]=(pc[p]||0)+1;});
958
- const protoRows=Object.entries(pc).sort((a,b)=>b[1]-a[1]).map(([p,c])=>`<tr><td>${p}</td><td>${c.toLocaleString()}</td><td>${(c/total*100).toFixed(1)}%</td></tr>`).join('');
959
- const topThreats=csvResults.filter(r=>r.isI).slice(0,100);
960
- const threatRows=topThreats.map(({rowNum,row,cls,conf,sev})=>`<tr><td>${rowNum}</td><td>${row.protocol_type||'—'}</td><td>${row.service||'—'}</td><td>${(row.src_bytes||0).toLocaleString()}</td><td style="color:${colors[cls]};font-weight:bold">${cls}</td><td>${(conf*100).toFixed(1)}%</td><td style="color:${sevColors[sev]}">${sev}</td></tr>`).join('');
961
- const html=`<!DOCTYPE html><html><head><meta charset="UTF-8"/><title>SentinelNet Report</title>
962
- <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600;700&display=swap" rel="stylesheet">
963
- <style>*{box-sizing:border-box;margin:0;padding:0}body{font-family:'IBM Plex Mono',monospace;background:#04080d;color:#d8eeff;padding:40px;font-size:11px}h1{color:#00e87a;font-size:22px;border-bottom:2px solid #00e87a;padding-bottom:12px;margin-bottom:8px}h2{color:#00c8e8;font-size:13px;margin:28px 0 12px;letter-spacing:2px;text-transform:uppercase}.meta{color:#4a6a88;font-size:10px;margin-bottom:32px;line-height:2.2}.grid{display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin-bottom:32px}.stat{background:#080e16;border:1px solid rgba(0,210,130,0.15);border-radius:10px;padding:16px;text-align:center}.stat-val{font-size:24px;font-weight:700;margin-bottom:5px}.stat-lbl{font-size:8px;color:#4a6a88;letter-spacing:2px;text-transform:uppercase}table{width:100%;border-collapse:collapse;margin-bottom:28px}th{background:#0d1520;padding:9px 12px;text-align:left;color:#4a6a88;font-size:8px;letter-spacing:1.5px;border-bottom:1px solid rgba(0,210,130,0.2)}td{padding:7px 12px;border-top:1px solid rgba(0,210,130,0.06)}.risk-box{background:#0d1520;border:1px solid rgba(0,210,130,0.2);border-radius:12px;padding:20px;text-align:center;margin-bottom:28px}.risk-num{font-size:40px;font-weight:700;color:${riskScore<33?'#00e87a':riskScore<66?'#ffaa00':'#ff3d5a'}}.footer{margin-top:40px;padding-top:14px;border-top:1px solid rgba(0,210,130,0.12);color:#4a6a88;font-size:9px;text-align:center}@media print{body{background:#04080d!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}}</style></head><body>
964
- <h1>🛡 SentinelNet — Threat Analysis Report</h1>
965
- <div class="meta">File: <strong style="color:#d8eeff">${fileName}</strong> &nbsp;|&nbsp; Date: ${dateStr} &nbsp;|&nbsp; Model: ${modelSrc} &nbsp;|&nbsp; Duration: ${formatETA(elapsed)}</div>
966
- <div class="grid"><div class="stat"><div class="stat-val" style="color:#00c8e8">${total.toLocaleString()}</div><div class="stat-lbl">Total Rows</div></div><div class="stat"><div class="stat-val" style="color:#ff3d5a">${threats.toLocaleString()}</div><div class="stat-lbl">Threats Found</div></div><div class="stat"><div class="stat-val" style="color:#ffaa00">${rate}%</div><div class="stat-lbl">Threat Rate</div></div><div class="stat"><div class="stat-val" style="color:#00e87a">${avgConf}%</div><div class="stat-lbl">Avg Confidence</div></div><div class="stat"><div class="stat-val" style="color:${riskScore<33?'#00e87a':riskScore<66?'#ffaa00':'#ff3d5a'}">${riskScore}/100</div><div class="stat-lbl">Risk Score</div></div></div>
967
- <div class="risk-box"><div class="risk-num">${riskScore}</div><div style="font-size:13px;color:${riskScore<33?'#00e87a':riskScore<66?'#ffaa00':'#ff3d5a'};margin-top:4px">${riskLbl} RISK</div><div style="color:#4a6a88;font-size:10px;margin-top:8px">Weighted: DoS×0.4 · U2R×0.35 · R2L×0.15 · Probe×0.1</div></div>
968
- <h2>Attack Class Distribution</h2><table><thead><tr><th>CLASS</th><th>COUNT</th><th>PERCENTAGE</th><th>SEVERITY</th></tr></thead><tbody>${distRows}</tbody></table>
969
- <h2>Protocol Breakdown</h2><table><thead><tr><th>PROTOCOL</th><th>COUNT</th><th>PERCENTAGE</th></tr></thead><tbody>${protoRows}</tbody></table>
970
- <h2>Severity Summary</h2><table><thead><tr><th>SEVERITY</th><th>COUNT</th><th>PERCENTAGE</th></tr></thead><tbody>${['Critical','High','Medium','None'].map(s=>`<tr><td style="color:${sevColors[s]}">${s}</td><td>${(csvSevCounts[s]||0).toLocaleString()}</td><td>${((csvSevCounts[s]||0)/total*100).toFixed(1)}%</td></tr>`).join('')}</tbody></table>
971
- <h2>Detected Threats — Top 100</h2><table><thead><tr><th>ROW</th><th>PROTOCOL</th><th>SERVICE</th><th>SRC BYTES</th><th>CLASS</th><th>CONFIDENCE</th><th>SEVERITY</th></tr></thead><tbody>${threatRows||'<tr><td colspan="7" style="color:#00e87a;text-align:center;padding:20px">No threats detected</td></tr>'}</tbody></table>
972
- <div class="footer">Generated by SentinelNet · ${dateStr} · NSL-KDD Intrusion Detection</div>
973
- </body></html>`;
974
- const win=window.open('','_blank','width=1100,height=900');
975
- if(!win){alert('Pop-up blocked.');return;}
976
- win.document.write(html);win.document.close();setTimeout(()=>win.print(),900);
977
- }
978
-
979
- function downloadFile(filename,content,type){
980
- const blob=new Blob([content],{type});const url=URL.createObjectURL(blob);
981
- const a=document.createElement('a');a.href=url;a.download=filename;a.click();URL.revokeObjectURL(url);
982
- }
983
-
984
- function resetCsvTab(){
985
- csvRunning=false;csvRows=[];csvResults=[];csvIndex=0;csvConfSum=0;csvIntrusionCount=0;
986
- csvConfHistory=[];batchNum=0;reportPage=0;
987
- Object.keys(csvCounts).forEach(k=>csvCounts[k]=0);
988
- Object.keys(csvSevCounts).forEach(k=>csvSevCounts[k]=0);
989
- csvStartTime=null;csvUsingReal=false;csvFormatInfo='';totalBatches=0;
990
- document.getElementById('csvUploadSection').style.display='';
991
- document.getElementById('csvProcessingArea').classList.remove('visible');
992
- document.getElementById('csvProgressBlock').style.display='none';
993
- document.getElementById('csvLiveGrid').style.display='none';
994
- document.getElementById('reportSection').classList.remove('visible');
995
- document.getElementById('csvFeedBody').innerHTML='';
996
- document.getElementById('reportTableBody').innerHTML='';
997
- document.getElementById('csvFileInput').value='';
998
- document.getElementById('formatBanner').style.display='none';
999
- document.getElementById('liveDot').className='dot';setText('liveStatus','IDLE');
1000
- document.getElementById('csvStartBtn').disabled=false;
1001
- document.getElementById('csvStopBtn').disabled=true;
1002
- document.getElementById('batchStatus').innerHTML='';
1003
- document.getElementById('csvProgressFill').style.width='0%';
1004
- document.getElementById('csvProgressFill').classList.remove('warning');
1005
- }
1006
-
1007
- // ── LIVE MONITOR ─────────────────────────────────
1008
- function generatePacket(){
1009
- const label=LABEL_POOL[Math.floor(Math.random()*LABEL_POOL.length)];
1010
- const isAtk=label!=='normal';
1011
- const protocol=PROTOCOLS[Math.floor(Math.random()*3)];
1012
- const service=SERVICES[Math.floor(Math.random()*SERVICES.length)];
1013
- const flag=isAtk&&Math.random()>0.5?['S0','REJ','RSTO'][Math.floor(Math.random()*3)]:'SF';
1014
- const srcBytes=isAtk?Math.floor(Math.random()*200000):Math.floor(Math.random()*4000);
1015
- return{duration:Math.floor(Math.random()*3600),protocol_type:protocol,service,flag,src_bytes:srcBytes,dst_bytes:Math.floor(Math.random()*8000),land:0,wrong_fragment:Math.floor(Math.random()*2),urgent:0,hot:Math.floor(Math.random()*8),num_failed_logins:isAtk?Math.floor(Math.random()*3):0,logged_in:Math.random()>0.4?1:0,num_compromised:isAtk?Math.floor(Math.random()*5):0,root_shell:0,su_attempted:0,num_root:0,num_file_creations:0,num_shells:0,num_access_files:0,num_outbound_cmds:0,is_host_login:0,is_guest_login:Math.random()>0.95?1:0,count:Math.floor(Math.random()*511),srv_count:Math.floor(Math.random()*511),serror_rate:+(Math.random()).toFixed(2),srv_serror_rate:+(Math.random()).toFixed(2),rerror_rate:+(Math.random()).toFixed(2),srv_rerror_rate:+(Math.random()).toFixed(2),same_srv_rate:+(Math.random()).toFixed(2),diff_srv_rate:+(Math.random()).toFixed(2),srv_diff_host_rate:+(Math.random()).toFixed(2),dst_host_count:Math.floor(Math.random()*255),dst_host_srv_count:Math.floor(Math.random()*255),dst_host_same_srv_rate:+(Math.random()).toFixed(2),dst_host_diff_srv_rate:+(Math.random()).toFixed(2),dst_host_same_src_port_rate:+(Math.random()).toFixed(2),dst_host_srv_diff_host_rate:+(Math.random()).toFixed(2),dst_host_serror_rate:+(Math.random()).toFixed(2),dst_host_srv_serror_rate:+(Math.random()).toFixed(2),dst_host_rerror_rate:+(Math.random()).toFixed(2),dst_host_srv_rerror_rate:+(Math.random()).toFixed(2),label,difficulty_level:Math.floor(Math.random()*21)};
1016
- }
1017
-
1018
- async function tick(){
1019
- const packet=generatePacket();
1020
- let result;
1021
- const api=await predictSingle(packet);
1022
- if(api){result=api;if(!usingRealModel){usingRealModel=true;document.getElementById('connBadge').textContent='✓ REAL MODEL';document.getElementById('connBadge').className='real';document.getElementById('sum-model').textContent='REAL RF';termLog('info','Connected to model');}}
1023
- else{result=classifyLocal(packet);if(usingRealModel){usingRealModel=false;document.getElementById('connBadge').textContent='⚠ LOCAL SIM';document.getElementById('connBadge').className='local';document.getElementById('sum-model').textContent='LOCAL';}}
1024
- packetId++;totalPackets++;
1025
- const cls=result.predicted_class,conf=result.confidence,sev=result.severity,isI=result.is_intrusion;
1026
- counts[cls]=(counts[cls]||0)+1;
1027
- if(isI){totalIntrusions++;if(!peakClass||counts[cls]>(counts[peakClass]||0))peakClass=cls;}
1028
- confSum+=conf;
1029
- const cp=conf*100;
1030
- if(cp>=90)confBuckets[90]++;else if(cp>=80)confBuckets[80]++;else if(cp>=70)confBuckets[70]++;else confBuckets.low++;
1031
- timelineBuckets[sessionSeconds%60]+=(isI?1:0);
1032
- heatmapCells.shift();heatmapCells.push(cls);
1033
- addFeedRow(packet,cls,sev,conf);
1034
- if(cls==='U2R')termLog('crit',`U2R ALERT — Privilege escalation! Conf: ${(conf*100).toFixed(1)}%`);
1035
- else if(cls==='DoS'&&Math.random()<0.15)termLog('warn',`DoS — ${packet.service} flood`);
1036
- else if(cls==='normal'&&totalPackets%50===0)termLog('ok',`${totalPackets} packets. Rate: ${(totalIntrusions/totalPackets*100).toFixed(1)}%`);
1037
- updateMetrics();updateBars();updateConfBars();
1038
- // Throttle timeline — only redraw every 5 ticks — PERF FIX
1039
- tlDirty=true;
1040
- updateHeatmap();updateSummary();flashMetric(cls);
1041
- }
1042
-
1043
- // Separate timer for timeline — PERF FIX
1044
- setInterval(()=>{if(tlDirty){updateTimeline();tlDirty=false;}},500);
1045
-
1046
- function addFeedRow(packet,cls,sev,conf){
1047
- const tbody=document.getElementById('feedBody');
1048
- const now=new Date(),ts=`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
1049
- const tr=document.createElement('tr');tr.className='new-row';
1050
- tr.innerHTML=`<td style="color:var(--muted)">${packetId}</td><td style="color:var(--muted)">${ts}</td><td style="color:var(--cyan)">${packet.protocol_type}</td><td>${packet.src_bytes.toLocaleString()}</td><td><span class="cls-badge cls-${cls}">${cls}</span></td><td style="color:${conf>0.9?'var(--accent)':conf>0.8?'var(--cyan)':'var(--amber)'}">${(conf*100).toFixed(1)}%</td><td style="color:${SEV_COLOR[sev]}">● ${sev}</td>`;
1051
- tbody.insertBefore(tr,tbody.firstChild);
1052
- // Cap at 100 DOM rows — PERF FIX (was 200)
1053
- while(tbody.children.length>100)tbody.removeChild(tbody.lastChild);
1054
- document.getElementById('emptyState').style.display='none';
1055
- document.getElementById('feedTable').style.display='';
1056
- document.getElementById('alertCount').textContent=totalIntrusions+' ALERTS';
1057
- }
1058
-
1059
- function updateMetrics(){
1060
- const pct=n=>totalPackets>0?(n/totalPackets*100).toFixed(1)+'%':'—';
1061
- setText('m-total',totalPackets.toLocaleString());setText('m-normal',counts.normal||0);setText('m-normal-pct',pct(counts.normal||0));
1062
- setText('m-intrusions',totalIntrusions);setText('m-intrusions-pct',pct(totalIntrusions));
1063
- setText('m-dos',counts.DoS||0);setText('m-probe',counts.Probe||0);setText('m-u2r',counts.U2R||0);
1064
- setText('m-rate',(totalPackets/Math.max(sessionSeconds,1)).toFixed(1)+' /sec');
1065
- setWidth('mb-total',Math.min(totalPackets/500*100,100));setWidth('mb-normal',pctW(counts.normal));
1066
- setWidth('mb-intrusions',pctW(totalIntrusions));setWidth('mb-dos',pctW(counts.DoS));setWidth('mb-probe',pctW(counts.Probe));setWidth('mb-u2r',pctW(counts.U2R));
1067
- }
1068
- function pctW(n){return totalPackets>0?(n/totalPackets*100):0;}
1069
- function updateBars(){
1070
- const cls=['normal','DoS','Probe','R2L','U2R'];const mx=Math.max(...cls.map(c=>counts[c]||0),1);
1071
- cls.forEach(c=>{setWidth('bar-'+c,(counts[c]||0)/mx*100);setText('bc-'+c,counts[c]||0);});
1072
- setText('distTotal',totalPackets+' total');
1073
- }
1074
- function updateConfBars(){
1075
- const mx=Math.max(...Object.values(confBuckets),1);
1076
- setWidth('conf-90',confBuckets[90]/mx*100);setWidth('conf-80',confBuckets[80]/mx*100);
1077
- setWidth('conf-70',confBuckets[70]/mx*100);setWidth('conf-low',confBuckets.low/mx*100);
1078
- setText('cbc-90',confBuckets[90]);setText('cbc-80',confBuckets[80]);setText('cbc-70',confBuckets[70]);setText('cbc-low',confBuckets.low);
1079
- setText('avgConf','avg: '+(totalPackets>0?(confSum/totalPackets*100).toFixed(1)+'%':'—'));
1080
- }
1081
- function updateTimeline(){
1082
- const canvas=document.getElementById('tlCanvas');if(!canvas)return;
1083
- const ctx=canvas.getContext('2d');const W=canvas.offsetWidth;const H=80;
1084
- if(!W)return;
1085
- canvas.width=W*devicePixelRatio;canvas.height=H*devicePixelRatio;
1086
- ctx.scale(devicePixelRatio,devicePixelRatio);ctx.clearRect(0,0,W,H);
1087
- const data=timelineBuckets;const mx=Math.max(...data,1);const step=W/data.length;
1088
- ctx.strokeStyle='rgba(0,200,120,.06)';ctx.lineWidth=1;
1089
- [.25,.5,.75,1].forEach(f=>{ctx.beginPath();ctx.moveTo(0,H*f);ctx.lineTo(W,H*f);ctx.stroke();});
1090
- ctx.beginPath();ctx.moveTo(0,H);
1091
- data.forEach((v,i)=>{const x=i*step,y=H-(v/mx*(H-10));ctx.lineTo(x,y);});
1092
- ctx.lineTo(W,H);ctx.closePath();ctx.fillStyle='rgba(255,61,90,.12)';ctx.fill();
1093
- ctx.beginPath();ctx.strokeStyle='#ff3d5a';ctx.lineWidth=1.5;
1094
- data.forEach((v,i)=>{const x=i*step,y=H-(v/mx*(H-10));i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);});
1095
- ctx.stroke();
1096
- }
1097
- const HM={normal:'rgba(0,232,122,0.4)',DoS:'rgba(255,61,90,0.65)',Probe:'rgba(0,200,232,0.5)',R2L:'rgba(255,170,0,0.55)',U2R:'rgba(176,111,255,0.75)',null:'rgba(255,255,255,0.04)'};
1098
- function updateHeatmap(){
1099
- const grid=document.getElementById('heatmap');
1100
- if(!grid.children.length){
1101
- const f=document.createDocumentFragment();
1102
- for(let i=0;i<60;i++){const d=document.createElement('div');d.className='hm-cell';f.appendChild(d);}
1103
- grid.appendChild(f);
1104
- }
1105
- heatmapCells.forEach((cls,i)=>{grid.children[i].style.background=HM[cls]||HM[null];});
1106
- }
1107
- function updateSummary(){
1108
- setText('sum-rate',totalPackets>0?(totalIntrusions/totalPackets*100).toFixed(1)+'%':'—');
1109
- setText('sum-conf',totalPackets>0?(confSum/totalPackets*100).toFixed(1)+'%':'—');
1110
- setText('sum-peak',peakClass||'—');
1111
- }
1112
- function termLog(type,msg){
1113
- const wrap=document.getElementById('termWrap');const now=new Date();
1114
- const ts=`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
1115
- const div=document.createElement('div');div.className='term-line';
1116
- div.innerHTML=`<span class="ts">[${ts}]</span> <span class="${type}">${msg}</span>`;
1117
- wrap.appendChild(div);wrap.scrollTop=wrap.scrollHeight;
1118
- while(wrap.children.length>50)wrap.removeChild(wrap.firstChild);
1119
- }
1120
- const CLS_TO_MC={DoS:'mc-intrusions',Probe:'mc-probe',R2L:'mc-intrusions',U2R:'mc-u2r',normal:'mc-normal'};
1121
- function flashMetric(cls){const el=document.getElementById(CLS_TO_MC[cls]||'mc-total');if(!el)return;el.classList.remove('flash');void el.offsetWidth;el.classList.add('flash');}
1122
-
1123
- function startMonitor(){
1124
- if(isRunning)return;isRunning=true;
1125
- const speed=parseInt(document.getElementById('speedSel').value);
1126
- document.getElementById('startBtn').disabled=true;document.getElementById('stopBtn').disabled=false;
1127
- setText('liveStatus','LIVE');document.getElementById('liveDot').className='dot';
1128
- document.getElementById('connBadge').textContent='⟳ CONNECTING';document.getElementById('connBadge').className='idle';
1129
- termLog('info','Monitor started');
1130
- monitorInterval=setInterval(tick,speed);
1131
- sessionInterval=setInterval(()=>{
1132
- sessionSeconds++;
1133
- const m=Math.floor(sessionSeconds/60),s=sessionSeconds%60;
1134
- setText('sessionClock',`Session: ${pad(m)}:${pad(s)}`);
1135
- timelineBuckets[sessionSeconds%60]=0;
1136
- },1000);
1137
- }
1138
- function stopMonitor(){
1139
- if(!isRunning)return;isRunning=false;
1140
- clearInterval(monitorInterval);clearInterval(sessionInterval);
1141
- document.getElementById('startBtn').disabled=false;document.getElementById('stopBtn').disabled=true;
1142
- setText('liveStatus','PAUSED');document.getElementById('liveDot').className='dot red';
1143
- document.getElementById('connBadge').textContent='■ STOPPED';document.getElementById('connBadge').className='idle';
1144
- termLog('warn',`Stopped. ${totalPackets} packets analyzed.`);
1145
- }
1146
- function clearAll(){
1147
- stopMonitor();Object.keys(counts).forEach(k=>counts[k]=0);
1148
- totalPackets=0;totalIntrusions=0;confSum=0;packetId=0;
1149
- confBuckets={90:0,80:0,70:0,low:0};
1150
- timelineBuckets=Array(60).fill(0);heatmapCells=Array(60).fill(null);
1151
- peakClass=null;sessionSeconds=0;usingRealModel=false;
1152
- setText('sessionClock','Session: 00:00');setText('liveStatus','IDLE');
1153
- document.getElementById('liveDot').className='dot';
1154
- document.getElementById('feedBody').innerHTML='';
1155
- document.getElementById('feedTable').style.display='none';
1156
- document.getElementById('emptyState').style.display='';
1157
- document.getElementById('alertCount').textContent='0 ALERTS';
1158
- document.getElementById('connBadge').className='idle';
1159
- document.getElementById('connBadge').textContent='— IDLE';
1160
- document.getElementById('termWrap').innerHTML=`<div class="term-line"><span class="ts">[--:--:--]</span> <span class="info">Reset.</span></div>`;
1161
- updateMetrics();updateBars();updateConfBars();updateHeatmap();
1162
- ['sum-rate','sum-conf','sum-peak'].forEach(id=>setText(id,'—'));
1163
- setText('sum-model','LOCAL');
1164
- }
1165
-
1166
- // ── UTILS ────────────────────────────────────────
1167
- function setText(id,v){const el=document.getElementById(id);if(el)el.textContent=v;}
1168
- function setWidth(id,pct){const el=document.getElementById(id);if(el)el.style.width=pct+'%';}
1169
- function pad(n){return String(n).padStart(2,'0');}
1170
- function formatETA(secs){if(secs<60)return secs.toFixed(0)+'s';return`${Math.floor(secs/60)}m ${pad(Math.floor(secs%60))}s`;}
1171
-
1172
- // Init — deferred to after page load — PERF FIX
1173
- window.addEventListener('load',()=>{updateHeatmap();updateTimeline();});
1174
- </script>
1175
- </body>
1176
- </html>