toolnet-memory 0.3.17 → 0.3.18
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/README.md +8 -16
- package/bundle/public/index.html +1087 -2353
- package/package.json +1 -1
- package/release-manifest.json +6 -39
package/bundle/public/index.html
CHANGED
|
@@ -1,1549 +1,952 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<meta
|
|
7
|
-
name="viewport"
|
|
8
|
-
content="width=device-width, initial-scale=1, viewport-fit=cover"
|
|
9
|
-
/>
|
|
10
|
-
|
|
11
|
-
<title>
|
|
12
|
-
ToolNet Memory · Graph
|
|
13
|
-
</title>
|
|
14
|
-
|
|
15
|
-
<style>
|
|
16
|
-
* {
|
|
17
|
-
box-sizing: border-box;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
html,
|
|
21
|
-
body {
|
|
22
|
-
width: 100%;
|
|
23
|
-
height: 100%;
|
|
24
|
-
margin: 0;
|
|
25
|
-
overflow: hidden;
|
|
26
|
-
|
|
27
|
-
background:
|
|
28
|
-
#050608;
|
|
29
|
-
|
|
30
|
-
color:
|
|
31
|
-
#f6f7f9;
|
|
32
|
-
|
|
33
|
-
font-family:
|
|
34
|
-
Inter,
|
|
35
|
-
ui-sans-serif,
|
|
36
|
-
system-ui,
|
|
37
|
-
-apple-system,
|
|
38
|
-
BlinkMacSystemFont,
|
|
39
|
-
"Segoe UI",
|
|
40
|
-
sans-serif;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
#graph {
|
|
44
|
-
position: fixed;
|
|
45
|
-
inset: 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.panel {
|
|
49
|
-
position: fixed;
|
|
50
|
-
z-index: 20;
|
|
51
|
-
|
|
52
|
-
border:
|
|
53
|
-
1px solid
|
|
54
|
-
rgba(255,255,255,.12);
|
|
55
|
-
|
|
56
|
-
background:
|
|
57
|
-
rgba(10,12,17,.88);
|
|
58
|
-
|
|
59
|
-
backdrop-filter:
|
|
60
|
-
blur(20px);
|
|
61
|
-
|
|
62
|
-
-webkit-backdrop-filter:
|
|
63
|
-
blur(20px);
|
|
64
|
-
|
|
65
|
-
box-shadow:
|
|
66
|
-
0 22px 60px
|
|
67
|
-
rgba(0,0,0,.42);
|
|
68
|
-
|
|
69
|
-
border-radius:
|
|
70
|
-
18px;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
#toolbar {
|
|
74
|
-
top:
|
|
75
|
-
max(
|
|
76
|
-
14px,
|
|
77
|
-
env(safe-area-inset-top)
|
|
78
|
-
);
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
79
5
|
|
|
80
|
-
|
|
81
|
-
14px;
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
82
7
|
|
|
83
|
-
|
|
84
|
-
14px;
|
|
8
|
+
<title>ToolNet Memory · Graph</title>
|
|
85
9
|
|
|
86
|
-
|
|
87
|
-
|
|
10
|
+
<style>
|
|
11
|
+
* {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
88
14
|
|
|
89
|
-
|
|
90
|
-
|
|
15
|
+
html,
|
|
16
|
+
body {
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: 100%;
|
|
19
|
+
margin: 0;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
|
|
22
|
+
background: #050608;
|
|
23
|
+
|
|
24
|
+
color: #f6f7f9;
|
|
25
|
+
|
|
26
|
+
font-family:
|
|
27
|
+
Inter,
|
|
28
|
+
ui-sans-serif,
|
|
29
|
+
system-ui,
|
|
30
|
+
-apple-system,
|
|
31
|
+
BlinkMacSystemFont,
|
|
32
|
+
'Segoe UI',
|
|
33
|
+
sans-serif;
|
|
34
|
+
}
|
|
91
35
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
minmax(150px, .7fr)
|
|
97
|
-
auto;
|
|
36
|
+
#graph {
|
|
37
|
+
position: fixed;
|
|
38
|
+
inset: 0;
|
|
39
|
+
}
|
|
98
40
|
|
|
99
|
-
|
|
100
|
-
|
|
41
|
+
.panel {
|
|
42
|
+
position: fixed;
|
|
43
|
+
z-index: 20;
|
|
101
44
|
|
|
102
|
-
|
|
103
|
-
center;
|
|
104
|
-
}
|
|
45
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
105
46
|
|
|
106
|
-
|
|
107
|
-
padding:
|
|
108
|
-
0 8px;
|
|
47
|
+
background: rgba(10, 12, 17, 0.88);
|
|
109
48
|
|
|
110
|
-
|
|
111
|
-
nowrap;
|
|
49
|
+
backdrop-filter: blur(20px);
|
|
112
50
|
|
|
113
|
-
|
|
114
|
-
750;
|
|
51
|
+
-webkit-backdrop-filter: blur(20px);
|
|
115
52
|
|
|
116
|
-
|
|
117
|
-
18px;
|
|
118
|
-
}
|
|
53
|
+
box-shadow: 0 22px 60px rgba(0, 0, 0, 0.42);
|
|
119
54
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.45;
|
|
55
|
+
border-radius: 18px;
|
|
56
|
+
}
|
|
123
57
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
58
|
+
#toolbar {
|
|
59
|
+
top: max(14px, env(safe-area-inset-top));
|
|
127
60
|
|
|
128
|
-
|
|
129
|
-
select,
|
|
130
|
-
button {
|
|
131
|
-
height:
|
|
132
|
-
42px;
|
|
61
|
+
left: 14px;
|
|
133
62
|
|
|
134
|
-
|
|
135
|
-
0;
|
|
63
|
+
right: 14px;
|
|
136
64
|
|
|
137
|
-
|
|
138
|
-
1px solid
|
|
139
|
-
rgba(255,255,255,.12);
|
|
65
|
+
padding: 14px;
|
|
140
66
|
|
|
141
|
-
|
|
142
|
-
11px;
|
|
67
|
+
display: grid;
|
|
143
68
|
|
|
144
|
-
|
|
145
|
-
|
|
69
|
+
grid-template-columns:
|
|
70
|
+
auto minmax(170px, 1fr)
|
|
71
|
+
minmax(170px, 1fr)
|
|
72
|
+
minmax(150px, 0.7fr)
|
|
73
|
+
minmax(150px, 0.7fr)
|
|
74
|
+
auto;
|
|
146
75
|
|
|
147
|
-
|
|
148
|
-
white;
|
|
76
|
+
gap: 10px;
|
|
149
77
|
|
|
150
|
-
|
|
151
|
-
|
|
78
|
+
align-items: center;
|
|
79
|
+
}
|
|
152
80
|
|
|
153
|
-
|
|
154
|
-
|
|
81
|
+
.brand {
|
|
82
|
+
padding: 0 8px;
|
|
155
83
|
|
|
156
|
-
|
|
157
|
-
inherit;
|
|
158
|
-
}
|
|
84
|
+
white-space: nowrap;
|
|
159
85
|
|
|
160
|
-
|
|
161
|
-
cursor:
|
|
162
|
-
pointer;
|
|
163
|
-
}
|
|
86
|
+
font-weight: 750;
|
|
164
87
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
650;
|
|
168
|
-
}
|
|
88
|
+
font-size: 18px;
|
|
89
|
+
}
|
|
169
90
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
14px;
|
|
91
|
+
.brand span {
|
|
92
|
+
opacity: 0.45;
|
|
173
93
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
14px,
|
|
177
|
-
env(safe-area-inset-bottom)
|
|
178
|
-
);
|
|
94
|
+
font-weight: 500;
|
|
95
|
+
}
|
|
179
96
|
|
|
180
|
-
|
|
181
|
-
|
|
97
|
+
input,
|
|
98
|
+
select,
|
|
99
|
+
button {
|
|
100
|
+
height: 42px;
|
|
182
101
|
|
|
183
|
-
|
|
184
|
-
14px 16px;
|
|
102
|
+
min-width: 0;
|
|
185
103
|
|
|
186
|
-
|
|
187
|
-
13px;
|
|
104
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
188
105
|
|
|
189
|
-
|
|
190
|
-
1.7;
|
|
191
|
-
}
|
|
106
|
+
border-radius: 11px;
|
|
192
107
|
|
|
193
|
-
|
|
194
|
-
margin-top:
|
|
195
|
-
5px;
|
|
108
|
+
background: rgba(255, 255, 255, 0.06);
|
|
196
109
|
|
|
197
|
-
|
|
198
|
-
.55;
|
|
110
|
+
color: white;
|
|
199
111
|
|
|
200
|
-
|
|
201
|
-
11px;
|
|
202
|
-
}
|
|
112
|
+
padding: 0 12px;
|
|
203
113
|
|
|
204
|
-
|
|
205
|
-
top:
|
|
206
|
-
96px;
|
|
114
|
+
outline: none;
|
|
207
115
|
|
|
208
|
-
|
|
209
|
-
|
|
116
|
+
font: inherit;
|
|
117
|
+
}
|
|
210
118
|
|
|
211
|
-
|
|
212
|
-
|
|
119
|
+
button {
|
|
120
|
+
cursor: pointer;
|
|
121
|
+
}
|
|
213
122
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
calc(100vw - 28px)
|
|
218
|
-
);
|
|
123
|
+
#project {
|
|
124
|
+
font-weight: 650;
|
|
125
|
+
}
|
|
219
126
|
|
|
220
|
-
|
|
221
|
-
|
|
127
|
+
#stats {
|
|
128
|
+
left: 14px;
|
|
222
129
|
|
|
223
|
-
|
|
224
|
-
auto;
|
|
130
|
+
bottom: max(14px, env(safe-area-inset-bottom));
|
|
225
131
|
|
|
226
|
-
|
|
227
|
-
translateX(
|
|
228
|
-
calc(100% + 30px)
|
|
229
|
-
);
|
|
132
|
+
min-width: 220px;
|
|
230
133
|
|
|
231
|
-
|
|
232
|
-
transform .2s ease;
|
|
233
|
-
}
|
|
134
|
+
padding: 14px 16px;
|
|
234
135
|
|
|
235
|
-
|
|
236
|
-
transform:
|
|
237
|
-
translateX(0);
|
|
238
|
-
}
|
|
136
|
+
font-size: 13px;
|
|
239
137
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
138
|
+
line-height: 1.7;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.status {
|
|
142
|
+
margin-top: 5px;
|
|
243
143
|
|
|
244
|
-
|
|
245
|
-
14px;
|
|
144
|
+
opacity: 0.55;
|
|
246
145
|
|
|
247
|
-
|
|
248
|
-
|
|
146
|
+
font-size: 11px;
|
|
147
|
+
}
|
|
249
148
|
|
|
250
|
-
|
|
251
|
-
|
|
149
|
+
#details {
|
|
150
|
+
top: 96px;
|
|
252
151
|
|
|
253
|
-
|
|
254
|
-
44px;
|
|
152
|
+
right: 14px;
|
|
255
153
|
|
|
256
|
-
|
|
257
|
-
grid;
|
|
154
|
+
bottom: 14px;
|
|
258
155
|
|
|
259
|
-
|
|
260
|
-
center;
|
|
156
|
+
width: min(410px, calc(100vw - 28px));
|
|
261
157
|
|
|
262
|
-
|
|
263
|
-
0;
|
|
158
|
+
padding: 68px 20px 20px;
|
|
264
159
|
|
|
265
|
-
|
|
266
|
-
50%;
|
|
160
|
+
overflow-y: auto;
|
|
267
161
|
|
|
268
|
-
|
|
269
|
-
28px;
|
|
162
|
+
transform: translateX(calc(100% + 30px));
|
|
270
163
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
164
|
+
transition: transform 0.2s ease;
|
|
165
|
+
}
|
|
274
166
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
167
|
+
#details.open {
|
|
168
|
+
transform: translateX(0);
|
|
169
|
+
}
|
|
278
170
|
|
|
279
|
-
|
|
280
|
-
|
|
171
|
+
.details-close {
|
|
172
|
+
position: absolute;
|
|
281
173
|
|
|
282
|
-
|
|
283
|
-
1.25;
|
|
174
|
+
top: 14px;
|
|
284
175
|
|
|
285
|
-
|
|
286
|
-
anywhere;
|
|
287
|
-
}
|
|
176
|
+
right: 14px;
|
|
288
177
|
|
|
289
|
-
|
|
290
|
-
margin-top:
|
|
291
|
-
8px;
|
|
178
|
+
width: 44px;
|
|
292
179
|
|
|
293
|
-
|
|
294
|
-
.55;
|
|
180
|
+
height: 44px;
|
|
295
181
|
|
|
296
|
-
|
|
297
|
-
ui-monospace,
|
|
298
|
-
SFMono-Regular,
|
|
299
|
-
Menlo,
|
|
300
|
-
monospace;
|
|
182
|
+
display: grid;
|
|
301
183
|
|
|
302
|
-
|
|
303
|
-
12px;
|
|
184
|
+
place-items: center;
|
|
304
185
|
|
|
305
|
-
|
|
306
|
-
anywhere;
|
|
307
|
-
}
|
|
186
|
+
padding: 0;
|
|
308
187
|
|
|
309
|
-
|
|
310
|
-
display:
|
|
311
|
-
inline-flex;
|
|
188
|
+
border-radius: 50%;
|
|
312
189
|
|
|
313
|
-
|
|
314
|
-
10px 5px 0 0;
|
|
190
|
+
font-size: 28px;
|
|
315
191
|
|
|
316
|
-
|
|
317
|
-
|
|
192
|
+
line-height: 1;
|
|
193
|
+
}
|
|
318
194
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
rgba(255,255,255,.12);
|
|
195
|
+
.detail-title {
|
|
196
|
+
font-size: 21px;
|
|
322
197
|
|
|
323
|
-
|
|
324
|
-
999px;
|
|
198
|
+
font-weight: 750;
|
|
325
199
|
|
|
326
|
-
|
|
327
|
-
rgba(255,255,255,.06);
|
|
200
|
+
line-height: 1.25;
|
|
328
201
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
202
|
+
overflow-wrap: anywhere;
|
|
203
|
+
}
|
|
332
204
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
24px;
|
|
336
|
-
}
|
|
205
|
+
.path {
|
|
206
|
+
margin-top: 8px;
|
|
337
207
|
|
|
338
|
-
|
|
339
|
-
margin-bottom:
|
|
340
|
-
8px;
|
|
208
|
+
opacity: 0.55;
|
|
341
209
|
|
|
342
|
-
|
|
343
|
-
.5;
|
|
210
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
344
211
|
|
|
345
|
-
|
|
346
|
-
11px;
|
|
212
|
+
font-size: 12px;
|
|
347
213
|
|
|
348
|
-
|
|
349
|
-
|
|
214
|
+
overflow-wrap: anywhere;
|
|
215
|
+
}
|
|
350
216
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
217
|
+
.tag {
|
|
218
|
+
display: inline-flex;
|
|
354
219
|
|
|
355
|
-
|
|
356
|
-
display:
|
|
357
|
-
flex;
|
|
220
|
+
margin: 10px 5px 0 0;
|
|
358
221
|
|
|
359
|
-
|
|
360
|
-
space-between;
|
|
222
|
+
padding: 5px 9px;
|
|
361
223
|
|
|
362
|
-
|
|
363
|
-
12px;
|
|
224
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
364
225
|
|
|
365
|
-
|
|
366
|
-
9px 0;
|
|
226
|
+
border-radius: 999px;
|
|
367
227
|
|
|
368
|
-
|
|
369
|
-
1px solid
|
|
370
|
-
rgba(255,255,255,.06);
|
|
228
|
+
background: rgba(255, 255, 255, 0.06);
|
|
371
229
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
}
|
|
375
232
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
233
|
+
.section {
|
|
234
|
+
margin-top: 24px;
|
|
235
|
+
}
|
|
379
236
|
|
|
380
|
-
|
|
381
|
-
|
|
237
|
+
.section-title {
|
|
238
|
+
margin-bottom: 8px;
|
|
382
239
|
|
|
383
|
-
|
|
384
|
-
100;
|
|
240
|
+
opacity: 0.5;
|
|
385
241
|
|
|
386
|
-
|
|
387
|
-
grid;
|
|
242
|
+
font-size: 11px;
|
|
388
243
|
|
|
389
|
-
|
|
390
|
-
center;
|
|
244
|
+
letter-spacing: 0.1em;
|
|
391
245
|
|
|
392
|
-
|
|
393
|
-
|
|
246
|
+
text-transform: uppercase;
|
|
247
|
+
}
|
|
394
248
|
|
|
395
|
-
|
|
396
|
-
|
|
249
|
+
.row {
|
|
250
|
+
display: flex;
|
|
397
251
|
|
|
398
|
-
|
|
399
|
-
center;
|
|
400
|
-
}
|
|
252
|
+
justify-content: space-between;
|
|
401
253
|
|
|
402
|
-
|
|
403
|
-
max-width: 1000px
|
|
404
|
-
) {
|
|
405
|
-
#toolbar {
|
|
406
|
-
grid-template-columns:
|
|
407
|
-
1fr 1fr;
|
|
254
|
+
gap: 12px;
|
|
408
255
|
|
|
409
|
-
|
|
410
|
-
max(
|
|
411
|
-
10px,
|
|
412
|
-
env(safe-area-inset-top)
|
|
413
|
-
);
|
|
256
|
+
padding: 9px 0;
|
|
414
257
|
|
|
415
|
-
|
|
416
|
-
10px;
|
|
258
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
417
259
|
|
|
418
|
-
|
|
419
|
-
10px;
|
|
260
|
+
font-size: 13px;
|
|
420
261
|
}
|
|
421
262
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
1 / -1;
|
|
263
|
+
#loading {
|
|
264
|
+
position: fixed;
|
|
425
265
|
|
|
426
|
-
|
|
427
|
-
17px;
|
|
428
|
-
}
|
|
266
|
+
inset: 0;
|
|
429
267
|
|
|
430
|
-
|
|
431
|
-
grid-column:
|
|
432
|
-
1 / -1;
|
|
433
|
-
}
|
|
268
|
+
z-index: 100;
|
|
434
269
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
270
|
+
display: grid;
|
|
271
|
+
|
|
272
|
+
place-items: center;
|
|
438
273
|
|
|
439
|
-
|
|
440
|
-
10px;
|
|
274
|
+
background: #050608;
|
|
441
275
|
|
|
442
|
-
|
|
443
|
-
|
|
276
|
+
padding: 24px;
|
|
277
|
+
|
|
278
|
+
text-align: center;
|
|
444
279
|
}
|
|
445
280
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
281
|
+
@media (max-width: 1000px) {
|
|
282
|
+
#toolbar {
|
|
283
|
+
grid-template-columns: 1fr 1fr;
|
|
449
284
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
285
|
+
top: max(10px, env(safe-area-inset-top));
|
|
286
|
+
|
|
287
|
+
left: 10px;
|
|
288
|
+
|
|
289
|
+
right: 10px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.brand {
|
|
293
|
+
grid-column: 1 / -1;
|
|
294
|
+
|
|
295
|
+
font-size: 17px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#search {
|
|
299
|
+
grid-column: 1 / -1;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#details {
|
|
303
|
+
top: 10px;
|
|
304
|
+
|
|
305
|
+
right: 10px;
|
|
306
|
+
|
|
307
|
+
bottom: 10px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#stats {
|
|
311
|
+
left: 10px;
|
|
312
|
+
|
|
313
|
+
bottom: max(10px, env(safe-area-inset-bottom));
|
|
314
|
+
}
|
|
455
315
|
}
|
|
456
|
-
}
|
|
457
316
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
317
|
+
@media (max-width: 1000px) {
|
|
318
|
+
#toolbar {
|
|
319
|
+
grid-template-columns: repeat(12, minmax(0, 1fr));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.brand {
|
|
323
|
+
grid-column: 1 / -1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
#project {
|
|
327
|
+
grid-column: 1 / 10;
|
|
328
|
+
|
|
329
|
+
width: 100%;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#reset {
|
|
333
|
+
grid-column: 10 / 13;
|
|
334
|
+
|
|
335
|
+
width: 100%;
|
|
336
|
+
|
|
337
|
+
padding-left: 8px;
|
|
338
|
+
|
|
339
|
+
padding-right: 8px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
#search {
|
|
343
|
+
grid-column: 1 / -1;
|
|
344
|
+
|
|
345
|
+
width: 100%;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
#cluster {
|
|
349
|
+
grid-column: 1 / 7;
|
|
350
|
+
|
|
351
|
+
width: 100%;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
#edge {
|
|
355
|
+
grid-column: 7 / 13;
|
|
356
|
+
|
|
357
|
+
width: 100%;
|
|
358
|
+
}
|
|
464
359
|
}
|
|
465
360
|
|
|
466
|
-
.
|
|
467
|
-
grid
|
|
468
|
-
|
|
361
|
+
.intel-summary {
|
|
362
|
+
display: grid;
|
|
363
|
+
|
|
364
|
+
grid-template-columns: 1fr 1fr;
|
|
365
|
+
|
|
366
|
+
gap: 8px;
|
|
367
|
+
|
|
368
|
+
margin-top: 18px;
|
|
469
369
|
}
|
|
470
370
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
1 / 10;
|
|
371
|
+
.intel-card {
|
|
372
|
+
padding: 12px;
|
|
474
373
|
|
|
475
|
-
|
|
476
|
-
|
|
374
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
375
|
+
|
|
376
|
+
border-radius: 12px;
|
|
377
|
+
|
|
378
|
+
background: rgba(255, 255, 255, 0.035);
|
|
477
379
|
}
|
|
478
380
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
10 / 13;
|
|
381
|
+
.intel-card strong {
|
|
382
|
+
display: block;
|
|
482
383
|
|
|
483
|
-
|
|
484
|
-
100%;
|
|
384
|
+
font-size: 18px;
|
|
485
385
|
|
|
486
|
-
|
|
487
|
-
|
|
386
|
+
margin-top: 3px;
|
|
387
|
+
}
|
|
488
388
|
|
|
489
|
-
|
|
490
|
-
|
|
389
|
+
.intel-card span {
|
|
390
|
+
opacity: 0.55;
|
|
391
|
+
|
|
392
|
+
font-size: 11px;
|
|
491
393
|
}
|
|
492
394
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
395
|
+
.relation-list {
|
|
396
|
+
display: flex;
|
|
397
|
+
|
|
398
|
+
flex-direction: column;
|
|
496
399
|
|
|
497
|
-
|
|
498
|
-
100%;
|
|
400
|
+
gap: 6px;
|
|
499
401
|
}
|
|
500
402
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
403
|
+
.relation-item {
|
|
404
|
+
width: 100%;
|
|
405
|
+
|
|
406
|
+
height: auto;
|
|
407
|
+
|
|
408
|
+
min-height: 44px;
|
|
409
|
+
|
|
410
|
+
padding: 9px 11px;
|
|
411
|
+
|
|
412
|
+
display: block;
|
|
413
|
+
|
|
414
|
+
text-align: left;
|
|
504
415
|
|
|
505
|
-
|
|
506
|
-
|
|
416
|
+
border-radius: 10px;
|
|
417
|
+
|
|
418
|
+
cursor: pointer;
|
|
507
419
|
}
|
|
508
420
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
421
|
+
.relation-name {
|
|
422
|
+
display: block;
|
|
423
|
+
|
|
424
|
+
font-size: 12px;
|
|
512
425
|
|
|
513
|
-
|
|
514
|
-
|
|
426
|
+
font-weight: 650;
|
|
427
|
+
|
|
428
|
+
overflow-wrap: anywhere;
|
|
515
429
|
}
|
|
516
|
-
}
|
|
517
430
|
|
|
431
|
+
.relation-meta {
|
|
432
|
+
display: block;
|
|
518
433
|
|
|
519
|
-
|
|
520
|
-
display:
|
|
521
|
-
grid;
|
|
434
|
+
margin-top: 4px;
|
|
522
435
|
|
|
523
|
-
|
|
524
|
-
1fr 1fr;
|
|
436
|
+
opacity: 0.45;
|
|
525
437
|
|
|
526
|
-
|
|
527
|
-
8px;
|
|
438
|
+
font-size: 10px;
|
|
528
439
|
|
|
529
|
-
|
|
530
|
-
18px;
|
|
531
|
-
}
|
|
440
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
532
441
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
12px;
|
|
442
|
+
overflow-wrap: anywhere;
|
|
443
|
+
}
|
|
536
444
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
rgba(255,255,255,.08);
|
|
445
|
+
.relation-more {
|
|
446
|
+
margin-top: 7px;
|
|
540
447
|
|
|
541
|
-
|
|
542
|
-
12px;
|
|
448
|
+
opacity: 0.45;
|
|
543
449
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
450
|
+
font-size: 11px;
|
|
451
|
+
}
|
|
547
452
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
block;
|
|
453
|
+
.risk {
|
|
454
|
+
display: inline-flex;
|
|
551
455
|
|
|
552
|
-
|
|
553
|
-
18px;
|
|
456
|
+
padding: 5px 9px;
|
|
554
457
|
|
|
555
|
-
|
|
556
|
-
3px;
|
|
557
|
-
}
|
|
458
|
+
margin-top: 8px;
|
|
558
459
|
|
|
559
|
-
|
|
560
|
-
opacity:
|
|
561
|
-
.55;
|
|
460
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
562
461
|
|
|
563
|
-
|
|
564
|
-
11px;
|
|
565
|
-
}
|
|
462
|
+
border-radius: 999px;
|
|
566
463
|
|
|
567
|
-
|
|
568
|
-
display:
|
|
569
|
-
flex;
|
|
464
|
+
font-size: 11px;
|
|
570
465
|
|
|
571
|
-
|
|
572
|
-
|
|
466
|
+
font-weight: 700;
|
|
467
|
+
}
|
|
573
468
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
469
|
+
.path-actions {
|
|
470
|
+
display: flex;
|
|
577
471
|
|
|
578
|
-
|
|
579
|
-
width:
|
|
580
|
-
100%;
|
|
472
|
+
gap: 8px;
|
|
581
473
|
|
|
582
|
-
|
|
583
|
-
|
|
474
|
+
margin-top: 10px;
|
|
475
|
+
}
|
|
584
476
|
|
|
585
|
-
|
|
586
|
-
|
|
477
|
+
.path-actions button {
|
|
478
|
+
flex: 1;
|
|
587
479
|
|
|
588
|
-
|
|
589
|
-
9px 11px;
|
|
480
|
+
height: auto;
|
|
590
481
|
|
|
591
|
-
|
|
592
|
-
block;
|
|
482
|
+
min-height: 40px;
|
|
593
483
|
|
|
594
|
-
|
|
595
|
-
|
|
484
|
+
white-space: normal;
|
|
485
|
+
}
|
|
596
486
|
|
|
597
|
-
|
|
598
|
-
|
|
487
|
+
.path-chain {
|
|
488
|
+
display: flex;
|
|
599
489
|
|
|
600
|
-
|
|
601
|
-
pointer;
|
|
602
|
-
}
|
|
490
|
+
flex-direction: column;
|
|
603
491
|
|
|
604
|
-
|
|
605
|
-
display:
|
|
606
|
-
block;
|
|
492
|
+
gap: 5px;
|
|
607
493
|
|
|
608
|
-
|
|
609
|
-
|
|
494
|
+
margin-top: 9px;
|
|
495
|
+
}
|
|
610
496
|
|
|
611
|
-
|
|
612
|
-
|
|
497
|
+
.path-node {
|
|
498
|
+
height: auto;
|
|
613
499
|
|
|
614
|
-
|
|
615
|
-
anywhere;
|
|
616
|
-
}
|
|
500
|
+
min-height: 36px;
|
|
617
501
|
|
|
618
|
-
|
|
619
|
-
display:
|
|
620
|
-
block;
|
|
502
|
+
padding: 7px 10px;
|
|
621
503
|
|
|
622
|
-
|
|
623
|
-
4px;
|
|
504
|
+
text-align: left;
|
|
624
505
|
|
|
625
|
-
|
|
626
|
-
|
|
506
|
+
font-size: 11px;
|
|
507
|
+
}
|
|
508
|
+
</style>
|
|
509
|
+
</head>
|
|
627
510
|
|
|
628
|
-
|
|
629
|
-
|
|
511
|
+
<body>
|
|
512
|
+
<div id="loading">Loading ToolNet projects…</div>
|
|
630
513
|
|
|
631
|
-
|
|
632
|
-
ui-monospace,
|
|
633
|
-
SFMono-Regular,
|
|
634
|
-
Menlo,
|
|
635
|
-
monospace;
|
|
514
|
+
<div id="graph"></div>
|
|
636
515
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
516
|
+
<div id="toolbar" class="panel">
|
|
517
|
+
<div class="brand">
|
|
518
|
+
ToolNet Memory
|
|
519
|
+
<span>/ Graph</span>
|
|
520
|
+
</div>
|
|
640
521
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
522
|
+
<select id="project" aria-label="Project">
|
|
523
|
+
<option>Loading…</option>
|
|
524
|
+
</select>
|
|
644
525
|
|
|
645
|
-
|
|
646
|
-
.45;
|
|
526
|
+
<button id="reset">Reset</button>
|
|
647
527
|
|
|
648
|
-
|
|
649
|
-
11px;
|
|
650
|
-
}
|
|
528
|
+
<input id="search" placeholder="Search symbol or file…" />
|
|
651
529
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
530
|
+
<select id="cluster" aria-label="Subsystem">
|
|
531
|
+
<option value="">All subsystems</option>
|
|
532
|
+
</select>
|
|
655
533
|
|
|
656
|
-
|
|
657
|
-
|
|
534
|
+
<select id="edge" aria-label="Relation">
|
|
535
|
+
<option value="">All relations</option>
|
|
536
|
+
</select>
|
|
537
|
+
</div>
|
|
658
538
|
|
|
659
|
-
|
|
660
|
-
8px;
|
|
539
|
+
<div id="stats" class="panel"></div>
|
|
661
540
|
|
|
662
|
-
|
|
663
|
-
1px solid
|
|
664
|
-
rgba(255,255,255,.12);
|
|
541
|
+
<div id="details" class="panel"></div>
|
|
665
542
|
|
|
666
|
-
|
|
667
|
-
999px;
|
|
543
|
+
<script src="/vendor/3d-force-graph.min.js"></script>
|
|
668
544
|
|
|
669
|
-
|
|
670
|
-
|
|
545
|
+
<script>
|
|
546
|
+
const GRAPH_TOKEN_SESSION_KEY = 'toolnet.graph.access-token';
|
|
547
|
+
function readGraphToken() {
|
|
548
|
+
try {
|
|
549
|
+
return sessionStorage.getItem(GRAPH_TOKEN_SESSION_KEY) || '';
|
|
550
|
+
} catch {
|
|
551
|
+
return '';
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function writeGraphToken(value) {
|
|
555
|
+
try {
|
|
556
|
+
if (value) {
|
|
557
|
+
sessionStorage.setItem(GRAPH_TOKEN_SESSION_KEY, value);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
sessionStorage.removeItem(GRAPH_TOKEN_SESSION_KEY);
|
|
561
|
+
} catch {
|
|
562
|
+
// Session storage is optional.
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function graphApiRequest(input, init, token) {
|
|
566
|
+
const headers = new Headers(init?.headers);
|
|
567
|
+
if (token) {
|
|
568
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
569
|
+
}
|
|
570
|
+
return window.fetch(input, {
|
|
571
|
+
...init,
|
|
572
|
+
headers,
|
|
573
|
+
cache: 'no-store',
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
async function graphApiFetch(input, init = {}) {
|
|
577
|
+
let token = readGraphToken();
|
|
578
|
+
let response = await graphApiRequest(input, init, token);
|
|
579
|
+
if (response.status !== 401) {
|
|
580
|
+
return response;
|
|
581
|
+
}
|
|
582
|
+
// Existing token is invalid/stale.
|
|
583
|
+
writeGraphToken('');
|
|
584
|
+
token = window.prompt('ToolNet Graph access token')?.trim() || '';
|
|
585
|
+
if (!token) {
|
|
586
|
+
return response;
|
|
587
|
+
}
|
|
588
|
+
writeGraphToken(token);
|
|
589
|
+
response = await graphApiRequest(input, init, token);
|
|
590
|
+
if (response.status === 401) {
|
|
591
|
+
writeGraphToken('');
|
|
592
|
+
}
|
|
593
|
+
return response;
|
|
594
|
+
}
|
|
595
|
+
const state = {
|
|
596
|
+
raw: null,
|
|
671
597
|
|
|
672
|
-
|
|
673
|
-
700;
|
|
674
|
-
}
|
|
598
|
+
graph: null,
|
|
675
599
|
|
|
676
|
-
|
|
677
|
-
display:
|
|
678
|
-
flex;
|
|
600
|
+
project: '',
|
|
679
601
|
|
|
680
|
-
|
|
681
|
-
8px;
|
|
602
|
+
search: '',
|
|
682
603
|
|
|
683
|
-
|
|
684
|
-
10px;
|
|
685
|
-
}
|
|
604
|
+
cluster: '',
|
|
686
605
|
|
|
687
|
-
|
|
688
|
-
flex:
|
|
689
|
-
1;
|
|
606
|
+
edge: '',
|
|
690
607
|
|
|
691
|
-
|
|
692
|
-
auto;
|
|
608
|
+
selected: null,
|
|
693
609
|
|
|
694
|
-
|
|
695
|
-
40px;
|
|
610
|
+
pathStart: null,
|
|
696
611
|
|
|
697
|
-
|
|
698
|
-
normal;
|
|
699
|
-
}
|
|
612
|
+
highlightedPath: [],
|
|
700
613
|
|
|
701
|
-
|
|
702
|
-
display:
|
|
703
|
-
flex;
|
|
614
|
+
viewMode: 'overview',
|
|
704
615
|
|
|
705
|
-
|
|
706
|
-
column;
|
|
616
|
+
viewCluster: '',
|
|
707
617
|
|
|
708
|
-
|
|
709
|
-
|
|
618
|
+
viewFile: '',
|
|
619
|
+
};
|
|
710
620
|
|
|
711
|
-
|
|
712
|
-
9px;
|
|
713
|
-
}
|
|
621
|
+
const graphEl = document.getElementById('graph');
|
|
714
622
|
|
|
715
|
-
|
|
716
|
-
height:
|
|
717
|
-
auto;
|
|
623
|
+
const projectEl = document.getElementById('project');
|
|
718
624
|
|
|
719
|
-
|
|
720
|
-
36px;
|
|
625
|
+
const searchEl = document.getElementById('search');
|
|
721
626
|
|
|
722
|
-
|
|
723
|
-
7px 10px;
|
|
627
|
+
const clusterEl = document.getElementById('cluster');
|
|
724
628
|
|
|
725
|
-
|
|
726
|
-
left;
|
|
629
|
+
const edgeEl = document.getElementById('edge');
|
|
727
630
|
|
|
728
|
-
|
|
729
|
-
11px;
|
|
730
|
-
}
|
|
631
|
+
const statsEl = document.getElementById('stats');
|
|
731
632
|
|
|
732
|
-
|
|
733
|
-
</head>
|
|
633
|
+
const detailsEl = document.getElementById('details');
|
|
734
634
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
635
|
+
const colors = [
|
|
636
|
+
'#62a8ff',
|
|
637
|
+
'#ff8f6b',
|
|
638
|
+
'#67d49b',
|
|
639
|
+
'#c493ff',
|
|
640
|
+
'#ffd166',
|
|
641
|
+
'#73d2de',
|
|
642
|
+
'#ff6b9a',
|
|
643
|
+
'#9fd356',
|
|
644
|
+
'#e5989b',
|
|
645
|
+
'#8ecae6',
|
|
646
|
+
'#c9cba3',
|
|
647
|
+
'#f4a261',
|
|
648
|
+
];
|
|
739
649
|
|
|
740
|
-
|
|
650
|
+
function escapeHtml(value) {
|
|
651
|
+
return String(value ?? '')
|
|
652
|
+
.replaceAll('&', '&')
|
|
653
|
+
.replaceAll('<', '<')
|
|
654
|
+
.replaceAll('>', '>')
|
|
655
|
+
.replaceAll('"', '"')
|
|
656
|
+
.replaceAll("'", ''');
|
|
657
|
+
}
|
|
741
658
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
class="panel"
|
|
745
|
-
>
|
|
746
|
-
<div class="brand">
|
|
747
|
-
ToolNet Memory
|
|
748
|
-
<span>/ Graph</span>
|
|
749
|
-
</div>
|
|
659
|
+
function hashCode(value) {
|
|
660
|
+
let hash = 0;
|
|
750
661
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
<option>
|
|
756
|
-
Loading…
|
|
757
|
-
</option>
|
|
758
|
-
</select>
|
|
759
|
-
|
|
760
|
-
<button id="reset">
|
|
761
|
-
Reset
|
|
762
|
-
</button>
|
|
763
|
-
|
|
764
|
-
<input
|
|
765
|
-
id="search"
|
|
766
|
-
placeholder="Search symbol or file…"
|
|
767
|
-
/>
|
|
768
|
-
|
|
769
|
-
<select
|
|
770
|
-
id="cluster"
|
|
771
|
-
aria-label="Subsystem"
|
|
772
|
-
>
|
|
773
|
-
<option value="">
|
|
774
|
-
All subsystems
|
|
775
|
-
</option>
|
|
776
|
-
</select>
|
|
777
|
-
|
|
778
|
-
<select
|
|
779
|
-
id="edge"
|
|
780
|
-
aria-label="Relation"
|
|
781
|
-
>
|
|
782
|
-
<option value="">
|
|
783
|
-
All relations
|
|
784
|
-
</option>
|
|
785
|
-
</select>
|
|
786
|
-
|
|
787
|
-
</div>
|
|
788
|
-
|
|
789
|
-
<div
|
|
790
|
-
id="stats"
|
|
791
|
-
class="panel"
|
|
792
|
-
></div>
|
|
793
|
-
|
|
794
|
-
<div
|
|
795
|
-
id="details"
|
|
796
|
-
class="panel"
|
|
797
|
-
></div>
|
|
798
|
-
|
|
799
|
-
<script src="/vendor/3d-force-graph.min.js"></script>
|
|
800
|
-
|
|
801
|
-
<script>
|
|
802
|
-
const GRAPH_TOKEN_SESSION_KEY = "toolnet.graph.access-token";
|
|
803
|
-
function readGraphToken() {
|
|
804
|
-
try {
|
|
805
|
-
return sessionStorage.getItem(GRAPH_TOKEN_SESSION_KEY) || "";
|
|
806
|
-
} catch {
|
|
807
|
-
return "";
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
function writeGraphToken(value) {
|
|
811
|
-
try {
|
|
812
|
-
if (value) {
|
|
813
|
-
sessionStorage.setItem(GRAPH_TOKEN_SESSION_KEY, value);
|
|
814
|
-
return;
|
|
662
|
+
for (let i = 0; i < value.length; i++) {
|
|
663
|
+
hash = (hash << 5) - hash + value.charCodeAt(i);
|
|
664
|
+
|
|
665
|
+
hash |= 0;
|
|
815
666
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
// Session storage is optional.
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
function graphApiRequest(input, init, token) {
|
|
822
|
-
const headers = new Headers(init?.headers);
|
|
823
|
-
if (token) {
|
|
824
|
-
headers.set("Authorization", `Bearer ${token}`);
|
|
825
|
-
}
|
|
826
|
-
return window.fetch(input, {
|
|
827
|
-
...init,
|
|
828
|
-
headers,
|
|
829
|
-
cache: "no-store",
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
async function graphApiFetch(input, init = {}) {
|
|
833
|
-
let token = readGraphToken();
|
|
834
|
-
let response = await graphApiRequest(input, init, token);
|
|
835
|
-
if (response.status !== 401) {
|
|
836
|
-
return response;
|
|
837
|
-
}
|
|
838
|
-
// Existing token is invalid/stale.
|
|
839
|
-
writeGraphToken("");
|
|
840
|
-
token = window.prompt("ToolNet Graph access token")?.trim() || "";
|
|
841
|
-
if (!token) {
|
|
842
|
-
return response;
|
|
667
|
+
|
|
668
|
+
return Math.abs(hash);
|
|
843
669
|
}
|
|
844
|
-
writeGraphToken(token);
|
|
845
|
-
response = await graphApiRequest(input, init, token);
|
|
846
|
-
if (response.status === 401) {
|
|
847
|
-
writeGraphToken("");
|
|
848
|
-
}
|
|
849
|
-
return response;
|
|
850
|
-
}
|
|
851
|
-
const state = {
|
|
852
|
-
raw:
|
|
853
|
-
null,
|
|
854
|
-
|
|
855
|
-
graph:
|
|
856
|
-
null,
|
|
857
|
-
|
|
858
|
-
project:
|
|
859
|
-
"",
|
|
860
|
-
|
|
861
|
-
search:
|
|
862
|
-
"",
|
|
863
|
-
|
|
864
|
-
cluster:
|
|
865
|
-
"",
|
|
866
|
-
|
|
867
|
-
edge:
|
|
868
|
-
"",
|
|
869
|
-
|
|
870
|
-
selected:
|
|
871
|
-
null,
|
|
872
|
-
|
|
873
|
-
pathStart:
|
|
874
|
-
null,
|
|
875
|
-
|
|
876
|
-
highlightedPath:
|
|
877
|
-
[],
|
|
878
|
-
|
|
879
|
-
viewMode:
|
|
880
|
-
"overview",
|
|
881
|
-
|
|
882
|
-
viewCluster:
|
|
883
|
-
"",
|
|
884
|
-
|
|
885
|
-
viewFile:
|
|
886
|
-
"",
|
|
887
|
-
};
|
|
888
|
-
|
|
889
|
-
const graphEl =
|
|
890
|
-
document.getElementById(
|
|
891
|
-
"graph"
|
|
892
|
-
);
|
|
893
|
-
|
|
894
|
-
const projectEl =
|
|
895
|
-
document.getElementById(
|
|
896
|
-
"project"
|
|
897
|
-
);
|
|
898
|
-
|
|
899
|
-
const searchEl =
|
|
900
|
-
document.getElementById(
|
|
901
|
-
"search"
|
|
902
|
-
);
|
|
903
|
-
|
|
904
|
-
const clusterEl =
|
|
905
|
-
document.getElementById(
|
|
906
|
-
"cluster"
|
|
907
|
-
);
|
|
908
|
-
|
|
909
|
-
const edgeEl =
|
|
910
|
-
document.getElementById(
|
|
911
|
-
"edge"
|
|
912
|
-
);
|
|
913
|
-
|
|
914
|
-
const statsEl =
|
|
915
|
-
document.getElementById(
|
|
916
|
-
"stats"
|
|
917
|
-
);
|
|
918
|
-
|
|
919
|
-
const detailsEl =
|
|
920
|
-
document.getElementById(
|
|
921
|
-
"details"
|
|
922
|
-
);
|
|
923
|
-
|
|
924
|
-
const colors = [
|
|
925
|
-
"#62a8ff",
|
|
926
|
-
"#ff8f6b",
|
|
927
|
-
"#67d49b",
|
|
928
|
-
"#c493ff",
|
|
929
|
-
"#ffd166",
|
|
930
|
-
"#73d2de",
|
|
931
|
-
"#ff6b9a",
|
|
932
|
-
"#9fd356",
|
|
933
|
-
"#e5989b",
|
|
934
|
-
"#8ecae6",
|
|
935
|
-
"#c9cba3",
|
|
936
|
-
"#f4a261",
|
|
937
|
-
];
|
|
938
|
-
|
|
939
|
-
function escapeHtml(
|
|
940
|
-
value
|
|
941
|
-
) {
|
|
942
|
-
return String(
|
|
943
|
-
value ?? ""
|
|
944
|
-
)
|
|
945
|
-
.replaceAll(
|
|
946
|
-
"&",
|
|
947
|
-
"&"
|
|
948
|
-
)
|
|
949
|
-
.replaceAll(
|
|
950
|
-
"<",
|
|
951
|
-
"<"
|
|
952
|
-
)
|
|
953
|
-
.replaceAll(
|
|
954
|
-
">",
|
|
955
|
-
">"
|
|
956
|
-
)
|
|
957
|
-
.replaceAll(
|
|
958
|
-
'"',
|
|
959
|
-
"""
|
|
960
|
-
)
|
|
961
|
-
.replaceAll(
|
|
962
|
-
"'",
|
|
963
|
-
"'"
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
function hashCode(
|
|
968
|
-
value
|
|
969
|
-
) {
|
|
970
|
-
let hash =
|
|
971
|
-
0;
|
|
972
|
-
|
|
973
|
-
for (
|
|
974
|
-
let i = 0;
|
|
975
|
-
i < value.length;
|
|
976
|
-
i++
|
|
977
|
-
) {
|
|
978
|
-
hash =
|
|
979
|
-
(
|
|
980
|
-
(
|
|
981
|
-
hash << 5
|
|
982
|
-
) -
|
|
983
|
-
hash
|
|
984
|
-
) +
|
|
985
|
-
value.charCodeAt(
|
|
986
|
-
i
|
|
987
|
-
);
|
|
988
670
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
return Math.abs(
|
|
994
|
-
hash
|
|
995
|
-
);
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
function nodeColor(
|
|
999
|
-
node
|
|
1000
|
-
) {
|
|
1001
|
-
const value =
|
|
1002
|
-
node.clusterLabel ||
|
|
1003
|
-
node.cluster ||
|
|
1004
|
-
node.layer ||
|
|
1005
|
-
node.type ||
|
|
1006
|
-
"other";
|
|
1007
|
-
|
|
1008
|
-
return colors[
|
|
1009
|
-
hashCode(
|
|
1010
|
-
value
|
|
1011
|
-
) %
|
|
1012
|
-
colors.length
|
|
1013
|
-
];
|
|
1014
|
-
}
|
|
671
|
+
function nodeColor(node) {
|
|
672
|
+
const value = node.clusterLabel || node.cluster || node.layer || node.type || 'other';
|
|
1015
673
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
) {
|
|
1019
|
-
const map = {
|
|
1020
|
-
CALL_REFERENCE:
|
|
1021
|
-
"rgba(255,190,90,.72)",
|
|
674
|
+
return colors[hashCode(value) % colors.length];
|
|
675
|
+
}
|
|
1022
676
|
|
|
1023
|
-
|
|
1024
|
-
|
|
677
|
+
function linkColor(link) {
|
|
678
|
+
const map = {
|
|
679
|
+
CALL_REFERENCE: 'rgba(255,190,90,.72)',
|
|
1025
680
|
|
|
1026
|
-
|
|
1027
|
-
"rgba(100,170,255,.46)",
|
|
681
|
+
CALLS: 'rgba(255,255,255,.30)',
|
|
1028
682
|
|
|
1029
|
-
|
|
1030
|
-
"rgba(190,130,255,.44)",
|
|
683
|
+
IMPORTS: 'rgba(100,170,255,.46)',
|
|
1031
684
|
|
|
1032
|
-
|
|
1033
|
-
"rgba(255,100,100,.60)",
|
|
685
|
+
USES_TYPE: 'rgba(190,130,255,.44)',
|
|
1034
686
|
|
|
1035
|
-
|
|
1036
|
-
"rgba(100,220,150,.42)",
|
|
687
|
+
WRITES: 'rgba(255,100,100,.60)',
|
|
1037
688
|
|
|
1038
|
-
|
|
1039
|
-
"rgba(100,220,220,.48)",
|
|
689
|
+
TESTS: 'rgba(100,220,150,.42)',
|
|
1040
690
|
|
|
1041
|
-
|
|
1042
|
-
"rgba(235,140,255,.50)",
|
|
1043
|
-
};
|
|
691
|
+
IMPLEMENTS: 'rgba(100,220,220,.48)',
|
|
1044
692
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
"rgba(255,255,255,.18)"
|
|
1048
|
-
);
|
|
1049
|
-
}
|
|
693
|
+
INHERITS: 'rgba(235,140,255,.50)',
|
|
694
|
+
};
|
|
1050
695
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
null;
|
|
696
|
+
return map[link.type] || 'rgba(255,255,255,.18)';
|
|
697
|
+
}
|
|
1054
698
|
|
|
1055
|
-
|
|
1056
|
-
.
|
|
1057
|
-
.remove(
|
|
1058
|
-
"open"
|
|
1059
|
-
);
|
|
699
|
+
function closeDetails() {
|
|
700
|
+
state.selected = null;
|
|
1060
701
|
|
|
1061
|
-
|
|
1062
|
-
"";
|
|
1063
|
-
}
|
|
702
|
+
detailsEl.classList.remove('open');
|
|
1064
703
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
"CALLS",
|
|
1068
|
-
"CALL_REFERENCE",
|
|
1069
|
-
]);
|
|
704
|
+
detailsEl.innerHTML = '';
|
|
705
|
+
}
|
|
1070
706
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
707
|
+
const CALL_RELATIONS = new Set(['CALLS', 'CALL_REFERENCE']);
|
|
708
|
+
|
|
709
|
+
const DEPENDENCY_RELATIONS = new Set([
|
|
710
|
+
'CALLS',
|
|
711
|
+
'CALL_REFERENCE',
|
|
712
|
+
'IMPORTS',
|
|
713
|
+
'USES_TYPE',
|
|
714
|
+
'WRITES',
|
|
715
|
+
'INHERITS',
|
|
716
|
+
'IMPLEMENTS',
|
|
717
|
+
'ROUTE',
|
|
1081
718
|
]);
|
|
1082
719
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
const value =
|
|
1101
|
-
link[side];
|
|
1102
|
-
|
|
1103
|
-
return typeof value ===
|
|
1104
|
-
"object"
|
|
1105
|
-
? value.id
|
|
1106
|
-
: value;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
function rawNode(
|
|
1110
|
-
id
|
|
1111
|
-
) {
|
|
1112
|
-
return state.raw
|
|
1113
|
-
?.nodes
|
|
1114
|
-
?.find(
|
|
1115
|
-
node =>
|
|
1116
|
-
node.id === id
|
|
1117
|
-
);
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
function uniqueNodes(
|
|
1121
|
-
values
|
|
1122
|
-
) {
|
|
1123
|
-
const map =
|
|
1124
|
-
new Map();
|
|
1125
|
-
|
|
1126
|
-
for (
|
|
1127
|
-
const node
|
|
1128
|
-
of values
|
|
1129
|
-
) {
|
|
1130
|
-
if (
|
|
1131
|
-
node &&
|
|
1132
|
-
!map.has(
|
|
1133
|
-
node.id
|
|
1134
|
-
)
|
|
1135
|
-
) {
|
|
1136
|
-
map.set(
|
|
1137
|
-
node.id,
|
|
1138
|
-
node
|
|
1139
|
-
);
|
|
1140
|
-
}
|
|
720
|
+
const IMPACT_WEIGHTS = {
|
|
721
|
+
CALLS: 4,
|
|
722
|
+
CALL_REFERENCE: 6,
|
|
723
|
+
USES_TYPE: 4,
|
|
724
|
+
TESTS: 2,
|
|
725
|
+
ROUTE: 4,
|
|
726
|
+
WRITES: 3,
|
|
727
|
+
IMPORTS: 3,
|
|
728
|
+
INHERITS: 5,
|
|
729
|
+
IMPLEMENTS: 5,
|
|
730
|
+
DEFINES: 1,
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
function endpointId(link, side) {
|
|
734
|
+
const value = link[side];
|
|
735
|
+
|
|
736
|
+
return typeof value === 'object' ? value.id : value;
|
|
1141
737
|
}
|
|
1142
738
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
[];
|
|
1155
|
-
|
|
1156
|
-
for (
|
|
1157
|
-
const link
|
|
1158
|
-
of state.raw.links
|
|
1159
|
-
) {
|
|
1160
|
-
if (
|
|
1161
|
-
allowed &&
|
|
1162
|
-
!allowed.has(
|
|
1163
|
-
link.type
|
|
1164
|
-
)
|
|
1165
|
-
) {
|
|
1166
|
-
continue;
|
|
739
|
+
function rawNode(id) {
|
|
740
|
+
return state.raw?.nodes?.find((node) => node.id === id);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function uniqueNodes(values) {
|
|
744
|
+
const map = new Map();
|
|
745
|
+
|
|
746
|
+
for (const node of values) {
|
|
747
|
+
if (node && !map.has(node.id)) {
|
|
748
|
+
map.set(node.id, node);
|
|
749
|
+
}
|
|
1167
750
|
}
|
|
1168
751
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
link,
|
|
1172
|
-
"source"
|
|
1173
|
-
);
|
|
752
|
+
return [...map.values()];
|
|
753
|
+
}
|
|
1174
754
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
link,
|
|
1178
|
-
"target"
|
|
1179
|
-
);
|
|
755
|
+
function relatedNodes(nodeId, direction, allowed) {
|
|
756
|
+
const result = [];
|
|
1180
757
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
target ===
|
|
1185
|
-
nodeId
|
|
1186
|
-
) {
|
|
1187
|
-
const node =
|
|
1188
|
-
rawNode(
|
|
1189
|
-
source
|
|
1190
|
-
);
|
|
1191
|
-
|
|
1192
|
-
if (node) {
|
|
1193
|
-
result.push(
|
|
1194
|
-
node
|
|
1195
|
-
);
|
|
758
|
+
for (const link of state.raw.links) {
|
|
759
|
+
if (allowed && !allowed.has(link.type)) {
|
|
760
|
+
continue;
|
|
1196
761
|
}
|
|
1197
|
-
}
|
|
1198
762
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
763
|
+
const source = endpointId(link, 'source');
|
|
764
|
+
|
|
765
|
+
const target = endpointId(link, 'target');
|
|
766
|
+
|
|
767
|
+
if (direction === 'incoming' && target === nodeId) {
|
|
768
|
+
const node = rawNode(source);
|
|
769
|
+
|
|
770
|
+
if (node) {
|
|
771
|
+
result.push(node);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (direction === 'outgoing' && source === nodeId) {
|
|
776
|
+
const node = rawNode(target);
|
|
777
|
+
|
|
778
|
+
if (node) {
|
|
779
|
+
result.push(node);
|
|
780
|
+
}
|
|
1214
781
|
}
|
|
1215
782
|
}
|
|
1216
|
-
}
|
|
1217
783
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
);
|
|
1221
|
-
}
|
|
784
|
+
return uniqueNodes(result);
|
|
785
|
+
}
|
|
1222
786
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
maxDepth = 3
|
|
1226
|
-
) {
|
|
1227
|
-
const visited =
|
|
1228
|
-
new Map();
|
|
787
|
+
function blastRadius(startId, maxDepth = 3) {
|
|
788
|
+
const visited = new Map();
|
|
1229
789
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
startId,
|
|
790
|
+
const queue = [
|
|
791
|
+
{
|
|
792
|
+
id: startId,
|
|
1234
793
|
|
|
1235
|
-
|
|
1236
|
-
0,
|
|
794
|
+
depth: 0,
|
|
1237
795
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
];
|
|
796
|
+
score: 0,
|
|
797
|
+
},
|
|
798
|
+
];
|
|
1242
799
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
{
|
|
1246
|
-
depth:
|
|
1247
|
-
0,
|
|
800
|
+
visited.set(startId, {
|
|
801
|
+
depth: 0,
|
|
1248
802
|
|
|
1249
|
-
score:
|
|
1250
|
-
|
|
1251
|
-
}
|
|
1252
|
-
);
|
|
1253
|
-
|
|
1254
|
-
while (
|
|
1255
|
-
queue.length
|
|
1256
|
-
) {
|
|
1257
|
-
const current =
|
|
1258
|
-
queue.shift();
|
|
1259
|
-
|
|
1260
|
-
if (
|
|
1261
|
-
current.depth >=
|
|
1262
|
-
maxDepth
|
|
1263
|
-
) {
|
|
1264
|
-
continue;
|
|
1265
|
-
}
|
|
803
|
+
score: 0,
|
|
804
|
+
});
|
|
1266
805
|
|
|
1267
|
-
|
|
1268
|
-
const
|
|
1269
|
-
of state.raw.links
|
|
1270
|
-
) {
|
|
1271
|
-
const weight =
|
|
1272
|
-
IMPACT_WEIGHTS[
|
|
1273
|
-
link.type
|
|
1274
|
-
];
|
|
806
|
+
while (queue.length) {
|
|
807
|
+
const current = queue.shift();
|
|
1275
808
|
|
|
1276
|
-
if (
|
|
809
|
+
if (current.depth >= maxDepth) {
|
|
1277
810
|
continue;
|
|
1278
811
|
}
|
|
1279
812
|
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
link,
|
|
1283
|
-
"source"
|
|
1284
|
-
);
|
|
1285
|
-
|
|
1286
|
-
const target =
|
|
1287
|
-
endpointId(
|
|
1288
|
-
link,
|
|
1289
|
-
"target"
|
|
1290
|
-
);
|
|
1291
|
-
|
|
1292
|
-
/*
|
|
1293
|
-
* Reverse traversal:
|
|
1294
|
-
* if source depends on target,
|
|
1295
|
-
* changing target may affect source.
|
|
1296
|
-
*/
|
|
1297
|
-
if (
|
|
1298
|
-
target !==
|
|
1299
|
-
current.id
|
|
1300
|
-
) {
|
|
1301
|
-
continue;
|
|
1302
|
-
}
|
|
813
|
+
for (const link of state.raw.links) {
|
|
814
|
+
const weight = IMPACT_WEIGHTS[link.type];
|
|
1303
815
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
const nextScore =
|
|
1309
|
-
current.score +
|
|
1310
|
-
weight *
|
|
1311
|
-
(
|
|
1312
|
-
maxDepth -
|
|
1313
|
-
current.depth
|
|
1314
|
-
);
|
|
1315
|
-
|
|
1316
|
-
const previous =
|
|
1317
|
-
visited.get(
|
|
1318
|
-
source
|
|
1319
|
-
);
|
|
1320
|
-
|
|
1321
|
-
if (
|
|
1322
|
-
!previous ||
|
|
1323
|
-
nextDepth <
|
|
1324
|
-
previous.depth ||
|
|
1325
|
-
nextScore >
|
|
1326
|
-
previous.score
|
|
1327
|
-
) {
|
|
1328
|
-
visited.set(
|
|
1329
|
-
source,
|
|
1330
|
-
{
|
|
1331
|
-
depth:
|
|
1332
|
-
nextDepth,
|
|
1333
|
-
|
|
1334
|
-
score:
|
|
1335
|
-
nextScore,
|
|
1336
|
-
}
|
|
1337
|
-
);
|
|
816
|
+
if (!weight) {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
1338
819
|
|
|
1339
|
-
|
|
1340
|
-
id:
|
|
1341
|
-
source,
|
|
820
|
+
const source = endpointId(link, 'source');
|
|
1342
821
|
|
|
1343
|
-
|
|
1344
|
-
nextDepth,
|
|
822
|
+
const target = endpointId(link, 'target');
|
|
1345
823
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
824
|
+
/*
|
|
825
|
+
* Reverse traversal:
|
|
826
|
+
* if source depends on target,
|
|
827
|
+
* changing target may affect source.
|
|
828
|
+
*/
|
|
829
|
+
if (target !== current.id) {
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const nextDepth = current.depth + 1;
|
|
834
|
+
|
|
835
|
+
const nextScore = current.score + weight * (maxDepth - current.depth);
|
|
836
|
+
|
|
837
|
+
const previous = visited.get(source);
|
|
838
|
+
|
|
839
|
+
if (!previous || nextDepth < previous.depth || nextScore > previous.score) {
|
|
840
|
+
visited.set(source, {
|
|
841
|
+
depth: nextDepth,
|
|
842
|
+
|
|
843
|
+
score: nextScore,
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
queue.push({
|
|
847
|
+
id: source,
|
|
848
|
+
|
|
849
|
+
depth: nextDepth,
|
|
850
|
+
|
|
851
|
+
score: nextScore,
|
|
852
|
+
});
|
|
853
|
+
}
|
|
1349
854
|
}
|
|
1350
855
|
}
|
|
1351
|
-
}
|
|
1352
856
|
|
|
1353
|
-
|
|
1354
|
-
startId
|
|
1355
|
-
);
|
|
857
|
+
visited.delete(startId);
|
|
1356
858
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
]
|
|
1361
|
-
.map(
|
|
1362
|
-
([id, info]) => ({
|
|
1363
|
-
node:
|
|
1364
|
-
rawNode(
|
|
1365
|
-
id
|
|
1366
|
-
),
|
|
859
|
+
const impacted = [...visited.entries()]
|
|
860
|
+
.map(([id, info]) => ({
|
|
861
|
+
node: rawNode(id),
|
|
1367
862
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
)
|
|
1371
|
-
.
|
|
1372
|
-
item =>
|
|
1373
|
-
item.node
|
|
1374
|
-
)
|
|
1375
|
-
.sort(
|
|
1376
|
-
(a, b) =>
|
|
1377
|
-
b.score -
|
|
1378
|
-
a.score
|
|
1379
|
-
);
|
|
863
|
+
...info,
|
|
864
|
+
}))
|
|
865
|
+
.filter((item) => item.node)
|
|
866
|
+
.sort((a, b) => b.score - a.score);
|
|
1380
867
|
|
|
1381
|
-
|
|
1382
|
-
impacted.reduce(
|
|
1383
|
-
(
|
|
1384
|
-
total,
|
|
1385
|
-
item
|
|
1386
|
-
) =>
|
|
1387
|
-
total +
|
|
1388
|
-
item.score,
|
|
1389
|
-
0
|
|
1390
|
-
);
|
|
868
|
+
const score = impacted.reduce((total, item) => total + item.score, 0);
|
|
1391
869
|
|
|
1392
|
-
|
|
1393
|
-
"LOW";
|
|
1394
|
-
|
|
1395
|
-
if (
|
|
1396
|
-
score >= 250 ||
|
|
1397
|
-
impacted.length >= 40
|
|
1398
|
-
) {
|
|
1399
|
-
risk =
|
|
1400
|
-
"CRITICAL";
|
|
1401
|
-
} else if (
|
|
1402
|
-
score >= 120 ||
|
|
1403
|
-
impacted.length >= 20
|
|
1404
|
-
) {
|
|
1405
|
-
risk =
|
|
1406
|
-
"HIGH";
|
|
1407
|
-
} else if (
|
|
1408
|
-
score >= 40 ||
|
|
1409
|
-
impacted.length >= 8
|
|
1410
|
-
) {
|
|
1411
|
-
risk =
|
|
1412
|
-
"MEDIUM";
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
return {
|
|
1416
|
-
impacted,
|
|
1417
|
-
score,
|
|
1418
|
-
risk,
|
|
1419
|
-
};
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
function shortestPath(
|
|
1423
|
-
fromId,
|
|
1424
|
-
toId,
|
|
1425
|
-
maxDepth = 10
|
|
1426
|
-
) {
|
|
1427
|
-
if (
|
|
1428
|
-
fromId ===
|
|
1429
|
-
toId
|
|
1430
|
-
) {
|
|
1431
|
-
return [
|
|
1432
|
-
fromId
|
|
1433
|
-
];
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
const queue = [
|
|
1437
|
-
{
|
|
1438
|
-
id:
|
|
1439
|
-
fromId,
|
|
870
|
+
let risk = 'LOW';
|
|
1440
871
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
872
|
+
if (score >= 250 || impacted.length >= 40) {
|
|
873
|
+
risk = 'CRITICAL';
|
|
874
|
+
} else if (score >= 120 || impacted.length >= 20) {
|
|
875
|
+
risk = 'HIGH';
|
|
876
|
+
} else if (score >= 40 || impacted.length >= 8) {
|
|
877
|
+
risk = 'MEDIUM';
|
|
1444
878
|
}
|
|
1445
|
-
];
|
|
1446
879
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
) {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
if (
|
|
1459
|
-
current.path.length >
|
|
1460
|
-
maxDepth
|
|
1461
|
-
) {
|
|
1462
|
-
continue;
|
|
880
|
+
return {
|
|
881
|
+
impacted,
|
|
882
|
+
score,
|
|
883
|
+
risk,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function shortestPath(fromId, toId, maxDepth = 10) {
|
|
888
|
+
if (fromId === toId) {
|
|
889
|
+
return [fromId];
|
|
1463
890
|
}
|
|
1464
891
|
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
) {
|
|
1469
|
-
if (
|
|
1470
|
-
!DEPENDENCY_RELATIONS
|
|
1471
|
-
.has(
|
|
1472
|
-
link.type
|
|
1473
|
-
)
|
|
1474
|
-
) {
|
|
1475
|
-
continue;
|
|
1476
|
-
}
|
|
892
|
+
const queue = [
|
|
893
|
+
{
|
|
894
|
+
id: fromId,
|
|
1477
895
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
"source"
|
|
1482
|
-
) !==
|
|
1483
|
-
current.id
|
|
1484
|
-
) {
|
|
1485
|
-
continue;
|
|
1486
|
-
}
|
|
896
|
+
path: [fromId],
|
|
897
|
+
},
|
|
898
|
+
];
|
|
1487
899
|
|
|
1488
|
-
|
|
1489
|
-
endpointId(
|
|
1490
|
-
link,
|
|
1491
|
-
"target"
|
|
1492
|
-
);
|
|
900
|
+
const visited = new Set([fromId]);
|
|
1493
901
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
) {
|
|
902
|
+
while (queue.length) {
|
|
903
|
+
const current = queue.shift();
|
|
904
|
+
|
|
905
|
+
if (current.path.length > maxDepth) {
|
|
1499
906
|
continue;
|
|
1500
907
|
}
|
|
1501
908
|
|
|
1502
|
-
const
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
909
|
+
for (const link of state.raw.links) {
|
|
910
|
+
if (!DEPENDENCY_RELATIONS.has(link.type)) {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
1506
913
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
) {
|
|
1511
|
-
return path;
|
|
1512
|
-
}
|
|
914
|
+
if (endpointId(link, 'source') !== current.id) {
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
1513
917
|
|
|
1514
|
-
|
|
1515
|
-
next
|
|
1516
|
-
);
|
|
918
|
+
const next = endpointId(link, 'target');
|
|
1517
919
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
920
|
+
if (visited.has(next)) {
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
1521
923
|
|
|
1522
|
-
path,
|
|
1523
|
-
|
|
924
|
+
const path = [...current.path, next];
|
|
925
|
+
|
|
926
|
+
if (next === toId) {
|
|
927
|
+
return path;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
visited.add(next);
|
|
931
|
+
|
|
932
|
+
queue.push({
|
|
933
|
+
id: next,
|
|
934
|
+
|
|
935
|
+
path,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
1524
938
|
}
|
|
1525
|
-
}
|
|
1526
939
|
|
|
1527
|
-
|
|
1528
|
-
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
1529
942
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
nodes,
|
|
1533
|
-
limit = 8
|
|
1534
|
-
) {
|
|
1535
|
-
const visible =
|
|
1536
|
-
nodes.slice(
|
|
1537
|
-
0,
|
|
1538
|
-
limit
|
|
1539
|
-
);
|
|
943
|
+
function relationHtml(title, nodes, limit = 8) {
|
|
944
|
+
const visible = nodes.slice(0, limit);
|
|
1540
945
|
|
|
1541
|
-
|
|
946
|
+
return `
|
|
1542
947
|
<div class="section">
|
|
1543
948
|
<div class="section-title">
|
|
1544
|
-
${escapeHtml(
|
|
1545
|
-
title
|
|
1546
|
-
)}
|
|
949
|
+
${escapeHtml(title)}
|
|
1547
950
|
· ${nodes.length}
|
|
1548
951
|
</div>
|
|
1549
952
|
|
|
@@ -1551,33 +954,24 @@
|
|
|
1551
954
|
visible.length
|
|
1552
955
|
? `
|
|
1553
956
|
<div class="relation-list">
|
|
1554
|
-
${
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
node => `
|
|
957
|
+
${visible
|
|
958
|
+
.map(
|
|
959
|
+
(node) => `
|
|
1558
960
|
<button
|
|
1559
961
|
class="relation-item"
|
|
1560
|
-
data-jump-node="${escapeHtml(
|
|
1561
|
-
node.id
|
|
1562
|
-
)}"
|
|
962
|
+
data-jump-node="${escapeHtml(node.id)}"
|
|
1563
963
|
>
|
|
1564
964
|
<span class="relation-name">
|
|
1565
|
-
${escapeHtml(
|
|
1566
|
-
node.qualifiedName ||
|
|
1567
|
-
node.name
|
|
1568
|
-
)}
|
|
965
|
+
${escapeHtml(node.qualifiedName || node.name)}
|
|
1569
966
|
</span>
|
|
1570
967
|
|
|
1571
968
|
<span class="relation-meta">
|
|
1572
|
-
${escapeHtml(
|
|
1573
|
-
node.filePath
|
|
1574
|
-
)}
|
|
969
|
+
${escapeHtml(node.filePath)}
|
|
1575
970
|
</span>
|
|
1576
971
|
</button>
|
|
1577
972
|
`
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
}
|
|
973
|
+
)
|
|
974
|
+
.join('')}
|
|
1581
975
|
</div>
|
|
1582
976
|
`
|
|
1583
977
|
: `
|
|
@@ -1588,331 +982,167 @@
|
|
|
1588
982
|
}
|
|
1589
983
|
|
|
1590
984
|
${
|
|
1591
|
-
nodes.length >
|
|
1592
|
-
limit
|
|
985
|
+
nodes.length > limit
|
|
1593
986
|
? `
|
|
1594
987
|
<div class="relation-more">
|
|
1595
|
-
+${
|
|
1596
|
-
nodes.length -
|
|
1597
|
-
limit
|
|
1598
|
-
} more
|
|
988
|
+
+${nodes.length - limit} more
|
|
1599
989
|
</div>
|
|
1600
990
|
`
|
|
1601
|
-
:
|
|
991
|
+
: ''
|
|
1602
992
|
}
|
|
1603
993
|
</div>
|
|
1604
994
|
`;
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
function resetPathHighlight() {
|
|
1608
|
-
state.highlightedPath =
|
|
1609
|
-
[];
|
|
1610
|
-
|
|
1611
|
-
if (
|
|
1612
|
-
!state.graph
|
|
1613
|
-
) {
|
|
1614
|
-
return;
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
state.graph
|
|
1618
|
-
.linkWidth(
|
|
1619
|
-
link =>
|
|
1620
|
-
link.type ===
|
|
1621
|
-
"CALL_REFERENCE"
|
|
1622
|
-
? 1.2
|
|
1623
|
-
: .45
|
|
1624
|
-
)
|
|
1625
|
-
.linkColor(
|
|
1626
|
-
link =>
|
|
1627
|
-
linkColor(
|
|
1628
|
-
link
|
|
1629
|
-
)
|
|
1630
|
-
);
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
function isPathLink(
|
|
1634
|
-
link,
|
|
1635
|
-
path
|
|
1636
|
-
) {
|
|
1637
|
-
const source =
|
|
1638
|
-
endpointId(
|
|
1639
|
-
link,
|
|
1640
|
-
"source"
|
|
1641
|
-
);
|
|
995
|
+
}
|
|
1642
996
|
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
link,
|
|
1646
|
-
"target"
|
|
1647
|
-
);
|
|
997
|
+
function resetPathHighlight() {
|
|
998
|
+
state.highlightedPath = [];
|
|
1648
999
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
i <
|
|
1652
|
-
path.length - 1;
|
|
1653
|
-
i++
|
|
1654
|
-
) {
|
|
1655
|
-
if (
|
|
1656
|
-
path[i] ===
|
|
1657
|
-
source &&
|
|
1658
|
-
path[i + 1] ===
|
|
1659
|
-
target
|
|
1660
|
-
) {
|
|
1661
|
-
return true;
|
|
1000
|
+
if (!state.graph) {
|
|
1001
|
+
return;
|
|
1662
1002
|
}
|
|
1003
|
+
|
|
1004
|
+
state.graph
|
|
1005
|
+
.linkWidth((link) => (link.type === 'CALL_REFERENCE' ? 1.2 : 0.45))
|
|
1006
|
+
.linkColor((link) => linkColor(link));
|
|
1663
1007
|
}
|
|
1664
1008
|
|
|
1665
|
-
|
|
1666
|
-
|
|
1009
|
+
function isPathLink(link, path) {
|
|
1010
|
+
const source = endpointId(link, 'source');
|
|
1667
1011
|
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1012
|
+
const target = endpointId(link, 'target');
|
|
1013
|
+
|
|
1014
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1015
|
+
if (path[i] === source && path[i + 1] === target) {
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1673
1019
|
|
|
1674
|
-
|
|
1675
|
-
!state.graph
|
|
1676
|
-
) {
|
|
1677
|
-
return;
|
|
1020
|
+
return false;
|
|
1678
1021
|
}
|
|
1679
1022
|
|
|
1680
|
-
|
|
1681
|
-
.
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1023
|
+
function highlightPath(path) {
|
|
1024
|
+
state.highlightedPath = path || [];
|
|
1025
|
+
|
|
1026
|
+
if (!state.graph) {
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
state.graph
|
|
1031
|
+
.linkWidth((link) =>
|
|
1032
|
+
isPathLink(link, state.highlightedPath)
|
|
1687
1033
|
? 3.5
|
|
1688
|
-
:
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
link =>
|
|
1697
|
-
isPathLink(
|
|
1698
|
-
link,
|
|
1699
|
-
state.highlightedPath
|
|
1700
|
-
)
|
|
1701
|
-
? "rgba(255,225,80,.98)"
|
|
1702
|
-
: linkColor(
|
|
1703
|
-
link
|
|
1704
|
-
)
|
|
1705
|
-
);
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
function flyToNode(
|
|
1709
|
-
node
|
|
1710
|
-
) {
|
|
1711
|
-
const distance =
|
|
1712
|
-
70;
|
|
1713
|
-
|
|
1714
|
-
const length =
|
|
1715
|
-
Math.hypot(
|
|
1716
|
-
node.x || 1,
|
|
1717
|
-
node.y || 1,
|
|
1718
|
-
node.z || 1
|
|
1719
|
-
);
|
|
1034
|
+
: link.type === 'CALL_REFERENCE'
|
|
1035
|
+
? 1.2
|
|
1036
|
+
: 0.35
|
|
1037
|
+
)
|
|
1038
|
+
.linkColor((link) =>
|
|
1039
|
+
isPathLink(link, state.highlightedPath) ? 'rgba(255,225,80,.98)' : linkColor(link)
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1720
1042
|
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
distance /
|
|
1724
|
-
length;
|
|
1043
|
+
function flyToNode(node) {
|
|
1044
|
+
const distance = 70;
|
|
1725
1045
|
|
|
1726
|
-
|
|
1727
|
-
|
|
1046
|
+
const length = Math.hypot(node.x || 1, node.y || 1, node.z || 1);
|
|
1047
|
+
|
|
1048
|
+
const ratio = 1 + distance / length;
|
|
1049
|
+
|
|
1050
|
+
state.graph.cameraPosition(
|
|
1728
1051
|
{
|
|
1729
|
-
x:
|
|
1730
|
-
node.x *
|
|
1731
|
-
ratio,
|
|
1052
|
+
x: node.x * ratio,
|
|
1732
1053
|
|
|
1733
|
-
y:
|
|
1734
|
-
node.y *
|
|
1735
|
-
ratio,
|
|
1054
|
+
y: node.y * ratio,
|
|
1736
1055
|
|
|
1737
|
-
z:
|
|
1738
|
-
node.z *
|
|
1739
|
-
ratio,
|
|
1056
|
+
z: node.z * ratio,
|
|
1740
1057
|
},
|
|
1741
1058
|
|
|
1742
1059
|
node,
|
|
1743
1060
|
|
|
1744
1061
|
800
|
|
1745
1062
|
);
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
state.edge =
|
|
1770
|
-
"";
|
|
1771
|
-
|
|
1772
|
-
applyFilters();
|
|
1773
|
-
|
|
1774
|
-
setTimeout(
|
|
1775
|
-
() => {
|
|
1776
|
-
const node =
|
|
1777
|
-
state.graph
|
|
1778
|
-
.graphData()
|
|
1779
|
-
.nodes
|
|
1780
|
-
.find(
|
|
1781
|
-
item =>
|
|
1782
|
-
item.id ===
|
|
1783
|
-
id
|
|
1784
|
-
);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function jumpToNode(id) {
|
|
1066
|
+
/*
|
|
1067
|
+
* Clear filters if destination is hidden.
|
|
1068
|
+
*/
|
|
1069
|
+
searchEl.value = '';
|
|
1070
|
+
|
|
1071
|
+
clusterEl.value = '';
|
|
1072
|
+
|
|
1073
|
+
edgeEl.value = '';
|
|
1074
|
+
|
|
1075
|
+
state.search = '';
|
|
1076
|
+
|
|
1077
|
+
state.cluster = '';
|
|
1078
|
+
|
|
1079
|
+
state.edge = '';
|
|
1080
|
+
|
|
1081
|
+
applyFilters();
|
|
1082
|
+
|
|
1083
|
+
setTimeout(() => {
|
|
1084
|
+
const node = state.graph.graphData().nodes.find((item) => item.id === id);
|
|
1785
1085
|
|
|
1786
1086
|
if (!node) {
|
|
1787
1087
|
return;
|
|
1788
1088
|
}
|
|
1789
1089
|
|
|
1790
|
-
showDetails(
|
|
1791
|
-
node
|
|
1792
|
-
);
|
|
1090
|
+
showDetails(node);
|
|
1793
1091
|
|
|
1794
|
-
flyToNode(
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
},
|
|
1798
|
-
100
|
|
1799
|
-
);
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
function showDetails(
|
|
1803
|
-
node
|
|
1804
|
-
) {
|
|
1805
|
-
if (
|
|
1806
|
-
node.viewKind ===
|
|
1807
|
-
"cluster"
|
|
1808
|
-
) {
|
|
1809
|
-
void loadProject(
|
|
1810
|
-
state.project,
|
|
1811
|
-
{
|
|
1812
|
-
mode:
|
|
1813
|
-
"files",
|
|
1092
|
+
flyToNode(node);
|
|
1093
|
+
}, 100);
|
|
1094
|
+
}
|
|
1814
1095
|
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
);
|
|
1096
|
+
function showDetails(node) {
|
|
1097
|
+
if (node.viewKind === 'cluster') {
|
|
1098
|
+
void loadProject(state.project, {
|
|
1099
|
+
mode: 'files',
|
|
1820
1100
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1101
|
+
cluster: node.clusterLabel || node.name,
|
|
1102
|
+
});
|
|
1823
1103
|
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
"file"
|
|
1827
|
-
) {
|
|
1828
|
-
void loadProject(
|
|
1829
|
-
state.project,
|
|
1830
|
-
{
|
|
1831
|
-
mode:
|
|
1832
|
-
"symbols",
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1833
1106
|
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
);
|
|
1107
|
+
if (node.viewKind === 'file') {
|
|
1108
|
+
void loadProject(state.project, {
|
|
1109
|
+
mode: 'symbols',
|
|
1838
1110
|
|
|
1839
|
-
|
|
1840
|
-
|
|
1111
|
+
file: node.filePath,
|
|
1112
|
+
});
|
|
1841
1113
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1844
1116
|
|
|
1845
|
-
|
|
1846
|
-
.classList
|
|
1847
|
-
.add(
|
|
1848
|
-
"open"
|
|
1849
|
-
);
|
|
1117
|
+
state.selected = node;
|
|
1850
1118
|
|
|
1851
|
-
|
|
1852
|
-
relatedNodes(
|
|
1853
|
-
node.id,
|
|
1854
|
-
"incoming",
|
|
1855
|
-
CALL_RELATIONS
|
|
1856
|
-
);
|
|
1119
|
+
detailsEl.classList.add('open');
|
|
1857
1120
|
|
|
1858
|
-
|
|
1859
|
-
relatedNodes(
|
|
1860
|
-
node.id,
|
|
1861
|
-
"outgoing",
|
|
1862
|
-
CALL_RELATIONS
|
|
1863
|
-
);
|
|
1121
|
+
const callers = relatedNodes(node.id, 'incoming', CALL_RELATIONS);
|
|
1864
1122
|
|
|
1865
|
-
|
|
1866
|
-
relatedNodes(
|
|
1867
|
-
node.id,
|
|
1868
|
-
"outgoing",
|
|
1869
|
-
DEPENDENCY_RELATIONS
|
|
1870
|
-
);
|
|
1123
|
+
const callees = relatedNodes(node.id, 'outgoing', CALL_RELATIONS);
|
|
1871
1124
|
|
|
1872
|
-
|
|
1873
|
-
relatedNodes(
|
|
1874
|
-
node.id,
|
|
1875
|
-
"incoming",
|
|
1876
|
-
DEPENDENCY_RELATIONS
|
|
1877
|
-
);
|
|
1125
|
+
const dependencies = relatedNodes(node.id, 'outgoing', DEPENDENCY_RELATIONS);
|
|
1878
1126
|
|
|
1879
|
-
|
|
1880
|
-
blastRadius(
|
|
1881
|
-
node.id,
|
|
1882
|
-
3
|
|
1883
|
-
);
|
|
1127
|
+
const dependents = relatedNodes(node.id, 'incoming', DEPENDENCY_RELATIONS);
|
|
1884
1128
|
|
|
1885
|
-
|
|
1886
|
-
null;
|
|
1887
|
-
|
|
1888
|
-
if (
|
|
1889
|
-
state.pathStart &&
|
|
1890
|
-
state.pathStart !==
|
|
1891
|
-
node.id
|
|
1892
|
-
) {
|
|
1893
|
-
path =
|
|
1894
|
-
shortestPath(
|
|
1895
|
-
state.pathStart,
|
|
1896
|
-
node.id
|
|
1897
|
-
);
|
|
1129
|
+
const blast = blastRadius(node.id, 3);
|
|
1898
1130
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
);
|
|
1903
|
-
|
|
1904
|
-
|
|
1131
|
+
let path = null;
|
|
1132
|
+
|
|
1133
|
+
if (state.pathStart && state.pathStart !== node.id) {
|
|
1134
|
+
path = shortestPath(state.pathStart, node.id);
|
|
1135
|
+
|
|
1136
|
+
if (path) {
|
|
1137
|
+
highlightPath(path);
|
|
1138
|
+
} else {
|
|
1139
|
+
resetPathHighlight();
|
|
1140
|
+
}
|
|
1905
1141
|
}
|
|
1906
|
-
}
|
|
1907
1142
|
|
|
1908
|
-
|
|
1909
|
-
state.pathStart
|
|
1910
|
-
? rawNode(
|
|
1911
|
-
state.pathStart
|
|
1912
|
-
)
|
|
1913
|
-
: null;
|
|
1143
|
+
const pathStartNode = state.pathStart ? rawNode(state.pathStart) : null;
|
|
1914
1144
|
|
|
1915
|
-
|
|
1145
|
+
detailsEl.innerHTML = `
|
|
1916
1146
|
<button
|
|
1917
1147
|
class="details-close"
|
|
1918
1148
|
id="details-close"
|
|
@@ -1923,47 +1153,36 @@
|
|
|
1923
1153
|
</button>
|
|
1924
1154
|
|
|
1925
1155
|
<div class="detail-title">
|
|
1926
|
-
${escapeHtml(
|
|
1927
|
-
node.qualifiedName ||
|
|
1928
|
-
node.name
|
|
1929
|
-
)}
|
|
1156
|
+
${escapeHtml(node.qualifiedName || node.name)}
|
|
1930
1157
|
</div>
|
|
1931
1158
|
|
|
1932
1159
|
<div class="path">
|
|
1933
|
-
${escapeHtml(
|
|
1934
|
-
node.filePath
|
|
1935
|
-
)}
|
|
1160
|
+
${escapeHtml(node.filePath)}
|
|
1936
1161
|
</div>
|
|
1937
1162
|
|
|
1938
1163
|
<div>
|
|
1939
1164
|
<span class="tag">
|
|
1940
|
-
${escapeHtml(
|
|
1941
|
-
node.type
|
|
1942
|
-
)}
|
|
1165
|
+
${escapeHtml(node.type)}
|
|
1943
1166
|
</span>
|
|
1944
1167
|
|
|
1945
1168
|
${
|
|
1946
1169
|
node.clusterLabel
|
|
1947
1170
|
? `
|
|
1948
1171
|
<span class="tag">
|
|
1949
|
-
${escapeHtml(
|
|
1950
|
-
node.clusterLabel
|
|
1951
|
-
)}
|
|
1172
|
+
${escapeHtml(node.clusterLabel)}
|
|
1952
1173
|
</span>
|
|
1953
1174
|
`
|
|
1954
|
-
:
|
|
1175
|
+
: ''
|
|
1955
1176
|
}
|
|
1956
1177
|
|
|
1957
1178
|
${
|
|
1958
1179
|
node.layer
|
|
1959
1180
|
? `
|
|
1960
1181
|
<span class="tag">
|
|
1961
|
-
${escapeHtml(
|
|
1962
|
-
node.layer
|
|
1963
|
-
)}
|
|
1182
|
+
${escapeHtml(node.layer)}
|
|
1964
1183
|
</span>
|
|
1965
1184
|
`
|
|
1966
|
-
:
|
|
1185
|
+
: ''
|
|
1967
1186
|
}
|
|
1968
1187
|
|
|
1969
1188
|
${
|
|
@@ -1971,13 +1190,10 @@
|
|
|
1971
1190
|
? `
|
|
1972
1191
|
<span class="tag">
|
|
1973
1192
|
dead-code:
|
|
1974
|
-
${escapeHtml(
|
|
1975
|
-
node.deadCode
|
|
1976
|
-
.confidence
|
|
1977
|
-
)}
|
|
1193
|
+
${escapeHtml(node.deadCode.confidence)}
|
|
1978
1194
|
</span>
|
|
1979
1195
|
`
|
|
1980
|
-
:
|
|
1196
|
+
: ''
|
|
1981
1197
|
}
|
|
1982
1198
|
</div>
|
|
1983
1199
|
|
|
@@ -2045,39 +1261,19 @@
|
|
|
2045
1261
|
${
|
|
2046
1262
|
blast.impacted.length
|
|
2047
1263
|
? relationHtml(
|
|
2048
|
-
|
|
2049
|
-
blast.impacted
|
|
2050
|
-
.slice(
|
|
2051
|
-
0,
|
|
2052
|
-
8
|
|
2053
|
-
)
|
|
2054
|
-
.map(
|
|
2055
|
-
item =>
|
|
2056
|
-
item.node
|
|
2057
|
-
)
|
|
1264
|
+
'Top Impacted',
|
|
1265
|
+
blast.impacted.slice(0, 8).map((item) => item.node)
|
|
2058
1266
|
)
|
|
2059
|
-
:
|
|
1267
|
+
: ''
|
|
2060
1268
|
}
|
|
2061
1269
|
|
|
2062
|
-
${relationHtml(
|
|
2063
|
-
"Callers",
|
|
2064
|
-
callers
|
|
2065
|
-
)}
|
|
1270
|
+
${relationHtml('Callers', callers)}
|
|
2066
1271
|
|
|
2067
|
-
${relationHtml(
|
|
2068
|
-
"Callees",
|
|
2069
|
-
callees
|
|
2070
|
-
)}
|
|
1272
|
+
${relationHtml('Callees', callees)}
|
|
2071
1273
|
|
|
2072
|
-
${relationHtml(
|
|
2073
|
-
"Dependencies",
|
|
2074
|
-
dependencies
|
|
2075
|
-
)}
|
|
1274
|
+
${relationHtml('Dependencies', dependencies)}
|
|
2076
1275
|
|
|
2077
|
-
${relationHtml(
|
|
2078
|
-
"Dependents",
|
|
2079
|
-
dependents
|
|
2080
|
-
)}
|
|
1276
|
+
${relationHtml('Dependents', dependents)}
|
|
2081
1277
|
|
|
2082
1278
|
<div class="section">
|
|
2083
1279
|
<div class="section-title">
|
|
@@ -2096,11 +1292,7 @@
|
|
|
2096
1292
|
<div class="path">
|
|
2097
1293
|
From:
|
|
2098
1294
|
${escapeHtml(
|
|
2099
|
-
pathStartNode
|
|
2100
|
-
?.qualifiedName ||
|
|
2101
|
-
pathStartNode
|
|
2102
|
-
?.name ||
|
|
2103
|
-
state.pathStart
|
|
1295
|
+
pathStartNode?.qualifiedName || pathStartNode?.name || state.pathStart
|
|
2104
1296
|
)}
|
|
2105
1297
|
</div>
|
|
2106
1298
|
`
|
|
@@ -2111,12 +1303,7 @@
|
|
|
2111
1303
|
id="path-start"
|
|
2112
1304
|
type="button"
|
|
2113
1305
|
>
|
|
2114
|
-
${
|
|
2115
|
-
state.pathStart ===
|
|
2116
|
-
node.id
|
|
2117
|
-
? "Start selected"
|
|
2118
|
-
: "Set path start"
|
|
2119
|
-
}
|
|
1306
|
+
${state.pathStart === node.id ? 'Start selected' : 'Set path start'}
|
|
2120
1307
|
</button>
|
|
2121
1308
|
|
|
2122
1309
|
${
|
|
@@ -2129,333 +1316,159 @@
|
|
|
2129
1316
|
Clear path
|
|
2130
1317
|
</button>
|
|
2131
1318
|
`
|
|
2132
|
-
:
|
|
1319
|
+
: ''
|
|
2133
1320
|
}
|
|
2134
1321
|
</div>
|
|
2135
1322
|
|
|
2136
1323
|
${
|
|
2137
|
-
state.pathStart &&
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
? (
|
|
2141
|
-
path
|
|
2142
|
-
? `
|
|
1324
|
+
state.pathStart && state.pathStart !== node.id
|
|
1325
|
+
? path
|
|
1326
|
+
? `
|
|
2143
1327
|
<div class="path-chain">
|
|
2144
|
-
${
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
id
|
|
2150
|
-
);
|
|
2151
|
-
|
|
2152
|
-
return `
|
|
1328
|
+
${path
|
|
1329
|
+
.map((id) => {
|
|
1330
|
+
const item = rawNode(id);
|
|
1331
|
+
|
|
1332
|
+
return `
|
|
2153
1333
|
<button
|
|
2154
1334
|
class="path-node"
|
|
2155
|
-
data-jump-node="${escapeHtml(
|
|
2156
|
-
id
|
|
2157
|
-
)}"
|
|
1335
|
+
data-jump-node="${escapeHtml(id)}"
|
|
2158
1336
|
>
|
|
2159
|
-
${escapeHtml(
|
|
2160
|
-
item?.qualifiedName ||
|
|
2161
|
-
item?.name ||
|
|
2162
|
-
id
|
|
2163
|
-
)}
|
|
1337
|
+
${escapeHtml(item?.qualifiedName || item?.name || id)}
|
|
2164
1338
|
</button>
|
|
2165
1339
|
`;
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
}
|
|
1340
|
+
})
|
|
1341
|
+
.join('')}
|
|
2169
1342
|
</div>
|
|
2170
1343
|
`
|
|
2171
|
-
|
|
1344
|
+
: `
|
|
2172
1345
|
<div class="relation-more">
|
|
2173
1346
|
Không tìm thấy directed path
|
|
2174
1347
|
trong 10 bước.
|
|
2175
1348
|
</div>
|
|
2176
1349
|
`
|
|
2177
|
-
|
|
2178
|
-
: ""
|
|
1350
|
+
: ''
|
|
2179
1351
|
}
|
|
2180
1352
|
</div>
|
|
2181
1353
|
`;
|
|
2182
1354
|
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
)
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
event => {
|
|
1355
|
+
document.getElementById('details-close')?.addEventListener('click', (event) => {
|
|
1356
|
+
event.stopPropagation();
|
|
1357
|
+
closeDetails();
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
detailsEl.querySelectorAll('[data-jump-node]').forEach((element) => {
|
|
1361
|
+
element.addEventListener('click', (event) => {
|
|
2190
1362
|
event.stopPropagation();
|
|
2191
|
-
closeDetails();
|
|
2192
|
-
}
|
|
2193
|
-
);
|
|
2194
1363
|
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
)
|
|
2199
|
-
.forEach(
|
|
2200
|
-
element => {
|
|
2201
|
-
element
|
|
2202
|
-
.addEventListener(
|
|
2203
|
-
"click",
|
|
2204
|
-
event => {
|
|
2205
|
-
event.stopPropagation();
|
|
2206
|
-
|
|
2207
|
-
jumpToNode(
|
|
2208
|
-
element.dataset
|
|
2209
|
-
.jumpNode
|
|
2210
|
-
);
|
|
2211
|
-
}
|
|
2212
|
-
);
|
|
2213
|
-
}
|
|
2214
|
-
);
|
|
1364
|
+
jumpToNode(element.dataset.jumpNode);
|
|
1365
|
+
});
|
|
1366
|
+
});
|
|
2215
1367
|
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
"path-start"
|
|
2219
|
-
)
|
|
2220
|
-
?.addEventListener(
|
|
2221
|
-
"click",
|
|
2222
|
-
event => {
|
|
2223
|
-
event.stopPropagation();
|
|
1368
|
+
document.getElementById('path-start')?.addEventListener('click', (event) => {
|
|
1369
|
+
event.stopPropagation();
|
|
2224
1370
|
|
|
2225
|
-
|
|
2226
|
-
node.id;
|
|
1371
|
+
state.pathStart = node.id;
|
|
2227
1372
|
|
|
2228
|
-
|
|
1373
|
+
resetPathHighlight();
|
|
2229
1374
|
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
);
|
|
2233
|
-
}
|
|
2234
|
-
);
|
|
1375
|
+
showDetails(node);
|
|
1376
|
+
});
|
|
2235
1377
|
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
"path-clear"
|
|
2239
|
-
)
|
|
2240
|
-
?.addEventListener(
|
|
2241
|
-
"click",
|
|
2242
|
-
event => {
|
|
2243
|
-
event.stopPropagation();
|
|
1378
|
+
document.getElementById('path-clear')?.addEventListener('click', (event) => {
|
|
1379
|
+
event.stopPropagation();
|
|
2244
1380
|
|
|
2245
|
-
|
|
2246
|
-
null;
|
|
1381
|
+
state.pathStart = null;
|
|
2247
1382
|
|
|
2248
|
-
|
|
1383
|
+
resetPathHighlight();
|
|
2249
1384
|
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
}
|
|
2254
|
-
);
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
function populateFilters() {
|
|
2258
|
-
clusterEl.innerHTML =
|
|
2259
|
-
'<option value="">All subsystems</option>';
|
|
2260
|
-
|
|
2261
|
-
edgeEl.innerHTML =
|
|
2262
|
-
'<option value="">All relations</option>';
|
|
2263
|
-
|
|
2264
|
-
const clusters =
|
|
2265
|
-
[
|
|
2266
|
-
...new Set(
|
|
2267
|
-
state.raw.nodes
|
|
2268
|
-
.map(
|
|
2269
|
-
node =>
|
|
2270
|
-
node.clusterLabel
|
|
2271
|
-
)
|
|
2272
|
-
.filter(Boolean)
|
|
2273
|
-
)
|
|
2274
|
-
]
|
|
2275
|
-
.sort();
|
|
2276
|
-
|
|
2277
|
-
for (
|
|
2278
|
-
const value
|
|
2279
|
-
of clusters
|
|
2280
|
-
) {
|
|
2281
|
-
const option =
|
|
2282
|
-
document.createElement(
|
|
2283
|
-
"option"
|
|
2284
|
-
);
|
|
1385
|
+
showDetails(node);
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
2285
1388
|
|
|
2286
|
-
|
|
2287
|
-
|
|
1389
|
+
function populateFilters() {
|
|
1390
|
+
clusterEl.innerHTML = '<option value="">All subsystems</option>';
|
|
2288
1391
|
|
|
2289
|
-
|
|
2290
|
-
value;
|
|
1392
|
+
edgeEl.innerHTML = '<option value="">All relations</option>';
|
|
2291
1393
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
);
|
|
2295
|
-
}
|
|
1394
|
+
const clusters = [
|
|
1395
|
+
...new Set(state.raw.nodes.map((node) => node.clusterLabel).filter(Boolean)),
|
|
1396
|
+
].sort();
|
|
2296
1397
|
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
...new Set(
|
|
2300
|
-
state.raw.links
|
|
2301
|
-
.map(
|
|
2302
|
-
link =>
|
|
2303
|
-
link.type
|
|
2304
|
-
)
|
|
2305
|
-
)
|
|
2306
|
-
]
|
|
2307
|
-
.sort();
|
|
2308
|
-
|
|
2309
|
-
for (
|
|
2310
|
-
const value
|
|
2311
|
-
of edges
|
|
2312
|
-
) {
|
|
2313
|
-
const option =
|
|
2314
|
-
document.createElement(
|
|
2315
|
-
"option"
|
|
2316
|
-
);
|
|
1398
|
+
for (const value of clusters) {
|
|
1399
|
+
const option = document.createElement('option');
|
|
2317
1400
|
|
|
2318
|
-
|
|
2319
|
-
value;
|
|
1401
|
+
option.value = value;
|
|
2320
1402
|
|
|
2321
|
-
|
|
2322
|
-
value;
|
|
1403
|
+
option.textContent = value;
|
|
2323
1404
|
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
);
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
function filteredData() {
|
|
2331
|
-
const search =
|
|
2332
|
-
state.search
|
|
2333
|
-
.trim()
|
|
2334
|
-
.toLowerCase();
|
|
2335
|
-
|
|
2336
|
-
let nodes =
|
|
2337
|
-
state.raw.nodes;
|
|
2338
|
-
|
|
2339
|
-
if (
|
|
2340
|
-
state.cluster
|
|
2341
|
-
) {
|
|
2342
|
-
nodes =
|
|
2343
|
-
nodes.filter(
|
|
2344
|
-
node =>
|
|
2345
|
-
node.clusterLabel ===
|
|
2346
|
-
state.cluster
|
|
2347
|
-
);
|
|
2348
|
-
}
|
|
1405
|
+
clusterEl.appendChild(option);
|
|
1406
|
+
}
|
|
2349
1407
|
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
node.qualifiedName
|
|
2363
|
-
?.toLowerCase()
|
|
2364
|
-
.includes(
|
|
2365
|
-
search
|
|
2366
|
-
) ||
|
|
2367
|
-
|
|
2368
|
-
node.filePath
|
|
2369
|
-
?.toLowerCase()
|
|
2370
|
-
.includes(
|
|
2371
|
-
search
|
|
2372
|
-
)
|
|
2373
|
-
);
|
|
1408
|
+
const edges = [...new Set(state.raw.links.map((link) => link.type))].sort();
|
|
1409
|
+
|
|
1410
|
+
for (const value of edges) {
|
|
1411
|
+
const option = document.createElement('option');
|
|
1412
|
+
|
|
1413
|
+
option.value = value;
|
|
1414
|
+
|
|
1415
|
+
option.textContent = value;
|
|
1416
|
+
|
|
1417
|
+
edgeEl.appendChild(option);
|
|
1418
|
+
}
|
|
2374
1419
|
}
|
|
2375
1420
|
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
nodes.map(
|
|
2379
|
-
node =>
|
|
2380
|
-
node.id
|
|
2381
|
-
)
|
|
2382
|
-
);
|
|
1421
|
+
function filteredData() {
|
|
1422
|
+
const search = state.search.trim().toLowerCase();
|
|
2383
1423
|
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
typeof link.source ===
|
|
2390
|
-
"object"
|
|
2391
|
-
? link.source.id
|
|
2392
|
-
: link.source;
|
|
2393
|
-
|
|
2394
|
-
const target =
|
|
2395
|
-
typeof link.target ===
|
|
2396
|
-
"object"
|
|
2397
|
-
? link.target.id
|
|
2398
|
-
: link.target;
|
|
2399
|
-
|
|
2400
|
-
return (
|
|
2401
|
-
ids.has(
|
|
2402
|
-
source
|
|
2403
|
-
) &&
|
|
2404
|
-
ids.has(
|
|
2405
|
-
target
|
|
2406
|
-
)
|
|
2407
|
-
);
|
|
2408
|
-
}
|
|
2409
|
-
);
|
|
1424
|
+
let nodes = state.raw.nodes;
|
|
1425
|
+
|
|
1426
|
+
if (state.cluster) {
|
|
1427
|
+
nodes = nodes.filter((node) => node.clusterLabel === state.cluster);
|
|
1428
|
+
}
|
|
2410
1429
|
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
link.type ===
|
|
2418
|
-
state.edge
|
|
1430
|
+
if (search) {
|
|
1431
|
+
nodes = nodes.filter(
|
|
1432
|
+
(node) =>
|
|
1433
|
+
node.name?.toLowerCase().includes(search) ||
|
|
1434
|
+
node.qualifiedName?.toLowerCase().includes(search) ||
|
|
1435
|
+
node.filePath?.toLowerCase().includes(search)
|
|
2419
1436
|
);
|
|
2420
|
-
|
|
1437
|
+
}
|
|
2421
1438
|
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
),
|
|
2429
|
-
|
|
2430
|
-
links:
|
|
2431
|
-
links.map(
|
|
2432
|
-
link => ({
|
|
2433
|
-
...link,
|
|
2434
|
-
|
|
2435
|
-
source:
|
|
2436
|
-
typeof link.source ===
|
|
2437
|
-
"object"
|
|
2438
|
-
? link.source.id
|
|
2439
|
-
: link.source,
|
|
2440
|
-
|
|
2441
|
-
target:
|
|
2442
|
-
typeof link.target ===
|
|
2443
|
-
"object"
|
|
2444
|
-
? link.target.id
|
|
2445
|
-
: link.target,
|
|
2446
|
-
})
|
|
2447
|
-
),
|
|
2448
|
-
};
|
|
2449
|
-
}
|
|
1439
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
1440
|
+
|
|
1441
|
+
let links = state.raw.links.filter((link) => {
|
|
1442
|
+
const source = typeof link.source === 'object' ? link.source.id : link.source;
|
|
1443
|
+
|
|
1444
|
+
const target = typeof link.target === 'object' ? link.target.id : link.target;
|
|
2450
1445
|
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
) {
|
|
2454
|
-
const dashboard =
|
|
2455
|
-
state.raw.dashboard ||
|
|
2456
|
-
{};
|
|
1446
|
+
return ids.has(source) && ids.has(target);
|
|
1447
|
+
});
|
|
2457
1448
|
|
|
2458
|
-
|
|
1449
|
+
if (state.edge) {
|
|
1450
|
+
links = links.filter((link) => link.type === state.edge);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
return {
|
|
1454
|
+
nodes: nodes.map((node) => ({
|
|
1455
|
+
...node,
|
|
1456
|
+
})),
|
|
1457
|
+
|
|
1458
|
+
links: links.map((link) => ({
|
|
1459
|
+
...link,
|
|
1460
|
+
|
|
1461
|
+
source: typeof link.source === 'object' ? link.source.id : link.source,
|
|
1462
|
+
|
|
1463
|
+
target: typeof link.target === 'object' ? link.target.id : link.target,
|
|
1464
|
+
})),
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function renderStats(data) {
|
|
1469
|
+
const dashboard = state.raw.dashboard || {};
|
|
1470
|
+
|
|
1471
|
+
statsEl.innerHTML = `
|
|
2459
1472
|
<div>
|
|
2460
1473
|
<strong>
|
|
2461
1474
|
${data.nodes.length}
|
|
@@ -2485,552 +1498,273 @@
|
|
|
2485
1498
|
</div>
|
|
2486
1499
|
|
|
2487
1500
|
<div class="status">
|
|
2488
|
-
${escapeHtml(
|
|
2489
|
-
dashboard.name ||
|
|
2490
|
-
state.project
|
|
2491
|
-
)}
|
|
1501
|
+
${escapeHtml(dashboard.name || state.project)}
|
|
2492
1502
|
|
|
2493
|
-
${
|
|
2494
|
-
dashboard.hasArchitecture
|
|
2495
|
-
? " · architecture"
|
|
2496
|
-
: ""
|
|
2497
|
-
}
|
|
1503
|
+
${dashboard.hasArchitecture ? ' · architecture' : ''}
|
|
2498
1504
|
|
|
2499
|
-
${
|
|
2500
|
-
dashboard.hasAnalysis
|
|
2501
|
-
? " · analysis"
|
|
2502
|
-
: ""
|
|
2503
|
-
}
|
|
1505
|
+
${dashboard.hasAnalysis ? ' · analysis' : ''}
|
|
2504
1506
|
</div>
|
|
2505
1507
|
`;
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
function applyFilters() {
|
|
2509
|
-
const data =
|
|
2510
|
-
filteredData();
|
|
2511
|
-
|
|
2512
|
-
state.graph.graphData(
|
|
2513
|
-
data
|
|
2514
|
-
);
|
|
2515
|
-
|
|
2516
|
-
renderStats(
|
|
2517
|
-
data
|
|
2518
|
-
);
|
|
2519
|
-
}
|
|
2520
|
-
|
|
2521
|
-
async function loadProject(
|
|
2522
|
-
project,
|
|
2523
|
-
options = {}
|
|
2524
|
-
) {
|
|
2525
|
-
closeDetails();
|
|
2526
|
-
|
|
2527
|
-
const mode =
|
|
2528
|
-
options.mode ||
|
|
2529
|
-
"overview";
|
|
2530
|
-
|
|
2531
|
-
let loading =
|
|
2532
|
-
document.getElementById(
|
|
2533
|
-
"loading"
|
|
2534
|
-
);
|
|
1508
|
+
}
|
|
2535
1509
|
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
document.createElement(
|
|
2539
|
-
"div"
|
|
2540
|
-
);
|
|
1510
|
+
function applyFilters() {
|
|
1511
|
+
const data = filteredData();
|
|
2541
1512
|
|
|
2542
|
-
|
|
2543
|
-
"loading";
|
|
1513
|
+
state.graph.graphData(data);
|
|
2544
1514
|
|
|
2545
|
-
|
|
2546
|
-
.appendChild(
|
|
2547
|
-
loading
|
|
2548
|
-
);
|
|
1515
|
+
renderStats(data);
|
|
2549
1516
|
}
|
|
2550
1517
|
|
|
2551
|
-
|
|
2552
|
-
|
|
1518
|
+
async function loadProject(project, options = {}) {
|
|
1519
|
+
closeDetails();
|
|
2553
1520
|
|
|
2554
|
-
|
|
2555
|
-
new URLSearchParams();
|
|
1521
|
+
const mode = options.mode || 'overview';
|
|
2556
1522
|
|
|
2557
|
-
|
|
2558
|
-
"project",
|
|
2559
|
-
project
|
|
2560
|
-
);
|
|
1523
|
+
let loading = document.getElementById('loading');
|
|
2561
1524
|
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
mode
|
|
2565
|
-
);
|
|
1525
|
+
if (!loading) {
|
|
1526
|
+
loading = document.createElement('div');
|
|
2566
1527
|
|
|
2567
|
-
|
|
2568
|
-
options.cluster
|
|
2569
|
-
) {
|
|
2570
|
-
params.set(
|
|
2571
|
-
"cluster",
|
|
2572
|
-
options.cluster
|
|
2573
|
-
);
|
|
2574
|
-
}
|
|
1528
|
+
loading.id = 'loading';
|
|
2575
1529
|
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
) {
|
|
2579
|
-
params.set(
|
|
2580
|
-
"file",
|
|
2581
|
-
options.file
|
|
2582
|
-
);
|
|
2583
|
-
}
|
|
1530
|
+
document.body.appendChild(loading);
|
|
1531
|
+
}
|
|
2584
1532
|
|
|
2585
|
-
|
|
2586
|
-
await graphApiFetch(
|
|
2587
|
-
`/api/graph?${params.toString()}`
|
|
2588
|
-
);
|
|
1533
|
+
loading.textContent = `Loading ${project} · ${mode}…`;
|
|
2589
1534
|
|
|
2590
|
-
|
|
2591
|
-
!response.ok
|
|
2592
|
-
) {
|
|
2593
|
-
const error =
|
|
2594
|
-
await response
|
|
2595
|
-
.json()
|
|
2596
|
-
.catch(
|
|
2597
|
-
() => null
|
|
2598
|
-
);
|
|
2599
|
-
|
|
2600
|
-
throw new Error(
|
|
2601
|
-
error?.error ||
|
|
2602
|
-
"Unable to load graph"
|
|
2603
|
-
);
|
|
2604
|
-
}
|
|
1535
|
+
const params = new URLSearchParams();
|
|
2605
1536
|
|
|
2606
|
-
|
|
2607
|
-
project;
|
|
1537
|
+
params.set('project', project);
|
|
2608
1538
|
|
|
2609
|
-
|
|
2610
|
-
mode;
|
|
1539
|
+
params.set('mode', mode);
|
|
2611
1540
|
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
1541
|
+
if (options.cluster) {
|
|
1542
|
+
params.set('cluster', options.cluster);
|
|
1543
|
+
}
|
|
2615
1544
|
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
1545
|
+
if (options.file) {
|
|
1546
|
+
params.set('file', options.file);
|
|
1547
|
+
}
|
|
2619
1548
|
|
|
2620
|
-
|
|
2621
|
-
await response.json();
|
|
1549
|
+
const response = await graphApiFetch(`/api/graph?${params.toString()}`);
|
|
2622
1550
|
|
|
2623
|
-
|
|
2624
|
-
|
|
1551
|
+
if (!response.ok) {
|
|
1552
|
+
const error = await response.json().catch(() => null);
|
|
2625
1553
|
|
|
2626
|
-
|
|
2627
|
-
|
|
1554
|
+
throw new Error(error?.error || 'Unable to load graph');
|
|
1555
|
+
}
|
|
2628
1556
|
|
|
2629
|
-
|
|
2630
|
-
"";
|
|
1557
|
+
state.project = project;
|
|
2631
1558
|
|
|
2632
|
-
|
|
2633
|
-
null;
|
|
1559
|
+
state.viewMode = mode;
|
|
2634
1560
|
|
|
2635
|
-
|
|
2636
|
-
"";
|
|
1561
|
+
state.viewCluster = options.cluster || '';
|
|
2637
1562
|
|
|
2638
|
-
|
|
2639
|
-
"";
|
|
1563
|
+
state.viewFile = options.file || '';
|
|
2640
1564
|
|
|
2641
|
-
|
|
2642
|
-
"";
|
|
1565
|
+
state.raw = await response.json();
|
|
2643
1566
|
|
|
2644
|
-
|
|
1567
|
+
state.search = '';
|
|
2645
1568
|
|
|
2646
|
-
|
|
2647
|
-
filteredData();
|
|
1569
|
+
state.cluster = '';
|
|
2648
1570
|
|
|
2649
|
-
|
|
2650
|
-
!state.graph
|
|
2651
|
-
) {
|
|
2652
|
-
state.graph =
|
|
2653
|
-
ForceGraph3D()(
|
|
2654
|
-
graphEl
|
|
2655
|
-
)
|
|
2656
|
-
.backgroundColor(
|
|
2657
|
-
"#050608"
|
|
2658
|
-
)
|
|
1571
|
+
state.edge = '';
|
|
2659
1572
|
|
|
2660
|
-
|
|
2661
|
-
node =>
|
|
2662
|
-
`${
|
|
2663
|
-
node.qualifiedName ||
|
|
2664
|
-
node.name
|
|
2665
|
-
}\n${
|
|
2666
|
-
node.filePath ||
|
|
2667
|
-
""
|
|
2668
|
-
}`
|
|
2669
|
-
)
|
|
1573
|
+
state.pathStart = null;
|
|
2670
1574
|
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
1575
|
+
searchEl.value = '';
|
|
1576
|
+
|
|
1577
|
+
clusterEl.value = '';
|
|
1578
|
+
|
|
1579
|
+
edgeEl.value = '';
|
|
1580
|
+
|
|
1581
|
+
populateFilters();
|
|
1582
|
+
|
|
1583
|
+
const data = filteredData();
|
|
1584
|
+
|
|
1585
|
+
if (!state.graph) {
|
|
1586
|
+
state.graph = ForceGraph3D()(graphEl)
|
|
1587
|
+
.backgroundColor('#050608')
|
|
1588
|
+
|
|
1589
|
+
.nodeLabel((node) => `${node.qualifiedName || node.name}\n${node.filePath || ''}`)
|
|
1590
|
+
|
|
1591
|
+
.nodeColor((node) => nodeColor(node))
|
|
2677
1592
|
|
|
2678
1593
|
.nodeVal(
|
|
2679
|
-
node =>
|
|
2680
|
-
2 +
|
|
2681
|
-
Math.min(
|
|
2682
|
-
10,
|
|
2683
|
-
Math.log2(
|
|
2684
|
-
1 +
|
|
2685
|
-
(
|
|
2686
|
-
node.incoming ||
|
|
2687
|
-
0
|
|
2688
|
-
) +
|
|
2689
|
-
(
|
|
2690
|
-
node.outgoing ||
|
|
2691
|
-
0
|
|
2692
|
-
)
|
|
2693
|
-
)
|
|
2694
|
-
)
|
|
1594
|
+
(node) => 2 + Math.min(10, Math.log2(1 + (node.incoming || 0) + (node.outgoing || 0)))
|
|
2695
1595
|
)
|
|
2696
1596
|
|
|
2697
|
-
.linkColor(
|
|
2698
|
-
link =>
|
|
2699
|
-
linkColor(
|
|
2700
|
-
link
|
|
2701
|
-
)
|
|
2702
|
-
)
|
|
1597
|
+
.linkColor((link) => linkColor(link))
|
|
2703
1598
|
|
|
2704
|
-
.linkOpacity(
|
|
2705
|
-
.38
|
|
2706
|
-
)
|
|
1599
|
+
.linkOpacity(0.38)
|
|
2707
1600
|
|
|
2708
|
-
.linkWidth(
|
|
2709
|
-
link =>
|
|
2710
|
-
link.type ===
|
|
2711
|
-
"CALL_REFERENCE"
|
|
2712
|
-
? 1
|
|
2713
|
-
: .4
|
|
2714
|
-
)
|
|
1601
|
+
.linkWidth((link) => (link.type === 'CALL_REFERENCE' ? 1 : 0.4))
|
|
2715
1602
|
|
|
2716
1603
|
/*
|
|
2717
1604
|
* Particles across 200k relationships are extremely
|
|
2718
1605
|
* expensive on mobile GPUs. Keep them disabled.
|
|
2719
1606
|
*/
|
|
2720
|
-
.linkDirectionalParticles(
|
|
2721
|
-
0
|
|
2722
|
-
)
|
|
1607
|
+
.linkDirectionalParticles(0)
|
|
2723
1608
|
|
|
2724
|
-
.onBackgroundClick(
|
|
2725
|
-
()
|
|
2726
|
-
|
|
2727
|
-
}
|
|
2728
|
-
)
|
|
1609
|
+
.onBackgroundClick(() => {
|
|
1610
|
+
closeDetails();
|
|
1611
|
+
})
|
|
2729
1612
|
|
|
2730
|
-
.onNodeClick(
|
|
2731
|
-
node
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
node.viewKind ===
|
|
2736
|
-
"file"
|
|
2737
|
-
) {
|
|
2738
|
-
showDetails(
|
|
2739
|
-
node
|
|
2740
|
-
);
|
|
2741
|
-
|
|
2742
|
-
return;
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
showDetails(
|
|
2746
|
-
node
|
|
2747
|
-
);
|
|
2748
|
-
|
|
2749
|
-
flyToNode(
|
|
2750
|
-
node
|
|
2751
|
-
);
|
|
1613
|
+
.onNodeClick((node) => {
|
|
1614
|
+
if (node.viewKind === 'cluster' || node.viewKind === 'file') {
|
|
1615
|
+
showDetails(node);
|
|
1616
|
+
|
|
1617
|
+
return;
|
|
2752
1618
|
}
|
|
2753
|
-
);
|
|
2754
|
-
}
|
|
2755
1619
|
|
|
2756
|
-
|
|
2757
|
-
.cooldownTicks(
|
|
2758
|
-
mode ===
|
|
2759
|
-
"overview"
|
|
2760
|
-
? 50
|
|
2761
|
-
: 80
|
|
2762
|
-
);
|
|
1620
|
+
showDetails(node);
|
|
2763
1621
|
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
1622
|
+
flyToNode(node);
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
2767
1625
|
|
|
2768
|
-
|
|
2769
|
-
data
|
|
2770
|
-
);
|
|
1626
|
+
state.graph.cooldownTicks(mode === 'overview' ? 50 : 80);
|
|
2771
1627
|
|
|
2772
|
-
|
|
2773
|
-
`${
|
|
2774
|
-
state.raw.dashboard
|
|
2775
|
-
?.name ||
|
|
2776
|
-
project
|
|
2777
|
-
} · ToolNet Graph`;
|
|
1628
|
+
state.graph.graphData(data);
|
|
2778
1629
|
|
|
2779
|
-
|
|
2780
|
-
document.getElementById(
|
|
2781
|
-
"loading"
|
|
2782
|
-
);
|
|
1630
|
+
renderStats(data);
|
|
2783
1631
|
|
|
2784
|
-
|
|
2785
|
-
loading.remove();
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
setTimeout(
|
|
2789
|
-
() => {
|
|
2790
|
-
state.graph
|
|
2791
|
-
.zoomToFit(
|
|
2792
|
-
500,
|
|
2793
|
-
45
|
|
2794
|
-
);
|
|
2795
|
-
},
|
|
2796
|
-
150
|
|
2797
|
-
);
|
|
2798
|
-
}
|
|
2799
|
-
|
|
2800
|
-
async function main() {
|
|
2801
|
-
const response =
|
|
2802
|
-
await graphApiFetch(
|
|
2803
|
-
"/api/projects"
|
|
2804
|
-
);
|
|
1632
|
+
document.title = `${state.raw.dashboard?.name || project} · ToolNet Graph`;
|
|
2805
1633
|
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
1634
|
+
loading = document.getElementById('loading');
|
|
1635
|
+
|
|
1636
|
+
if (loading) {
|
|
1637
|
+
loading.remove();
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
setTimeout(() => {
|
|
1641
|
+
state.graph.zoomToFit(500, 45);
|
|
1642
|
+
}, 150);
|
|
2812
1643
|
}
|
|
2813
1644
|
|
|
2814
|
-
|
|
2815
|
-
await
|
|
1645
|
+
async function main() {
|
|
1646
|
+
const response = await graphApiFetch('/api/projects');
|
|
2816
1647
|
|
|
2817
|
-
|
|
2818
|
-
|
|
1648
|
+
if (!response.ok) {
|
|
1649
|
+
throw new Error('Unable to load ToolNet projects');
|
|
1650
|
+
}
|
|
2819
1651
|
|
|
2820
|
-
|
|
2821
|
-
const project
|
|
2822
|
-
of catalog.projects
|
|
2823
|
-
) {
|
|
2824
|
-
const option =
|
|
2825
|
-
document.createElement(
|
|
2826
|
-
"option"
|
|
2827
|
-
);
|
|
1652
|
+
const catalog = await response.json();
|
|
2828
1653
|
|
|
2829
|
-
|
|
2830
|
-
project.remote;
|
|
1654
|
+
projectEl.innerHTML = '';
|
|
2831
1655
|
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
? project.name
|
|
2835
|
-
: `${project.name} · not indexed`;
|
|
1656
|
+
for (const project of catalog.projects) {
|
|
1657
|
+
const option = document.createElement('option');
|
|
2836
1658
|
|
|
2837
|
-
|
|
2838
|
-
!project.hasGraph;
|
|
1659
|
+
option.value = project.remote;
|
|
2839
1660
|
|
|
2840
|
-
|
|
2841
|
-
option
|
|
2842
|
-
);
|
|
2843
|
-
}
|
|
1661
|
+
option.textContent = project.hasGraph ? project.name : `${project.name} · not indexed`;
|
|
2844
1662
|
|
|
2845
|
-
|
|
2846
|
-
new URLSearchParams(
|
|
2847
|
-
window.location.search
|
|
2848
|
-
).get(
|
|
2849
|
-
"project"
|
|
2850
|
-
);
|
|
1663
|
+
option.disabled = !project.hasGraph;
|
|
2851
1664
|
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
.some(
|
|
2855
|
-
project =>
|
|
2856
|
-
project.remote ===
|
|
2857
|
-
requested &&
|
|
2858
|
-
project.hasGraph
|
|
2859
|
-
);
|
|
1665
|
+
projectEl.appendChild(option);
|
|
1666
|
+
}
|
|
2860
1667
|
|
|
2861
|
-
|
|
2862
|
-
requestedValid
|
|
2863
|
-
? requested
|
|
2864
|
-
: catalog.defaultProject;
|
|
1668
|
+
const requested = new URLSearchParams(window.location.search).get('project');
|
|
2865
1669
|
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
"No indexed projects found"
|
|
1670
|
+
const requestedValid = catalog.projects.some(
|
|
1671
|
+
(project) => project.remote === requested && project.hasGraph
|
|
2869
1672
|
);
|
|
2870
|
-
}
|
|
2871
1673
|
|
|
2872
|
-
|
|
2873
|
-
|
|
1674
|
+
const selected = requestedValid ? requested : catalog.defaultProject;
|
|
1675
|
+
|
|
1676
|
+
if (!selected) {
|
|
1677
|
+
throw new Error('No indexed projects found');
|
|
1678
|
+
}
|
|
2874
1679
|
|
|
2875
|
-
|
|
2876
|
-
selected
|
|
2877
|
-
);
|
|
2878
|
-
}
|
|
1680
|
+
projectEl.value = selected;
|
|
2879
1681
|
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
1682
|
+
await loadProject(selected);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
projectEl.addEventListener('change', async () => {
|
|
1686
|
+
const project = projectEl.value;
|
|
2885
1687
|
|
|
2886
1688
|
if (!project) {
|
|
2887
1689
|
return;
|
|
2888
1690
|
}
|
|
2889
1691
|
|
|
2890
|
-
const url =
|
|
2891
|
-
new URL(
|
|
2892
|
-
window.location.href
|
|
2893
|
-
);
|
|
1692
|
+
const url = new URL(window.location.href);
|
|
2894
1693
|
|
|
2895
|
-
url.searchParams.set(
|
|
2896
|
-
"project",
|
|
2897
|
-
project
|
|
2898
|
-
);
|
|
1694
|
+
url.searchParams.set('project', project);
|
|
2899
1695
|
|
|
2900
|
-
history.replaceState(
|
|
2901
|
-
{},
|
|
2902
|
-
"",
|
|
2903
|
-
url
|
|
2904
|
-
);
|
|
1696
|
+
history.replaceState({}, '', url);
|
|
2905
1697
|
|
|
2906
1698
|
try {
|
|
2907
|
-
await loadProject(
|
|
2908
|
-
|
|
2909
|
-
);
|
|
2910
|
-
} catch (
|
|
2911
|
-
error
|
|
2912
|
-
) {
|
|
2913
|
-
alert(
|
|
2914
|
-
error.message
|
|
2915
|
-
);
|
|
1699
|
+
await loadProject(project);
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
alert(error.message);
|
|
2916
1702
|
}
|
|
2917
|
-
}
|
|
2918
|
-
);
|
|
1703
|
+
});
|
|
2919
1704
|
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
() => {
|
|
2923
|
-
state.search =
|
|
2924
|
-
searchEl.value;
|
|
1705
|
+
searchEl.addEventListener('input', () => {
|
|
1706
|
+
state.search = searchEl.value;
|
|
2925
1707
|
|
|
2926
1708
|
applyFilters();
|
|
2927
|
-
}
|
|
2928
|
-
);
|
|
1709
|
+
});
|
|
2929
1710
|
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
() => {
|
|
2933
|
-
state.cluster =
|
|
2934
|
-
clusterEl.value;
|
|
1711
|
+
clusterEl.addEventListener('change', () => {
|
|
1712
|
+
state.cluster = clusterEl.value;
|
|
2935
1713
|
|
|
2936
1714
|
applyFilters();
|
|
2937
|
-
}
|
|
2938
|
-
);
|
|
1715
|
+
});
|
|
2939
1716
|
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
() => {
|
|
2943
|
-
state.edge =
|
|
2944
|
-
edgeEl.value;
|
|
1717
|
+
edgeEl.addEventListener('change', () => {
|
|
1718
|
+
state.edge = edgeEl.value;
|
|
2945
1719
|
|
|
2946
1720
|
applyFilters();
|
|
2947
|
-
}
|
|
2948
|
-
);
|
|
2949
|
-
|
|
2950
|
-
document
|
|
2951
|
-
.getElementById(
|
|
2952
|
-
"reset"
|
|
2953
|
-
)
|
|
2954
|
-
.addEventListener(
|
|
2955
|
-
"click",
|
|
2956
|
-
() => {
|
|
2957
|
-
if (
|
|
2958
|
-
state.viewMode !==
|
|
2959
|
-
"overview"
|
|
2960
|
-
) {
|
|
2961
|
-
void loadProject(
|
|
2962
|
-
state.project
|
|
2963
|
-
);
|
|
1721
|
+
});
|
|
2964
1722
|
|
|
2965
|
-
|
|
2966
|
-
|
|
1723
|
+
document.getElementById('reset').addEventListener('click', () => {
|
|
1724
|
+
if (state.viewMode !== 'overview') {
|
|
1725
|
+
void loadProject(state.project);
|
|
1726
|
+
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
2967
1729
|
|
|
2968
|
-
|
|
2969
|
-
"";
|
|
1730
|
+
searchEl.value = '';
|
|
2970
1731
|
|
|
2971
|
-
|
|
2972
|
-
"";
|
|
1732
|
+
clusterEl.value = '';
|
|
2973
1733
|
|
|
2974
|
-
|
|
2975
|
-
"";
|
|
1734
|
+
edgeEl.value = '';
|
|
2976
1735
|
|
|
2977
|
-
|
|
2978
|
-
"";
|
|
1736
|
+
state.search = '';
|
|
2979
1737
|
|
|
2980
|
-
|
|
2981
|
-
"";
|
|
1738
|
+
state.cluster = '';
|
|
2982
1739
|
|
|
2983
|
-
|
|
2984
|
-
"";
|
|
1740
|
+
state.edge = '';
|
|
2985
1741
|
|
|
2986
|
-
|
|
2987
|
-
null;
|
|
1742
|
+
state.pathStart = null;
|
|
2988
1743
|
|
|
2989
|
-
|
|
1744
|
+
resetPathHighlight();
|
|
2990
1745
|
|
|
2991
|
-
|
|
1746
|
+
closeDetails();
|
|
2992
1747
|
|
|
2993
|
-
|
|
1748
|
+
applyFilters();
|
|
2994
1749
|
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
}
|
|
3001
|
-
);
|
|
3002
|
-
|
|
3003
|
-
document.addEventListener(
|
|
3004
|
-
"keydown",
|
|
3005
|
-
event => {
|
|
3006
|
-
if (
|
|
3007
|
-
event.key ===
|
|
3008
|
-
"Escape"
|
|
3009
|
-
) {
|
|
1750
|
+
state.graph.zoomToFit(700, 60);
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
document.addEventListener('keydown', (event) => {
|
|
1754
|
+
if (event.key === 'Escape') {
|
|
3010
1755
|
closeDetails();
|
|
3011
1756
|
}
|
|
3012
|
-
}
|
|
3013
|
-
);
|
|
1757
|
+
});
|
|
3014
1758
|
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
const loading =
|
|
3018
|
-
document.getElementById(
|
|
3019
|
-
"loading"
|
|
3020
|
-
);
|
|
1759
|
+
main().catch((error) => {
|
|
1760
|
+
const loading = document.getElementById('loading');
|
|
3021
1761
|
|
|
3022
|
-
if (
|
|
3023
|
-
loading
|
|
3024
|
-
) {
|
|
3025
|
-
loading.textContent =
|
|
3026
|
-
error.message;
|
|
1762
|
+
if (loading) {
|
|
1763
|
+
loading.textContent = error.message;
|
|
3027
1764
|
}
|
|
3028
1765
|
|
|
3029
|
-
console.error(
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
);
|
|
3034
|
-
</script>
|
|
3035
|
-
</body>
|
|
1766
|
+
console.error(error);
|
|
1767
|
+
});
|
|
1768
|
+
</script>
|
|
1769
|
+
</body>
|
|
3036
1770
|
</html>
|