@oas-tools/oas-telemetry 0.4.0 → 0.5.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 +0 -0
- package/README.md +151 -133
- package/dist/client.cjs +14 -0
- package/dist/config.cjs +27 -0
- package/dist/controllers/pluginController.cjs +118 -0
- package/dist/controllers/telemetryController.cjs +92 -0
- package/dist/controllers/uiController.cjs +78 -0
- package/dist/exporters/InMemoryDbExporter.cjs +45 -42
- package/dist/exporters/consoleExporter.cjs +52 -0
- package/dist/exporters/dynamicExporter.cjs +64 -0
- package/dist/index.cjs +61 -271
- package/dist/middleware/auth.cjs +17 -0
- package/dist/middleware/authMiddleware.cjs +19 -0
- package/dist/openTelemetry.cjs +20 -0
- package/dist/routes/authRoutes.cjs +79 -0
- package/dist/routes/telemetryRoutes.cjs +31 -0
- package/{src/ui.js → dist/services/uiService.cjs} +1140 -813
- package/dist/telemetry.cjs +0 -0
- package/dist/types/exporters/InMemoryDbExporter.d.ts +0 -0
- package/dist/types/index.d.ts +0 -0
- package/dist/types/telemetry.d.ts +0 -0
- package/dist/types/ui.d.ts +0 -0
- package/dist/ui.cjs +0 -0
- package/package.json +75 -71
- package/src/config.js +19 -0
- package/src/controllers/pluginController.js +115 -0
- package/src/controllers/telemetryController.js +68 -0
- package/src/controllers/uiController.js +69 -0
- package/src/dev/ui/login.html +32 -0
- package/src/exporters/InMemoryDbExporter.js +180 -175
- package/src/exporters/consoleExporter.js +47 -0
- package/src/exporters/dynamicExporter.js +62 -0
- package/src/index.js +85 -310
- package/src/middleware/authMiddleware.js +14 -0
- package/src/openTelemetry.js +22 -0
- package/src/routes/authRoutes.js +53 -0
- package/src/routes/telemetryRoutes.js +38 -0
- package/src/services/uiService.js +1520 -0
- package/src/telemetry.js +0 -25
|
@@ -0,0 +1,1520 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
// WARNING: This file is autogenerated. DO NOT EDIT!
|
|
4
|
+
//This file is autogenerated by dev/ui/exportHtmlToUi.js
|
|
5
|
+
const ui = (baseURL) => {
|
|
6
|
+
if(!baseURL) return htmlMap;
|
|
7
|
+
return Object.keys(htmlMap).reduce((acc, key) => {
|
|
8
|
+
acc[key] = htmlMap[key].replace(/\/telemetry/g, baseURL);
|
|
9
|
+
return acc;
|
|
10
|
+
}, {});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const htmlMap =
|
|
14
|
+
{
|
|
15
|
+
detail: `<!DOCTYPE html>
|
|
16
|
+
<html lang="en">
|
|
17
|
+
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="UTF-8">
|
|
20
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
21
|
+
<title>OAS - Telemetry</title>
|
|
22
|
+
<style>
|
|
23
|
+
.box {
|
|
24
|
+
width: 100%;
|
|
25
|
+
margin: 0 auto;
|
|
26
|
+
background: rgba(255, 255, 255, 0.2);
|
|
27
|
+
padding: 35px;
|
|
28
|
+
border: 2px solid #fff;
|
|
29
|
+
border-radius: 20px/50px;
|
|
30
|
+
background-clip: padding-box;
|
|
31
|
+
text-align: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.overlay {
|
|
35
|
+
position: fixed;
|
|
36
|
+
top: 0;
|
|
37
|
+
bottom: 0;
|
|
38
|
+
left: 0;
|
|
39
|
+
right: 0;
|
|
40
|
+
background: rgba(0, 0, 0, 0.7);
|
|
41
|
+
transition: opacity 500ms;
|
|
42
|
+
visibility: hidden;
|
|
43
|
+
opacity: 0;
|
|
44
|
+
overflow: scroll;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.overlay:target {
|
|
48
|
+
visibility: visible;
|
|
49
|
+
opacity: 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.popup {
|
|
53
|
+
margin: 70px auto;
|
|
54
|
+
padding: 20px;
|
|
55
|
+
background: #fff;
|
|
56
|
+
border-radius: 5px;
|
|
57
|
+
width: 70%;
|
|
58
|
+
position: relative;
|
|
59
|
+
transition: all 5s ease-in-out;
|
|
60
|
+
font-size: small;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.popup .close {
|
|
64
|
+
position: absolute;
|
|
65
|
+
top: 20px;
|
|
66
|
+
right: 30px;
|
|
67
|
+
transition: all 200ms;
|
|
68
|
+
font-size: 30px;
|
|
69
|
+
font-weight: bold;
|
|
70
|
+
text-decoration: none;
|
|
71
|
+
color: #333;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
<style>
|
|
75
|
+
body {
|
|
76
|
+
font-family: Arial, sans-serif;
|
|
77
|
+
margin: 0;
|
|
78
|
+
padding: 0;
|
|
79
|
+
background-color: #f5f5f5;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.header {
|
|
83
|
+
background-color: #333;
|
|
84
|
+
color: #fff;
|
|
85
|
+
padding: 20px;
|
|
86
|
+
text-align: left;
|
|
87
|
+
display: flex;
|
|
88
|
+
justify-content: space-between;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.header h1 {
|
|
92
|
+
display: inline-block;
|
|
93
|
+
margin: 0;
|
|
94
|
+
font-size: 24px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.header .links {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.links a {
|
|
103
|
+
color: #fff;
|
|
104
|
+
text-decoration: none;
|
|
105
|
+
margin-left: 30px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.page {
|
|
109
|
+
margin: 0;
|
|
110
|
+
padding: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.panel-conainer {
|
|
114
|
+
min-width: 60%;
|
|
115
|
+
width: fit-content;
|
|
116
|
+
margin: 20px auto;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.panel {
|
|
120
|
+
background-color: #fff;
|
|
121
|
+
border: 1px solid #ddd;
|
|
122
|
+
margin: 10px;
|
|
123
|
+
border-radius: 4px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.panel-header {
|
|
127
|
+
background-color: #f1f1f1;
|
|
128
|
+
padding: 10px;
|
|
129
|
+
font-weight: bold;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.panel-content {
|
|
134
|
+
display: none;
|
|
135
|
+
padding: 15px;
|
|
136
|
+
|
|
137
|
+
/* items margin */
|
|
138
|
+
>* {
|
|
139
|
+
margin: 5px 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.panel.open .panel-content {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
padding: 15px;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
align-items: center;
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
button {
|
|
155
|
+
background-color: #007bff;
|
|
156
|
+
color: white;
|
|
157
|
+
border: none;
|
|
158
|
+
padding: 10px 15px;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
border-radius: 4px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
button:hover {
|
|
164
|
+
background-color: #0056b3;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
table {
|
|
168
|
+
width: fit-content;
|
|
169
|
+
border-collapse: collapse;
|
|
170
|
+
margin: 100%;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
th,
|
|
174
|
+
td {
|
|
175
|
+
border: 1px solid #dddddd;
|
|
176
|
+
padding: 8px;
|
|
177
|
+
text-align: left;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
th {
|
|
181
|
+
background-color: #f2f2f2;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
.spaced-row {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
justify-content: space-between;
|
|
190
|
+
width: 100%;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.row {
|
|
194
|
+
display: flex;
|
|
195
|
+
align-items: center;
|
|
196
|
+
}
|
|
197
|
+
</style>
|
|
198
|
+
<script>
|
|
199
|
+
async function checkTelemetryStatus() {
|
|
200
|
+
try {
|
|
201
|
+
const response = await fetch('/telemetry/check');
|
|
202
|
+
const data = await response.json();
|
|
203
|
+
if (!data.valid) {
|
|
204
|
+
window.location.href = '/telemetry/login';
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error('Error checking telemetry status:', error);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
setInterval(checkTelemetryStatus, 5000); // Check every 5 seconds
|
|
212
|
+
</script>
|
|
213
|
+
</head>
|
|
214
|
+
|
|
215
|
+
<body>
|
|
216
|
+
<div class="header">
|
|
217
|
+
<h1>OAS-Telemetry</h1>
|
|
218
|
+
<div class="links">
|
|
219
|
+
<a href="/telemetry/">Back</a>
|
|
220
|
+
<a target="_blank" href="https://github.com/oas-tools/oas-telemetry">Documentation</a>
|
|
221
|
+
<a target="_blank" href="https://www.npmjs.com/package/@oas-tools/oas-telemetry">NPM</a>
|
|
222
|
+
<a target="_blank" href="https://github.com/oas-tools/oas-telemetry">GitHub</a>
|
|
223
|
+
<script>
|
|
224
|
+
// Check if the user is logged in
|
|
225
|
+
fetch('/telemetry/check')
|
|
226
|
+
.then(response => response.json())
|
|
227
|
+
.then(data => {
|
|
228
|
+
if (data.valid) {
|
|
229
|
+
const logoutLink = document.createElement('a');
|
|
230
|
+
logoutLink.href = '/telemetry/logout';
|
|
231
|
+
logoutLink.textContent = 'Logout';
|
|
232
|
+
document.querySelector('.links').appendChild(logoutLink);
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
.catch(error => console.error('Error:', error));
|
|
236
|
+
</script>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
<div class="page">
|
|
240
|
+
<div class="panel-conainer">
|
|
241
|
+
<!-- Endpoint Info Panel -->
|
|
242
|
+
<div class="panel open" id="panel1">
|
|
243
|
+
<div class="panel-header" onclick="togglePanel('panel1')">Endpoint Information</div>
|
|
244
|
+
<div class="panel-content">
|
|
245
|
+
<h2><span id="heading">Telemetry spans for...</span></h2>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<!-- Traces Panel -->
|
|
250
|
+
<div class="panel open" id="panel2">
|
|
251
|
+
<div class="panel-header" onclick="togglePanel('panel2')">Request Traces</div>
|
|
252
|
+
<div class="panel-content">
|
|
253
|
+
<table id="apiTable">
|
|
254
|
+
<thead>
|
|
255
|
+
<tr>
|
|
256
|
+
<th onclick="sortTable(0)">TimeStamp</th>
|
|
257
|
+
<th onclick="sortTable(1)">End point</th>
|
|
258
|
+
<th onclick="sortTable(2)">Method</th>
|
|
259
|
+
<th onclick="sortTable(3)">Status</th>
|
|
260
|
+
<th onclick="sortTable(4)" style="text-align: center;">Response time<br> (sec)</th>
|
|
261
|
+
</tr>
|
|
262
|
+
</thead>
|
|
263
|
+
<tbody>
|
|
264
|
+
</tbody>
|
|
265
|
+
</table>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<!-- Popup overlay -->
|
|
272
|
+
<div id="popupOverlay" class="overlay">
|
|
273
|
+
<div class="popup">
|
|
274
|
+
<pre id="tracePopup">
|
|
275
|
+
"attributes": {
|
|
276
|
+
"http": {
|
|
277
|
+
"url": "http://localhost:3000/api/v1/test",
|
|
278
|
+
"host": "localhost:3000",
|
|
279
|
+
"method": "GET",
|
|
280
|
+
"scheme": "http",
|
|
281
|
+
"target": "/api/v1/test",
|
|
282
|
+
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
|
|
283
|
+
"flavor": "1.1",
|
|
284
|
+
"status_code": 304,
|
|
285
|
+
"status_text": "NOT MODIFIED"
|
|
286
|
+
},
|
|
287
|
+
"net": {
|
|
288
|
+
"host": {
|
|
289
|
+
"name": "localhost",
|
|
290
|
+
"ip": "::1",
|
|
291
|
+
"port": 3000
|
|
292
|
+
},
|
|
293
|
+
"transport": "ip_tcp",
|
|
294
|
+
"peer": {
|
|
295
|
+
"ip": "::1",
|
|
296
|
+
"port": 58101
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
"links": {
|
|
301
|
+
|
|
302
|
+
},
|
|
303
|
+
"events": {
|
|
304
|
+
|
|
305
|
+
},
|
|
306
|
+
"_droppedAttributesCount": 0,
|
|
307
|
+
"_droppedEventsCount": 0,
|
|
308
|
+
"_droppedLinksCount": 0,
|
|
309
|
+
"status": {
|
|
310
|
+
"code": 0
|
|
311
|
+
},
|
|
312
|
+
"endTime": {
|
|
313
|
+
"0": 1724019619,
|
|
314
|
+
"1": 414126300
|
|
315
|
+
},
|
|
316
|
+
"_ended": true,
|
|
317
|
+
"_duration": {
|
|
318
|
+
"0": 0,
|
|
319
|
+
"1": 2126300
|
|
320
|
+
},
|
|
321
|
+
"name": "GET",
|
|
322
|
+
"_spanContext": {
|
|
323
|
+
"traceId": "ee70c9a937bbf95940a8971dc96077b3",
|
|
324
|
+
"spanId": "4fe34ee075253ecb",
|
|
325
|
+
"traceFlags": 1
|
|
326
|
+
},
|
|
327
|
+
"kind": 1,
|
|
328
|
+
"_performanceStartTime": 40561.097599983215,
|
|
329
|
+
"_performanceOffset": -8.425537109375,
|
|
330
|
+
"_startTimeProvided": false,
|
|
331
|
+
"startTime": {
|
|
332
|
+
"0": 1724019619,
|
|
333
|
+
"1": 412000000
|
|
334
|
+
},
|
|
335
|
+
"resource": {
|
|
336
|
+
"_attributes": {
|
|
337
|
+
"service": {
|
|
338
|
+
"name": "unknown_service:node"
|
|
339
|
+
},
|
|
340
|
+
"telemetry": {
|
|
341
|
+
"sdk": {
|
|
342
|
+
"language": "nodejs",
|
|
343
|
+
"name": "opentelemetry",
|
|
344
|
+
"version": "1.22.0"
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
"process": {
|
|
348
|
+
"pid": 23128,
|
|
349
|
+
"executable": {
|
|
350
|
+
"name": "npm",
|
|
351
|
+
"path": "C:\\Program Files\\nodejs\\node.exe"
|
|
352
|
+
},
|
|
353
|
+
"command_args": {
|
|
354
|
+
"0": "C:\\Program Files\\nodejs\\node.exe",
|
|
355
|
+
"1": "C:\\Personal\\ISA\\telemetry\\ot-ui-poc\\index"
|
|
356
|
+
},
|
|
357
|
+
"runtime": {
|
|
358
|
+
"version": "14.21.3",
|
|
359
|
+
"name": "nodejs",
|
|
360
|
+
"description": "Node.js"
|
|
361
|
+
},
|
|
362
|
+
"command": "C:\\Personal\\ISA\\telemetry\\ot-ui-poc\\index",
|
|
363
|
+
"owner": "manol"
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
"asyncAttributesPending": false,
|
|
367
|
+
"_syncAttributes": {
|
|
368
|
+
"service": {
|
|
369
|
+
"name": "unknown_service:node"
|
|
370
|
+
},
|
|
371
|
+
"telemetry": {
|
|
372
|
+
"sdk": {
|
|
373
|
+
"language": "nodejs",
|
|
374
|
+
"name": "opentelemetry",
|
|
375
|
+
"version": "1.22.0"
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
"_asyncAttributesPromise": {
|
|
380
|
+
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
"instrumentationLibrary": {
|
|
384
|
+
"name": "@opentelemetry/instrumentation-http",
|
|
385
|
+
"version": "0.51.0"
|
|
386
|
+
},
|
|
387
|
+
"_spanLimits": {
|
|
388
|
+
"attributeValueLengthLimit": null,
|
|
389
|
+
"attributeCountLimit": 128,
|
|
390
|
+
"linkCountLimit": 128,
|
|
391
|
+
"eventCountLimit": 128,
|
|
392
|
+
"attributePerEventCountLimit": 128,
|
|
393
|
+
"attributePerLinkCountLimit": 128
|
|
394
|
+
},
|
|
395
|
+
"_attributeValueLengthLimit": null,
|
|
396
|
+
"_spanProcessor": "oas-telemetry skips this field to avoid circular reference",
|
|
397
|
+
"_id": "f2989F2IDm3uSfml"
|
|
398
|
+
</pre>
|
|
399
|
+
<a class="close" href="#" onclick="hidePopup()">×</a>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<script>
|
|
404
|
+
let traceCount = -1;
|
|
405
|
+
let LOG = true;
|
|
406
|
+
|
|
407
|
+
function log(s) {
|
|
408
|
+
if (LOG)
|
|
409
|
+
console.log(s);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function parsePath() {
|
|
413
|
+
|
|
414
|
+
let detailPath = window.location.pathname.split("/");
|
|
415
|
+
|
|
416
|
+
if (detailPath.length < 6 || detailPath[5] == "") {
|
|
417
|
+
alert("Wrong invocation params");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
let status = parseInt(detailPath[3]);
|
|
422
|
+
|
|
423
|
+
if (Number.isNaN(status))
|
|
424
|
+
status = -1;
|
|
425
|
+
|
|
426
|
+
const method = detailPath[4];
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
const path = decodeURI("/" + detailPath
|
|
430
|
+
.splice(5, detailPath.length - 5)
|
|
431
|
+
.filter(c => (c != ""))
|
|
432
|
+
.join("/"));
|
|
433
|
+
|
|
434
|
+
headingObj = document.getElementById('heading');
|
|
435
|
+
headingObj.textContent = \`Telemetry for \${path} - \${method} - \${status} \`;
|
|
436
|
+
fetchTracesByFind(path, method, status);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
async function fetchTracesByFind(path, method, status) {
|
|
441
|
+
try {
|
|
442
|
+
let statusOr = [{ "attributes.http.status_code": parseInt(status) }]
|
|
443
|
+
if (status == "200") {
|
|
444
|
+
statusOr.push({ "attributes.http.status_code": 304 }); //Some servers return 304 instead of 200
|
|
445
|
+
}
|
|
446
|
+
log(\`Fetching traces for <\${path}> - \${method} - \${status} \`);
|
|
447
|
+
const body = {
|
|
448
|
+
"flags": { "containsRegex": true },
|
|
449
|
+
"config": { "regexIds": ["attributes.http.target", "attributes.http.status_code"] },
|
|
450
|
+
"search": {
|
|
451
|
+
"attributes.http.target": getPathRegEx(path),
|
|
452
|
+
"attributes.http.method": method.toUpperCase(),
|
|
453
|
+
"\$or": statusOr
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
log("body: " + JSON.stringify(body, null, 2));
|
|
457
|
+
//response is to the post at /telemetry/find
|
|
458
|
+
const response = await fetch("/telemetry/find", {
|
|
459
|
+
method: "POST",
|
|
460
|
+
headers: {
|
|
461
|
+
"Content-Type": "application/json"
|
|
462
|
+
},
|
|
463
|
+
body: JSON.stringify(body)
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (!response.ok) {
|
|
467
|
+
throw new Error("ERROR getting the Traces.");
|
|
468
|
+
}
|
|
469
|
+
response.json().then(data => {
|
|
470
|
+
//Data should be {spansCount: 0, spans: []}
|
|
471
|
+
log("Data: " + JSON.stringify(data, null, 2));
|
|
472
|
+
if (data && data.spans) {
|
|
473
|
+
loadTraces(data.spans);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
} catch (error) {
|
|
478
|
+
console.error("ERROR getting the Traces :", error);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function getPathRegEx(path) {
|
|
483
|
+
let pathComponents = path.split("/");
|
|
484
|
+
let pathRegExpStr = "^"
|
|
485
|
+
|
|
486
|
+
pathComponents.forEach(c => {
|
|
487
|
+
if (c != "") {
|
|
488
|
+
pathRegExpStr += "/";
|
|
489
|
+
if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
|
|
490
|
+
// Ensure it matches at least one character (.+)
|
|
491
|
+
pathRegExpStr += "(.+)";
|
|
492
|
+
} else {
|
|
493
|
+
pathRegExpStr += c;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Allow an optional trailing slash
|
|
499
|
+
pathRegExpStr += "/?\$";
|
|
500
|
+
|
|
501
|
+
return pathRegExpStr;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
function calculateTiming(startSecInput, startNanoSecInput, endSecInput, endNanoSecInput, precision = 3) {
|
|
506
|
+
// Convert inputs to numbers
|
|
507
|
+
let startSec = parseInt(startSecInput);
|
|
508
|
+
let startNanoSec = parseInt(startNanoSecInput);
|
|
509
|
+
let endSec = parseInt(endSecInput);
|
|
510
|
+
let endNanoSec = parseInt(endNanoSecInput);
|
|
511
|
+
|
|
512
|
+
// Convert nanoseconds to fractional seconds and add to seconds
|
|
513
|
+
let preciseStart = startSec + startNanoSec / 1e9; // Nanoseconds to seconds
|
|
514
|
+
let preciseEnd = endSec + endNanoSec / 1e9; // Nanoseconds to seconds
|
|
515
|
+
|
|
516
|
+
// Calculate duration
|
|
517
|
+
let preciseDuration = preciseEnd - preciseStart;
|
|
518
|
+
|
|
519
|
+
// Create Date objects and ISO timestamps
|
|
520
|
+
let startDate = new Date(preciseStart * 1000);
|
|
521
|
+
let endDate = new Date(preciseEnd * 1000);
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
preciseStart: preciseStart, // Precise start time in seconds
|
|
525
|
+
preciseEnd: preciseEnd, // Precise end time in seconds
|
|
526
|
+
preciseDuration: preciseDuration, // Duration in seconds
|
|
527
|
+
start: parseFloat(preciseStart.toFixed(precision)), // Rounded start time
|
|
528
|
+
end: parseFloat(preciseEnd.toFixed(precision)), // Rounded end time
|
|
529
|
+
duration: parseFloat(preciseDuration.toFixed(precision)), // Rounded duration
|
|
530
|
+
startDate: startDate, // Date object for start time
|
|
531
|
+
endDate: endDate, // Date object for end time
|
|
532
|
+
startTS: startDate.toISOString(), // ISO timestamp for start
|
|
533
|
+
endTS: endDate.toISOString() // ISO timestamp for end
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function parseTraceInfo(t) {
|
|
538
|
+
const ep = t.attributes.http.target;
|
|
539
|
+
const method = t.attributes.http.method.toLowerCase();
|
|
540
|
+
const status = t.attributes.http.status_code;
|
|
541
|
+
|
|
542
|
+
const timing = calculateTiming(t.startTime[0], t.startTime[1], t.endTime[0], t.endTime[1]);
|
|
543
|
+
|
|
544
|
+
log(\`\${timing.startTS} - \${timing.endTS} - \${t._spanContext.traceId} - \${t.name} - \${ep} - \${status} - \${timing.duration}\`);
|
|
545
|
+
return {
|
|
546
|
+
ts: timing.startTS,
|
|
547
|
+
ep: ep,
|
|
548
|
+
method: method,
|
|
549
|
+
status: status,
|
|
550
|
+
duration: timing.duration
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function loadTraces(traces) {
|
|
555
|
+
|
|
556
|
+
const tableBody = document.getElementById('apiTable').getElementsByTagName('tbody')[0];
|
|
557
|
+
while (tableBody.hasChildNodes()) {
|
|
558
|
+
tableBody.removeChild(tableBody.lastChild);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
traces.forEach(trace => {
|
|
562
|
+
const row = tableBody.insertRow();
|
|
563
|
+
const cellTS = row.insertCell(0);
|
|
564
|
+
const cellEP = row.insertCell(1);
|
|
565
|
+
const cellMethod = row.insertCell(2);
|
|
566
|
+
cellMethod.style.textAlign = "center";
|
|
567
|
+
const cellStatus = row.insertCell(3);
|
|
568
|
+
cellStatus.style.textAlign = "center";
|
|
569
|
+
const cellDuration = row.insertCell(4);
|
|
570
|
+
cellDuration.style.textAlign = "center";
|
|
571
|
+
|
|
572
|
+
let t = parseTraceInfo(trace);
|
|
573
|
+
|
|
574
|
+
cellTS.textContent = t.ts;
|
|
575
|
+
cellEP.textContent = t.ep;
|
|
576
|
+
cellMethod.textContent = t.method;
|
|
577
|
+
cellStatus.textContent = t.status;
|
|
578
|
+
cellDuration.textContent = t.duration.toFixed(3);
|
|
579
|
+
|
|
580
|
+
row.trace = trace;
|
|
581
|
+
row.onclick = function () {
|
|
582
|
+
const popup = document.getElementById("tracePopup");
|
|
583
|
+
popup.firstChild.nodeValue = JSON.stringify(this.trace, null, 2);
|
|
584
|
+
const popupOverlay = document.getElementById("popupOverlay");
|
|
585
|
+
popupOverlay.style.visibility = "visible";
|
|
586
|
+
popupOverlay.style.opacity = 1;
|
|
587
|
+
};
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function sortTable(column) {
|
|
592
|
+
const table = document.getElementById('apiTable');
|
|
593
|
+
let rows, switching, i, x, y, shouldSwitch;
|
|
594
|
+
switching = true;
|
|
595
|
+
// Loop until no switching has been done:
|
|
596
|
+
while (switching) {
|
|
597
|
+
switching = false;
|
|
598
|
+
rows = table.rows;
|
|
599
|
+
// Loop through all table rows (except the first, which contains table headers):
|
|
600
|
+
for (i = 1; i < (rows.length - 1); i++) {
|
|
601
|
+
shouldSwitch = false;
|
|
602
|
+
// Get the two elements you want to compare, one from current row and one from the next:
|
|
603
|
+
x = rows[i].getElementsByTagName("TD")[column];
|
|
604
|
+
y = rows[i + 1].getElementsByTagName("TD")[column];
|
|
605
|
+
// Check if the two rows should switch place:
|
|
606
|
+
if (x.textContent.toLowerCase() > y.textContent.toLowerCase()) {
|
|
607
|
+
shouldSwitch = true;
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (shouldSwitch) {
|
|
612
|
+
// If a switch has been marked, make the switch and mark that a switch has been done:
|
|
613
|
+
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
|
|
614
|
+
switching = true;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function hidePopup() {
|
|
620
|
+
const popupOverlay = document.getElementById("popupOverlay");
|
|
621
|
+
popupOverlay.style.visibility = "hidden";
|
|
622
|
+
popupOverlay.style.opacity = 0;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function togglePanel(panelId) {
|
|
626
|
+
const panel = document.getElementById(panelId);
|
|
627
|
+
panel.classList.toggle('open');
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
window.onload = parsePath();
|
|
631
|
+
</script>
|
|
632
|
+
|
|
633
|
+
</body>
|
|
634
|
+
|
|
635
|
+
</html>`,
|
|
636
|
+
login: `<!DOCTYPE html>
|
|
637
|
+
<html lang="en">
|
|
638
|
+
<head>
|
|
639
|
+
<meta charset="UTF-8">
|
|
640
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
641
|
+
<title>Telemetry Login</title>
|
|
642
|
+
<style>
|
|
643
|
+
body {
|
|
644
|
+
font-family: Arial, sans-serif;
|
|
645
|
+
margin: 0;
|
|
646
|
+
padding: 0;
|
|
647
|
+
background-color: #f5f5f5;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.header {
|
|
651
|
+
background-color: #333;
|
|
652
|
+
color: #fff;
|
|
653
|
+
padding: 20px;
|
|
654
|
+
text-align: left;
|
|
655
|
+
display: flex;
|
|
656
|
+
justify-content: space-between;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.header h1 {
|
|
660
|
+
display: inline-block;
|
|
661
|
+
margin: 0;
|
|
662
|
+
font-size: 24px;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.header .links {
|
|
666
|
+
display: flex;
|
|
667
|
+
align-items: center;
|
|
668
|
+
}
|
|
669
|
+
.links a {
|
|
670
|
+
color: #fff;
|
|
671
|
+
text-decoration: none;
|
|
672
|
+
margin-left: 30px;
|
|
673
|
+
}
|
|
674
|
+
.login-container {
|
|
675
|
+
max-width: 400px;
|
|
676
|
+
margin: 50px auto;
|
|
677
|
+
padding: 20px;
|
|
678
|
+
border: 1px solid #ccc;
|
|
679
|
+
border-radius: 10px;
|
|
680
|
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
681
|
+
background-color: #f9f9f9;
|
|
682
|
+
flex-grow: 1;
|
|
683
|
+
}
|
|
684
|
+
.login-container h1 {
|
|
685
|
+
text-align: center;
|
|
686
|
+
margin-bottom: 20px;
|
|
687
|
+
}
|
|
688
|
+
.login-container label {
|
|
689
|
+
display: block;
|
|
690
|
+
margin-bottom: 5px;
|
|
691
|
+
}
|
|
692
|
+
.login-container input {
|
|
693
|
+
width: calc(100% - 22px);
|
|
694
|
+
padding: 10px;
|
|
695
|
+
margin-bottom: 10px;
|
|
696
|
+
border: 1px solid #ccc;
|
|
697
|
+
border-radius: 5px;
|
|
698
|
+
}
|
|
699
|
+
.login-container button {
|
|
700
|
+
width: 100%;
|
|
701
|
+
padding: 10px;
|
|
702
|
+
background-color: #007BFF;
|
|
703
|
+
color: white;
|
|
704
|
+
border: none;
|
|
705
|
+
border-radius: 5px;
|
|
706
|
+
cursor: pointer;
|
|
707
|
+
}
|
|
708
|
+
.login-container button:hover {
|
|
709
|
+
background-color: #0056b3;
|
|
710
|
+
}
|
|
711
|
+
.notification {
|
|
712
|
+
display: none;
|
|
713
|
+
position: fixed;
|
|
714
|
+
top: 20px;
|
|
715
|
+
right: 20px;
|
|
716
|
+
padding: 15px;
|
|
717
|
+
background-color: #007BFF;
|
|
718
|
+
color: white;
|
|
719
|
+
border-radius: 5px;
|
|
720
|
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
721
|
+
}
|
|
722
|
+
.notification.error {
|
|
723
|
+
background-color: #FF0000;
|
|
724
|
+
}
|
|
725
|
+
.notification.success {
|
|
726
|
+
background-color: #00FF00;
|
|
727
|
+
}
|
|
728
|
+
@media (max-width: 600px) {
|
|
729
|
+
.login-container {
|
|
730
|
+
margin: 20px;
|
|
731
|
+
padding: 15px;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
</style>
|
|
735
|
+
</head>
|
|
736
|
+
<body>
|
|
737
|
+
<header>
|
|
738
|
+
<div class="header">
|
|
739
|
+
<h1>OAS-Telemetry</h1>
|
|
740
|
+
<div class="links">
|
|
741
|
+
<a target="_blank" href="https://github.com/oas-tools/oas-telemetry">Documentation</a>
|
|
742
|
+
<a target="_blank" href="https://www.npmjs.com/package/@oas-tools/oas-telemetry">NPM</a>
|
|
743
|
+
<a target="_blank" href="https://github.com/oas-tools/oas-telemetry">GitHub</a>
|
|
744
|
+
<script>
|
|
745
|
+
// Check if the user is logged in
|
|
746
|
+
fetch('/telemetry/check')
|
|
747
|
+
.then(response => response.json())
|
|
748
|
+
.then(data => {
|
|
749
|
+
if (data.valid) {
|
|
750
|
+
const logoutLink = document.createElement('a');
|
|
751
|
+
logoutLink.href = '/telemetry/logout';
|
|
752
|
+
logoutLink.textContent = 'Logout';
|
|
753
|
+
document.querySelector('.links').appendChild(logoutLink);
|
|
754
|
+
}
|
|
755
|
+
})
|
|
756
|
+
.catch(error => console.error('Error:', error));
|
|
757
|
+
</script>
|
|
758
|
+
</div>
|
|
759
|
+
</div>
|
|
760
|
+
</header>
|
|
761
|
+
<main>
|
|
762
|
+
<div class="login-container">
|
|
763
|
+
<h1>Login</h1>
|
|
764
|
+
<form id="loginForm">
|
|
765
|
+
<label for="password">Password:</label>
|
|
766
|
+
<input type="text" id="password" name="password" required>
|
|
767
|
+
<button type="submit">Login</button>
|
|
768
|
+
</form>
|
|
769
|
+
</div>
|
|
770
|
+
</main>
|
|
771
|
+
<div id="notification" class="notification"></div>
|
|
772
|
+
<script>
|
|
773
|
+
function showNotification(message, type) {
|
|
774
|
+
const notification = document.getElementById('notification');
|
|
775
|
+
notification.textContent = message;
|
|
776
|
+
notification.className = 'notification ' + type;
|
|
777
|
+
notification.style.display = 'block';
|
|
778
|
+
setTimeout(() => {
|
|
779
|
+
notification.style.display = 'none';
|
|
780
|
+
}, 3000);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
document.getElementById('loginForm').addEventListener('submit', async function(event) {
|
|
784
|
+
event.preventDefault();
|
|
785
|
+
const password = document.getElementById('password').value;
|
|
786
|
+
console.log(password);
|
|
787
|
+
try {
|
|
788
|
+
const response = await fetch('/telemetry/login', {
|
|
789
|
+
method: 'POST',
|
|
790
|
+
headers: { 'Content-Type': 'application/json' },
|
|
791
|
+
body: JSON.stringify({ password })
|
|
792
|
+
});
|
|
793
|
+
if (!response.ok) {
|
|
794
|
+
throw new Error('Network response was not ok');
|
|
795
|
+
}
|
|
796
|
+
console.log("Response:", response);
|
|
797
|
+
const result = await response.json();
|
|
798
|
+
console.log(result);
|
|
799
|
+
if (result.valid) {
|
|
800
|
+
showNotification('Login successful', 'success');
|
|
801
|
+
setTimeout(() => {
|
|
802
|
+
window.location.href = '/telemetry';
|
|
803
|
+
}, 1000);
|
|
804
|
+
} else {
|
|
805
|
+
showNotification('Invalid API Key', 'error');
|
|
806
|
+
}
|
|
807
|
+
} catch (error) {
|
|
808
|
+
console.error('Error:', error);
|
|
809
|
+
|
|
810
|
+
showNotification('An error occurred while checking the API Key.', 'error');
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
</script>
|
|
814
|
+
</body>
|
|
815
|
+
</html>
|
|
816
|
+
`,
|
|
817
|
+
main: `<!DOCTYPE html>
|
|
818
|
+
<html lang="en">
|
|
819
|
+
|
|
820
|
+
<head>
|
|
821
|
+
<meta charset="UTF-8">
|
|
822
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
823
|
+
<title>OAS - Telemetry</title>
|
|
824
|
+
<style>
|
|
825
|
+
body {
|
|
826
|
+
font-family: Arial, sans-serif;
|
|
827
|
+
margin: 0;
|
|
828
|
+
padding: 0;
|
|
829
|
+
background-color: #f5f5f5;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.header {
|
|
833
|
+
background-color: #333;
|
|
834
|
+
color: #fff;
|
|
835
|
+
padding: 20px;
|
|
836
|
+
text-align: left;
|
|
837
|
+
display: flex;
|
|
838
|
+
justify-content: space-between;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.header h1 {
|
|
842
|
+
display: inline-block;
|
|
843
|
+
margin: 0;
|
|
844
|
+
font-size: 24px;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.header .links {
|
|
848
|
+
display: flex;
|
|
849
|
+
align-items: center;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.links a {
|
|
853
|
+
color: #fff;
|
|
854
|
+
text-decoration: none;
|
|
855
|
+
margin-left: 30px;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.page {
|
|
859
|
+
margin: 0;
|
|
860
|
+
padding: 0;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
.panel-conainer {
|
|
864
|
+
min-width: 60%;
|
|
865
|
+
width: fit-content;
|
|
866
|
+
margin: 20px auto;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.panel {
|
|
870
|
+
background-color: #fff;
|
|
871
|
+
border: 1px solid #ddd;
|
|
872
|
+
margin: 10px;
|
|
873
|
+
border-radius: 4px;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.panel-header {
|
|
877
|
+
background-color: #f1f1f1;
|
|
878
|
+
padding: 10px;
|
|
879
|
+
font-weight: bold;
|
|
880
|
+
cursor: pointer;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.panel-content {
|
|
884
|
+
display: none;
|
|
885
|
+
padding: 15px;
|
|
886
|
+
|
|
887
|
+
/* items margin */
|
|
888
|
+
>* {
|
|
889
|
+
margin: 5px 0;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.panel.open .panel-content {
|
|
895
|
+
display: flex;
|
|
896
|
+
flex-direction: column;
|
|
897
|
+
padding: 15px;
|
|
898
|
+
justify-content: center;
|
|
899
|
+
align-items: center;
|
|
900
|
+
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
button {
|
|
905
|
+
background-color: #007bff;
|
|
906
|
+
color: white;
|
|
907
|
+
border: none;
|
|
908
|
+
padding: 10px 15px;
|
|
909
|
+
cursor: pointer;
|
|
910
|
+
border-radius: 4px;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
button:hover {
|
|
914
|
+
background-color: #0056b3;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
table {
|
|
918
|
+
width: fit-content;
|
|
919
|
+
border-collapse: collapse;
|
|
920
|
+
margin: 100%;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
th,
|
|
924
|
+
td {
|
|
925
|
+
border: 1px solid #dddddd;
|
|
926
|
+
padding: 8px;
|
|
927
|
+
text-align: left;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
th {
|
|
931
|
+
background-color: #f2f2f2;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
.spaced-row {
|
|
937
|
+
display: flex;
|
|
938
|
+
align-items: center;
|
|
939
|
+
justify-content: space-between;
|
|
940
|
+
width: 100%;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.row {
|
|
944
|
+
display: flex;
|
|
945
|
+
align-items: center;
|
|
946
|
+
}
|
|
947
|
+
</style>
|
|
948
|
+
<style>
|
|
949
|
+
.toggle-container {
|
|
950
|
+
display: flex;
|
|
951
|
+
flex: none;
|
|
952
|
+
width: max-content;
|
|
953
|
+
align-items: center;
|
|
954
|
+
margin: 10px 0;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.toggle {
|
|
958
|
+
display: flex;
|
|
959
|
+
align-items: center;
|
|
960
|
+
min-width: 40px;
|
|
961
|
+
min-height: 20px;
|
|
962
|
+
border-radius: 12px;
|
|
963
|
+
background-color: gray;
|
|
964
|
+
position: relative;
|
|
965
|
+
cursor: pointer;
|
|
966
|
+
transition: background-color 0.3s;
|
|
967
|
+
margin: 0 10px;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
.toggle.circle {
|
|
971
|
+
border-radius: 50%;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.toggle.active {
|
|
975
|
+
background-color: green;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.circle-indicator {
|
|
979
|
+
width: 16px;
|
|
980
|
+
height: 16px;
|
|
981
|
+
margin: 0px 2px;
|
|
982
|
+
background-color: white;
|
|
983
|
+
border-radius: 50%;
|
|
984
|
+
transition: transform 0.3s;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.toggle.active .circle-indicator {
|
|
988
|
+
transform: translateX(20px);
|
|
989
|
+
/* Move the circle to the right when active */
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
.option-text {
|
|
993
|
+
transition: color 0.3s;
|
|
994
|
+
margin: 0 5px;
|
|
995
|
+
}
|
|
996
|
+
</style>
|
|
997
|
+
<script>
|
|
998
|
+
async function checkTelemetryStatus() {
|
|
999
|
+
try {
|
|
1000
|
+
const response = await fetch('/telemetry/check');
|
|
1001
|
+
const data = await response.json();
|
|
1002
|
+
if (!data.valid) {
|
|
1003
|
+
window.location.href = '/telemetry/login';
|
|
1004
|
+
}
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
console.error('Error checking telemetry status:', error);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
setInterval(checkTelemetryStatus, 5000); // Check every 5 seconds
|
|
1011
|
+
</script>
|
|
1012
|
+
</head>
|
|
1013
|
+
|
|
1014
|
+
<body>
|
|
1015
|
+
|
|
1016
|
+
<div class="header">
|
|
1017
|
+
<h1>OAS-Telemetry</h1>
|
|
1018
|
+
<div class="links">
|
|
1019
|
+
<a target="_blank" href="https://github.com/oas-tools/oas-telemetry">Documentation</a>
|
|
1020
|
+
<a target="_blank" href="https://www.npmjs.com/package/@oas-tools/oas-telemetry">NPM</a>
|
|
1021
|
+
<a target="_blank" href="https://github.com/oas-tools/oas-telemetry">GitHub</a>
|
|
1022
|
+
<script>
|
|
1023
|
+
// Check if the user is logged in
|
|
1024
|
+
fetch('/telemetry/check')
|
|
1025
|
+
.then(response => response.json())
|
|
1026
|
+
.then(data => {
|
|
1027
|
+
if (data.valid) {
|
|
1028
|
+
const logoutLink = document.createElement('a');
|
|
1029
|
+
logoutLink.href = '/telemetry/logout';
|
|
1030
|
+
logoutLink.textContent = 'Logout';
|
|
1031
|
+
document.querySelector('.links').appendChild(logoutLink);
|
|
1032
|
+
}
|
|
1033
|
+
})
|
|
1034
|
+
.catch(error => console.error('Error:', error));
|
|
1035
|
+
</script>
|
|
1036
|
+
</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
<div class="page">
|
|
1039
|
+
<div class="panel-conainer">
|
|
1040
|
+
<!-- Panel 1 -->
|
|
1041
|
+
<div class="panel open" id="panel1">
|
|
1042
|
+
<div class="panel-header" onclick="togglePanel('panel1')">Telemetry Management</div>
|
|
1043
|
+
<div class="panel-content">
|
|
1044
|
+
<div class="spaced-row">
|
|
1045
|
+
<div id="toggleTelemetry"></div>
|
|
1046
|
+
<button onclick="fetch('/telemetry/reset');fetchTelemetryStatus();">Reset Telemetry
|
|
1047
|
+
Data</button>
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
</div>
|
|
1051
|
+
|
|
1052
|
+
<!-- Panel 2 -->
|
|
1053
|
+
<div class="panel open" id="panel2">
|
|
1054
|
+
<div class="panel-header" onclick="togglePanel('panel2')">Heap Stats</div>
|
|
1055
|
+
<div class="panel-content">
|
|
1056
|
+
<div class="spaced-row">
|
|
1057
|
+
<div id="heapAutoUpdate"></div>
|
|
1058
|
+
<button onclick="populateHeapStats()">Update</button>
|
|
1059
|
+
</div>
|
|
1060
|
+
<table id="heapStatsTable">
|
|
1061
|
+
<thead>
|
|
1062
|
+
<tr>
|
|
1063
|
+
<th>Stat Name</th>
|
|
1064
|
+
<th>Value</th>
|
|
1065
|
+
</tr>
|
|
1066
|
+
</thead>
|
|
1067
|
+
<tbody>
|
|
1068
|
+
</tbody>
|
|
1069
|
+
</table>
|
|
1070
|
+
|
|
1071
|
+
</div>
|
|
1072
|
+
</div>
|
|
1073
|
+
|
|
1074
|
+
<!-- Panel 3 -->
|
|
1075
|
+
<div class="panel no-user-select open" id="panel3">
|
|
1076
|
+
<div class="panel-header" onclick="togglePanel('panel3')">Telemetry Endpoints</div>
|
|
1077
|
+
<div class="panel-content">
|
|
1078
|
+
<div class="spaced-row">
|
|
1079
|
+
<div id="autoUpdateApiTable"></div>
|
|
1080
|
+
</div>
|
|
1081
|
+
|
|
1082
|
+
<table id="apiTable">
|
|
1083
|
+
<thead>
|
|
1084
|
+
<tr>
|
|
1085
|
+
<th onclick="sortTable(0)">Path</th>
|
|
1086
|
+
<th onclick="sortTable(1)">Method</th>
|
|
1087
|
+
<th onclick="sortTable(2)">Status</th>
|
|
1088
|
+
<th onclick="sortTable(3)">Description</th>
|
|
1089
|
+
<th onclick="sortTable(4)" style="text-align: center;">Request Count</th>
|
|
1090
|
+
<th onclick="sortTable(5)" style="text-align: center;">Average response time (sec)
|
|
1091
|
+
</th>
|
|
1092
|
+
<th style="text-align: center;">Options</th>
|
|
1093
|
+
</tr>
|
|
1094
|
+
</thead>
|
|
1095
|
+
<tbody>
|
|
1096
|
+
</tbody>
|
|
1097
|
+
</table>
|
|
1098
|
+
</table>
|
|
1099
|
+
</div>
|
|
1100
|
+
</div>
|
|
1101
|
+
</div>
|
|
1102
|
+
</div>
|
|
1103
|
+
<!-- Scripts -->
|
|
1104
|
+
<script>
|
|
1105
|
+
// Open Close Panel
|
|
1106
|
+
function togglePanel(panelId) {
|
|
1107
|
+
const panel = document.getElementById(panelId);
|
|
1108
|
+
panel.classList.toggle('open');
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Create a toggle component
|
|
1112
|
+
* @param {string} title - Title of the toggle
|
|
1113
|
+
* @param {string} falseText - Text to display when false
|
|
1114
|
+
* @param {string} falseColor - Color of the text when false
|
|
1115
|
+
* @param {string} trueText - Text to display when true
|
|
1116
|
+
* @param {string} trueColor - Color of the text when true
|
|
1117
|
+
* @param {function} handler - Function to call when the toggle is clicked
|
|
1118
|
+
* @param {boolean} defaultValue - Default value of the toggle
|
|
1119
|
+
* @returns {HTMLDivElement} - The toggle component
|
|
1120
|
+
*/
|
|
1121
|
+
function createToggle(title, falseText, falseColor, trueText, trueColor, handler, defaultValue = false) {
|
|
1122
|
+
const container = document.createElement('div');
|
|
1123
|
+
container.className = 'toggle-container';
|
|
1124
|
+
|
|
1125
|
+
const label = document.createElement('span');
|
|
1126
|
+
label.textContent = title + ':';
|
|
1127
|
+
label.style.marginRight = '10px';
|
|
1128
|
+
|
|
1129
|
+
const falseTextSpan = document.createElement('span');
|
|
1130
|
+
falseTextSpan.className = 'option-text';
|
|
1131
|
+
falseTextSpan.textContent = falseText;
|
|
1132
|
+
falseTextSpan.style.color = defaultValue ? 'gray' : falseColor;
|
|
1133
|
+
|
|
1134
|
+
const toggle = document.createElement('div');
|
|
1135
|
+
toggle.className = 'toggle';
|
|
1136
|
+
const circleIndicator = document.createElement('div');
|
|
1137
|
+
circleIndicator.className = 'circle-indicator';
|
|
1138
|
+
toggle.appendChild(circleIndicator);
|
|
1139
|
+
|
|
1140
|
+
toggle.addEventListener('click', () => {
|
|
1141
|
+
toggle.classList.toggle('active');
|
|
1142
|
+
const isActive = toggle.classList.contains('active');
|
|
1143
|
+
|
|
1144
|
+
// Update colors. Not selected option to default color, selected option to active color
|
|
1145
|
+
falseTextSpan.style.color = isActive ? 'gray' : falseColor;
|
|
1146
|
+
trueTextSpan.style.color = isActive ? trueColor : 'gray';
|
|
1147
|
+
|
|
1148
|
+
handler(isActive);
|
|
1149
|
+
});
|
|
1150
|
+
toggle.classList.toggle('active', defaultValue);
|
|
1151
|
+
|
|
1152
|
+
const trueTextSpan = document.createElement('span');
|
|
1153
|
+
trueTextSpan.className = 'option-text';
|
|
1154
|
+
trueTextSpan.textContent = trueText; // Always display trueText
|
|
1155
|
+
trueTextSpan.style.color = defaultValue ? trueColor : 'gray';
|
|
1156
|
+
|
|
1157
|
+
container.appendChild(label);
|
|
1158
|
+
container.appendChild(falseTextSpan);
|
|
1159
|
+
container.appendChild(toggle);
|
|
1160
|
+
container.appendChild(trueTextSpan);
|
|
1161
|
+
|
|
1162
|
+
return container;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const localStorageManager = {
|
|
1166
|
+
get: (key) => {
|
|
1167
|
+
const value = localStorage.getItem(key);
|
|
1168
|
+
return value ? JSON.parse(value) : null;
|
|
1169
|
+
},
|
|
1170
|
+
set: (key, value) => {
|
|
1171
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
</script>
|
|
1176
|
+
<script>
|
|
1177
|
+
let LOG = true;
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
let intervalTimer = {
|
|
1181
|
+
disabled: true,
|
|
1182
|
+
period: 2000,
|
|
1183
|
+
subscribers: [],
|
|
1184
|
+
start: function () {
|
|
1185
|
+
this.disabled = false;
|
|
1186
|
+
this.interval = setInterval(() => this.tick(), this.period);
|
|
1187
|
+
log("interval started with period: " + this.period);
|
|
1188
|
+
},
|
|
1189
|
+
stop: function () {
|
|
1190
|
+
this.disabled = true;
|
|
1191
|
+
clearInterval(this.interval);
|
|
1192
|
+
log("interval stopped");
|
|
1193
|
+
},
|
|
1194
|
+
tick: function () {
|
|
1195
|
+
if (this.disabled) return;
|
|
1196
|
+
log("tick");
|
|
1197
|
+
this.subscribers.forEach(callback => callback());//execute all the callbacks
|
|
1198
|
+
},
|
|
1199
|
+
subscribe: function (callback) {
|
|
1200
|
+
this.subscribers.push(callback);
|
|
1201
|
+
},
|
|
1202
|
+
unsubscribe: function (callback) {
|
|
1203
|
+
this.subscribers.pop(callback)
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function log(s) {
|
|
1208
|
+
if (LOG) console.log(s);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
async function fetchSpec() {
|
|
1212
|
+
try {
|
|
1213
|
+
const response = await fetch("/telemetry/spec");
|
|
1214
|
+
if (!response.ok) {
|
|
1215
|
+
throw new Error("ERROR getting the Spec");
|
|
1216
|
+
}
|
|
1217
|
+
apiSpec = await response.json();
|
|
1218
|
+
return apiSpec;
|
|
1219
|
+
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
console.error("ERROR getting the Spec :", error);
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
async function fetchTelemetryStatus() {
|
|
1227
|
+
const response = await fetch("/telemetry/status");
|
|
1228
|
+
if (!response.ok) {
|
|
1229
|
+
throw new Error("ERROR getting the Status");
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
tStatus = await response.json();
|
|
1233
|
+
|
|
1234
|
+
log("tStatus: " + JSON.stringify(tStatus, null, 2));
|
|
1235
|
+
return tStatus.active;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function getPathRegEx(path) {
|
|
1239
|
+
let pathComponents = path.split("/");
|
|
1240
|
+
let pathRegExpStr = "^"
|
|
1241
|
+
|
|
1242
|
+
pathComponents.forEach(c => {
|
|
1243
|
+
if (c != "") {
|
|
1244
|
+
pathRegExpStr += "/";
|
|
1245
|
+
if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
|
|
1246
|
+
// Ensure it matches at least one character (.+)
|
|
1247
|
+
pathRegExpStr += "(.+)";
|
|
1248
|
+
} else {
|
|
1249
|
+
pathRegExpStr += c;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
// Allow an optional trailing slash
|
|
1255
|
+
pathRegExpStr += "/?\$";
|
|
1256
|
+
|
|
1257
|
+
return pathRegExpStr;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async function fetchTracesByFind(path, method, status) {
|
|
1261
|
+
try {
|
|
1262
|
+
let statusOr = [{ "attributes.http.status_code": parseInt(status) }]
|
|
1263
|
+
if (status == "200") {
|
|
1264
|
+
statusOr.push({ "attributes.http.status_code": 304 }); //Some servers return 304 instead of 200
|
|
1265
|
+
}
|
|
1266
|
+
log(\`Fetching traces for <\${path}> - \${method} - \${status} \`);
|
|
1267
|
+
const body = {
|
|
1268
|
+
"flags": { "containsRegex": true },
|
|
1269
|
+
"config": { "regexIds": ["attributes.http.target", "attributes.http.status_code"] },
|
|
1270
|
+
"search": {
|
|
1271
|
+
"attributes.http.target": getPathRegEx(path),
|
|
1272
|
+
"attributes.http.method": method.toUpperCase(),
|
|
1273
|
+
"\$or": statusOr
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
log("body: " + JSON.stringify(body, null, 2));
|
|
1277
|
+
//response is to the post at /telemetry/find
|
|
1278
|
+
const response = await fetch("/telemetry/find", {
|
|
1279
|
+
method: "POST",
|
|
1280
|
+
headers: {
|
|
1281
|
+
"Content-Type": "application/json"
|
|
1282
|
+
},
|
|
1283
|
+
body: JSON.stringify(body)
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
if (!response.ok) {
|
|
1287
|
+
throw new Error("ERROR getting the Traces.");
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const responseJSON = await response.json();
|
|
1291
|
+
const traces = responseJSON.spans;
|
|
1292
|
+
|
|
1293
|
+
log(\`Fetched \${traces.length} traces.\`);
|
|
1294
|
+
return traces;
|
|
1295
|
+
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
console.error("ERROR getting the Traces :", error);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function calculateTiming(startSecInput, startNanoSecInput, endSecInput, endNanoSecInput, precision = 3) {
|
|
1302
|
+
// Convert inputs to numbers
|
|
1303
|
+
let startSec = parseInt(startSecInput);
|
|
1304
|
+
let startNanoSec = parseInt(startNanoSecInput);
|
|
1305
|
+
let endSec = parseInt(endSecInput);
|
|
1306
|
+
let endNanoSec = parseInt(endNanoSecInput);
|
|
1307
|
+
|
|
1308
|
+
// Convert nanoseconds to fractional seconds and add to seconds
|
|
1309
|
+
let preciseStart = startSec + startNanoSec / 1e9; // Nanoseconds to seconds
|
|
1310
|
+
let preciseEnd = endSec + endNanoSec / 1e9; // Nanoseconds to seconds
|
|
1311
|
+
|
|
1312
|
+
// Calculate duration
|
|
1313
|
+
let preciseDuration = preciseEnd - preciseStart;
|
|
1314
|
+
|
|
1315
|
+
// Create Date objects and ISO timestamps
|
|
1316
|
+
let startDate = new Date(preciseStart * 1000);
|
|
1317
|
+
let endDate = new Date(preciseEnd * 1000);
|
|
1318
|
+
|
|
1319
|
+
return {
|
|
1320
|
+
preciseStart: preciseStart, // Precise start time in seconds
|
|
1321
|
+
preciseEnd: preciseEnd, // Precise end time in seconds
|
|
1322
|
+
preciseDuration: preciseDuration, // Duration in seconds
|
|
1323
|
+
start: parseFloat(preciseStart.toFixed(precision)), // Rounded start time
|
|
1324
|
+
end: parseFloat(preciseEnd.toFixed(precision)), // Rounded end time
|
|
1325
|
+
duration: parseFloat(preciseDuration.toFixed(precision)), // Rounded duration
|
|
1326
|
+
startDate: startDate, // Date object for start time
|
|
1327
|
+
endDate: endDate, // Date object for end time
|
|
1328
|
+
startTS: startDate.toISOString(), // ISO timestamp for start
|
|
1329
|
+
endTS: endDate.toISOString() // ISO timestamp for end
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
function parseTraceInfo(t) {
|
|
1335
|
+
const ep = t.attributes.http.target;
|
|
1336
|
+
const method = t.attributes.http.method.toLowerCase();
|
|
1337
|
+
const status = t.attributes.http.status_code;
|
|
1338
|
+
|
|
1339
|
+
const timing = calculateTiming(t.startTime[0], t.startTime[1], t.endTime[0], t.endTime[1]);
|
|
1340
|
+
|
|
1341
|
+
log(\`\${timing.startTS} - \${timing.endTS} - \${t._spanContext.traceId} - \${t.name} - \${ep} - \${status} - \${timing.duration}\`);
|
|
1342
|
+
return {
|
|
1343
|
+
ts: timing.startTS,
|
|
1344
|
+
ep: ep,
|
|
1345
|
+
method: method,
|
|
1346
|
+
status: status,
|
|
1347
|
+
duration: timing.duration
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
async function loadStats(path, method, status, cellRequestCount, cellAverageResponseTime) {
|
|
1352
|
+
log(\`loadStats(\${path}, \${method}, \${status}, \${cellRequestCount}, \${cellAverageResponseTime})\`);
|
|
1353
|
+
let traces = await fetchTracesByFind(path, method, status);
|
|
1354
|
+
let requestCount = traces.length;
|
|
1355
|
+
let averageResponseTime = 0;
|
|
1356
|
+
|
|
1357
|
+
traces.forEach(trace => {
|
|
1358
|
+
t = parseTraceInfo(trace);
|
|
1359
|
+
log(JSON.stringify(t, null, 2));
|
|
1360
|
+
averageResponseTime += parseFloat(t.duration);
|
|
1361
|
+
log(\`averageResponseTime += t.duration --> \${averageResponseTime} += \${t.duration}\`);
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
averageResponseTime = averageResponseTime / requestCount;
|
|
1365
|
+
|
|
1366
|
+
log(\`averageResponseTime = averageResponseTime / requestCount --> \${averageResponseTime} = \${averageResponseTime} / \${requestCount}\`);
|
|
1367
|
+
|
|
1368
|
+
cellRequestCount.textContent = requestCount;
|
|
1369
|
+
cellAverageResponseTime.textContent = requestCount ? averageResponseTime.toFixed(3) : "--";
|
|
1370
|
+
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
async function populateApiTable() {
|
|
1374
|
+
const apiSpec = await fetchSpec()
|
|
1375
|
+
const tableBody = document.getElementById('apiTable').getElementsByTagName('tbody')[0];
|
|
1376
|
+
tableBody.innerHTML = "";
|
|
1377
|
+
Object.keys(apiSpec.paths).forEach(path => {
|
|
1378
|
+
Object.keys(apiSpec.paths[path]).forEach(method => {
|
|
1379
|
+
Object.keys(apiSpec.paths[path][method].responses).forEach(responseType => {
|
|
1380
|
+
if (!Number.isNaN(parseInt(responseType))) {
|
|
1381
|
+
const row = tableBody.insertRow();
|
|
1382
|
+
const cellPath = row.insertCell(0);
|
|
1383
|
+
const cellMethod = row.insertCell(1);
|
|
1384
|
+
const cellStatus = row.insertCell(2);
|
|
1385
|
+
const cellDescription = row.insertCell(3);
|
|
1386
|
+
const cellRequestCount = row.insertCell(4);
|
|
1387
|
+
cellRequestCount.style = "text-align: center;";
|
|
1388
|
+
cellRequestCount.textContent = "--";
|
|
1389
|
+
const cellAverageResponseTime = row.insertCell(5);
|
|
1390
|
+
cellAverageResponseTime.style.textAlign = "center";
|
|
1391
|
+
cellAverageResponseTime.textContent = "--";
|
|
1392
|
+
|
|
1393
|
+
const basePath = apiSpec.basePath ? apiSpec.basePath : "";
|
|
1394
|
+
const fullPath = basePath + path;
|
|
1395
|
+
cellPath.textContent = path;
|
|
1396
|
+
cellPath.style.cursor = 'pointer';
|
|
1397
|
+
cellPath.onclick = function () {
|
|
1398
|
+
window.location.href = row.detailPath;
|
|
1399
|
+
};
|
|
1400
|
+
cellMethod.textContent = method.toUpperCase();
|
|
1401
|
+
cellStatus.textContent = responseType;
|
|
1402
|
+
cellDescription.textContent = apiSpec.paths[path][method].summary
|
|
1403
|
+
+ " - "
|
|
1404
|
+
+ apiSpec.paths[path][method].responses[responseType].description;
|
|
1405
|
+
|
|
1406
|
+
row.detailPath = \`/telemetry/detail/\${responseType}/\${method.toLowerCase()}\${fullPath}\`;
|
|
1407
|
+
const cellOptions = row.insertCell(6);
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
// Create a button for updating the endpoint spaced row but centered
|
|
1411
|
+
const updateButton = document.createElement('button');
|
|
1412
|
+
updateButton.textContent = "Update";
|
|
1413
|
+
updateButton.onclick = () => loadStats(path, method, responseType, cellRequestCount, cellAverageResponseTime);
|
|
1414
|
+
cellOptions.appendChild(updateButton);
|
|
1415
|
+
cellOptions.style.display = "flex";
|
|
1416
|
+
cellOptions.style.justifyContent = "center";
|
|
1417
|
+
loadStats(path, method, responseType, cellRequestCount, cellAverageResponseTime);
|
|
1418
|
+
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
});
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
function populateHeapStats() {
|
|
1426
|
+
// heapstats at /telemetry/heapstats
|
|
1427
|
+
const tableBody = document.getElementById('heapStatsTable').getElementsByTagName('tbody')[0];
|
|
1428
|
+
fetch('/telemetry/heapstats').then(response => response.json()).then(heapStats => {
|
|
1429
|
+
tableBody.innerHTML = "";
|
|
1430
|
+
Object.keys(heapStats).forEach(statName => {
|
|
1431
|
+
const row = tableBody.insertRow();
|
|
1432
|
+
const cellStatName = row.insertCell(0);
|
|
1433
|
+
const cellValue = row.insertCell(1);
|
|
1434
|
+
cellStatName.textContent = statName;
|
|
1435
|
+
//format always to 3 decimals (if number)
|
|
1436
|
+
const formattedValue = typeof heapStats[statName] === 'number' ? heapStats[statName].toFixed(3) : heapStats[statName];
|
|
1437
|
+
cellValue.textContent = heapStats[statName];
|
|
1438
|
+
cellValue.style.textAlign = "right";
|
|
1439
|
+
});
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function sortTable(column) {
|
|
1444
|
+
const table = document.getElementById('apiTable');
|
|
1445
|
+
let rows, switching, i, x, y, shouldSwitch;
|
|
1446
|
+
switching = true;
|
|
1447
|
+
while (switching) {
|
|
1448
|
+
switching = false;
|
|
1449
|
+
rows = table.rows;
|
|
1450
|
+
for (i = 1; i < (rows.length - 1); i++) {
|
|
1451
|
+
shouldSwitch = false;
|
|
1452
|
+
x = rows[i].getElementsByTagName("TD")[column];
|
|
1453
|
+
y = rows[i + 1].getElementsByTagName("TD")[column];
|
|
1454
|
+
if (x.textContent.toLowerCase() > y.textContent.toLowerCase()) {
|
|
1455
|
+
shouldSwitch = true;
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
if (shouldSwitch) {
|
|
1460
|
+
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
|
|
1461
|
+
switching = true;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
document.getElementById('toggleTelemetry').appendChild(createToggle(
|
|
1467
|
+
'Telemetry status',
|
|
1468
|
+
'Stopped', 'red', // false state
|
|
1469
|
+
'Active', 'green', // true state
|
|
1470
|
+
async (status) => {
|
|
1471
|
+
const response = await fetch('/telemetry/' + (status ? 'start' : 'stop'));
|
|
1472
|
+
if (!response.ok) {
|
|
1473
|
+
throw new Error("ERROR setting the Telemetry status");
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
));
|
|
1477
|
+
|
|
1478
|
+
document.getElementById('autoUpdateApiTable').appendChild(createToggle(
|
|
1479
|
+
'Auto Update',
|
|
1480
|
+
'Manual', 'orange', // false state
|
|
1481
|
+
'Auto', 'green', // true state
|
|
1482
|
+
(status) => {
|
|
1483
|
+
const callback = () => { populateApiTable(); };
|
|
1484
|
+
if (status) {
|
|
1485
|
+
intervalTimer.subscribe(callback);
|
|
1486
|
+
} else {
|
|
1487
|
+
intervalTimer.unsubscribe(callback);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
));
|
|
1491
|
+
|
|
1492
|
+
document.getElementById('heapAutoUpdate').appendChild(createToggle(
|
|
1493
|
+
'Auto Update',
|
|
1494
|
+
'Manual', 'orange', // false state
|
|
1495
|
+
'Auto', 'green', // true state
|
|
1496
|
+
(status) => {
|
|
1497
|
+
if (status) {
|
|
1498
|
+
intervalTimer.subscribe(populateHeapStats);
|
|
1499
|
+
} else {
|
|
1500
|
+
intervalTimer.unsubscribe(populateHeapStats);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
));
|
|
1504
|
+
|
|
1505
|
+
window.onload = async function () {
|
|
1506
|
+
document.getElementById('autoUpdateApiTable').querySelector('.toggle').classList.toggle('active', localStorageManager.get('autoUpdateApiTable'));
|
|
1507
|
+
const activeTelemetry = await fetchTelemetryStatus();
|
|
1508
|
+
document.getElementById('toggleTelemetry').querySelector('.toggle').classList.toggle('active', activeTelemetry);
|
|
1509
|
+
populateApiTable();
|
|
1510
|
+
populateHeapStats();
|
|
1511
|
+
intervalTimer.start();
|
|
1512
|
+
};
|
|
1513
|
+
</script>
|
|
1514
|
+
</body>
|
|
1515
|
+
|
|
1516
|
+
</html>`,
|
|
1517
|
+
};
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
export default ui
|