aura-security 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +446 -0
- package/deploy/AWS-DEPLOYMENT.md +358 -0
- package/deploy/terraform/main.tf +362 -0
- package/deploy/terraform/terraform.tfvars.example +6 -0
- package/dist/agents/base.d.ts +44 -0
- package/dist/agents/base.js +96 -0
- package/dist/agents/index.d.ts +14 -0
- package/dist/agents/index.js +17 -0
- package/dist/agents/policy/evaluator.d.ts +15 -0
- package/dist/agents/policy/evaluator.js +183 -0
- package/dist/agents/policy/index.d.ts +12 -0
- package/dist/agents/policy/index.js +15 -0
- package/dist/agents/policy/validator.d.ts +15 -0
- package/dist/agents/policy/validator.js +182 -0
- package/dist/agents/scanners/gitleaks.d.ts +14 -0
- package/dist/agents/scanners/gitleaks.js +155 -0
- package/dist/agents/scanners/grype.d.ts +14 -0
- package/dist/agents/scanners/grype.js +109 -0
- package/dist/agents/scanners/index.d.ts +15 -0
- package/dist/agents/scanners/index.js +27 -0
- package/dist/agents/scanners/npm-audit.d.ts +13 -0
- package/dist/agents/scanners/npm-audit.js +129 -0
- package/dist/agents/scanners/semgrep.d.ts +14 -0
- package/dist/agents/scanners/semgrep.js +131 -0
- package/dist/agents/scanners/trivy.d.ts +14 -0
- package/dist/agents/scanners/trivy.js +122 -0
- package/dist/agents/types.d.ts +137 -0
- package/dist/agents/types.js +91 -0
- package/dist/auditor/index.d.ts +3 -0
- package/dist/auditor/index.js +2 -0
- package/dist/auditor/pipeline.d.ts +19 -0
- package/dist/auditor/pipeline.js +240 -0
- package/dist/auditor/validator.d.ts +17 -0
- package/dist/auditor/validator.js +58 -0
- package/dist/aura/client.d.ts +29 -0
- package/dist/aura/client.js +125 -0
- package/dist/aura/index.d.ts +4 -0
- package/dist/aura/index.js +2 -0
- package/dist/aura/server.d.ts +45 -0
- package/dist/aura/server.js +343 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +1433 -0
- package/dist/client/index.d.ts +41 -0
- package/dist/client/index.js +170 -0
- package/dist/compliance/index.d.ts +40 -0
- package/dist/compliance/index.js +292 -0
- package/dist/database/index.d.ts +77 -0
- package/dist/database/index.js +395 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +762 -0
- package/dist/integrations/aura-scanner.d.ts +69 -0
- package/dist/integrations/aura-scanner.js +155 -0
- package/dist/integrations/aws-scanner.d.ts +63 -0
- package/dist/integrations/aws-scanner.js +624 -0
- package/dist/integrations/config.d.ts +69 -0
- package/dist/integrations/config.js +212 -0
- package/dist/integrations/github.d.ts +45 -0
- package/dist/integrations/github.js +201 -0
- package/dist/integrations/gitlab.d.ts +36 -0
- package/dist/integrations/gitlab.js +110 -0
- package/dist/integrations/index.d.ts +11 -0
- package/dist/integrations/index.js +11 -0
- package/dist/integrations/local-scanner.d.ts +146 -0
- package/dist/integrations/local-scanner.js +1654 -0
- package/dist/integrations/notifications.d.ts +99 -0
- package/dist/integrations/notifications.js +305 -0
- package/dist/integrations/scanners.d.ts +57 -0
- package/dist/integrations/scanners.js +217 -0
- package/dist/integrations/slop-scanner.d.ts +69 -0
- package/dist/integrations/slop-scanner.js +155 -0
- package/dist/integrations/webhook.d.ts +37 -0
- package/dist/integrations/webhook.js +256 -0
- package/dist/orchestrator/index.d.ts +72 -0
- package/dist/orchestrator/index.js +187 -0
- package/dist/output/index.d.ts +152 -0
- package/dist/output/index.js +399 -0
- package/dist/pipeline/index.d.ts +72 -0
- package/dist/pipeline/index.js +313 -0
- package/dist/sbom/index.d.ts +94 -0
- package/dist/sbom/index.js +298 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/input.schema.d.ts +87 -0
- package/dist/schemas/input.schema.js +44 -0
- package/dist/schemas/output.schema.d.ts +115 -0
- package/dist/schemas/output.schema.js +64 -0
- package/dist/serve-visualizer.d.ts +2 -0
- package/dist/serve-visualizer.js +78 -0
- package/dist/slop/client.d.ts +29 -0
- package/dist/slop/client.js +125 -0
- package/dist/slop/index.d.ts +4 -0
- package/dist/slop/index.js +2 -0
- package/dist/slop/server.d.ts +45 -0
- package/dist/slop/server.js +343 -0
- package/dist/types/events.d.ts +62 -0
- package/dist/types/events.js +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/visualizer/index.d.ts +4 -0
- package/dist/visualizer/index.js +181 -0
- package/dist/websocket/index.d.ts +88 -0
- package/dist/websocket/index.js +195 -0
- package/dist/zones/index.d.ts +7 -0
- package/dist/zones/index.js +7 -0
- package/dist/zones/manager.d.ts +101 -0
- package/dist/zones/manager.js +304 -0
- package/dist/zones/types.d.ts +78 -0
- package/dist/zones/types.js +33 -0
- package/package.json +84 -0
- package/visualizer/app.js +0 -0
- package/visualizer/index-minimal.html +1771 -0
- package/visualizer/index.html +2933 -0
- package/visualizer/landing.html +1328 -0
- package/visualizer/styles.css +0 -0
|
@@ -0,0 +1,2933 @@
|
|
|
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>Aura Control Plane - Security Auditor</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
body {
|
|
10
|
+
background: #0a0a0f;
|
|
11
|
+
font-family: 'Courier New', monospace;
|
|
12
|
+
color: #00ff88;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#canvas-container {
|
|
17
|
+
position: fixed;
|
|
18
|
+
top: 0;
|
|
19
|
+
left: 0;
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
z-index: 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* UI Overlay */
|
|
26
|
+
.ui-layer {
|
|
27
|
+
position: fixed;
|
|
28
|
+
z-index: 100;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ui-layer > * {
|
|
33
|
+
pointer-events: auto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Header */
|
|
37
|
+
#header {
|
|
38
|
+
top: 0;
|
|
39
|
+
left: 0;
|
|
40
|
+
right: 0;
|
|
41
|
+
height: 50px;
|
|
42
|
+
background: linear-gradient(180deg, rgba(0,30,15,0.95) 0%, rgba(0,15,8,0.9) 100%);
|
|
43
|
+
border-bottom: 2px solid #00ff88;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
padding: 0 20px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#header h1 {
|
|
51
|
+
font-size: 16px;
|
|
52
|
+
letter-spacing: 3px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.status-badge {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 8px;
|
|
59
|
+
font-size: 12px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.status-dot {
|
|
63
|
+
width: 10px;
|
|
64
|
+
height: 10px;
|
|
65
|
+
border-radius: 50%;
|
|
66
|
+
background: #00ff88;
|
|
67
|
+
animation: pulse 2s infinite;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.status-dot.error { background: #ff4444; }
|
|
71
|
+
|
|
72
|
+
@keyframes pulse {
|
|
73
|
+
0%, 100% { opacity: 1; box-shadow: 0 0 10px #00ff88; }
|
|
74
|
+
50% { opacity: 0.5; box-shadow: 0 0 5px #00ff88; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Left Panel - Controls */
|
|
78
|
+
#left-panel {
|
|
79
|
+
top: 60px;
|
|
80
|
+
left: 20px;
|
|
81
|
+
width: 320px;
|
|
82
|
+
max-height: calc(100vh - 80px);
|
|
83
|
+
overflow-y: auto;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.panel {
|
|
87
|
+
background: rgba(0, 20, 10, 0.92);
|
|
88
|
+
border: 1px solid #00ff88;
|
|
89
|
+
padding: 15px;
|
|
90
|
+
margin-bottom: 10px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.panel-title {
|
|
94
|
+
font-size: 12px;
|
|
95
|
+
font-weight: bold;
|
|
96
|
+
text-transform: uppercase;
|
|
97
|
+
letter-spacing: 2px;
|
|
98
|
+
margin-bottom: 12px;
|
|
99
|
+
padding-bottom: 8px;
|
|
100
|
+
border-bottom: 1px solid #00ff8844;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Legend */
|
|
104
|
+
.legend-item {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 10px;
|
|
108
|
+
margin: 8px 0;
|
|
109
|
+
font-size: 11px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.legend-icon {
|
|
113
|
+
width: 20px;
|
|
114
|
+
height: 20px;
|
|
115
|
+
border-radius: 3px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.legend-icon.module { background: #00ff88; }
|
|
119
|
+
.legend-icon.auditor { background: #00aaff; border-radius: 50%; }
|
|
120
|
+
.legend-icon.connection { height: 3px; background: linear-gradient(90deg, #00ff88, #00aaff); }
|
|
121
|
+
.legend-icon.finding-crit { background: #ff0044; }
|
|
122
|
+
.legend-icon.finding-high { background: #ff4400; }
|
|
123
|
+
.legend-icon.finding-med { background: #ffaa00; }
|
|
124
|
+
.legend-icon.finding-low { background: #00ff88; }
|
|
125
|
+
|
|
126
|
+
/* Action Buttons */
|
|
127
|
+
.btn {
|
|
128
|
+
width: 100%;
|
|
129
|
+
padding: 12px;
|
|
130
|
+
margin: 5px 0;
|
|
131
|
+
background: linear-gradient(180deg, #00ff88 0%, #00aa55 100%);
|
|
132
|
+
border: none;
|
|
133
|
+
color: #000;
|
|
134
|
+
font-family: inherit;
|
|
135
|
+
font-size: 12px;
|
|
136
|
+
font-weight: bold;
|
|
137
|
+
text-transform: uppercase;
|
|
138
|
+
letter-spacing: 1px;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
transition: all 0.2s;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.btn:hover {
|
|
144
|
+
transform: translateY(-2px);
|
|
145
|
+
box-shadow: 0 5px 20px rgba(0, 255, 136, 0.3);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.btn.danger {
|
|
149
|
+
background: linear-gradient(180deg, #ff4444 0%, #aa2222 100%);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.btn.secondary {
|
|
153
|
+
background: transparent;
|
|
154
|
+
border: 1px solid #00ff88;
|
|
155
|
+
color: #00ff88;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.btn-group {
|
|
159
|
+
display: grid;
|
|
160
|
+
grid-template-columns: 1fr 1fr;
|
|
161
|
+
gap: 8px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Right Panel - Findings */
|
|
165
|
+
#right-panel {
|
|
166
|
+
top: 60px;
|
|
167
|
+
right: 20px;
|
|
168
|
+
width: 350px;
|
|
169
|
+
max-height: calc(100vh - 80px);
|
|
170
|
+
overflow-y: auto;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.finding {
|
|
174
|
+
margin: 10px 0;
|
|
175
|
+
padding: 12px;
|
|
176
|
+
border-left: 4px solid;
|
|
177
|
+
background: rgba(0, 0, 0, 0.4);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.finding.critical { border-color: #ff0044; }
|
|
181
|
+
.finding.high { border-color: #ff4400; }
|
|
182
|
+
.finding.medium { border-color: #ffaa00; }
|
|
183
|
+
.finding.low { border-color: #00ff88; }
|
|
184
|
+
|
|
185
|
+
.finding-header {
|
|
186
|
+
display: flex;
|
|
187
|
+
justify-content: space-between;
|
|
188
|
+
align-items: center;
|
|
189
|
+
margin-bottom: 8px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.severity-badge {
|
|
193
|
+
font-size: 10px;
|
|
194
|
+
font-weight: bold;
|
|
195
|
+
padding: 3px 8px;
|
|
196
|
+
color: #000;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.severity-badge.critical { background: #ff0044; color: #fff; }
|
|
200
|
+
.severity-badge.high { background: #ff4400; }
|
|
201
|
+
.severity-badge.medium { background: #ffaa00; }
|
|
202
|
+
.severity-badge.low { background: #00ff88; }
|
|
203
|
+
|
|
204
|
+
.finding-module {
|
|
205
|
+
font-size: 10px;
|
|
206
|
+
color: #888;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.finding-claim {
|
|
210
|
+
font-size: 12px;
|
|
211
|
+
line-height: 1.4;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.finding-details {
|
|
215
|
+
font-size: 10px;
|
|
216
|
+
color: #666;
|
|
217
|
+
margin-top: 8px;
|
|
218
|
+
display: none;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.finding:hover .finding-details {
|
|
222
|
+
display: block;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Bottom Stats */
|
|
226
|
+
#stats-bar {
|
|
227
|
+
bottom: 20px;
|
|
228
|
+
left: 50%;
|
|
229
|
+
transform: translateX(-50%);
|
|
230
|
+
display: flex;
|
|
231
|
+
gap: 20px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.stat-box {
|
|
235
|
+
text-align: center;
|
|
236
|
+
padding: 15px 25px;
|
|
237
|
+
background: rgba(0, 20, 10, 0.9);
|
|
238
|
+
border: 1px solid #00ff8844;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.stat-value {
|
|
242
|
+
font-size: 32px;
|
|
243
|
+
font-weight: bold;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.stat-label {
|
|
247
|
+
font-size: 10px;
|
|
248
|
+
text-transform: uppercase;
|
|
249
|
+
letter-spacing: 1px;
|
|
250
|
+
color: #888;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.stat-box.critical .stat-value { color: #ff0044; }
|
|
254
|
+
.stat-box.high .stat-value { color: #ff4400; }
|
|
255
|
+
.stat-box.medium .stat-value { color: #ffaa00; }
|
|
256
|
+
.stat-box.low .stat-value { color: #00ff88; }
|
|
257
|
+
|
|
258
|
+
/* Module Info Tooltip */
|
|
259
|
+
#tooltip {
|
|
260
|
+
position: fixed;
|
|
261
|
+
background: rgba(0, 30, 15, 0.95);
|
|
262
|
+
border: 1px solid #00ff88;
|
|
263
|
+
padding: 12px;
|
|
264
|
+
font-size: 11px;
|
|
265
|
+
max-width: 250px;
|
|
266
|
+
display: none;
|
|
267
|
+
z-index: 200;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#tooltip.visible { display: block; }
|
|
271
|
+
#tooltip h4 { margin-bottom: 5px; font-size: 13px; }
|
|
272
|
+
#tooltip .module-status { margin: 5px 0; }
|
|
273
|
+
|
|
274
|
+
/* Module Management Modal */
|
|
275
|
+
#module-modal {
|
|
276
|
+
position: fixed;
|
|
277
|
+
top: 0;
|
|
278
|
+
left: 0;
|
|
279
|
+
right: 0;
|
|
280
|
+
bottom: 0;
|
|
281
|
+
background: rgba(0, 10, 5, 0.95);
|
|
282
|
+
display: none;
|
|
283
|
+
align-items: center;
|
|
284
|
+
justify-content: center;
|
|
285
|
+
z-index: 400;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#module-modal.visible { display: flex; }
|
|
289
|
+
|
|
290
|
+
.modal-content {
|
|
291
|
+
background: rgba(0, 30, 15, 0.98);
|
|
292
|
+
border: 2px solid #00ff88;
|
|
293
|
+
padding: 25px;
|
|
294
|
+
min-width: 400px;
|
|
295
|
+
max-width: 500px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.modal-header {
|
|
299
|
+
display: flex;
|
|
300
|
+
justify-content: space-between;
|
|
301
|
+
align-items: center;
|
|
302
|
+
margin-bottom: 20px;
|
|
303
|
+
padding-bottom: 15px;
|
|
304
|
+
border-bottom: 1px solid #00ff8844;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.modal-header h3 {
|
|
308
|
+
font-size: 14px;
|
|
309
|
+
letter-spacing: 2px;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.modal-close {
|
|
313
|
+
background: none;
|
|
314
|
+
border: none;
|
|
315
|
+
color: #00ff88;
|
|
316
|
+
font-size: 24px;
|
|
317
|
+
cursor: pointer;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.modal-body { margin-bottom: 20px; }
|
|
321
|
+
|
|
322
|
+
.form-group {
|
|
323
|
+
margin-bottom: 15px;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.form-group label {
|
|
327
|
+
display: block;
|
|
328
|
+
font-size: 11px;
|
|
329
|
+
text-transform: uppercase;
|
|
330
|
+
letter-spacing: 1px;
|
|
331
|
+
margin-bottom: 8px;
|
|
332
|
+
color: #888;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.form-group input, .form-group select {
|
|
336
|
+
width: 100%;
|
|
337
|
+
padding: 10px;
|
|
338
|
+
background: rgba(0, 20, 10, 0.8);
|
|
339
|
+
border: 1px solid #00ff88;
|
|
340
|
+
color: #00ff88;
|
|
341
|
+
font-family: inherit;
|
|
342
|
+
font-size: 12px;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.form-group input::placeholder { color: #00ff8866; }
|
|
346
|
+
|
|
347
|
+
/* Settings Panel Styles */
|
|
348
|
+
.settings-row {
|
|
349
|
+
margin-bottom: 15px;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.settings-row label {
|
|
353
|
+
display: block;
|
|
354
|
+
font-size: 12px;
|
|
355
|
+
margin-bottom: 6px;
|
|
356
|
+
color: #00ff88;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.settings-row input[type="checkbox"] {
|
|
360
|
+
margin-right: 8px;
|
|
361
|
+
accent-color: #00ff88;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.settings-tab {
|
|
365
|
+
padding: 8px 15px !important;
|
|
366
|
+
font-size: 11px !important;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.settings-tab.active {
|
|
370
|
+
background: #00ff88 !important;
|
|
371
|
+
color: #000 !important;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.settings-panel {
|
|
375
|
+
padding: 10px 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.module-toggle {
|
|
379
|
+
display: flex;
|
|
380
|
+
align-items: center;
|
|
381
|
+
justify-content: space-between;
|
|
382
|
+
padding: 10px;
|
|
383
|
+
background: rgba(0, 20, 10, 0.5);
|
|
384
|
+
margin: 5px 0;
|
|
385
|
+
cursor: pointer;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.module-toggle:hover { background: rgba(0, 40, 20, 0.5); }
|
|
389
|
+
|
|
390
|
+
.module-toggle.disabled { opacity: 0.5; }
|
|
391
|
+
|
|
392
|
+
.toggle-switch {
|
|
393
|
+
width: 40px;
|
|
394
|
+
height: 20px;
|
|
395
|
+
background: #333;
|
|
396
|
+
border-radius: 10px;
|
|
397
|
+
position: relative;
|
|
398
|
+
cursor: pointer;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.toggle-switch::after {
|
|
402
|
+
content: '';
|
|
403
|
+
position: absolute;
|
|
404
|
+
width: 16px;
|
|
405
|
+
height: 16px;
|
|
406
|
+
background: #888;
|
|
407
|
+
border-radius: 50%;
|
|
408
|
+
top: 2px;
|
|
409
|
+
left: 2px;
|
|
410
|
+
transition: all 0.2s;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.toggle-switch.active { background: #00ff8844; }
|
|
414
|
+
.toggle-switch.active::after {
|
|
415
|
+
background: #00ff88;
|
|
416
|
+
left: 22px;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* Selected module highlight */
|
|
420
|
+
.module-selected {
|
|
421
|
+
box-shadow: 0 0 30px #00ff88;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* Scrollbar */
|
|
425
|
+
::-webkit-scrollbar { width: 6px; }
|
|
426
|
+
::-webkit-scrollbar-track { background: #0a0a0f; }
|
|
427
|
+
::-webkit-scrollbar-thumb { background: #00ff88; }
|
|
428
|
+
|
|
429
|
+
/* Toast */
|
|
430
|
+
#toast-container {
|
|
431
|
+
position: fixed;
|
|
432
|
+
top: 60px;
|
|
433
|
+
right: 20px;
|
|
434
|
+
z-index: 300;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.toast {
|
|
438
|
+
background: rgba(0, 40, 20, 0.95);
|
|
439
|
+
border: 1px solid #00ff88;
|
|
440
|
+
padding: 12px 20px;
|
|
441
|
+
margin-bottom: 10px;
|
|
442
|
+
animation: slideIn 0.3s ease;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.toast.error { border-color: #ff4444; color: #ff4444; }
|
|
446
|
+
|
|
447
|
+
@keyframes slideIn {
|
|
448
|
+
from { transform: translateX(100%); opacity: 0; }
|
|
449
|
+
to { transform: translateX(0); opacity: 1; }
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/* Loading */
|
|
453
|
+
#loading {
|
|
454
|
+
position: fixed;
|
|
455
|
+
top: 0;
|
|
456
|
+
left: 0;
|
|
457
|
+
right: 0;
|
|
458
|
+
bottom: 0;
|
|
459
|
+
background: rgba(0, 10, 5, 0.9);
|
|
460
|
+
display: flex;
|
|
461
|
+
align-items: center;
|
|
462
|
+
justify-content: center;
|
|
463
|
+
z-index: 500;
|
|
464
|
+
opacity: 0;
|
|
465
|
+
pointer-events: none;
|
|
466
|
+
transition: opacity 0.3s;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
#loading.active {
|
|
470
|
+
opacity: 1;
|
|
471
|
+
pointer-events: auto;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.spinner {
|
|
475
|
+
width: 50px;
|
|
476
|
+
height: 50px;
|
|
477
|
+
border: 3px solid #00ff8833;
|
|
478
|
+
border-top-color: #00ff88;
|
|
479
|
+
border-radius: 50%;
|
|
480
|
+
animation: spin 1s linear infinite;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
484
|
+
</style>
|
|
485
|
+
</head>
|
|
486
|
+
<body>
|
|
487
|
+
<div id="canvas-container"></div>
|
|
488
|
+
|
|
489
|
+
<!-- Header -->
|
|
490
|
+
<header id="header" class="ui-layer">
|
|
491
|
+
<h1>AURA CONTROL PLANE</h1>
|
|
492
|
+
<div class="status-badge">
|
|
493
|
+
<div class="status-dot" id="statusDot"></div>
|
|
494
|
+
<span id="statusText">Connecting...</span>
|
|
495
|
+
<span style="margin-left: 15px;" id="auditCount">0 Audits</span>
|
|
496
|
+
</div>
|
|
497
|
+
</header>
|
|
498
|
+
|
|
499
|
+
<!-- Left Panel -->
|
|
500
|
+
<div id="left-panel" class="ui-layer">
|
|
501
|
+
<!-- Legend -->
|
|
502
|
+
<div class="panel">
|
|
503
|
+
<div class="panel-title">Control Plane Legend</div>
|
|
504
|
+
<div class="legend-item">
|
|
505
|
+
<div class="legend-icon auditor"></div>
|
|
506
|
+
<span>Security Auditor (Central Agent)</span>
|
|
507
|
+
</div>
|
|
508
|
+
<div class="legend-item">
|
|
509
|
+
<div class="legend-icon module"></div>
|
|
510
|
+
<span>System Module (Auth, DB, API, etc.)</span>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="legend-item">
|
|
513
|
+
<div class="legend-icon connection"></div>
|
|
514
|
+
<span>Data Flow / Audit Connection</span>
|
|
515
|
+
</div>
|
|
516
|
+
<div class="legend-item">
|
|
517
|
+
<div class="legend-icon finding-crit"></div>
|
|
518
|
+
<span>Critical Finding</span>
|
|
519
|
+
</div>
|
|
520
|
+
<div class="legend-item">
|
|
521
|
+
<div class="legend-icon finding-high"></div>
|
|
522
|
+
<span>High Severity Finding</span>
|
|
523
|
+
</div>
|
|
524
|
+
<div class="legend-item">
|
|
525
|
+
<div class="legend-icon finding-med"></div>
|
|
526
|
+
<span>Medium Severity Finding</span>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
<!-- Project Scanner -->
|
|
531
|
+
<div class="panel">
|
|
532
|
+
<div class="panel-title">Project Scanner</div>
|
|
533
|
+
|
|
534
|
+
<!-- Local Path Section -->
|
|
535
|
+
<div style="margin-bottom: 12px;">
|
|
536
|
+
<div style="font-size: 10px; color: #888; margin-bottom: 5px;">SCAN LOCAL DIRECTORY:</div>
|
|
537
|
+
<input type="text" id="scanPath" placeholder="C:\path\to\project" style="width: 100%; padding: 8px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88; font-family: inherit; margin-bottom: 6px;">
|
|
538
|
+
<button class="btn" onclick="doScan()" style="width: 100%;">Scan Local</button>
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<!-- Divider -->
|
|
542
|
+
<div style="border-top: 1px solid #00ff8833; margin: 12px 0;"></div>
|
|
543
|
+
|
|
544
|
+
<!-- Git URL Section -->
|
|
545
|
+
<div style="margin-bottom: 10px;">
|
|
546
|
+
<div style="font-size: 10px; color: #888; margin-bottom: 5px;">SCAN REMOTE GIT REPO (no clone):</div>
|
|
547
|
+
<input type="text" id="gitUrl" placeholder="https://github.com/user/repo" style="width: 100%; padding: 8px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88; font-family: inherit; margin-bottom: 6px;">
|
|
548
|
+
<button class="btn secondary" onclick="scanGitRepo()" style="width: 100%;">Scan Remote</button>
|
|
549
|
+
<div style="font-size: 9px; color: #666; margin-top: 4px;">Fetches files via API - safe to scan untrusted repos</div>
|
|
550
|
+
</div>
|
|
551
|
+
|
|
552
|
+
<!-- Project Tabs -->
|
|
553
|
+
<div id="projectTabs" style="margin-top: 12px; display: none;">
|
|
554
|
+
<div style="font-size: 10px; color: #888; margin-bottom: 5px;">SCANNED PROJECTS:</div>
|
|
555
|
+
<div id="projectTabList" style="display: flex; flex-wrap: wrap; gap: 5px;"></div>
|
|
556
|
+
</div>
|
|
557
|
+
|
|
558
|
+
<!-- Scan Status -->
|
|
559
|
+
<div id="scanStatus" style="margin-top: 10px; font-size: 11px; color: #888;"></div>
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
<!-- Demo Mode -->
|
|
563
|
+
<div class="panel" id="demoPresets">
|
|
564
|
+
<div class="panel-title">Demo Presets</div>
|
|
565
|
+
<p style="font-size: 10px; color: #666; margin-bottom: 8px;">Test with sample security scenarios</p>
|
|
566
|
+
<button class="btn secondary" onclick="runPreset('secrets')">Secrets Leak</button>
|
|
567
|
+
<button class="btn secondary" onclick="runPreset('vulns')">Vulnerabilities</button>
|
|
568
|
+
<button class="btn secondary" onclick="runPreset('infra')">Infra Change</button>
|
|
569
|
+
<button class="btn secondary" onclick="runPreset('prod-deploy')">Prod Deploy</button>
|
|
570
|
+
</div>
|
|
571
|
+
|
|
572
|
+
<!-- Module Management -->
|
|
573
|
+
<div class="panel">
|
|
574
|
+
<div class="panel-title">Module Management</div>
|
|
575
|
+
<p style="font-size: 10px; color: #666; margin-bottom: 10px;">Click modules in 3D view to select. Use buttons below to manage.</p>
|
|
576
|
+
<button class="btn secondary" onclick="openModuleManager()">Manage Modules</button>
|
|
577
|
+
<button class="btn secondary" onclick="addNewModule()">Add New Module</button>
|
|
578
|
+
<div id="selectedModuleInfo" style="margin-top: 10px; font-size: 11px; display: none;">
|
|
579
|
+
<strong>Selected:</strong> <span id="selectedModuleName"></span>
|
|
580
|
+
<button class="btn danger" style="margin-top: 8px; padding: 8px;" onclick="removeSelectedModule()">Remove Module</button>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
|
|
584
|
+
<!-- Module Status -->
|
|
585
|
+
<div class="panel">
|
|
586
|
+
<div class="panel-title">Module Status</div>
|
|
587
|
+
<div id="moduleStatus">
|
|
588
|
+
<div class="legend-item"><div class="legend-icon module"></div><span>AUTH - Secure</span></div>
|
|
589
|
+
<div class="legend-item"><div class="legend-icon module"></div><span>DATABASE - Secure</span></div>
|
|
590
|
+
<div class="legend-item"><div class="legend-icon module"></div><span>API - Secure</span></div>
|
|
591
|
+
<div class="legend-item"><div class="legend-icon module"></div><span>INFRA - Secure</span></div>
|
|
592
|
+
<div class="legend-item"><div class="legend-icon module"></div><span>BILLING - Secure</span></div>
|
|
593
|
+
<div class="legend-item"><div class="legend-icon module"></div><span>SECRETS - Secure</span></div>
|
|
594
|
+
</div>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
<!-- Right Panel -->
|
|
599
|
+
<div id="right-panel" class="ui-layer">
|
|
600
|
+
<!-- Audit History Panel -->
|
|
601
|
+
<div class="panel">
|
|
602
|
+
<div class="panel-title">Audit History <span id="auditHistoryCount" style="color: #888; font-size: 10px;">(0)</span></div>
|
|
603
|
+
<div id="auditHistoryList" style="max-height: 200px; overflow-y: auto;">
|
|
604
|
+
<p style="color: #666; font-size: 11px;">Loading audits...</p>
|
|
605
|
+
</div>
|
|
606
|
+
<button class="btn secondary" style="margin-top: 10px; font-size: 10px;" onclick="refreshAuditHistory()">Refresh History</button>
|
|
607
|
+
</div>
|
|
608
|
+
|
|
609
|
+
<!-- Selected Audit Details -->
|
|
610
|
+
<div class="panel" id="selectedAuditPanel" style="display: none;">
|
|
611
|
+
<div class="panel-title">Selected Audit</div>
|
|
612
|
+
<div id="selectedAuditInfo" style="font-size: 11px; margin-bottom: 10px;"></div>
|
|
613
|
+
<button class="btn secondary" style="font-size: 10px;" onclick="clearSelectedAudit()">Clear Selection</button>
|
|
614
|
+
</div>
|
|
615
|
+
|
|
616
|
+
<div class="panel">
|
|
617
|
+
<div class="panel-title">Security Findings</div>
|
|
618
|
+
<div id="findingsList">
|
|
619
|
+
<p style="color: #666; font-size: 11px;">No findings yet. Run an audit to detect issues.</p>
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
|
|
623
|
+
<!-- Settings Panel -->
|
|
624
|
+
<div class="panel">
|
|
625
|
+
<div class="panel-title">Settings <span id="settingsStatus" style="color: #666; font-size: 10px;"></span></div>
|
|
626
|
+
<button class="btn secondary" style="width: 100%;" onclick="openSettingsModal()">Configure Integrations</button>
|
|
627
|
+
</div>
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
<!-- Settings Modal -->
|
|
631
|
+
<div id="settingsModal" class="modal" style="display: none;">
|
|
632
|
+
<div class="modal-content" style="max-width: 600px;">
|
|
633
|
+
<div class="modal-header">
|
|
634
|
+
<h3>Integration Settings</h3>
|
|
635
|
+
<button class="modal-close" onclick="closeSettingsModal()">×</button>
|
|
636
|
+
</div>
|
|
637
|
+
<div class="modal-body" style="max-height: 70vh; overflow-y: auto;">
|
|
638
|
+
<!-- Tabs -->
|
|
639
|
+
<div style="display: flex; gap: 5px; margin-bottom: 15px; border-bottom: 1px solid #00ff8844; padding-bottom: 10px;">
|
|
640
|
+
<button class="btn secondary settings-tab active" data-tab="aws" onclick="switchSettingsTab('aws')">AWS</button>
|
|
641
|
+
<button class="btn secondary settings-tab" data-tab="slack" onclick="switchSettingsTab('slack')">Slack</button>
|
|
642
|
+
<button class="btn secondary settings-tab" data-tab="discord" onclick="switchSettingsTab('discord')">Discord</button>
|
|
643
|
+
<button class="btn secondary settings-tab" data-tab="webhook" onclick="switchSettingsTab('webhook')">Webhook</button>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<!-- AWS Settings -->
|
|
647
|
+
<div id="settings-aws" class="settings-panel">
|
|
648
|
+
<div class="settings-row">
|
|
649
|
+
<label>
|
|
650
|
+
<input type="checkbox" id="aws-enabled" onchange="markSettingsDirty()">
|
|
651
|
+
Enable AWS Scanning
|
|
652
|
+
</label>
|
|
653
|
+
</div>
|
|
654
|
+
<div class="settings-row">
|
|
655
|
+
<label>AWS Region:</label>
|
|
656
|
+
<select id="aws-region" style="width: 100%; padding: 8px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88;" onchange="markSettingsDirty()">
|
|
657
|
+
<option value="us-east-1">us-east-1 (N. Virginia)</option>
|
|
658
|
+
<option value="us-east-2">us-east-2 (Ohio)</option>
|
|
659
|
+
<option value="us-west-1">us-west-1 (N. California)</option>
|
|
660
|
+
<option value="us-west-2">us-west-2 (Oregon)</option>
|
|
661
|
+
<option value="eu-west-1">eu-west-1 (Ireland)</option>
|
|
662
|
+
<option value="eu-central-1">eu-central-1 (Frankfurt)</option>
|
|
663
|
+
<option value="ap-southeast-1">ap-southeast-1 (Singapore)</option>
|
|
664
|
+
<option value="ap-northeast-1">ap-northeast-1 (Tokyo)</option>
|
|
665
|
+
</select>
|
|
666
|
+
</div>
|
|
667
|
+
<div class="settings-row">
|
|
668
|
+
<label>AWS Profile (optional):</label>
|
|
669
|
+
<input type="text" id="aws-profile" placeholder="default" style="width: 100%; padding: 8px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88;" onchange="markSettingsDirty()">
|
|
670
|
+
</div>
|
|
671
|
+
<div class="settings-row">
|
|
672
|
+
<label>Services to Scan:</label>
|
|
673
|
+
<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 5px;">
|
|
674
|
+
<label><input type="checkbox" id="aws-iam" checked onchange="markSettingsDirty()"> IAM</label>
|
|
675
|
+
<label><input type="checkbox" id="aws-s3" checked onchange="markSettingsDirty()"> S3</label>
|
|
676
|
+
<label><input type="checkbox" id="aws-ec2" checked onchange="markSettingsDirty()"> EC2</label>
|
|
677
|
+
<label><input type="checkbox" id="aws-lambda" checked onchange="markSettingsDirty()"> Lambda</label>
|
|
678
|
+
<label><input type="checkbox" id="aws-rds" checked onchange="markSettingsDirty()"> RDS</label>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
<button class="btn" style="margin-top: 15px;" onclick="runAWSScan()">Run AWS Scan Now</button>
|
|
682
|
+
</div>
|
|
683
|
+
|
|
684
|
+
<!-- Slack Settings -->
|
|
685
|
+
<div id="settings-slack" class="settings-panel" style="display: none;">
|
|
686
|
+
<div class="settings-row">
|
|
687
|
+
<label>
|
|
688
|
+
<input type="checkbox" id="slack-enabled" onchange="markSettingsDirty()">
|
|
689
|
+
Enable Slack Notifications
|
|
690
|
+
</label>
|
|
691
|
+
</div>
|
|
692
|
+
<div class="settings-row">
|
|
693
|
+
<label>Webhook URL:</label>
|
|
694
|
+
<input type="text" id="slack-webhook" placeholder="https://hooks.slack.com/services/..." style="width: 100%; padding: 8px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88;" onchange="markSettingsDirty()">
|
|
695
|
+
</div>
|
|
696
|
+
<div class="settings-row">
|
|
697
|
+
<label>Channel (optional):</label>
|
|
698
|
+
<input type="text" id="slack-channel" placeholder="#security-alerts" style="width: 100%; padding: 8px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88;" onchange="markSettingsDirty()">
|
|
699
|
+
</div>
|
|
700
|
+
<button class="btn secondary" style="margin-top: 15px;" onclick="testNotification('slack')">Test Slack</button>
|
|
701
|
+
</div>
|
|
702
|
+
|
|
703
|
+
<!-- Discord Settings -->
|
|
704
|
+
<div id="settings-discord" class="settings-panel" style="display: none;">
|
|
705
|
+
<div class="settings-row">
|
|
706
|
+
<label>
|
|
707
|
+
<input type="checkbox" id="discord-enabled" onchange="markSettingsDirty()">
|
|
708
|
+
Enable Discord Notifications
|
|
709
|
+
</label>
|
|
710
|
+
</div>
|
|
711
|
+
<div class="settings-row">
|
|
712
|
+
<label>Webhook URL:</label>
|
|
713
|
+
<input type="text" id="discord-webhook" placeholder="https://discord.com/api/webhooks/..." style="width: 100%; padding: 8px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88;" onchange="markSettingsDirty()">
|
|
714
|
+
</div>
|
|
715
|
+
<button class="btn secondary" style="margin-top: 15px;" onclick="testNotification('discord')">Test Discord</button>
|
|
716
|
+
</div>
|
|
717
|
+
|
|
718
|
+
<!-- Custom Webhook Settings -->
|
|
719
|
+
<div id="settings-webhook" class="settings-panel" style="display: none;">
|
|
720
|
+
<div class="settings-row">
|
|
721
|
+
<label>
|
|
722
|
+
<input type="checkbox" id="webhook-enabled" onchange="markSettingsDirty()">
|
|
723
|
+
Enable Custom Webhook
|
|
724
|
+
</label>
|
|
725
|
+
</div>
|
|
726
|
+
<div class="settings-row">
|
|
727
|
+
<label>Webhook URL:</label>
|
|
728
|
+
<input type="text" id="webhook-url" placeholder="https://your-server.com/webhook" style="width: 100%; padding: 8px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88;" onchange="markSettingsDirty()">
|
|
729
|
+
</div>
|
|
730
|
+
<div class="settings-row">
|
|
731
|
+
<label>Custom Headers (JSON, optional):</label>
|
|
732
|
+
<textarea id="webhook-headers" placeholder='{"Authorization": "Bearer token"}' style="width: 100%; height: 60px; padding: 8px; background: rgba(0,20,10,0.8); border: 1px solid #00ff88; color: #00ff88; font-family: inherit; resize: vertical;" onchange="markSettingsDirty()"></textarea>
|
|
733
|
+
</div>
|
|
734
|
+
<button class="btn secondary" style="margin-top: 15px;" onclick="testNotification('webhook')">Test Webhook</button>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
<div class="modal-footer" style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 15px; padding-top: 15px; border-top: 1px solid #00ff8844;">
|
|
738
|
+
<button class="btn secondary" onclick="closeSettingsModal()">Cancel</button>
|
|
739
|
+
<button class="btn" id="saveSettingsBtn" onclick="saveSettings()" disabled>Save Settings</button>
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
743
|
+
|
|
744
|
+
<!-- Stats Bar -->
|
|
745
|
+
<div id="stats-bar" class="ui-layer">
|
|
746
|
+
<div class="stat-box critical">
|
|
747
|
+
<div class="stat-value" id="statCritical">0</div>
|
|
748
|
+
<div class="stat-label">Critical</div>
|
|
749
|
+
</div>
|
|
750
|
+
<div class="stat-box high">
|
|
751
|
+
<div class="stat-value" id="statHigh">0</div>
|
|
752
|
+
<div class="stat-label">High</div>
|
|
753
|
+
</div>
|
|
754
|
+
<div class="stat-box medium">
|
|
755
|
+
<div class="stat-value" id="statMedium">0</div>
|
|
756
|
+
<div class="stat-label">Medium</div>
|
|
757
|
+
</div>
|
|
758
|
+
<div class="stat-box low">
|
|
759
|
+
<div class="stat-value" id="statLow">0</div>
|
|
760
|
+
<div class="stat-label">Low</div>
|
|
761
|
+
</div>
|
|
762
|
+
</div>
|
|
763
|
+
|
|
764
|
+
<!-- Tooltip -->
|
|
765
|
+
<div id="tooltip">
|
|
766
|
+
<h4 id="tooltipTitle">Module</h4>
|
|
767
|
+
<div id="tooltipContent"></div>
|
|
768
|
+
</div>
|
|
769
|
+
|
|
770
|
+
<!-- Toast Container -->
|
|
771
|
+
<div id="toast-container"></div>
|
|
772
|
+
|
|
773
|
+
<!-- Loading -->
|
|
774
|
+
<div id="loading"><div class="spinner"></div></div>
|
|
775
|
+
|
|
776
|
+
<!-- Module Manager Modal -->
|
|
777
|
+
<div id="module-modal">
|
|
778
|
+
<div class="modal-content">
|
|
779
|
+
<div class="modal-header">
|
|
780
|
+
<h3 id="modalTitle">MANAGE MODULES</h3>
|
|
781
|
+
<button class="modal-close" onclick="closeModal()">×</button>
|
|
782
|
+
</div>
|
|
783
|
+
<div class="modal-body" id="modalBody">
|
|
784
|
+
<!-- Content injected by JS -->
|
|
785
|
+
</div>
|
|
786
|
+
<div class="modal-footer">
|
|
787
|
+
<button class="btn" onclick="saveModuleChanges()">Save Changes</button>
|
|
788
|
+
<button class="btn secondary" onclick="closeModal()">Cancel</button>
|
|
789
|
+
</div>
|
|
790
|
+
</div>
|
|
791
|
+
</div>
|
|
792
|
+
|
|
793
|
+
<script type="importmap">
|
|
794
|
+
{
|
|
795
|
+
"imports": {
|
|
796
|
+
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
|
|
797
|
+
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
</script>
|
|
801
|
+
|
|
802
|
+
<script type="module">
|
|
803
|
+
import * as THREE from 'three';
|
|
804
|
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
805
|
+
|
|
806
|
+
// ========== CONFIGURATION ==========
|
|
807
|
+
const AURA_URL = 'http://127.0.0.1:3000';
|
|
808
|
+
const WS_URL = 'ws://127.0.0.1:3001';
|
|
809
|
+
const POLL_INTERVAL = 5000; // Slower polling as WebSocket handles real-time
|
|
810
|
+
let wsConnected = false;
|
|
811
|
+
let ws = null;
|
|
812
|
+
|
|
813
|
+
// Module definitions - what systems exist in the control plane
|
|
814
|
+
// Base modules - always restored after clearing the map
|
|
815
|
+
const BASE_MODULES = [
|
|
816
|
+
{ id: 'auth', name: 'AUTH', desc: 'Authentication & Identity', pos: [-15, 0, -10], color: 0x00ff88 },
|
|
817
|
+
{ id: 'database', name: 'DATABASE', desc: 'Data Storage & Queries', pos: [15, 0, -10], color: 0x00aaff },
|
|
818
|
+
{ id: 'api', name: 'API', desc: 'External Endpoints', pos: [-15, 0, 10], color: 0xffaa00 },
|
|
819
|
+
{ id: 'infra', name: 'INFRA', desc: 'Infrastructure & Network', pos: [15, 0, 10], color: 0xff00ff },
|
|
820
|
+
{ id: 'billing', name: 'BILLING', desc: 'Payment Processing', pos: [-20, 0, 0], color: 0xff8800 },
|
|
821
|
+
{ id: 'secrets', name: 'SECRETS', desc: 'Credentials & Keys', pos: [20, 0, 0], color: 0xff0088 }
|
|
822
|
+
];
|
|
823
|
+
|
|
824
|
+
// Active modules - starts with base modules, can have more added
|
|
825
|
+
const MODULES = [...BASE_MODULES];
|
|
826
|
+
|
|
827
|
+
// Connections between modules (data flows)
|
|
828
|
+
const CONNECTIONS = [
|
|
829
|
+
{ from: 'auth', to: 'database', label: 'User Data' },
|
|
830
|
+
{ from: 'auth', to: 'secrets', label: 'Credentials' },
|
|
831
|
+
{ from: 'api', to: 'auth', label: 'Auth Requests' },
|
|
832
|
+
{ from: 'api', to: 'database', label: 'Queries' },
|
|
833
|
+
{ from: 'billing', to: 'database', label: 'Transactions' },
|
|
834
|
+
{ from: 'billing', to: 'secrets', label: 'Payment Keys' },
|
|
835
|
+
{ from: 'infra', to: 'database', label: 'Backups' },
|
|
836
|
+
{ from: 'infra', to: 'secrets', label: 'Service Accounts' }
|
|
837
|
+
];
|
|
838
|
+
|
|
839
|
+
// Presets for quick testing
|
|
840
|
+
const PRESETS = {
|
|
841
|
+
'secrets': {
|
|
842
|
+
change_event: {
|
|
843
|
+
id: 'preset-secrets-' + Date.now(),
|
|
844
|
+
type: 'pull_request',
|
|
845
|
+
environment: 'staging',
|
|
846
|
+
repo: 'acme/webapp',
|
|
847
|
+
commit: 'a'.repeat(40),
|
|
848
|
+
files_changed: ['src/config/secrets.ts', 'src/auth/oauth.ts'],
|
|
849
|
+
diff: '+const AWS_SECRET = "AKIAIOSFODNN7EXAMPLE";\n+const DB_PASSWORD = "super_secret_123";'
|
|
850
|
+
},
|
|
851
|
+
evidence_bundle: { vuln_scan: 'critical: 0\nhigh: 0' },
|
|
852
|
+
policy_context: { critical_assets: ['auth', 'secrets'], risk_tolerance: 'low' }
|
|
853
|
+
},
|
|
854
|
+
'vulns': {
|
|
855
|
+
change_event: {
|
|
856
|
+
id: 'preset-vulns-' + Date.now(),
|
|
857
|
+
type: 'pull_request',
|
|
858
|
+
environment: 'staging',
|
|
859
|
+
repo: 'acme/webapp',
|
|
860
|
+
commit: 'b'.repeat(40),
|
|
861
|
+
files_changed: ['package.json', 'package-lock.json'],
|
|
862
|
+
diff: '+ "lodash": "4.17.0"'
|
|
863
|
+
},
|
|
864
|
+
evidence_bundle: { vuln_scan: 'critical: 5\nhigh: 12\nmedium: 23' },
|
|
865
|
+
policy_context: { critical_assets: ['dependencies'], risk_tolerance: 'low' }
|
|
866
|
+
},
|
|
867
|
+
'prod-deploy': {
|
|
868
|
+
change_event: {
|
|
869
|
+
id: 'preset-deploy-' + Date.now(),
|
|
870
|
+
type: 'deploy',
|
|
871
|
+
environment: 'prod',
|
|
872
|
+
repo: 'acme/webapp',
|
|
873
|
+
commit: 'c'.repeat(40),
|
|
874
|
+
files_changed: ['dist/app.js'],
|
|
875
|
+
diff: ''
|
|
876
|
+
},
|
|
877
|
+
evidence_bundle: { vuln_scan: 'critical: 0\nhigh: 1' },
|
|
878
|
+
policy_context: { critical_assets: ['production'], risk_tolerance: 'medium' }
|
|
879
|
+
},
|
|
880
|
+
'infra': {
|
|
881
|
+
change_event: {
|
|
882
|
+
id: 'preset-infra-' + Date.now(),
|
|
883
|
+
type: 'infra_change',
|
|
884
|
+
environment: 'staging',
|
|
885
|
+
repo: 'acme/terraform',
|
|
886
|
+
commit: 'd'.repeat(40),
|
|
887
|
+
files_changed: ['modules/vpc/main.tf', 'modules/security-groups/main.tf'],
|
|
888
|
+
diff: '+resource "aws_security_group_rule" "allow_all" {\n+ cidr_blocks = ["0.0.0.0/0"]\n+}'
|
|
889
|
+
},
|
|
890
|
+
evidence_bundle: { iac_scan: 'warnings: 3' },
|
|
891
|
+
policy_context: { critical_assets: ['infra', 'network'], risk_tolerance: 'low' }
|
|
892
|
+
},
|
|
893
|
+
'clean': {
|
|
894
|
+
change_event: {
|
|
895
|
+
id: 'preset-clean-' + Date.now(),
|
|
896
|
+
type: 'pull_request',
|
|
897
|
+
environment: 'dev',
|
|
898
|
+
repo: 'acme/webapp',
|
|
899
|
+
commit: 'e'.repeat(40),
|
|
900
|
+
files_changed: ['src/utils/helpers.ts'],
|
|
901
|
+
diff: '+export function formatDate(d: Date) { return d.toISOString(); }'
|
|
902
|
+
},
|
|
903
|
+
evidence_bundle: { vuln_scan: 'critical: 0\nhigh: 0' },
|
|
904
|
+
policy_context: { critical_assets: ['auth'], risk_tolerance: 'high' }
|
|
905
|
+
},
|
|
906
|
+
'full-breach': {
|
|
907
|
+
change_event: {
|
|
908
|
+
id: 'preset-breach-' + Date.now(),
|
|
909
|
+
type: 'pull_request',
|
|
910
|
+
environment: 'prod',
|
|
911
|
+
repo: 'acme/webapp',
|
|
912
|
+
commit: 'f'.repeat(40),
|
|
913
|
+
files_changed: ['src/auth/admin.ts', 'src/billing/stripe.ts', 'config/database.yml'],
|
|
914
|
+
diff: '+const ADMIN_PASSWORD = "admin123";\n+const STRIPE_SECRET = "sk_live_xxx";\n+const DB_CONNECTION = "postgres://root:password@prod-db:5432";'
|
|
915
|
+
},
|
|
916
|
+
evidence_bundle: { vuln_scan: 'critical: 8\nhigh: 15', sast_results: 'sql_injection: 3\nxss: 5' },
|
|
917
|
+
policy_context: { critical_assets: ['auth', 'billing', 'database', 'secrets'], risk_tolerance: 'low' }
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
// ========== THREE.JS SETUP ==========
|
|
922
|
+
let scene, camera, renderer, controls;
|
|
923
|
+
let auditorNode, moduleNodes = {}, connectionLines = [], findingMarkers = [];
|
|
924
|
+
let raycaster, mouse;
|
|
925
|
+
let connected = false;
|
|
926
|
+
let currentFindings = [];
|
|
927
|
+
let mapCleared = false; // Track if map has been cleared for scan mode
|
|
928
|
+
|
|
929
|
+
// Multi-project support
|
|
930
|
+
const scannedProjects = {}; // { projectName: { path, scanResult, modules, services } }
|
|
931
|
+
let activeProject = null;
|
|
932
|
+
|
|
933
|
+
const COLORS = {
|
|
934
|
+
idle: 0x888888,
|
|
935
|
+
analyzing: 0x00aaff,
|
|
936
|
+
conflict: 0xffaa00,
|
|
937
|
+
escalated: 0xff00ff,
|
|
938
|
+
blocked: 0xff0044
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// Clear the map - remove all modules and connections to start fresh
|
|
942
|
+
function clearMap() {
|
|
943
|
+
console.log('Clearing map for fresh scan...');
|
|
944
|
+
|
|
945
|
+
if (!scene) {
|
|
946
|
+
console.warn('Scene not initialized yet');
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Remove all module nodes from scene
|
|
951
|
+
for (const id in moduleNodes) {
|
|
952
|
+
if (moduleNodes[id]) {
|
|
953
|
+
scene.remove(moduleNodes[id]);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
moduleNodes = {};
|
|
957
|
+
|
|
958
|
+
// Remove all connection lines from scene
|
|
959
|
+
connectionLines.forEach(line => {
|
|
960
|
+
if (line) scene.remove(line);
|
|
961
|
+
});
|
|
962
|
+
connectionLines = [];
|
|
963
|
+
|
|
964
|
+
// Remove all finding markers
|
|
965
|
+
findingMarkers.forEach(marker => {
|
|
966
|
+
if (marker) scene.remove(marker);
|
|
967
|
+
});
|
|
968
|
+
findingMarkers = [];
|
|
969
|
+
|
|
970
|
+
// Clear the MODULES array and restore base modules
|
|
971
|
+
MODULES.length = 0;
|
|
972
|
+
BASE_MODULES.forEach(mod => MODULES.push({...mod}));
|
|
973
|
+
|
|
974
|
+
// Clear module enabled states and restore base modules
|
|
975
|
+
for (const key in moduleEnabled) {
|
|
976
|
+
delete moduleEnabled[key];
|
|
977
|
+
}
|
|
978
|
+
BASE_MODULES.forEach(mod => moduleEnabled[mod.id] = true);
|
|
979
|
+
|
|
980
|
+
// Clear module statuses
|
|
981
|
+
for (const key in moduleStatuses) {
|
|
982
|
+
delete moduleStatuses[key];
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Update UI
|
|
986
|
+
updateModuleStatusPanel([]);
|
|
987
|
+
currentFindings = [];
|
|
988
|
+
selectedModule = null;
|
|
989
|
+
|
|
990
|
+
// Hide selected module info
|
|
991
|
+
const selectedInfo = document.getElementById('selectedModuleInfo');
|
|
992
|
+
if (selectedInfo) selectedInfo.style.display = 'none';
|
|
993
|
+
|
|
994
|
+
// Recreate base module nodes and connections in 3D
|
|
995
|
+
createModuleNodes();
|
|
996
|
+
createConnections();
|
|
997
|
+
|
|
998
|
+
mapCleared = true;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function initScene() {
|
|
1002
|
+
// Scene
|
|
1003
|
+
scene = new THREE.Scene();
|
|
1004
|
+
scene.background = new THREE.Color(0x0a0a0f);
|
|
1005
|
+
scene.fog = new THREE.Fog(0x0a0a0f, 60, 150);
|
|
1006
|
+
|
|
1007
|
+
// Camera
|
|
1008
|
+
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
1009
|
+
camera.position.set(0, 50, 50);
|
|
1010
|
+
camera.lookAt(0, 0, 0);
|
|
1011
|
+
|
|
1012
|
+
// Renderer
|
|
1013
|
+
renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
1014
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
1015
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
1016
|
+
document.getElementById('canvas-container').appendChild(renderer.domElement);
|
|
1017
|
+
|
|
1018
|
+
// Controls
|
|
1019
|
+
controls = new OrbitControls(camera, renderer.domElement);
|
|
1020
|
+
controls.enableDamping = true;
|
|
1021
|
+
controls.dampingFactor = 0.05;
|
|
1022
|
+
controls.maxPolarAngle = Math.PI / 2.2;
|
|
1023
|
+
controls.minDistance = 20;
|
|
1024
|
+
controls.maxDistance = 100;
|
|
1025
|
+
|
|
1026
|
+
// Raycaster for mouse interaction
|
|
1027
|
+
raycaster = new THREE.Raycaster();
|
|
1028
|
+
mouse = new THREE.Vector2();
|
|
1029
|
+
|
|
1030
|
+
// Lighting
|
|
1031
|
+
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
|
|
1032
|
+
scene.add(ambientLight);
|
|
1033
|
+
|
|
1034
|
+
const pointLight = new THREE.PointLight(0x00ff88, 1, 100);
|
|
1035
|
+
pointLight.position.set(0, 30, 0);
|
|
1036
|
+
scene.add(pointLight);
|
|
1037
|
+
|
|
1038
|
+
// Grid
|
|
1039
|
+
const gridHelper = new THREE.GridHelper(80, 40, 0x00ff88, 0x002211);
|
|
1040
|
+
scene.add(gridHelper);
|
|
1041
|
+
|
|
1042
|
+
// Ground plane
|
|
1043
|
+
const groundGeom = new THREE.PlaneGeometry(80, 80);
|
|
1044
|
+
const groundMat = new THREE.MeshBasicMaterial({ color: 0x001108, transparent: true, opacity: 0.8 });
|
|
1045
|
+
const ground = new THREE.Mesh(groundGeom, groundMat);
|
|
1046
|
+
ground.rotation.x = -Math.PI / 2;
|
|
1047
|
+
ground.position.y = -0.1;
|
|
1048
|
+
scene.add(ground);
|
|
1049
|
+
|
|
1050
|
+
// Create scene elements
|
|
1051
|
+
createAuditorNode();
|
|
1052
|
+
createModuleNodes();
|
|
1053
|
+
createConnections();
|
|
1054
|
+
createParticles();
|
|
1055
|
+
|
|
1056
|
+
// Event listeners
|
|
1057
|
+
window.addEventListener('resize', onWindowResize);
|
|
1058
|
+
window.addEventListener('mousemove', onMouseMove);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function createAuditorNode() {
|
|
1062
|
+
const group = new THREE.Group();
|
|
1063
|
+
|
|
1064
|
+
// Base platform
|
|
1065
|
+
const baseGeom = new THREE.CylinderGeometry(4, 5, 1.5, 8);
|
|
1066
|
+
const baseMat = new THREE.MeshPhongMaterial({
|
|
1067
|
+
color: 0x00aaff,
|
|
1068
|
+
emissive: 0x00aaff,
|
|
1069
|
+
emissiveIntensity: 0.3
|
|
1070
|
+
});
|
|
1071
|
+
const base = new THREE.Mesh(baseGeom, baseMat);
|
|
1072
|
+
base.name = 'auditor-base';
|
|
1073
|
+
group.add(base);
|
|
1074
|
+
|
|
1075
|
+
// Core sphere
|
|
1076
|
+
const coreGeom = new THREE.IcosahedronGeometry(2.5, 1);
|
|
1077
|
+
const coreMat = new THREE.MeshPhongMaterial({
|
|
1078
|
+
color: 0x00ff88,
|
|
1079
|
+
emissive: 0x00ff88,
|
|
1080
|
+
emissiveIntensity: 0.5,
|
|
1081
|
+
wireframe: false
|
|
1082
|
+
});
|
|
1083
|
+
const core = new THREE.Mesh(coreGeom, coreMat);
|
|
1084
|
+
core.position.y = 5;
|
|
1085
|
+
core.name = 'auditor-core';
|
|
1086
|
+
group.add(core);
|
|
1087
|
+
|
|
1088
|
+
// Rotating rings
|
|
1089
|
+
for (let i = 0; i < 3; i++) {
|
|
1090
|
+
const ringGeom = new THREE.TorusGeometry(3 + i * 0.5, 0.1, 8, 32);
|
|
1091
|
+
const ringMat = new THREE.MeshBasicMaterial({ color: 0x00ff88, transparent: true, opacity: 0.6 });
|
|
1092
|
+
const ring = new THREE.Mesh(ringGeom, ringMat);
|
|
1093
|
+
ring.position.y = 5;
|
|
1094
|
+
ring.rotation.x = Math.PI / 2 + (i * 0.3);
|
|
1095
|
+
ring.name = `auditor-ring-${i}`;
|
|
1096
|
+
group.add(ring);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Label
|
|
1100
|
+
const labelSprite = createTextSprite('SECURITY\nAUDITOR', 0x00aaff);
|
|
1101
|
+
labelSprite.position.y = 10;
|
|
1102
|
+
labelSprite.scale.set(8, 4, 1);
|
|
1103
|
+
group.add(labelSprite);
|
|
1104
|
+
|
|
1105
|
+
group.position.y = 1;
|
|
1106
|
+
group.userData = { type: 'auditor', name: 'Security Auditor', desc: 'Central security analysis agent' };
|
|
1107
|
+
auditorNode = group;
|
|
1108
|
+
scene.add(auditorNode);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function createModuleNodes() {
|
|
1112
|
+
MODULES.forEach(mod => {
|
|
1113
|
+
const group = new THREE.Group();
|
|
1114
|
+
|
|
1115
|
+
// Module box
|
|
1116
|
+
const boxGeom = new THREE.BoxGeometry(5, 3, 5);
|
|
1117
|
+
const boxMat = new THREE.MeshPhongMaterial({
|
|
1118
|
+
color: mod.color,
|
|
1119
|
+
emissive: mod.color,
|
|
1120
|
+
emissiveIntensity: 0.2
|
|
1121
|
+
});
|
|
1122
|
+
const box = new THREE.Mesh(boxGeom, boxMat);
|
|
1123
|
+
box.name = `module-${mod.id}`;
|
|
1124
|
+
group.add(box);
|
|
1125
|
+
|
|
1126
|
+
// Top indicator
|
|
1127
|
+
const indicatorGeom = new THREE.CylinderGeometry(0.5, 0.5, 0.3, 16);
|
|
1128
|
+
const indicatorMat = new THREE.MeshBasicMaterial({ color: 0x00ff88 });
|
|
1129
|
+
const indicator = new THREE.Mesh(indicatorGeom, indicatorMat);
|
|
1130
|
+
indicator.position.y = 1.8;
|
|
1131
|
+
indicator.name = `indicator-${mod.id}`;
|
|
1132
|
+
group.add(indicator);
|
|
1133
|
+
|
|
1134
|
+
// Label
|
|
1135
|
+
const labelSprite = createTextSprite(mod.name, mod.color);
|
|
1136
|
+
labelSprite.position.y = 4;
|
|
1137
|
+
labelSprite.scale.set(6, 3, 1);
|
|
1138
|
+
group.add(labelSprite);
|
|
1139
|
+
|
|
1140
|
+
group.position.set(mod.pos[0], mod.pos[1] + 2, mod.pos[2]);
|
|
1141
|
+
group.userData = { type: 'module', id: mod.id, name: mod.name, desc: mod.desc, status: 'secure' };
|
|
1142
|
+
moduleNodes[mod.id] = group;
|
|
1143
|
+
scene.add(group);
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function createConnections() {
|
|
1148
|
+
// Connections from auditor to all modules
|
|
1149
|
+
MODULES.forEach(mod => {
|
|
1150
|
+
const line = createConnectionLine(
|
|
1151
|
+
new THREE.Vector3(0, 5, 0),
|
|
1152
|
+
new THREE.Vector3(mod.pos[0], mod.pos[1] + 2, mod.pos[2]),
|
|
1153
|
+
0x00aaff,
|
|
1154
|
+
true
|
|
1155
|
+
);
|
|
1156
|
+
line.userData = { type: 'audit-connection', to: mod.id };
|
|
1157
|
+
connectionLines.push(line);
|
|
1158
|
+
scene.add(line);
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
// Connections between modules
|
|
1162
|
+
CONNECTIONS.forEach(conn => {
|
|
1163
|
+
const fromMod = MODULES.find(m => m.id === conn.from);
|
|
1164
|
+
const toMod = MODULES.find(m => m.id === conn.to);
|
|
1165
|
+
if (fromMod && toMod) {
|
|
1166
|
+
const line = createConnectionLine(
|
|
1167
|
+
new THREE.Vector3(fromMod.pos[0], fromMod.pos[1] + 2, fromMod.pos[2]),
|
|
1168
|
+
new THREE.Vector3(toMod.pos[0], toMod.pos[1] + 2, toMod.pos[2]),
|
|
1169
|
+
0x00ff8844,
|
|
1170
|
+
false
|
|
1171
|
+
);
|
|
1172
|
+
line.userData = { type: 'data-flow', from: conn.from, to: conn.to, label: conn.label };
|
|
1173
|
+
connectionLines.push(line);
|
|
1174
|
+
scene.add(line);
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function createConnectionLine(start, end, color, dashed) {
|
|
1180
|
+
const points = [start, end];
|
|
1181
|
+
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
1182
|
+
|
|
1183
|
+
let material;
|
|
1184
|
+
if (dashed) {
|
|
1185
|
+
material = new THREE.LineDashedMaterial({
|
|
1186
|
+
color: color,
|
|
1187
|
+
dashSize: 1,
|
|
1188
|
+
gapSize: 0.5,
|
|
1189
|
+
transparent: true,
|
|
1190
|
+
opacity: 0.6
|
|
1191
|
+
});
|
|
1192
|
+
} else {
|
|
1193
|
+
material = new THREE.LineBasicMaterial({
|
|
1194
|
+
color: color,
|
|
1195
|
+
transparent: true,
|
|
1196
|
+
opacity: 0.3
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
const line = new THREE.Line(geometry, material);
|
|
1201
|
+
if (dashed) line.computeLineDistances();
|
|
1202
|
+
return line;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function createTextSprite(text, color) {
|
|
1206
|
+
const canvas = document.createElement('canvas');
|
|
1207
|
+
const ctx = canvas.getContext('2d');
|
|
1208
|
+
canvas.width = 256;
|
|
1209
|
+
canvas.height = 128;
|
|
1210
|
+
|
|
1211
|
+
ctx.fillStyle = 'transparent';
|
|
1212
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1213
|
+
|
|
1214
|
+
ctx.font = 'bold 28px Courier New';
|
|
1215
|
+
ctx.fillStyle = '#' + color.toString(16).padStart(6, '0');
|
|
1216
|
+
ctx.textAlign = 'center';
|
|
1217
|
+
ctx.textBaseline = 'middle';
|
|
1218
|
+
|
|
1219
|
+
const lines = text.split('\n');
|
|
1220
|
+
lines.forEach((line, i) => {
|
|
1221
|
+
ctx.fillText(line, canvas.width / 2, canvas.height / 2 + (i - (lines.length - 1) / 2) * 30);
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
1225
|
+
const material = new THREE.SpriteMaterial({ map: texture, transparent: true });
|
|
1226
|
+
return new THREE.Sprite(material);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function createParticles() {
|
|
1230
|
+
const particleCount = 300;
|
|
1231
|
+
const geometry = new THREE.BufferGeometry();
|
|
1232
|
+
const positions = new Float32Array(particleCount * 3);
|
|
1233
|
+
|
|
1234
|
+
for (let i = 0; i < particleCount; i++) {
|
|
1235
|
+
positions[i * 3] = (Math.random() - 0.5) * 80;
|
|
1236
|
+
positions[i * 3 + 1] = Math.random() * 40;
|
|
1237
|
+
positions[i * 3 + 2] = (Math.random() - 0.5) * 80;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
1241
|
+
|
|
1242
|
+
const material = new THREE.PointsMaterial({
|
|
1243
|
+
color: 0x00ff88,
|
|
1244
|
+
size: 0.2,
|
|
1245
|
+
transparent: true,
|
|
1246
|
+
opacity: 0.4
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
const particles = new THREE.Points(geometry, material);
|
|
1250
|
+
particles.name = 'particles';
|
|
1251
|
+
scene.add(particles);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// ========== STATE UPDATES ==========
|
|
1255
|
+
function updateAuditorState(state) {
|
|
1256
|
+
const color = COLORS[state] || COLORS.idle;
|
|
1257
|
+
|
|
1258
|
+
auditorNode.traverse(child => {
|
|
1259
|
+
if (child.isMesh && child.name === 'auditor-base') {
|
|
1260
|
+
child.material.color.setHex(color);
|
|
1261
|
+
child.material.emissive.setHex(color);
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function updateModuleStatus(moduleId, status, severity) {
|
|
1267
|
+
const node = moduleNodes[moduleId];
|
|
1268
|
+
if (!node) {
|
|
1269
|
+
console.warn('[VISUALIZER] Module node not found:', moduleId);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
let color = 0x00ff88; // secure (green)
|
|
1274
|
+
if (status === 'warning') color = 0xffaa00; // warning (orange)
|
|
1275
|
+
if (status === 'critical') color = 0xff0044; // critical (red)
|
|
1276
|
+
|
|
1277
|
+
console.log('[VISUALIZER] Updating module:', moduleId, 'to', status, 'color:', color.toString(16));
|
|
1278
|
+
|
|
1279
|
+
// Update the status indicator sphere
|
|
1280
|
+
node.traverse(child => {
|
|
1281
|
+
if (child.name === `indicator-${moduleId}`) {
|
|
1282
|
+
child.material.color.setHex(color);
|
|
1283
|
+
// Make critical indicators glow more
|
|
1284
|
+
if (status === 'critical') {
|
|
1285
|
+
child.material.emissive = new THREE.Color(color);
|
|
1286
|
+
child.material.emissiveIntensity = 0.5;
|
|
1287
|
+
} else {
|
|
1288
|
+
child.material.emissiveIntensity = 0;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
// Also change the main module box color for critical findings
|
|
1292
|
+
if (child.name === `module-${moduleId}` && status === 'critical') {
|
|
1293
|
+
child.material.color.setHex(0xff2222);
|
|
1294
|
+
child.material.emissive = new THREE.Color(0xff0000);
|
|
1295
|
+
child.material.emissiveIntensity = 0.3;
|
|
1296
|
+
} else if (child.name === `module-${moduleId}` && status !== 'critical') {
|
|
1297
|
+
// Restore original color for the module
|
|
1298
|
+
const mod = MODULES.find(m => m.id === moduleId);
|
|
1299
|
+
if (mod) {
|
|
1300
|
+
child.material.color.setHex(mod.color);
|
|
1301
|
+
child.material.emissiveIntensity = 0.2;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
node.userData.status = status;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function clearFindingMarkers() {
|
|
1310
|
+
findingMarkers.forEach(marker => scene.remove(marker));
|
|
1311
|
+
findingMarkers = [];
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function addFindingMarker(finding, index) {
|
|
1315
|
+
const severity = finding.payload.severity;
|
|
1316
|
+
const colors = { critical: 0xff0044, high: 0xff4400, medium: 0xffaa00, low: 0x00ff88 };
|
|
1317
|
+
const color = colors[severity] || 0xffffff;
|
|
1318
|
+
|
|
1319
|
+
// Position around auditor
|
|
1320
|
+
const angle = (index / 8) * Math.PI * 2;
|
|
1321
|
+
const radius = 8;
|
|
1322
|
+
|
|
1323
|
+
const geometry = new THREE.OctahedronGeometry(0.8, 0);
|
|
1324
|
+
const material = new THREE.MeshBasicMaterial({ color });
|
|
1325
|
+
const marker = new THREE.Mesh(geometry, material);
|
|
1326
|
+
|
|
1327
|
+
marker.position.set(
|
|
1328
|
+
Math.cos(angle) * radius,
|
|
1329
|
+
6 + Math.sin(index) * 2,
|
|
1330
|
+
Math.sin(angle) * radius
|
|
1331
|
+
);
|
|
1332
|
+
|
|
1333
|
+
marker.userData = { finding, severity };
|
|
1334
|
+
findingMarkers.push(marker);
|
|
1335
|
+
scene.add(marker);
|
|
1336
|
+
|
|
1337
|
+
// Update affected modules
|
|
1338
|
+
finding.payload.affected_assets.forEach(asset => {
|
|
1339
|
+
const modId = asset.toLowerCase();
|
|
1340
|
+
if (moduleNodes[modId]) {
|
|
1341
|
+
updateModuleStatus(modId, severity === 'critical' || severity === 'high' ? 'critical' : 'warning', severity);
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function updateFindings(events) {
|
|
1347
|
+
clearFindingMarkers();
|
|
1348
|
+
currentFindings = events.filter(e =>
|
|
1349
|
+
e.event_type === 'finding_raised' || e.event_type === 'escalation_triggered'
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1352
|
+
currentFindings.forEach((event, i) => addFindingMarker(event, i));
|
|
1353
|
+
renderFindingsList(currentFindings);
|
|
1354
|
+
updateStats(currentFindings);
|
|
1355
|
+
updateModuleStatusPanel(currentFindings);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function renderFindingsList(findings) {
|
|
1359
|
+
const container = document.getElementById('findingsList');
|
|
1360
|
+
if (findings.length === 0) {
|
|
1361
|
+
container.innerHTML = '<p style="color: #666; font-size: 11px;">No findings yet. Run an audit to detect issues.</p>';
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
container.innerHTML = findings.map(f => `
|
|
1366
|
+
<div class="finding ${f.payload.severity}">
|
|
1367
|
+
<div class="finding-header">
|
|
1368
|
+
<span class="severity-badge ${f.payload.severity}">${f.payload.severity.toUpperCase()}</span>
|
|
1369
|
+
<span class="finding-module">${f.payload.affected_assets[0] || 'system'}</span>
|
|
1370
|
+
</div>
|
|
1371
|
+
<div class="finding-claim">${f.payload.claim}</div>
|
|
1372
|
+
<div class="finding-details">
|
|
1373
|
+
<strong>Attack Path:</strong><br>
|
|
1374
|
+
${f.payload.attack_path.map(p => '• ' + p).join('<br>')}
|
|
1375
|
+
<br><br>
|
|
1376
|
+
<strong>Confidence:</strong> ${(f.payload.confidence * 100).toFixed(0)}%
|
|
1377
|
+
</div>
|
|
1378
|
+
</div>
|
|
1379
|
+
`).join('');
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function updateStats(findings) {
|
|
1383
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
1384
|
+
findings.forEach(f => {
|
|
1385
|
+
if (counts[f.payload.severity] !== undefined) {
|
|
1386
|
+
counts[f.payload.severity]++;
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
document.getElementById('statCritical').textContent = counts.critical;
|
|
1391
|
+
document.getElementById('statHigh').textContent = counts.high;
|
|
1392
|
+
document.getElementById('statMedium').textContent = counts.medium;
|
|
1393
|
+
document.getElementById('statLow').textContent = counts.low;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function updateModuleStatusPanel(findings) {
|
|
1397
|
+
// Reset all modules
|
|
1398
|
+
MODULES.forEach(mod => updateModuleStatus(mod.id, 'secure', 'low'));
|
|
1399
|
+
|
|
1400
|
+
// Update based on findings
|
|
1401
|
+
findings.forEach(f => {
|
|
1402
|
+
f.payload.affected_assets.forEach(asset => {
|
|
1403
|
+
const modId = MODULES.find(m =>
|
|
1404
|
+
asset.toLowerCase().includes(m.id) ||
|
|
1405
|
+
m.id.includes(asset.toLowerCase())
|
|
1406
|
+
)?.id;
|
|
1407
|
+
|
|
1408
|
+
if (modId) {
|
|
1409
|
+
const status = f.payload.severity === 'critical' || f.payload.severity === 'high' ? 'critical' : 'warning';
|
|
1410
|
+
updateModuleStatus(modId, status, f.payload.severity);
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
// Refresh the sidebar display
|
|
1416
|
+
refreshModuleStatusPanel();
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// Refresh the module status sidebar without resetting statuses
|
|
1420
|
+
function refreshModuleStatusPanel() {
|
|
1421
|
+
const container = document.getElementById('moduleStatus');
|
|
1422
|
+
container.innerHTML = MODULES.map(mod => {
|
|
1423
|
+
const node = moduleNodes[mod.id];
|
|
1424
|
+
const status = node?.userData?.status || 'secure';
|
|
1425
|
+
const color = status === 'critical' ? '#ff0044' : status === 'warning' ? '#ffaa00' : '#00ff88';
|
|
1426
|
+
return `<div class="legend-item">
|
|
1427
|
+
<div class="legend-icon module" style="background: ${color}"></div>
|
|
1428
|
+
<span>${mod.name} - ${status.toUpperCase()}</span>
|
|
1429
|
+
</div>`;
|
|
1430
|
+
}).join('');
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// ========== API INTEGRATION ==========
|
|
1434
|
+
async function pollAura() {
|
|
1435
|
+
try {
|
|
1436
|
+
const infoRes = await fetch(`${AURA_URL}/info`);
|
|
1437
|
+
if (!infoRes.ok) throw new Error('Not connected');
|
|
1438
|
+
|
|
1439
|
+
if (!connected) {
|
|
1440
|
+
connected = true;
|
|
1441
|
+
document.getElementById('statusDot').classList.remove('error');
|
|
1442
|
+
document.getElementById('statusText').textContent = wsConnected ? 'Connected (WS)' : 'Connected';
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// Use the real /stats endpoint for database-backed statistics
|
|
1446
|
+
const statsRes = await fetch(`${AURA_URL}/stats`);
|
|
1447
|
+
const stats = await statsRes.json();
|
|
1448
|
+
|
|
1449
|
+
document.getElementById('auditCount').textContent = `${stats.totalAudits || 0} Audits`;
|
|
1450
|
+
|
|
1451
|
+
// Update severity counts from real data (only if no audit is selected)
|
|
1452
|
+
if (!selectedAuditId) {
|
|
1453
|
+
const sev = stats.severityCounts || { critical: 0, high: 0, medium: 0, low: 0 };
|
|
1454
|
+
document.getElementById('statCritical').textContent = sev.critical;
|
|
1455
|
+
document.getElementById('statHigh').textContent = sev.high;
|
|
1456
|
+
document.getElementById('statMedium').textContent = sev.medium;
|
|
1457
|
+
document.getElementById('statLow').textContent = sev.low;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
if (connected) {
|
|
1462
|
+
connected = false;
|
|
1463
|
+
document.getElementById('statusDot').classList.add('error');
|
|
1464
|
+
document.getElementById('statusText').textContent = 'Disconnected';
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// ========== WEBSOCKET CONNECTION ==========
|
|
1470
|
+
function connectWebSocket() {
|
|
1471
|
+
if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) {
|
|
1472
|
+
return; // Already connected or connecting
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
try {
|
|
1476
|
+
ws = new WebSocket(WS_URL);
|
|
1477
|
+
|
|
1478
|
+
ws.onopen = () => {
|
|
1479
|
+
console.log('[WS] Connected to WebSocket server');
|
|
1480
|
+
wsConnected = true;
|
|
1481
|
+
document.getElementById('statusDot').classList.remove('error');
|
|
1482
|
+
document.getElementById('statusText').textContent = 'Connected (WS)';
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
ws.onmessage = (event) => {
|
|
1486
|
+
try {
|
|
1487
|
+
const msg = JSON.parse(event.data);
|
|
1488
|
+
handleWebSocketMessage(msg);
|
|
1489
|
+
} catch (err) {
|
|
1490
|
+
console.error('[WS] Invalid message:', err);
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
ws.onclose = () => {
|
|
1495
|
+
console.log('[WS] Disconnected');
|
|
1496
|
+
wsConnected = false;
|
|
1497
|
+
// Retry connection after 3 seconds
|
|
1498
|
+
setTimeout(connectWebSocket, 3000);
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
ws.onerror = (err) => {
|
|
1502
|
+
console.error('[WS] Error:', err);
|
|
1503
|
+
wsConnected = false;
|
|
1504
|
+
};
|
|
1505
|
+
} catch (err) {
|
|
1506
|
+
console.error('[WS] Failed to connect:', err);
|
|
1507
|
+
wsConnected = false;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function handleWebSocketMessage(msg) {
|
|
1512
|
+
switch (msg.type) {
|
|
1513
|
+
case 'audit_started':
|
|
1514
|
+
console.log('[WS] Audit started:', msg.payload);
|
|
1515
|
+
showToast(`Scanning: ${msg.payload.target}`, false);
|
|
1516
|
+
showLoading(true);
|
|
1517
|
+
break;
|
|
1518
|
+
|
|
1519
|
+
case 'audit_completed':
|
|
1520
|
+
console.log('[WS] Audit completed:', msg.payload);
|
|
1521
|
+
showLoading(false);
|
|
1522
|
+
// Refresh the audit history to show the new audit
|
|
1523
|
+
refreshAuditHistory();
|
|
1524
|
+
// Update stats display
|
|
1525
|
+
if (msg.payload.summary) {
|
|
1526
|
+
const s = msg.payload.summary;
|
|
1527
|
+
const total = s.critical + s.high + s.medium + s.low;
|
|
1528
|
+
showToast(`Scan complete: ${total} findings`, s.critical > 0 || s.high > 0);
|
|
1529
|
+
}
|
|
1530
|
+
break;
|
|
1531
|
+
|
|
1532
|
+
case 'finding':
|
|
1533
|
+
console.log('[WS] Finding:', msg.payload);
|
|
1534
|
+
// Could add real-time finding display here
|
|
1535
|
+
break;
|
|
1536
|
+
|
|
1537
|
+
case 'settings_changed':
|
|
1538
|
+
console.log('[WS] Settings changed:', msg.payload);
|
|
1539
|
+
showToast('Settings updated', false);
|
|
1540
|
+
break;
|
|
1541
|
+
|
|
1542
|
+
case 'ping':
|
|
1543
|
+
// Respond to ping
|
|
1544
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
1545
|
+
ws.send(JSON.stringify({ type: 'pong', payload: { time: Date.now() }, timestamp: new Date().toISOString() }));
|
|
1546
|
+
}
|
|
1547
|
+
break;
|
|
1548
|
+
|
|
1549
|
+
case 'status':
|
|
1550
|
+
console.log('[WS] Status:', msg.payload);
|
|
1551
|
+
break;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
async function runPreset(presetName) {
|
|
1556
|
+
showLoading(true);
|
|
1557
|
+
|
|
1558
|
+
const preset = PRESETS[presetName];
|
|
1559
|
+
if (!preset) {
|
|
1560
|
+
showToast('Unknown preset', true);
|
|
1561
|
+
showLoading(false);
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
preset.change_event.id = `preset-${presetName}-${Date.now()}`;
|
|
1566
|
+
|
|
1567
|
+
try {
|
|
1568
|
+
const res = await fetch(`${AURA_URL}/tools`, {
|
|
1569
|
+
method: 'POST',
|
|
1570
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1571
|
+
body: JSON.stringify({ tool: 'audit', arguments: preset })
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
const data = await res.json();
|
|
1575
|
+
|
|
1576
|
+
if (data.result) {
|
|
1577
|
+
updateAuditorState(data.result.agent_state);
|
|
1578
|
+
updateFindings(data.result.events);
|
|
1579
|
+
showToast(`Audit: ${data.result.agent_state.toUpperCase()}`);
|
|
1580
|
+
} else {
|
|
1581
|
+
showToast('Audit failed', true);
|
|
1582
|
+
}
|
|
1583
|
+
} catch (err) {
|
|
1584
|
+
showToast('Request failed: ' + err.message, true);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
showLoading(false);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// ========== AUDIT HISTORY ==========
|
|
1591
|
+
let auditHistoryCache = [];
|
|
1592
|
+
let selectedAuditId = null;
|
|
1593
|
+
|
|
1594
|
+
async function refreshAuditHistory() {
|
|
1595
|
+
try {
|
|
1596
|
+
// Use the new /audits database endpoint for real data
|
|
1597
|
+
const res = await fetch(`${AURA_URL}/audits?limit=20`);
|
|
1598
|
+
const data = await res.json();
|
|
1599
|
+
auditHistoryCache = data.audits || [];
|
|
1600
|
+
|
|
1601
|
+
document.getElementById('auditHistoryCount').textContent = `(${data.total || 0})`;
|
|
1602
|
+
|
|
1603
|
+
const listEl = document.getElementById('auditHistoryList');
|
|
1604
|
+
if (auditHistoryCache.length === 0) {
|
|
1605
|
+
listEl.innerHTML = '<p style="color: #666; font-size: 11px;">No audits yet. Run a scan to see results.</p>';
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
listEl.innerHTML = auditHistoryCache.map((audit) => {
|
|
1610
|
+
const shortId = audit.id.length > 25 ? audit.id.substring(0, 25) + '...' : audit.id;
|
|
1611
|
+
const shortTarget = audit.target ? (audit.target.length > 30 ? '...' + audit.target.slice(-30) : audit.target) : 'Unknown';
|
|
1612
|
+
const summary = audit.summary || { critical: 0, high: 0, medium: 0, low: 0 };
|
|
1613
|
+
const totalFindings = summary.critical + summary.high + summary.medium + summary.low;
|
|
1614
|
+
|
|
1615
|
+
// Color based on severity
|
|
1616
|
+
let stateColor = '#00ff88'; // green = safe
|
|
1617
|
+
if (summary.critical > 0) stateColor = '#ff0044';
|
|
1618
|
+
else if (summary.high > 0) stateColor = '#ff4400';
|
|
1619
|
+
else if (summary.medium > 0) stateColor = '#ffaa00';
|
|
1620
|
+
|
|
1621
|
+
const timestamp = new Date(audit.timestamp).toLocaleString();
|
|
1622
|
+
const isSelected = audit.id === selectedAuditId ? 'background: rgba(0,255,136,0.2);' : '';
|
|
1623
|
+
|
|
1624
|
+
return `
|
|
1625
|
+
<div class="audit-history-item" onclick="selectAudit('${audit.id}')" style="
|
|
1626
|
+
padding: 8px;
|
|
1627
|
+
margin: 4px 0;
|
|
1628
|
+
border-left: 3px solid ${stateColor};
|
|
1629
|
+
background: rgba(0,0,0,0.3);
|
|
1630
|
+
cursor: pointer;
|
|
1631
|
+
${isSelected}
|
|
1632
|
+
">
|
|
1633
|
+
<div style="font-size: 11px; font-weight: bold;">${audit.type.toUpperCase()} Scan</div>
|
|
1634
|
+
<div style="font-size: 10px; color: #888; margin: 2px 0;">${shortTarget}</div>
|
|
1635
|
+
<div style="font-size: 9px; color: ${stateColor};">
|
|
1636
|
+
${totalFindings > 0 ? `${totalFindings} findings (${summary.critical}C/${summary.high}H/${summary.medium}M/${summary.low}L)` : 'No issues found'}
|
|
1637
|
+
</div>
|
|
1638
|
+
<div style="font-size: 8px; color: #555; margin-top: 3px;">${timestamp}</div>
|
|
1639
|
+
</div>
|
|
1640
|
+
`;
|
|
1641
|
+
}).join('');
|
|
1642
|
+
|
|
1643
|
+
} catch (err) {
|
|
1644
|
+
console.error('Failed to load audit history:', err);
|
|
1645
|
+
document.getElementById('auditHistoryList').innerHTML =
|
|
1646
|
+
`<p style="color: #ff4444; font-size: 11px;">Error: ${err.message}</p>`;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
async function selectAudit(auditId) {
|
|
1651
|
+
selectedAuditId = auditId;
|
|
1652
|
+
showLoading(true);
|
|
1653
|
+
|
|
1654
|
+
try {
|
|
1655
|
+
// Use the new /audits/:id endpoint for real database data
|
|
1656
|
+
const res = await fetch(`${AURA_URL}/audits/${encodeURIComponent(auditId)}`);
|
|
1657
|
+
const audit = await res.json();
|
|
1658
|
+
|
|
1659
|
+
if (audit && !audit.error) {
|
|
1660
|
+
// Show selected audit panel
|
|
1661
|
+
const panel = document.getElementById('selectedAuditPanel');
|
|
1662
|
+
panel.style.display = 'block';
|
|
1663
|
+
|
|
1664
|
+
const summary = audit.summary || { critical: 0, high: 0, medium: 0, low: 0 };
|
|
1665
|
+
const scanData = audit.data || {};
|
|
1666
|
+
const totalFindings = summary.critical + summary.high + summary.medium + summary.low;
|
|
1667
|
+
|
|
1668
|
+
let info = `<div><strong>ID:</strong> ${audit.id}</div>`;
|
|
1669
|
+
info += `<div><strong>Type:</strong> ${audit.type.toUpperCase()}</div>`;
|
|
1670
|
+
info += `<div><strong>Target:</strong> ${audit.target}</div>`;
|
|
1671
|
+
info += `<div><strong>Time:</strong> ${new Date(audit.timestamp).toLocaleString()}</div>`;
|
|
1672
|
+
|
|
1673
|
+
info += `<div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #00ff8844;">`;
|
|
1674
|
+
info += `<div><strong>Findings:</strong> ${totalFindings}</div>`;
|
|
1675
|
+
info += `<div style="font-size: 10px; color: #888;">
|
|
1676
|
+
Critical: ${summary.critical} | High: ${summary.high} | Medium: ${summary.medium} | Low: ${summary.low}
|
|
1677
|
+
</div>`;
|
|
1678
|
+
|
|
1679
|
+
if (scanData.secrets && scanData.secrets.length > 0) {
|
|
1680
|
+
info += `<div style="margin-top: 5px;"><strong>Secrets Found:</strong> ${scanData.secrets.length}</div>`;
|
|
1681
|
+
}
|
|
1682
|
+
if (scanData.packages && scanData.packages.length > 0) {
|
|
1683
|
+
info += `<div><strong>Vulnerable Packages:</strong> ${scanData.packages.length}</div>`;
|
|
1684
|
+
}
|
|
1685
|
+
if (scanData.sastFindings && scanData.sastFindings.length > 0) {
|
|
1686
|
+
info += `<div><strong>SAST Issues:</strong> ${scanData.sastFindings.length}</div>`;
|
|
1687
|
+
}
|
|
1688
|
+
if (scanData.toolsUsed) {
|
|
1689
|
+
info += `<div><strong>Tools:</strong> ${scanData.toolsUsed.join(', ')}</div>`;
|
|
1690
|
+
}
|
|
1691
|
+
info += `</div>`;
|
|
1692
|
+
|
|
1693
|
+
// Show detailed findings if any
|
|
1694
|
+
if (scanData.secrets && scanData.secrets.length > 0) {
|
|
1695
|
+
info += `<div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #ff004444;">`;
|
|
1696
|
+
info += `<div style="color: #ff0044; font-weight: bold; margin-bottom: 5px;">SECRETS DETECTED:</div>`;
|
|
1697
|
+
scanData.secrets.slice(0, 5).forEach(s => {
|
|
1698
|
+
info += `<div style="font-size: 9px; color: #ff6666; margin: 2px 0;">
|
|
1699
|
+
${s.type}: ${s.file}:${s.line}
|
|
1700
|
+
</div>`;
|
|
1701
|
+
});
|
|
1702
|
+
if (scanData.secrets.length > 5) {
|
|
1703
|
+
info += `<div style="font-size: 9px; color: #888;">...and ${scanData.secrets.length - 5} more</div>`;
|
|
1704
|
+
}
|
|
1705
|
+
info += `</div>`;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
document.getElementById('selectedAuditInfo').innerHTML = info;
|
|
1709
|
+
|
|
1710
|
+
// Update stats bar with this audit's findings
|
|
1711
|
+
document.getElementById('statCritical').textContent = summary.critical;
|
|
1712
|
+
document.getElementById('statHigh').textContent = summary.high;
|
|
1713
|
+
document.getElementById('statMedium').textContent = summary.medium;
|
|
1714
|
+
document.getElementById('statLow').textContent = summary.low;
|
|
1715
|
+
|
|
1716
|
+
showToast(`Loaded audit: ${audit.type} scan`);
|
|
1717
|
+
} else {
|
|
1718
|
+
showToast('Audit not found', true);
|
|
1719
|
+
}
|
|
1720
|
+
} catch (err) {
|
|
1721
|
+
console.error('Failed to load audit:', err);
|
|
1722
|
+
showToast('Failed to load audit: ' + err.message, true);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
showLoading(false);
|
|
1726
|
+
refreshAuditHistory(); // Refresh to update selection highlight
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
function clearSelectedAudit() {
|
|
1730
|
+
selectedAuditId = null;
|
|
1731
|
+
document.getElementById('selectedAuditPanel').style.display = 'none';
|
|
1732
|
+
// Reset stats
|
|
1733
|
+
document.getElementById('statCritical').textContent = '0';
|
|
1734
|
+
document.getElementById('statHigh').textContent = '0';
|
|
1735
|
+
document.getElementById('statMedium').textContent = '0';
|
|
1736
|
+
document.getElementById('statLow').textContent = '0';
|
|
1737
|
+
refreshAuditHistory();
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
// ========== SETTINGS FUNCTIONS ==========
|
|
1741
|
+
let settingsDirty = false;
|
|
1742
|
+
|
|
1743
|
+
async function openSettingsModal() {
|
|
1744
|
+
document.getElementById('settingsModal').style.display = 'flex';
|
|
1745
|
+
settingsDirty = false;
|
|
1746
|
+
document.getElementById('saveSettingsBtn').disabled = true;
|
|
1747
|
+
|
|
1748
|
+
// Load current settings from server
|
|
1749
|
+
try {
|
|
1750
|
+
const res = await fetch(`${AURA_URL}/settings`);
|
|
1751
|
+
if (res.ok) {
|
|
1752
|
+
const data = await res.json();
|
|
1753
|
+
populateSettings(data.settings);
|
|
1754
|
+
}
|
|
1755
|
+
} catch (err) {
|
|
1756
|
+
console.error('Failed to load settings:', err);
|
|
1757
|
+
showToast('Failed to load settings', true);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function closeSettingsModal() {
|
|
1762
|
+
document.getElementById('settingsModal').style.display = 'none';
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
function switchSettingsTab(tab) {
|
|
1766
|
+
// Update tab buttons
|
|
1767
|
+
document.querySelectorAll('.settings-tab').forEach(btn => {
|
|
1768
|
+
btn.classList.toggle('active', btn.dataset.tab === tab);
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
// Show/hide panels
|
|
1772
|
+
document.querySelectorAll('.settings-panel').forEach(panel => {
|
|
1773
|
+
panel.style.display = 'none';
|
|
1774
|
+
});
|
|
1775
|
+
document.getElementById(`settings-${tab}`).style.display = 'block';
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
function markSettingsDirty() {
|
|
1779
|
+
settingsDirty = true;
|
|
1780
|
+
document.getElementById('saveSettingsBtn').disabled = false;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
function populateSettings(settings) {
|
|
1784
|
+
// AWS settings
|
|
1785
|
+
document.getElementById('aws-enabled').checked = settings['aws.enabled'] === 'true';
|
|
1786
|
+
document.getElementById('aws-region').value = settings['aws.region'] || 'us-east-1';
|
|
1787
|
+
document.getElementById('aws-profile').value = settings['aws.profile'] || '';
|
|
1788
|
+
document.getElementById('aws-iam').checked = settings['aws.services.iam'] !== 'false';
|
|
1789
|
+
document.getElementById('aws-s3').checked = settings['aws.services.s3'] !== 'false';
|
|
1790
|
+
document.getElementById('aws-ec2').checked = settings['aws.services.ec2'] !== 'false';
|
|
1791
|
+
document.getElementById('aws-lambda').checked = settings['aws.services.lambda'] !== 'false';
|
|
1792
|
+
document.getElementById('aws-rds').checked = settings['aws.services.rds'] !== 'false';
|
|
1793
|
+
|
|
1794
|
+
// Slack settings
|
|
1795
|
+
document.getElementById('slack-enabled').checked = settings['notifications.slack.enabled'] === 'true';
|
|
1796
|
+
document.getElementById('slack-webhook').value = settings['notifications.slack.webhookUrl'] || '';
|
|
1797
|
+
document.getElementById('slack-channel').value = settings['notifications.slack.channel'] || '';
|
|
1798
|
+
|
|
1799
|
+
// Discord settings
|
|
1800
|
+
document.getElementById('discord-enabled').checked = settings['notifications.discord.enabled'] === 'true';
|
|
1801
|
+
document.getElementById('discord-webhook').value = settings['notifications.discord.webhookUrl'] || '';
|
|
1802
|
+
|
|
1803
|
+
// Webhook settings
|
|
1804
|
+
document.getElementById('webhook-enabled').checked = settings['notifications.webhook.enabled'] === 'true';
|
|
1805
|
+
document.getElementById('webhook-url').value = settings['notifications.webhook.url'] || '';
|
|
1806
|
+
document.getElementById('webhook-headers').value = settings['notifications.webhook.headers'] || '';
|
|
1807
|
+
|
|
1808
|
+
// Update status indicator
|
|
1809
|
+
const enabledCount = [
|
|
1810
|
+
settings['aws.enabled'] === 'true',
|
|
1811
|
+
settings['notifications.slack.enabled'] === 'true',
|
|
1812
|
+
settings['notifications.discord.enabled'] === 'true',
|
|
1813
|
+
settings['notifications.webhook.enabled'] === 'true'
|
|
1814
|
+
].filter(Boolean).length;
|
|
1815
|
+
|
|
1816
|
+
document.getElementById('settingsStatus').textContent = enabledCount > 0 ? `(${enabledCount} active)` : '';
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
async function saveSettings() {
|
|
1820
|
+
const settings = {
|
|
1821
|
+
// AWS settings
|
|
1822
|
+
'aws.enabled': document.getElementById('aws-enabled').checked.toString(),
|
|
1823
|
+
'aws.region': document.getElementById('aws-region').value,
|
|
1824
|
+
'aws.profile': document.getElementById('aws-profile').value,
|
|
1825
|
+
'aws.services.iam': document.getElementById('aws-iam').checked.toString(),
|
|
1826
|
+
'aws.services.s3': document.getElementById('aws-s3').checked.toString(),
|
|
1827
|
+
'aws.services.ec2': document.getElementById('aws-ec2').checked.toString(),
|
|
1828
|
+
'aws.services.lambda': document.getElementById('aws-lambda').checked.toString(),
|
|
1829
|
+
'aws.services.rds': document.getElementById('aws-rds').checked.toString(),
|
|
1830
|
+
|
|
1831
|
+
// Slack settings
|
|
1832
|
+
'notifications.slack.enabled': document.getElementById('slack-enabled').checked.toString(),
|
|
1833
|
+
'notifications.slack.webhookUrl': document.getElementById('slack-webhook').value,
|
|
1834
|
+
'notifications.slack.channel': document.getElementById('slack-channel').value,
|
|
1835
|
+
|
|
1836
|
+
// Discord settings
|
|
1837
|
+
'notifications.discord.enabled': document.getElementById('discord-enabled').checked.toString(),
|
|
1838
|
+
'notifications.discord.webhookUrl': document.getElementById('discord-webhook').value,
|
|
1839
|
+
|
|
1840
|
+
// Webhook settings
|
|
1841
|
+
'notifications.webhook.enabled': document.getElementById('webhook-enabled').checked.toString(),
|
|
1842
|
+
'notifications.webhook.url': document.getElementById('webhook-url').value,
|
|
1843
|
+
'notifications.webhook.headers': document.getElementById('webhook-headers').value
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1846
|
+
try {
|
|
1847
|
+
const res = await fetch(`${AURA_URL}/settings`, {
|
|
1848
|
+
method: 'POST',
|
|
1849
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1850
|
+
body: JSON.stringify({ settings })
|
|
1851
|
+
});
|
|
1852
|
+
|
|
1853
|
+
if (res.ok) {
|
|
1854
|
+
showToast('Settings saved');
|
|
1855
|
+
settingsDirty = false;
|
|
1856
|
+
document.getElementById('saveSettingsBtn').disabled = true;
|
|
1857
|
+
closeSettingsModal();
|
|
1858
|
+
|
|
1859
|
+
// Update status indicator
|
|
1860
|
+
const enabledCount = [
|
|
1861
|
+
settings['aws.enabled'] === 'true',
|
|
1862
|
+
settings['notifications.slack.enabled'] === 'true',
|
|
1863
|
+
settings['notifications.discord.enabled'] === 'true',
|
|
1864
|
+
settings['notifications.webhook.enabled'] === 'true'
|
|
1865
|
+
].filter(Boolean).length;
|
|
1866
|
+
document.getElementById('settingsStatus').textContent = enabledCount > 0 ? `(${enabledCount} active)` : '';
|
|
1867
|
+
} else {
|
|
1868
|
+
showToast('Failed to save settings', true);
|
|
1869
|
+
}
|
|
1870
|
+
} catch (err) {
|
|
1871
|
+
console.error('Failed to save settings:', err);
|
|
1872
|
+
showToast('Failed to save settings', true);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
async function testNotification(channel) {
|
|
1877
|
+
showLoading(true);
|
|
1878
|
+
try {
|
|
1879
|
+
const res = await fetch(`${AURA_URL}/notifications/test`, {
|
|
1880
|
+
method: 'POST',
|
|
1881
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1882
|
+
body: JSON.stringify({ channel })
|
|
1883
|
+
});
|
|
1884
|
+
|
|
1885
|
+
const data = await res.json();
|
|
1886
|
+
if (data.success) {
|
|
1887
|
+
showToast(`${channel} test sent successfully`);
|
|
1888
|
+
} else {
|
|
1889
|
+
showToast(`${channel} test failed: ${data.error}`, true);
|
|
1890
|
+
}
|
|
1891
|
+
} catch (err) {
|
|
1892
|
+
showToast(`${channel} test failed`, true);
|
|
1893
|
+
}
|
|
1894
|
+
showLoading(false);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
async function runAWSScan() {
|
|
1898
|
+
// First save the settings
|
|
1899
|
+
await saveSettings();
|
|
1900
|
+
|
|
1901
|
+
showLoading(true);
|
|
1902
|
+
try {
|
|
1903
|
+
// Get AWS settings
|
|
1904
|
+
const region = document.getElementById('aws-region').value;
|
|
1905
|
+
const services = [];
|
|
1906
|
+
if (document.getElementById('aws-iam').checked) services.push('iam');
|
|
1907
|
+
if (document.getElementById('aws-s3').checked) services.push('s3');
|
|
1908
|
+
if (document.getElementById('aws-ec2').checked) services.push('ec2');
|
|
1909
|
+
if (document.getElementById('aws-lambda').checked) services.push('lambda');
|
|
1910
|
+
if (document.getElementById('aws-rds').checked) services.push('rds');
|
|
1911
|
+
|
|
1912
|
+
const res = await fetch(`${AURA_URL}/tools`, {
|
|
1913
|
+
method: 'POST',
|
|
1914
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1915
|
+
body: JSON.stringify({
|
|
1916
|
+
tool: 'scan-aws',
|
|
1917
|
+
arguments: { region, services }
|
|
1918
|
+
})
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
if (res.ok) {
|
|
1922
|
+
const data = await res.json();
|
|
1923
|
+
showToast('AWS scan started');
|
|
1924
|
+
closeSettingsModal();
|
|
1925
|
+
// Refresh audit history to show results
|
|
1926
|
+
setTimeout(refreshAuditHistory, 2000);
|
|
1927
|
+
} else {
|
|
1928
|
+
showToast('AWS scan failed', true);
|
|
1929
|
+
}
|
|
1930
|
+
} catch (err) {
|
|
1931
|
+
console.error('AWS scan error:', err);
|
|
1932
|
+
showToast('AWS scan failed', true);
|
|
1933
|
+
}
|
|
1934
|
+
showLoading(false);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// ========== UI HELPERS ==========
|
|
1938
|
+
function showLoading(show) {
|
|
1939
|
+
document.getElementById('loading').classList.toggle('active', show);
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
function showToast(message, isError = false) {
|
|
1943
|
+
const container = document.getElementById('toast-container');
|
|
1944
|
+
const toast = document.createElement('div');
|
|
1945
|
+
toast.className = `toast ${isError ? 'error' : ''}`;
|
|
1946
|
+
toast.textContent = message;
|
|
1947
|
+
container.appendChild(toast);
|
|
1948
|
+
setTimeout(() => toast.remove(), 4000);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
function onWindowResize() {
|
|
1952
|
+
camera.aspect = window.innerWidth / window.innerHeight;
|
|
1953
|
+
camera.updateProjectionMatrix();
|
|
1954
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
function onMouseMove(event) {
|
|
1958
|
+
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
1959
|
+
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
1960
|
+
|
|
1961
|
+
// Raycast for tooltips
|
|
1962
|
+
raycaster.setFromCamera(mouse, camera);
|
|
1963
|
+
const intersects = raycaster.intersectObjects(scene.children, true);
|
|
1964
|
+
|
|
1965
|
+
const tooltip = document.getElementById('tooltip');
|
|
1966
|
+
|
|
1967
|
+
if (intersects.length > 0) {
|
|
1968
|
+
let obj = intersects[0].object;
|
|
1969
|
+
while (obj.parent && !obj.userData.type) {
|
|
1970
|
+
obj = obj.parent;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
if (obj.userData.type) {
|
|
1974
|
+
tooltip.classList.add('visible');
|
|
1975
|
+
tooltip.style.left = event.clientX + 15 + 'px';
|
|
1976
|
+
tooltip.style.top = event.clientY + 15 + 'px';
|
|
1977
|
+
document.getElementById('tooltipTitle').textContent = obj.userData.name || obj.userData.type;
|
|
1978
|
+
document.getElementById('tooltipContent').innerHTML = `
|
|
1979
|
+
<div>${obj.userData.desc || ''}</div>
|
|
1980
|
+
${obj.userData.status ? `<div class="module-status">Status: ${obj.userData.status.toUpperCase()}</div>` : ''}
|
|
1981
|
+
`;
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
tooltip.classList.remove('visible');
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// ========== MODULE MANAGEMENT ==========
|
|
1990
|
+
let selectedModule = null;
|
|
1991
|
+
let moduleEnabled = {};
|
|
1992
|
+
|
|
1993
|
+
// Initialize all modules as enabled
|
|
1994
|
+
MODULES.forEach(mod => moduleEnabled[mod.id] = true);
|
|
1995
|
+
|
|
1996
|
+
function onMouseClick(event) {
|
|
1997
|
+
// Ignore clicks on UI elements
|
|
1998
|
+
if (event.target.closest('.ui-layer') || event.target.closest('#module-modal')) return;
|
|
1999
|
+
|
|
2000
|
+
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
2001
|
+
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
2002
|
+
|
|
2003
|
+
raycaster.setFromCamera(mouse, camera);
|
|
2004
|
+
const intersects = raycaster.intersectObjects(scene.children, true);
|
|
2005
|
+
|
|
2006
|
+
if (intersects.length > 0) {
|
|
2007
|
+
let obj = intersects[0].object;
|
|
2008
|
+
while (obj.parent && !obj.userData.type) {
|
|
2009
|
+
obj = obj.parent;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
if (obj.userData.type === 'module') {
|
|
2013
|
+
selectModule(obj.userData.id);
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
if (obj.userData.type === 'auditor') {
|
|
2018
|
+
selectModule(null);
|
|
2019
|
+
showToast('Security Auditor selected - central analysis node');
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// Clicked empty space - deselect
|
|
2025
|
+
selectModule(null);
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function selectModule(moduleId) {
|
|
2029
|
+
// Clear previous selection visual
|
|
2030
|
+
if (selectedModule && moduleNodes[selectedModule]) {
|
|
2031
|
+
moduleNodes[selectedModule].traverse(child => {
|
|
2032
|
+
if (child.isMesh && child.name?.startsWith('module-')) {
|
|
2033
|
+
child.material.emissiveIntensity = 0.2;
|
|
2034
|
+
}
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
selectedModule = moduleId;
|
|
2039
|
+
|
|
2040
|
+
if (moduleId && moduleNodes[moduleId]) {
|
|
2041
|
+
// Highlight selected module
|
|
2042
|
+
moduleNodes[moduleId].traverse(child => {
|
|
2043
|
+
if (child.isMesh && child.name?.startsWith('module-')) {
|
|
2044
|
+
child.material.emissiveIntensity = 0.8;
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
|
|
2048
|
+
const mod = MODULES.find(m => m.id === moduleId);
|
|
2049
|
+
document.getElementById('selectedModuleInfo').style.display = 'block';
|
|
2050
|
+
document.getElementById('selectedModuleName').textContent = mod?.name || moduleId;
|
|
2051
|
+
showToast(`Selected: ${mod?.name || moduleId}`);
|
|
2052
|
+
} else {
|
|
2053
|
+
document.getElementById('selectedModuleInfo').style.display = 'none';
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function openModuleManager() {
|
|
2058
|
+
document.getElementById('modalTitle').textContent = 'MANAGE MODULES';
|
|
2059
|
+
document.getElementById('modalBody').innerHTML = `
|
|
2060
|
+
<p style="font-size: 11px; color: #888; margin-bottom: 15px;">Toggle modules on/off to include them in security audits.</p>
|
|
2061
|
+
${MODULES.map(mod => `
|
|
2062
|
+
<div class="module-toggle ${moduleEnabled[mod.id] ? '' : 'disabled'}" onclick="toggleModule('${mod.id}')">
|
|
2063
|
+
<div>
|
|
2064
|
+
<strong style="color: #${mod.color.toString(16).padStart(6, '0')}">${mod.name}</strong>
|
|
2065
|
+
<div style="font-size: 10px; color: #666;">${mod.desc}</div>
|
|
2066
|
+
</div>
|
|
2067
|
+
<div class="toggle-switch ${moduleEnabled[mod.id] ? 'active' : ''}" id="toggle-${mod.id}"></div>
|
|
2068
|
+
</div>
|
|
2069
|
+
`).join('')}
|
|
2070
|
+
`;
|
|
2071
|
+
document.getElementById('module-modal').classList.add('visible');
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
function addNewModule() {
|
|
2075
|
+
document.getElementById('modalTitle').textContent = 'ADD NEW MODULE';
|
|
2076
|
+
document.getElementById('modalBody').innerHTML = `
|
|
2077
|
+
<div class="form-group">
|
|
2078
|
+
<label>Module ID (lowercase, no spaces)</label>
|
|
2079
|
+
<input type="text" id="newModuleId" placeholder="e.g., logging" pattern="[a-z0-9_-]+">
|
|
2080
|
+
</div>
|
|
2081
|
+
<div class="form-group">
|
|
2082
|
+
<label>Display Name</label>
|
|
2083
|
+
<input type="text" id="newModuleName" placeholder="e.g., LOGGING">
|
|
2084
|
+
</div>
|
|
2085
|
+
<div class="form-group">
|
|
2086
|
+
<label>Description</label>
|
|
2087
|
+
<input type="text" id="newModuleDesc" placeholder="e.g., Audit Logging & Events">
|
|
2088
|
+
</div>
|
|
2089
|
+
<div class="form-group">
|
|
2090
|
+
<label>Color</label>
|
|
2091
|
+
<select id="newModuleColor">
|
|
2092
|
+
<option value="0x00ff88">Green</option>
|
|
2093
|
+
<option value="0x00aaff">Blue</option>
|
|
2094
|
+
<option value="0xffaa00">Orange</option>
|
|
2095
|
+
<option value="0xff00ff">Purple</option>
|
|
2096
|
+
<option value="0xff0088">Pink</option>
|
|
2097
|
+
<option value="0xffff00">Yellow</option>
|
|
2098
|
+
<option value="0x00ffff">Cyan</option>
|
|
2099
|
+
</select>
|
|
2100
|
+
</div>
|
|
2101
|
+
<div class="form-group">
|
|
2102
|
+
<label>Connect To (existing module)</label>
|
|
2103
|
+
<select id="newModuleConnect">
|
|
2104
|
+
<option value="">-- No Connection --</option>
|
|
2105
|
+
${MODULES.map(m => `<option value="${m.id}">${m.name}</option>`).join('')}
|
|
2106
|
+
</select>
|
|
2107
|
+
</div>
|
|
2108
|
+
`;
|
|
2109
|
+
document.getElementById('module-modal').classList.add('visible');
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
function toggleModule(moduleId) {
|
|
2113
|
+
moduleEnabled[moduleId] = !moduleEnabled[moduleId];
|
|
2114
|
+
|
|
2115
|
+
const toggle = document.getElementById(`toggle-${moduleId}`);
|
|
2116
|
+
const toggleParent = toggle?.parentElement;
|
|
2117
|
+
|
|
2118
|
+
if (toggle) {
|
|
2119
|
+
toggle.classList.toggle('active', moduleEnabled[moduleId]);
|
|
2120
|
+
}
|
|
2121
|
+
if (toggleParent) {
|
|
2122
|
+
toggleParent.classList.toggle('disabled', !moduleEnabled[moduleId]);
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
// Visual feedback in 3D
|
|
2126
|
+
if (moduleNodes[moduleId]) {
|
|
2127
|
+
moduleNodes[moduleId].visible = moduleEnabled[moduleId];
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
// Update connections
|
|
2131
|
+
connectionLines.forEach(line => {
|
|
2132
|
+
if (line.userData.to === moduleId || line.userData.from === moduleId) {
|
|
2133
|
+
line.visible = moduleEnabled[moduleId];
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
function removeSelectedModule() {
|
|
2139
|
+
if (!selectedModule) {
|
|
2140
|
+
showToast('No module selected', true);
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
const modIndex = MODULES.findIndex(m => m.id === selectedModule);
|
|
2145
|
+
if (modIndex === -1) return;
|
|
2146
|
+
|
|
2147
|
+
// Remove from scene
|
|
2148
|
+
if (moduleNodes[selectedModule]) {
|
|
2149
|
+
scene.remove(moduleNodes[selectedModule]);
|
|
2150
|
+
delete moduleNodes[selectedModule];
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// Remove connections
|
|
2154
|
+
connectionLines = connectionLines.filter(line => {
|
|
2155
|
+
if (line.userData.to === selectedModule || line.userData.from === selectedModule) {
|
|
2156
|
+
scene.remove(line);
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
return true;
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
// Remove from MODULES array
|
|
2163
|
+
MODULES.splice(modIndex, 1);
|
|
2164
|
+
delete moduleEnabled[selectedModule];
|
|
2165
|
+
|
|
2166
|
+
showToast(`Removed module: ${selectedModule}`);
|
|
2167
|
+
selectModule(null);
|
|
2168
|
+
updateModuleStatusPanel([]);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
function saveModuleChanges() {
|
|
2172
|
+
const modalTitle = document.getElementById('modalTitle').textContent;
|
|
2173
|
+
|
|
2174
|
+
if (modalTitle === 'ADD NEW MODULE') {
|
|
2175
|
+
const id = document.getElementById('newModuleId')?.value?.toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
|
2176
|
+
const name = document.getElementById('newModuleName')?.value?.toUpperCase();
|
|
2177
|
+
const desc = document.getElementById('newModuleDesc')?.value;
|
|
2178
|
+
const color = parseInt(document.getElementById('newModuleColor')?.value || '0x00ff88');
|
|
2179
|
+
const connectTo = document.getElementById('newModuleConnect')?.value;
|
|
2180
|
+
|
|
2181
|
+
if (!id || !name) {
|
|
2182
|
+
showToast('Module ID and Name are required', true);
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
if (MODULES.find(m => m.id === id)) {
|
|
2187
|
+
showToast('Module ID already exists', true);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
// Calculate position (find empty spot)
|
|
2192
|
+
const angle = MODULES.length * (Math.PI / 4);
|
|
2193
|
+
const radius = 18;
|
|
2194
|
+
const pos = [
|
|
2195
|
+
Math.cos(angle) * radius,
|
|
2196
|
+
0,
|
|
2197
|
+
Math.sin(angle) * radius
|
|
2198
|
+
];
|
|
2199
|
+
|
|
2200
|
+
// Add to MODULES array
|
|
2201
|
+
const newMod = { id, name, desc, pos, color };
|
|
2202
|
+
MODULES.push(newMod);
|
|
2203
|
+
moduleEnabled[id] = true;
|
|
2204
|
+
|
|
2205
|
+
// Create 3D node
|
|
2206
|
+
createSingleModuleNode(newMod);
|
|
2207
|
+
|
|
2208
|
+
// Add audit connection
|
|
2209
|
+
const auditLine = createConnectionLine(
|
|
2210
|
+
new THREE.Vector3(0, 5, 0),
|
|
2211
|
+
new THREE.Vector3(pos[0], pos[1] + 2, pos[2]),
|
|
2212
|
+
0x00aaff,
|
|
2213
|
+
true
|
|
2214
|
+
);
|
|
2215
|
+
auditLine.userData = { type: 'audit-connection', to: id };
|
|
2216
|
+
connectionLines.push(auditLine);
|
|
2217
|
+
scene.add(auditLine);
|
|
2218
|
+
|
|
2219
|
+
// Add module-to-module connection if specified
|
|
2220
|
+
if (connectTo) {
|
|
2221
|
+
const targetMod = MODULES.find(m => m.id === connectTo);
|
|
2222
|
+
if (targetMod) {
|
|
2223
|
+
const dataLine = createConnectionLine(
|
|
2224
|
+
new THREE.Vector3(pos[0], pos[1] + 2, pos[2]),
|
|
2225
|
+
new THREE.Vector3(targetMod.pos[0], targetMod.pos[1] + 2, targetMod.pos[2]),
|
|
2226
|
+
0x00ff8844,
|
|
2227
|
+
false
|
|
2228
|
+
);
|
|
2229
|
+
dataLine.userData = { type: 'data-flow', from: id, to: connectTo, label: 'Data' };
|
|
2230
|
+
connectionLines.push(dataLine);
|
|
2231
|
+
scene.add(dataLine);
|
|
2232
|
+
CONNECTIONS.push({ from: id, to: connectTo, label: 'Data' });
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
showToast(`Added module: ${name}`);
|
|
2237
|
+
updateModuleStatusPanel([]);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
closeModal();
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
function createSingleModuleNode(mod) {
|
|
2244
|
+
const group = new THREE.Group();
|
|
2245
|
+
|
|
2246
|
+
const boxGeom = new THREE.BoxGeometry(5, 3, 5);
|
|
2247
|
+
const boxMat = new THREE.MeshPhongMaterial({
|
|
2248
|
+
color: mod.color,
|
|
2249
|
+
emissive: mod.color,
|
|
2250
|
+
emissiveIntensity: 0.2
|
|
2251
|
+
});
|
|
2252
|
+
const box = new THREE.Mesh(boxGeom, boxMat);
|
|
2253
|
+
box.name = `module-${mod.id}`;
|
|
2254
|
+
group.add(box);
|
|
2255
|
+
|
|
2256
|
+
const indicatorGeom = new THREE.CylinderGeometry(0.5, 0.5, 0.3, 16);
|
|
2257
|
+
const indicatorMat = new THREE.MeshBasicMaterial({ color: 0x00ff88 });
|
|
2258
|
+
const indicator = new THREE.Mesh(indicatorGeom, indicatorMat);
|
|
2259
|
+
indicator.position.y = 1.8;
|
|
2260
|
+
indicator.name = `indicator-${mod.id}`;
|
|
2261
|
+
group.add(indicator);
|
|
2262
|
+
|
|
2263
|
+
const labelSprite = createTextSprite(mod.name, mod.color);
|
|
2264
|
+
labelSprite.position.y = 4;
|
|
2265
|
+
labelSprite.scale.set(6, 3, 1);
|
|
2266
|
+
group.add(labelSprite);
|
|
2267
|
+
|
|
2268
|
+
group.position.set(mod.pos[0], mod.pos[1] + 2, mod.pos[2]);
|
|
2269
|
+
group.userData = { type: 'module', id: mod.id, name: mod.name, desc: mod.desc, status: 'secure' };
|
|
2270
|
+
moduleNodes[mod.id] = group;
|
|
2271
|
+
scene.add(group);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
function closeModal() {
|
|
2275
|
+
document.getElementById('module-modal').classList.remove('visible');
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
// ========== ANIMATION LOOP ==========
|
|
2279
|
+
let time = 0;
|
|
2280
|
+
|
|
2281
|
+
function animate() {
|
|
2282
|
+
requestAnimationFrame(animate);
|
|
2283
|
+
time += 0.01;
|
|
2284
|
+
|
|
2285
|
+
// Rotate auditor rings
|
|
2286
|
+
auditorNode.children.forEach(child => {
|
|
2287
|
+
if (child.name?.startsWith('auditor-ring')) {
|
|
2288
|
+
child.rotation.z += 0.01;
|
|
2289
|
+
}
|
|
2290
|
+
if (child.name === 'auditor-core') {
|
|
2291
|
+
child.rotation.y += 0.02;
|
|
2292
|
+
child.position.y = 5 + Math.sin(time * 2) * 0.3;
|
|
2293
|
+
}
|
|
2294
|
+
});
|
|
2295
|
+
|
|
2296
|
+
// Pulse finding markers
|
|
2297
|
+
findingMarkers.forEach((marker, i) => {
|
|
2298
|
+
marker.rotation.y += 0.03;
|
|
2299
|
+
marker.position.y = 6 + Math.sin(time * 3 + i) * 0.5;
|
|
2300
|
+
const scale = 1 + Math.sin(time * 4 + i) * 0.2;
|
|
2301
|
+
marker.scale.set(scale, scale, scale);
|
|
2302
|
+
});
|
|
2303
|
+
|
|
2304
|
+
// Animate particles
|
|
2305
|
+
const particles = scene.getObjectByName('particles');
|
|
2306
|
+
if (particles) {
|
|
2307
|
+
particles.rotation.y += 0.0003;
|
|
2308
|
+
const positions = particles.geometry.attributes.position.array;
|
|
2309
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
2310
|
+
positions[i + 1] += 0.01;
|
|
2311
|
+
if (positions[i + 1] > 40) positions[i + 1] = 0;
|
|
2312
|
+
}
|
|
2313
|
+
particles.geometry.attributes.position.needsUpdate = true;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
controls.update();
|
|
2317
|
+
renderer.render(scene, camera);
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// ========== PROJECT SCANNING ==========
|
|
2321
|
+
let currentScanDetails = null;
|
|
2322
|
+
let scanMode = 'local';
|
|
2323
|
+
|
|
2324
|
+
// Switch between local and git scan modes
|
|
2325
|
+
function setScanMode(mode) {
|
|
2326
|
+
scanMode = mode;
|
|
2327
|
+
document.getElementById('localInput').style.display = mode === 'local' ? 'block' : 'none';
|
|
2328
|
+
document.getElementById('gitInput').style.display = mode === 'git' ? 'block' : 'none';
|
|
2329
|
+
document.getElementById('tabLocal').className = mode === 'local' ? 'btn' : 'btn secondary';
|
|
2330
|
+
document.getElementById('tabGit').className = mode === 'git' ? 'btn' : 'btn secondary';
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
// Get project name from path or URL
|
|
2334
|
+
function getProjectName(pathOrUrl) {
|
|
2335
|
+
if (!pathOrUrl) return 'Project';
|
|
2336
|
+
// Handle git URLs
|
|
2337
|
+
if (pathOrUrl.includes('github.com') || pathOrUrl.includes('gitlab.com') || pathOrUrl.endsWith('.git')) {
|
|
2338
|
+
const match = pathOrUrl.match(/\/([^\/]+?)(\.git)?$/);
|
|
2339
|
+
return match ? match[1] : 'GitRepo';
|
|
2340
|
+
}
|
|
2341
|
+
// Handle local paths
|
|
2342
|
+
const parts = pathOrUrl.replace(/\\/g, '/').split('/').filter(p => p);
|
|
2343
|
+
return parts[parts.length - 1] || 'Project';
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// Main scan function
|
|
2347
|
+
async function doScan() {
|
|
2348
|
+
const targetPath = document.getElementById('scanPath').value.trim();
|
|
2349
|
+
if (!targetPath) {
|
|
2350
|
+
showToast('Enter a directory path', true);
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
await performScan(targetPath);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
// Scan a Git repository
|
|
2357
|
+
async function scanGitRepo() {
|
|
2358
|
+
const gitUrl = document.getElementById('gitUrl').value.trim();
|
|
2359
|
+
if (!gitUrl) {
|
|
2360
|
+
showToast('Enter a Git URL', true);
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
const statusEl = document.getElementById('scanStatus');
|
|
2365
|
+
statusEl.innerHTML = '<span style="color: #00aaff;">Scanning remote repository...</span>';
|
|
2366
|
+
showLoading(true);
|
|
2367
|
+
|
|
2368
|
+
// Clear map before scanning
|
|
2369
|
+
try {
|
|
2370
|
+
clearMap();
|
|
2371
|
+
} catch (e) {
|
|
2372
|
+
console.warn('clearMap error:', e);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
try {
|
|
2376
|
+
// Call backend to scan via API (no clone)
|
|
2377
|
+
const res = await fetch(`${AURA_URL}/tools`, {
|
|
2378
|
+
method: 'POST',
|
|
2379
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2380
|
+
body: JSON.stringify({
|
|
2381
|
+
tool: 'scan-local',
|
|
2382
|
+
arguments: { gitUrl: gitUrl }
|
|
2383
|
+
})
|
|
2384
|
+
});
|
|
2385
|
+
|
|
2386
|
+
const data = await res.json();
|
|
2387
|
+
console.log('Git scan response:', data);
|
|
2388
|
+
|
|
2389
|
+
if (data.result && data.result.scan_details) {
|
|
2390
|
+
await handleScanResult(data.result, gitUrl);
|
|
2391
|
+
} else if (data.error) {
|
|
2392
|
+
statusEl.innerHTML = `<span style="color: #ff4444;">Error: ${data.error}</span>`;
|
|
2393
|
+
showToast('Git scan failed', true);
|
|
2394
|
+
} else {
|
|
2395
|
+
statusEl.innerHTML = '<span style="color: #ff4444;">Scan failed</span>';
|
|
2396
|
+
}
|
|
2397
|
+
} catch (err) {
|
|
2398
|
+
console.error('Git scan error:', err);
|
|
2399
|
+
statusEl.innerHTML = `<span style="color: #ff4444;">Error: ${err.message}</span>`;
|
|
2400
|
+
showToast('Git scan error', true);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
showLoading(false);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// Perform local scan
|
|
2407
|
+
async function performScan(targetPath) {
|
|
2408
|
+
const statusEl = document.getElementById('scanStatus');
|
|
2409
|
+
statusEl.innerHTML = '<span style="color: #00aaff;">Scanning...</span>';
|
|
2410
|
+
showLoading(true);
|
|
2411
|
+
|
|
2412
|
+
// Clear map first
|
|
2413
|
+
try {
|
|
2414
|
+
clearMap();
|
|
2415
|
+
} catch (e) {
|
|
2416
|
+
console.warn('clearMap error:', e);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
try {
|
|
2420
|
+
console.log('Starting scan of:', targetPath);
|
|
2421
|
+
|
|
2422
|
+
const res = await fetch(`${AURA_URL}/tools`, {
|
|
2423
|
+
method: 'POST',
|
|
2424
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2425
|
+
body: JSON.stringify({
|
|
2426
|
+
tool: 'scan-local',
|
|
2427
|
+
arguments: { targetPath: targetPath }
|
|
2428
|
+
})
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
const data = await res.json();
|
|
2432
|
+
console.log('Scan response:', data);
|
|
2433
|
+
|
|
2434
|
+
if (data.result && data.result.scan_details) {
|
|
2435
|
+
await handleScanResult(data.result, targetPath);
|
|
2436
|
+
} else if (data.error) {
|
|
2437
|
+
statusEl.innerHTML = `<span style="color: #ff4444;">Error: ${data.error}</span>`;
|
|
2438
|
+
showToast('Scan failed', true);
|
|
2439
|
+
} else {
|
|
2440
|
+
statusEl.innerHTML = '<span style="color: #ff4444;">No scan results</span>';
|
|
2441
|
+
}
|
|
2442
|
+
} catch (err) {
|
|
2443
|
+
console.error('Scan error:', err);
|
|
2444
|
+
statusEl.innerHTML = `<span style="color: #ff4444;">Error: ${err.message}</span>`;
|
|
2445
|
+
showToast('Scan error', true);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
showLoading(false);
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
// Handle scan result
|
|
2452
|
+
async function handleScanResult(result, sourcePath) {
|
|
2453
|
+
const details = result.scan_details;
|
|
2454
|
+
currentScanDetails = details;
|
|
2455
|
+
|
|
2456
|
+
const projectName = getProjectName(sourcePath);
|
|
2457
|
+
|
|
2458
|
+
// Store project
|
|
2459
|
+
scannedProjects[projectName] = {
|
|
2460
|
+
path: details.path,
|
|
2461
|
+
scanResult: result,
|
|
2462
|
+
details: details
|
|
2463
|
+
};
|
|
2464
|
+
activeProject = projectName;
|
|
2465
|
+
|
|
2466
|
+
// Update UI
|
|
2467
|
+
updateProjectTabs();
|
|
2468
|
+
updateAuditorState(result.agent_state || 'idle');
|
|
2469
|
+
updateFindings(result.events || []);
|
|
2470
|
+
showScanSummary(details);
|
|
2471
|
+
|
|
2472
|
+
// Build the map from discovered items
|
|
2473
|
+
try {
|
|
2474
|
+
updateModulesFromScan(details);
|
|
2475
|
+
} catch (e) {
|
|
2476
|
+
console.warn('updateModulesFromScan error:', e);
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
showToast(`Scanned: ${projectName}`);
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
// Switch between scanned projects
|
|
2483
|
+
function switchProject(projectName) {
|
|
2484
|
+
if (!scannedProjects[projectName]) return;
|
|
2485
|
+
|
|
2486
|
+
activeProject = projectName;
|
|
2487
|
+
const project = scannedProjects[projectName];
|
|
2488
|
+
|
|
2489
|
+
try { clearMap(); } catch(e) { console.warn(e); }
|
|
2490
|
+
|
|
2491
|
+
currentScanDetails = project.details;
|
|
2492
|
+
updateAuditorState(project.scanResult.agent_state || 'idle');
|
|
2493
|
+
updateFindings(project.scanResult.events || []);
|
|
2494
|
+
showScanSummary(project.details);
|
|
2495
|
+
|
|
2496
|
+
try { updateModulesFromScan(project.details); } catch(e) { console.warn(e); }
|
|
2497
|
+
|
|
2498
|
+
updateProjectTabs();
|
|
2499
|
+
showToast(`Switched to: ${projectName}`);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
// Update project tabs UI
|
|
2503
|
+
function updateProjectTabs() {
|
|
2504
|
+
const projectNames = Object.keys(scannedProjects);
|
|
2505
|
+
const tabsContainer = document.getElementById('projectTabs');
|
|
2506
|
+
const tabList = document.getElementById('projectTabList');
|
|
2507
|
+
|
|
2508
|
+
if (projectNames.length === 0) {
|
|
2509
|
+
tabsContainer.style.display = 'none';
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
tabsContainer.style.display = 'block';
|
|
2514
|
+
tabList.innerHTML = projectNames.map(name => `
|
|
2515
|
+
<button onclick="switchProject('${name}')" style="
|
|
2516
|
+
padding: 4px 8px; font-size: 10px; cursor: pointer; font-family: inherit;
|
|
2517
|
+
background: ${name === activeProject ? '#00ff88' : 'rgba(0,20,10,0.8)'};
|
|
2518
|
+
color: ${name === activeProject ? '#000' : '#00ff88'};
|
|
2519
|
+
border: 1px solid #00ff88;
|
|
2520
|
+
">${name}</button>
|
|
2521
|
+
`).join('');
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// Show scan summary
|
|
2525
|
+
function showScanSummary(details) {
|
|
2526
|
+
const servicesCount = details.discovered_services?.length || 0;
|
|
2527
|
+
const modulesCount = details.discovered_modules?.length || 0;
|
|
2528
|
+
const sastCount = details.sast_findings || 0;
|
|
2529
|
+
const toolsUsed = details.tools_used || [];
|
|
2530
|
+
|
|
2531
|
+
document.getElementById('scanStatus').innerHTML = `
|
|
2532
|
+
<div style="color: #00ff88; font-weight: bold;">Scan Complete</div>
|
|
2533
|
+
<div style="margin-top: 5px; font-size: 10px; color: #666; word-break: break-all;">${details.path}</div>
|
|
2534
|
+
<div style="margin-top: 8px; display: grid; grid-template-columns: 1fr 1fr; gap: 5px;">
|
|
2535
|
+
<div>Secrets: <span style="color: ${details.secrets_found > 0 ? '#ff4444' : '#00ff88'}">${details.secrets_found || 0}</span></div>
|
|
2536
|
+
<div>Modules: <span style="color: #88ff00;">${modulesCount}</span></div>
|
|
2537
|
+
<div>Services: <span style="color: #00aaff;">${servicesCount}</span></div>
|
|
2538
|
+
<div>Packages: ${details.packages_scanned || 0}</div>
|
|
2539
|
+
<div>SAST: <span style="color: ${sastCount > 0 ? '#ffaa00' : '#00ff88'}">${sastCount}</span></div>
|
|
2540
|
+
<div>Vulns: <span style="color: ${details.package_vulns > 0 ? '#ff4444' : '#00ff88'}">${details.package_vulns || 0}</span></div>
|
|
2541
|
+
</div>
|
|
2542
|
+
${toolsUsed.length > 0 ? `<div style="margin-top: 8px; font-size: 10px; color: #00aaff;">
|
|
2543
|
+
Tools: ${toolsUsed.join(', ')}
|
|
2544
|
+
</div>` : ''}
|
|
2545
|
+
${modulesCount > 0 ? `<div style="margin-top: 5px; font-size: 10px; color: #666;">
|
|
2546
|
+
${details.discovered_modules.slice(0,4).map(m => m.name).join(', ')}${modulesCount > 4 ? '...' : ''}
|
|
2547
|
+
</div>` : ''}
|
|
2548
|
+
`;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// Legacy compatibility
|
|
2552
|
+
async function runLocalScan() { await doScan(); }
|
|
2553
|
+
async function scanProject() { await doScan(); }
|
|
2554
|
+
async function addProject() { await doScan(); }
|
|
2555
|
+
|
|
2556
|
+
// Service type to color mapping
|
|
2557
|
+
const SERVICE_COLORS = {
|
|
2558
|
+
database: 0x00aaff,
|
|
2559
|
+
cache: 0xff6600,
|
|
2560
|
+
cloud: 0xff00ff,
|
|
2561
|
+
api: 0xffaa00,
|
|
2562
|
+
messaging: 0x00ffaa,
|
|
2563
|
+
storage: 0x00ff88,
|
|
2564
|
+
auth: 0x00ff00,
|
|
2565
|
+
monitoring: 0x888888
|
|
2566
|
+
};
|
|
2567
|
+
|
|
2568
|
+
function updateModulesFromScan(scanDetails) {
|
|
2569
|
+
console.log('[VISUALIZER] updateModulesFromScan called', scanDetails);
|
|
2570
|
+
|
|
2571
|
+
// Reset all modules to secure first
|
|
2572
|
+
MODULES.forEach(mod => updateModuleStatus(mod.id, 'secure', 'low'));
|
|
2573
|
+
|
|
2574
|
+
if (!scanDetails.raw_findings) {
|
|
2575
|
+
console.log('[VISUALIZER] No raw_findings in scan details');
|
|
2576
|
+
// Still update the status panel
|
|
2577
|
+
refreshModuleStatusPanel();
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
const findings = scanDetails.raw_findings;
|
|
2582
|
+
console.log('[VISUALIZER] Processing findings:', findings.secrets?.length, 'secrets');
|
|
2583
|
+
|
|
2584
|
+
// Update SECRETS module if env files or secrets found
|
|
2585
|
+
if (findings.secrets?.length > 0 || findings.envFiles?.some(e => e.hasSecrets)) {
|
|
2586
|
+
const hasCritical = findings.secrets?.some(s => s.severity === 'critical');
|
|
2587
|
+
console.log('[VISUALIZER] Setting SECRETS to:', hasCritical ? 'CRITICAL' : 'WARNING');
|
|
2588
|
+
updateModuleStatus('secrets', hasCritical ? 'critical' : 'warning', hasCritical ? 'critical' : 'high');
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
// Check what types of secrets were found and update relevant modules
|
|
2592
|
+
findings.secrets?.forEach(secret => {
|
|
2593
|
+
const type = (secret.type || '').toLowerCase();
|
|
2594
|
+
if (type.includes('aws') || type.includes('connection')) {
|
|
2595
|
+
updateModuleStatus('infra', 'critical', 'critical');
|
|
2596
|
+
}
|
|
2597
|
+
if (type.includes('password') || type.includes('token')) {
|
|
2598
|
+
updateModuleStatus('auth', 'warning', 'high');
|
|
2599
|
+
}
|
|
2600
|
+
if (type.includes('database') || type.includes('postgres') || type.includes('mysql')) {
|
|
2601
|
+
updateModuleStatus('database', 'critical', 'critical');
|
|
2602
|
+
}
|
|
2603
|
+
});
|
|
2604
|
+
|
|
2605
|
+
// Update from package vulnerabilities
|
|
2606
|
+
findings.packages?.forEach(pkg => {
|
|
2607
|
+
if (pkg.severity === 'critical' || pkg.severity === 'high') {
|
|
2608
|
+
updateModuleStatus('api', 'warning', pkg.severity);
|
|
2609
|
+
}
|
|
2610
|
+
});
|
|
2611
|
+
|
|
2612
|
+
// Update from SAST findings (semgrep etc.)
|
|
2613
|
+
findings.sastFindings?.forEach(finding => {
|
|
2614
|
+
const rule = finding.rule?.toLowerCase() || '';
|
|
2615
|
+
const message = finding.message?.toLowerCase() || '';
|
|
2616
|
+
const sev = finding.severity?.toLowerCase() || 'warning';
|
|
2617
|
+
const severity = sev === 'error' ? 'critical' : sev === 'warning' ? 'high' : 'medium';
|
|
2618
|
+
|
|
2619
|
+
// Map SAST findings to modules based on rule type
|
|
2620
|
+
if (rule.includes('sql') || rule.includes('injection') || message.includes('sql')) {
|
|
2621
|
+
updateModuleStatus('database', 'critical', 'critical');
|
|
2622
|
+
}
|
|
2623
|
+
if (rule.includes('xss') || rule.includes('csrf') || message.includes('xss')) {
|
|
2624
|
+
updateModuleStatus('api', 'warning', severity);
|
|
2625
|
+
}
|
|
2626
|
+
if (rule.includes('auth') || rule.includes('password') || rule.includes('jwt')) {
|
|
2627
|
+
updateModuleStatus('auth', 'warning', severity);
|
|
2628
|
+
}
|
|
2629
|
+
if (rule.includes('secrets') || rule.includes('hardcoded') || rule.includes('credential')) {
|
|
2630
|
+
updateModuleStatus('secrets', 'critical', 'critical');
|
|
2631
|
+
}
|
|
2632
|
+
if (rule.includes('command') || rule.includes('exec') || rule.includes('shell')) {
|
|
2633
|
+
updateModuleStatus('infra', 'critical', 'critical');
|
|
2634
|
+
}
|
|
2635
|
+
});
|
|
2636
|
+
|
|
2637
|
+
// Refresh the status panel to show current module states
|
|
2638
|
+
refreshModuleStatusPanel();
|
|
2639
|
+
|
|
2640
|
+
// DYNAMIC MAP BUILDING: Add discovered services as new nodes
|
|
2641
|
+
if (scanDetails.discovered_services && scanDetails.discovered_services.length > 0) {
|
|
2642
|
+
addDiscoveredServicesToMap(scanDetails.discovered_services);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
// DYNAMIC MAP BUILDING: Add discovered code modules/directories
|
|
2646
|
+
if (scanDetails.discovered_modules && scanDetails.discovered_modules.length > 0) {
|
|
2647
|
+
addDiscoveredModulesToMap(scanDetails.discovered_modules);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
function addDiscoveredServicesToMap(services) {
|
|
2652
|
+
let addedCount = 0;
|
|
2653
|
+
|
|
2654
|
+
services.forEach((service, index) => {
|
|
2655
|
+
// Skip if module already exists
|
|
2656
|
+
if (moduleNodes[service.id] || MODULES.find(m => m.id === service.id)) {
|
|
2657
|
+
// Just update its status based on severity
|
|
2658
|
+
if (moduleNodes[service.id]) {
|
|
2659
|
+
const status = service.severity === 'critical' ? 'critical' :
|
|
2660
|
+
service.severity === 'high' ? 'warning' : 'secure';
|
|
2661
|
+
updateModuleStatus(service.id, status, service.severity);
|
|
2662
|
+
}
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// Calculate position for new module (spiral outward)
|
|
2667
|
+
const baseModuleCount = MODULES.length;
|
|
2668
|
+
const angle = (baseModuleCount + addedCount) * (Math.PI / 5);
|
|
2669
|
+
const radius = 22 + Math.floor(addedCount / 8) * 5;
|
|
2670
|
+
const pos = [
|
|
2671
|
+
Math.cos(angle) * radius,
|
|
2672
|
+
0,
|
|
2673
|
+
Math.sin(angle) * radius
|
|
2674
|
+
];
|
|
2675
|
+
|
|
2676
|
+
// Get color based on service type
|
|
2677
|
+
const color = SERVICE_COLORS[service.type] || 0x00ff88;
|
|
2678
|
+
|
|
2679
|
+
// Create new module definition
|
|
2680
|
+
const newMod = {
|
|
2681
|
+
id: service.id,
|
|
2682
|
+
name: service.name.toUpperCase(),
|
|
2683
|
+
desc: `${service.type.toUpperCase()} - Found in ${service.source}`,
|
|
2684
|
+
pos,
|
|
2685
|
+
color
|
|
2686
|
+
};
|
|
2687
|
+
|
|
2688
|
+
MODULES.push(newMod);
|
|
2689
|
+
moduleEnabled[service.id] = true;
|
|
2690
|
+
|
|
2691
|
+
// Create 3D node
|
|
2692
|
+
createSingleModuleNode(newMod);
|
|
2693
|
+
|
|
2694
|
+
// Add audit connection from central auditor
|
|
2695
|
+
const auditLine = createConnectionLine(
|
|
2696
|
+
new THREE.Vector3(0, 5, 0),
|
|
2697
|
+
new THREE.Vector3(pos[0], pos[1] + 2, pos[2]),
|
|
2698
|
+
0x00aaff,
|
|
2699
|
+
true
|
|
2700
|
+
);
|
|
2701
|
+
auditLine.userData = { type: 'audit-connection', to: service.id };
|
|
2702
|
+
connectionLines.push(auditLine);
|
|
2703
|
+
scene.add(auditLine);
|
|
2704
|
+
|
|
2705
|
+
// Connect to related existing modules based on type
|
|
2706
|
+
connectServiceToRelatedModules(service, pos);
|
|
2707
|
+
|
|
2708
|
+
// Set initial status based on severity
|
|
2709
|
+
const status = service.severity === 'critical' ? 'critical' :
|
|
2710
|
+
service.severity === 'high' ? 'warning' : 'secure';
|
|
2711
|
+
updateModuleStatus(service.id, status, service.severity);
|
|
2712
|
+
|
|
2713
|
+
addedCount++;
|
|
2714
|
+
showToast(`Discovered: ${service.name} (${service.type})`);
|
|
2715
|
+
});
|
|
2716
|
+
|
|
2717
|
+
if (addedCount > 0) {
|
|
2718
|
+
updateModuleStatusPanel(currentFindings);
|
|
2719
|
+
console.log(`Added ${addedCount} discovered services to map`);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
function connectServiceToRelatedModules(service, pos) {
|
|
2724
|
+
// Connect databases to the DATABASE module
|
|
2725
|
+
if (service.type === 'database' && moduleNodes['database']) {
|
|
2726
|
+
const dbMod = MODULES.find(m => m.id === 'database');
|
|
2727
|
+
if (dbMod) {
|
|
2728
|
+
const line = createConnectionLine(
|
|
2729
|
+
new THREE.Vector3(pos[0], pos[1] + 2, pos[2]),
|
|
2730
|
+
new THREE.Vector3(dbMod.pos[0], dbMod.pos[1] + 2, dbMod.pos[2]),
|
|
2731
|
+
0x00aaff44,
|
|
2732
|
+
false
|
|
2733
|
+
);
|
|
2734
|
+
line.userData = { type: 'data-flow', from: service.id, to: 'database', label: 'Data' };
|
|
2735
|
+
connectionLines.push(line);
|
|
2736
|
+
scene.add(line);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// Connect auth services to AUTH module
|
|
2741
|
+
if (service.type === 'auth' && moduleNodes['auth']) {
|
|
2742
|
+
const authMod = MODULES.find(m => m.id === 'auth');
|
|
2743
|
+
if (authMod) {
|
|
2744
|
+
const line = createConnectionLine(
|
|
2745
|
+
new THREE.Vector3(pos[0], pos[1] + 2, pos[2]),
|
|
2746
|
+
new THREE.Vector3(authMod.pos[0], authMod.pos[1] + 2, authMod.pos[2]),
|
|
2747
|
+
0x00ff8844,
|
|
2748
|
+
false
|
|
2749
|
+
);
|
|
2750
|
+
line.userData = { type: 'data-flow', from: service.id, to: 'auth', label: 'Auth' };
|
|
2751
|
+
connectionLines.push(line);
|
|
2752
|
+
scene.add(line);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
// Connect cloud services to INFRA module
|
|
2757
|
+
if ((service.type === 'cloud' || service.type === 'storage') && moduleNodes['infra']) {
|
|
2758
|
+
const infraMod = MODULES.find(m => m.id === 'infra');
|
|
2759
|
+
if (infraMod) {
|
|
2760
|
+
const line = createConnectionLine(
|
|
2761
|
+
new THREE.Vector3(pos[0], pos[1] + 2, pos[2]),
|
|
2762
|
+
new THREE.Vector3(infraMod.pos[0], infraMod.pos[1] + 2, infraMod.pos[2]),
|
|
2763
|
+
0xff00ff44,
|
|
2764
|
+
false
|
|
2765
|
+
);
|
|
2766
|
+
line.userData = { type: 'data-flow', from: service.id, to: 'infra', label: 'Cloud' };
|
|
2767
|
+
connectionLines.push(line);
|
|
2768
|
+
scene.add(line);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
// Connect API services to API module
|
|
2773
|
+
if (service.type === 'api' && moduleNodes['api']) {
|
|
2774
|
+
const apiMod = MODULES.find(m => m.id === 'api');
|
|
2775
|
+
if (apiMod) {
|
|
2776
|
+
const line = createConnectionLine(
|
|
2777
|
+
new THREE.Vector3(pos[0], pos[1] + 2, pos[2]),
|
|
2778
|
+
new THREE.Vector3(apiMod.pos[0], apiMod.pos[1] + 2, apiMod.pos[2]),
|
|
2779
|
+
0xffaa0044,
|
|
2780
|
+
false
|
|
2781
|
+
);
|
|
2782
|
+
line.userData = { type: 'data-flow', from: service.id, to: 'api', label: 'API' };
|
|
2783
|
+
connectionLines.push(line);
|
|
2784
|
+
scene.add(line);
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
// Colors for discovered code module types
|
|
2790
|
+
const MODULE_TYPE_COLORS = {
|
|
2791
|
+
source: 0x00ff88, // Green - source code
|
|
2792
|
+
component: 0x00aaff, // Blue - UI components
|
|
2793
|
+
service: 0xff00ff, // Magenta - services
|
|
2794
|
+
api: 0xffaa00, // Orange - API
|
|
2795
|
+
lib: 0x88ff00, // Lime - libraries
|
|
2796
|
+
config: 0xffff00, // Yellow - config
|
|
2797
|
+
test: 0x00ffff, // Cyan - tests
|
|
2798
|
+
infra: 0xff00aa, // Pink - infrastructure
|
|
2799
|
+
docs: 0x8888ff // Light blue - docs
|
|
2800
|
+
};
|
|
2801
|
+
|
|
2802
|
+
function addDiscoveredModulesToMap(modules) {
|
|
2803
|
+
let addedCount = 0;
|
|
2804
|
+
|
|
2805
|
+
modules.forEach((mod, index) => {
|
|
2806
|
+
// Skip if module already exists
|
|
2807
|
+
if (moduleNodes[mod.id] || MODULES.find(m => m.id === mod.id)) {
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
// Calculate position for new module (different ring from services)
|
|
2812
|
+
const baseCount = MODULES.length + addedCount;
|
|
2813
|
+
const angle = baseCount * (Math.PI / 6) + Math.PI / 12; // Offset to not overlap with services
|
|
2814
|
+
const radius = 18 + Math.floor(addedCount / 10) * 6;
|
|
2815
|
+
const pos = [
|
|
2816
|
+
Math.cos(angle) * radius,
|
|
2817
|
+
-2, // Slightly lower than services
|
|
2818
|
+
Math.sin(angle) * radius
|
|
2819
|
+
];
|
|
2820
|
+
|
|
2821
|
+
// Get color based on module type
|
|
2822
|
+
const color = MODULE_TYPE_COLORS[mod.type] || 0x00ff88;
|
|
2823
|
+
|
|
2824
|
+
// Create new module definition
|
|
2825
|
+
const newMod = {
|
|
2826
|
+
id: mod.id,
|
|
2827
|
+
name: mod.name.toUpperCase(),
|
|
2828
|
+
desc: `${mod.type.toUpperCase()} - ${mod.fileCount} files`,
|
|
2829
|
+
pos,
|
|
2830
|
+
color
|
|
2831
|
+
};
|
|
2832
|
+
|
|
2833
|
+
MODULES.push(newMod);
|
|
2834
|
+
moduleEnabled[mod.id] = true;
|
|
2835
|
+
|
|
2836
|
+
// Create 3D node
|
|
2837
|
+
createSingleModuleNode(newMod);
|
|
2838
|
+
|
|
2839
|
+
// Add audit connection from central auditor
|
|
2840
|
+
const auditLine = createConnectionLine(
|
|
2841
|
+
new THREE.Vector3(0, 5, 0),
|
|
2842
|
+
new THREE.Vector3(pos[0], pos[1] + 2, pos[2]),
|
|
2843
|
+
0x00aaff,
|
|
2844
|
+
true
|
|
2845
|
+
);
|
|
2846
|
+
auditLine.userData = { type: 'audit-connection', to: mod.id };
|
|
2847
|
+
connectionLines.push(auditLine);
|
|
2848
|
+
scene.add(auditLine);
|
|
2849
|
+
|
|
2850
|
+
// Connect modules based on their imports
|
|
2851
|
+
connectModuleByImports(mod, pos);
|
|
2852
|
+
|
|
2853
|
+
// Set status as secure by default (code modules aren't inherently insecure)
|
|
2854
|
+
updateModuleStatus(mod.id, 'secure', 'info');
|
|
2855
|
+
|
|
2856
|
+
addedCount++;
|
|
2857
|
+
showToast(`Mapped: ${mod.name} (${mod.type})`);
|
|
2858
|
+
});
|
|
2859
|
+
|
|
2860
|
+
if (addedCount > 0) {
|
|
2861
|
+
updateModuleStatusPanel(currentFindings);
|
|
2862
|
+
console.log(`Added ${addedCount} discovered code modules to map`);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
function connectModuleByImports(mod, pos) {
|
|
2867
|
+
// Connect to modules this one imports from
|
|
2868
|
+
if (mod.imports && mod.imports.length > 0) {
|
|
2869
|
+
mod.imports.forEach(importName => {
|
|
2870
|
+
const targetMod = MODULES.find(m =>
|
|
2871
|
+
m.id === importName.toLowerCase() ||
|
|
2872
|
+
m.name.toLowerCase() === importName.toLowerCase()
|
|
2873
|
+
);
|
|
2874
|
+
if (targetMod && moduleNodes[targetMod.id]) {
|
|
2875
|
+
const line = createConnectionLine(
|
|
2876
|
+
new THREE.Vector3(pos[0], pos[1] + 2, pos[2]),
|
|
2877
|
+
new THREE.Vector3(targetMod.pos[0], targetMod.pos[1] + 2, targetMod.pos[2]),
|
|
2878
|
+
0x00ff8844,
|
|
2879
|
+
false
|
|
2880
|
+
);
|
|
2881
|
+
line.userData = { type: 'import', from: mod.id, to: targetMod.id, label: 'imports' };
|
|
2882
|
+
connectionLines.push(line);
|
|
2883
|
+
scene.add(line);
|
|
2884
|
+
}
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// ========== INITIALIZE ==========
|
|
2890
|
+
// Expose functions to window for button onclick handlers
|
|
2891
|
+
window.runPreset = runPreset;
|
|
2892
|
+
window.runLocalScan = runLocalScan;
|
|
2893
|
+
window.doScan = doScan;
|
|
2894
|
+
window.scanProject = scanProject;
|
|
2895
|
+
window.addProject = addProject;
|
|
2896
|
+
window.switchProject = switchProject;
|
|
2897
|
+
window.setScanMode = setScanMode;
|
|
2898
|
+
window.scanGitRepo = scanGitRepo;
|
|
2899
|
+
window.openModuleManager = openModuleManager;
|
|
2900
|
+
window.addNewModule = addNewModule;
|
|
2901
|
+
window.toggleModule = toggleModule;
|
|
2902
|
+
window.removeSelectedModule = removeSelectedModule;
|
|
2903
|
+
window.saveModuleChanges = saveModuleChanges;
|
|
2904
|
+
window.closeModal = closeModal;
|
|
2905
|
+
window.refreshAuditHistory = refreshAuditHistory;
|
|
2906
|
+
window.selectAudit = selectAudit;
|
|
2907
|
+
window.clearSelectedAudit = clearSelectedAudit;
|
|
2908
|
+
window.openSettingsModal = openSettingsModal;
|
|
2909
|
+
window.closeSettingsModal = closeSettingsModal;
|
|
2910
|
+
window.switchSettingsTab = switchSettingsTab;
|
|
2911
|
+
window.markSettingsDirty = markSettingsDirty;
|
|
2912
|
+
window.saveSettings = saveSettings;
|
|
2913
|
+
window.testNotification = testNotification;
|
|
2914
|
+
window.runAWSScan = runAWSScan;
|
|
2915
|
+
|
|
2916
|
+
initScene();
|
|
2917
|
+
animate();
|
|
2918
|
+
pollAura();
|
|
2919
|
+
refreshAuditHistory();
|
|
2920
|
+
connectWebSocket(); // Connect to WebSocket for real-time updates
|
|
2921
|
+
setInterval(pollAura, POLL_INTERVAL);
|
|
2922
|
+
setInterval(refreshAuditHistory, POLL_INTERVAL * 2); // Refresh history less frequently
|
|
2923
|
+
|
|
2924
|
+
// Add click handler for module selection
|
|
2925
|
+
window.addEventListener('click', onMouseClick);
|
|
2926
|
+
|
|
2927
|
+
console.log('Aura Control Plane initialized');
|
|
2928
|
+
console.log('WebSocket: ws://127.0.0.1:3001 (real-time updates)');
|
|
2929
|
+
console.log('Enter a directory path and click "Scan Directory" to map your project.');
|
|
2930
|
+
console.log('Use "+ Add" to scan multiple projects and switch between them.');
|
|
2931
|
+
</script>
|
|
2932
|
+
</body>
|
|
2933
|
+
</html>
|