@oas-tools/oas-telemetry 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/ui.js CHANGED
@@ -10,12 +10,98 @@ export default function ui() {
10
10
  <style>
11
11
  body {
12
12
  font-family: Arial, sans-serif;
13
- margin: 20px;
13
+ margin: 0;
14
+ padding: 0;
15
+ background-color: #f5f5f5;
16
+ }
17
+
18
+ .header {
19
+ background-color: #333;
20
+ color: #fff;
21
+ padding: 20px;
22
+ text-align: left;
23
+ display: flex;
24
+ justify-content: space-between;
25
+ }
26
+
27
+ .header h1 {
28
+ display: inline-block;
29
+ margin: 0;
30
+ font-size: 24px;
31
+ }
32
+
33
+ .header .links {
34
+ display: flex;
35
+ align-items: center;
36
+ }
37
+ .links a {
38
+ color: #fff;
39
+ text-decoration: none;
40
+ margin-left: 30px;
41
+ }
42
+
43
+ .page {
44
+ margin: 0;
45
+ padding: 0;
46
+ }
47
+
48
+ .panel-conainer {
49
+ min-width: 60%;
50
+ width: fit-content;
51
+ margin: 20px auto;
52
+ }
53
+ .panel {
54
+ background-color: #fff;
55
+ border: 1px solid #ddd;
56
+ margin: 10px;
57
+ border-radius: 4px;
58
+ }
59
+
60
+ .panel-header {
61
+ background-color: #f1f1f1;
62
+ padding: 10px;
63
+ font-weight: bold;
64
+ cursor: pointer;
65
+ }
66
+
67
+ .panel-content {
68
+ display: none;
69
+ padding: 15px;
70
+
71
+ /* items margin */
72
+ >* {
73
+ margin: 5px 0;
74
+ }
75
+
76
+ }
77
+
78
+ .panel.open .panel-content {
79
+ display: flex;
80
+ flex-direction: column;
81
+ padding: 15px;
82
+ justify-content: center;
83
+ align-items: center;
84
+
85
+ }
86
+
87
+
88
+ button {
89
+ background-color: #007bff;
90
+ color: white;
91
+ border: none;
92
+ padding: 10px 15px;
93
+ cursor: pointer;
94
+ border-radius: 4px;
95
+ }
96
+
97
+ button:hover {
98
+ background-color: #0056b3;
14
99
  }
15
100
 
16
101
  table {
17
- width: 100%;
102
+ width: fit-content;
18
103
  border-collapse: collapse;
104
+ margin: 100%;
19
105
  }
20
106
 
21
107
  th,
@@ -29,40 +115,221 @@ export default function ui() {
29
115
  background-color: #f2f2f2;
30
116
  }
31
117
 
32
- #telemetryStatusSpan {
33
- color: rgb(0, 123, 6);
34
- font-size: x-small;
118
+
119
+
120
+ .spaced-row {
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: space-between;
124
+ width: 100%;
125
+ }
126
+
127
+ .row {
128
+ display: flex;
129
+ align-items: center;
130
+ }
131
+ </style>
132
+ <style>
133
+ .toggle-container {
134
+ display: flex;
135
+ flex: none;
136
+ width: max-content;
137
+ align-items: center;
138
+ margin: 10px 0;
139
+ }
140
+
141
+ .toggle {
142
+ display: flex;
143
+ align-items: center;
144
+ min-width: 40px;
145
+ min-height: 20px;
146
+ border-radius: 12px;
147
+ background-color: gray;
148
+ position: relative;
149
+ cursor: pointer;
150
+ transition: background-color 0.3s;
151
+ margin: 0 10px;
152
+ }
153
+
154
+ .toggle.circle {
155
+ border-radius: 50%;
156
+ }
157
+
158
+ .toggle.active {
159
+ background-color: green;
160
+ }
161
+
162
+ .circle-indicator {
163
+ width: 16px;
164
+ height: 16px;
165
+ margin: 0px 2px;
166
+ background-color: white;
167
+ border-radius: 50%;
168
+ transition: transform 0.3s;
169
+ }
170
+
171
+ .toggle.active .circle-indicator {
172
+ transform: translateX(20px);
173
+ /* Move the circle to the right when active */
174
+ }
175
+
176
+ .option-text {
177
+ transition: color 0.3s;
178
+ margin: 0 5px;
35
179
  }
36
180
  </style>
37
181
  </head>
38
182
 
39
183
  <body>
40
- <h1>Telemetry <span id="telemetryStatusSpan"></span></h1>
41
-
42
- <label><input type="checkbox" id="toggleTelemetry"> Allow Server Telemetry</label>
43
- <br><br>
44
- <button onclick="fetch('/telemetry/reset');showTelemetryStatus();">Reset Telemetry Data</button>
45
- <br><br>
46
- <label><input type="checkbox" id="autoUpdate"> Allow Client Auto Update</label>
47
- <br><br>
48
- <table id="apiTable">
49
- <thead>
50
- <tr>
51
- <th onclick="sortTable(0)">Path</th>
52
- <th onclick="sortTable(1)">Method</th>
53
- <th onclick="sortTable(2)">Status</th>
54
- <th onclick="sortTable(3)">Description</th>
55
- <th onclick="sortTable(4)" style="text-align: center;">Request <br> Count</th>
56
- <th onclick="sortTable(5)" style="text-align: center;">Average response time<br> (sec)</th>
57
- <th style="text-align: center;">Auto<br>Update</th>
58
- </tr>
59
- </thead>
60
- <tbody>
61
- </tbody>
62
- </table>
63
184
 
185
+ <div class="header">
186
+ <h1>OAS-Telemetry</h1>
187
+ <div class="links">
188
+ <a target="_blank" href="https://github.com/oas-tools/oas-telemetry">Documentation</a>
189
+ <a target="_blank" href="https://www.npmjs.com/package/@oas-tools/oas-telemetry">NPM</a>
190
+ <a target="_blank" href="https://github.com/oas-tools/oas-telemetry">GitHub</a>
191
+ </div>
192
+ </div>
193
+ <div class="page">
194
+ <div class="panel-conainer">
195
+ <!-- Panel 1 -->
196
+ <div class="panel open" id="panel1">
197
+ <div class="panel-header" onclick="togglePanel('panel1')">Telemetry Management</div>
198
+ <div class="panel-content">
199
+ <div class="spaced-row">
200
+ <div id="toggleTelemetry"></div>
201
+ <button onclick="fetch('/telemetry/reset');fetchTelemetryStatus();">Reset Telemetry
202
+ Data</button>
203
+ </div>
204
+ </div>
205
+ </div>
206
+
207
+ <!-- Panel 2 -->
208
+ <div class="panel open" id="panel2">
209
+ <div class="panel-header" onclick="togglePanel('panel2')">Heap Stats</div>
210
+ <div class="panel-content">
211
+ <div class="spaced-row">
212
+ <div id="heapAutoUpdate"></div>
213
+ <button onclick="populateHeapStats()">Update</button>
214
+ </div>
215
+ <table id="heapStatsTable">
216
+ <thead>
217
+ <tr>
218
+ <th>Stat Name</th>
219
+ <th>Value</th>
220
+ </tr>
221
+ </thead>
222
+ <tbody>
223
+ </tbody>
224
+ </table>
225
+
226
+ </div>
227
+ </div>
228
+
229
+ <!-- Panel 3 -->
230
+ <div class="panel no-user-select open" id="panel3">
231
+ <div class="panel-header" onclick="togglePanel('panel3')">Telemetry Endpoints</div>
232
+ <div class="panel-content">
233
+ <div class="spaced-row">
234
+ <div id="autoUpdateApiTable"></div>
235
+ </div>
236
+
237
+ <table id="apiTable">
238
+ <thead>
239
+ <tr>
240
+ <th onclick="sortTable(0)">Path</th>
241
+ <th onclick="sortTable(1)">Method</th>
242
+ <th onclick="sortTable(2)">Status</th>
243
+ <th onclick="sortTable(3)">Description</th>
244
+ <th onclick="sortTable(4)" style="text-align: center;">Request Count</th>
245
+ <th onclick="sortTable(5)" style="text-align: center;">Average response time (sec)
246
+ </th>
247
+ <th style="text-align: center;">Options</th>
248
+ </tr>
249
+ </thead>
250
+ <tbody>
251
+ </tbody>
252
+ </table>
253
+ </table>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ <!-- Scripts -->
259
+ <script>
260
+ // Open Close Panel
261
+ function togglePanel(panelId) {
262
+ const panel = document.getElementById(panelId);
263
+ panel.classList.toggle('open');
264
+ }
265
+ /**
266
+ * Create a toggle component
267
+ * @param {string} title - Title of the toggle
268
+ * @param {string} falseText - Text to display when false
269
+ * @param {string} falseColor - Color of the text when false
270
+ * @param {string} trueText - Text to display when true
271
+ * @param {string} trueColor - Color of the text when true
272
+ * @param {function} handler - Function to call when the toggle is clicked
273
+ * @param {boolean} defaultValue - Default value of the toggle
274
+ * @returns {HTMLDivElement} - The toggle component
275
+ */
276
+ function createToggle(title, falseText, falseColor, trueText, trueColor, handler, defaultValue = false) {
277
+ const container = document.createElement('div');
278
+ container.className = 'toggle-container';
279
+
280
+ const label = document.createElement('span');
281
+ label.textContent = title + ':';
282
+ label.style.marginRight = '10px';
283
+
284
+ const falseTextSpan = document.createElement('span');
285
+ falseTextSpan.className = 'option-text';
286
+ falseTextSpan.textContent = falseText;
287
+ falseTextSpan.style.color = defaultValue ? 'gray' : falseColor;
288
+
289
+ const toggle = document.createElement('div');
290
+ toggle.className = 'toggle';
291
+ const circleIndicator = document.createElement('div');
292
+ circleIndicator.className = 'circle-indicator';
293
+ toggle.appendChild(circleIndicator);
294
+
295
+ toggle.addEventListener('click', () => {
296
+ toggle.classList.toggle('active');
297
+ const isActive = toggle.classList.contains('active');
298
+
299
+ // Update colors. Not selected option to default color, selected option to active color
300
+ falseTextSpan.style.color = isActive ? 'gray' : falseColor;
301
+ trueTextSpan.style.color = isActive ? trueColor : 'gray';
302
+
303
+ handler(isActive);
304
+ });
305
+ toggle.classList.toggle('active', defaultValue);
306
+
307
+ const trueTextSpan = document.createElement('span');
308
+ trueTextSpan.className = 'option-text';
309
+ trueTextSpan.textContent = trueText; // Always display trueText
310
+ trueTextSpan.style.color = defaultValue ? trueColor : 'gray';
311
+
312
+ container.appendChild(label);
313
+ container.appendChild(falseTextSpan);
314
+ container.appendChild(toggle);
315
+ container.appendChild(trueTextSpan);
316
+
317
+ return container;
318
+ }
319
+
320
+ const localStorageManager = {
321
+ get: (key) => {
322
+ const value = localStorage.getItem(key);
323
+ return value ? JSON.parse(value) : null;
324
+ },
325
+ set: (key, value) => {
326
+ localStorage.setItem(key, JSON.stringify(value));
327
+ }
328
+ };
329
+
330
+ </script>
64
331
  <script>
65
- let LOG = false;
332
+ let LOG = true;
66
333
 
67
334
 
68
335
  let intervalTimer = {
@@ -70,7 +337,14 @@ export default function ui() {
70
337
  period: 2000,
71
338
  subscribers: [],
72
339
  start: function () {
340
+ this.disabled = false;
73
341
  this.interval = setInterval(() => this.tick(), this.period);
342
+ log("interval started with period: " + this.period);
343
+ },
344
+ stop: function () {
345
+ this.disabled = true;
346
+ clearInterval(this.interval);
347
+ log("interval stopped");
74
348
  },
75
349
  tick: function () {
76
350
  if (this.disabled) return;
@@ -85,10 +359,6 @@ export default function ui() {
85
359
  }
86
360
  }
87
361
 
88
-
89
-
90
-
91
-
92
362
  function log(s) {
93
363
  if (LOG) console.log(s);
94
364
  }
@@ -108,6 +378,17 @@ export default function ui() {
108
378
  }
109
379
  }
110
380
 
381
+ async function fetchTelemetryStatus() {
382
+ const response = await fetch("/telemetry/status");
383
+ if (!response.ok) {
384
+ throw new Error("ERROR getting the Status");
385
+ return false;
386
+ }
387
+ tStatus = await response.json();
388
+
389
+ log("tStatus: " + JSON.stringify(tStatus, null, 2));
390
+ return tStatus.active;
391
+ }
111
392
 
112
393
  function getPathRegEx(path) {
113
394
  let pathComponents = path.split("/");
@@ -124,7 +405,7 @@ export default function ui() {
124
405
  }
125
406
  }
126
407
  });
127
-
408
+
128
409
  // Allow an optional trailing slash
129
410
  pathRegExpStr += "/?\$";
130
411
 
@@ -238,11 +519,12 @@ export default function ui() {
238
519
  cellRequestCount.textContent = requestCount;
239
520
  cellAverageResponseTime.textContent = requestCount ? averageResponseTime.toFixed(3) : "--";
240
521
 
241
- // setTimeout(() => loadStats(path, method, status, cellRequestCount, cellAverageResponseTime), 2000);
242
522
  }
243
523
 
244
- function populateOASTable(apiSpec) {
524
+ async function populateApiTable() {
525
+ const apiSpec = await fetchSpec()
245
526
  const tableBody = document.getElementById('apiTable').getElementsByTagName('tbody')[0];
527
+ tableBody.innerHTML = "";
246
528
  Object.keys(apiSpec.paths).forEach(path => {
247
529
  Object.keys(apiSpec.paths[path]).forEach(method => {
248
530
  Object.keys(apiSpec.paths[path][method].responses).forEach(responseType => {
@@ -273,28 +555,17 @@ export default function ui() {
273
555
  + apiSpec.paths[path][method].responses[responseType].description;
274
556
 
275
557
  row.detailPath = \`/telemetry/detail/\${responseType}/\${method.toLowerCase()}\${fullPath}\`;
276
- const cellCheckbox = row.insertCell(6);
277
-
278
- // Create and insert checkbox
279
- const checkbox = document.createElement('input');
280
- checkbox.type = 'checkbox';
281
- cellCheckbox.appendChild(checkbox);
282
- cellCheckbox.style.textAlign = 'center';
283
- // class to search for the checkboxes
284
- checkbox.className = 'autoUpdateCheckbox';
285
- checkbox.disabled = intervalTimer.disabled;
286
-
287
- // Add event listener to handle checkbox auto-update
288
- checkbox.addEventListener('change', function () {
289
- if (this.checked)
290
- intervalTimer.subscribe(() =>
291
- loadStats(fullPath, method.toLowerCase(), responseType, cellRequestCount, cellAverageResponseTime)
292
- );
293
- else
294
- intervalTimer.unsubscribe(() =>
295
- loadStats(fullPath, method.toLowerCase(), responseType, cellRequestCount, cellAverageResponseTime)
296
- );
297
- });
558
+ const cellOptions = row.insertCell(6);
559
+
560
+
561
+ // Create a button for updating the endpoint spaced row but centered
562
+ const updateButton = document.createElement('button');
563
+ updateButton.textContent = "Update";
564
+ updateButton.onclick = () => loadStats(path, method, responseType, cellRequestCount, cellAverageResponseTime);
565
+ cellOptions.appendChild(updateButton);
566
+ cellOptions.style.display = "flex";
567
+ cellOptions.style.justifyContent = "center";
568
+ loadStats(path, method, responseType, cellRequestCount, cellAverageResponseTime);
298
569
 
299
570
  }
300
571
  });
@@ -302,6 +573,24 @@ export default function ui() {
302
573
  });
303
574
  }
304
575
 
576
+ function populateHeapStats() {
577
+ // heapstats at /telemetry/heapstats
578
+ const tableBody = document.getElementById('heapStatsTable').getElementsByTagName('tbody')[0];
579
+ fetch('/telemetry/heapstats').then(response => response.json()).then(heapStats => {
580
+ tableBody.innerHTML = "";
581
+ Object.keys(heapStats).forEach(statName => {
582
+ const row = tableBody.insertRow();
583
+ const cellStatName = row.insertCell(0);
584
+ const cellValue = row.insertCell(1);
585
+ cellStatName.textContent = statName;
586
+ //format always to 3 decimals (if number)
587
+ const formattedValue = typeof heapStats[statName] === 'number' ? heapStats[statName].toFixed(3) : heapStats[statName];
588
+ cellValue.textContent = heapStats[statName];
589
+ cellValue.style.textAlign = "right";
590
+ });
591
+ });
592
+ }
593
+
305
594
  function sortTable(column) {
306
595
  const table = document.getElementById('apiTable');
307
596
  let rows, switching, i, x, y, shouldSwitch;
@@ -325,50 +614,51 @@ export default function ui() {
325
614
  }
326
615
  }
327
616
 
328
- document.getElementById('autoUpdate').addEventListener('change', function () {
329
- const autoUpdateDisabled = !this.checked;
330
- intervalTimer.disabled = autoUpdateDisabled;
331
- // Disable/Enable all the checkboxes
332
- const checkboxes = document.getElementsByClassName('autoUpdateCheckbox');
333
- for (let i = 0; i < checkboxes.length; i++) {
334
- checkboxes[i].disabled = autoUpdateDisabled;
335
- }
336
- });
337
-
338
- async function showTelemetryStatus() {
339
- const tss = document.getElementById("telemetryStatusSpan");
340
- const response = await fetch("/telemetry/status");
341
- if (!response.ok) {
342
- throw new Error("ERROR getting the Status");
343
- return false;
617
+ document.getElementById('toggleTelemetry').appendChild(createToggle(
618
+ 'Telemetry status',
619
+ 'Stopped', 'red', // false state
620
+ 'Active', 'green', // true state
621
+ async (status) => {
622
+ const response = await fetch('/telemetry/' + (status ? 'start' : 'stop'));
623
+ if (!response.ok) {
624
+ throw new Error("ERROR setting the Telemetry status");
625
+ }
344
626
  }
345
- tStatus = await response.json();
346
-
347
- log(tStatus);
348
- if (tStatus.active) {
349
- tss.textContent = "active";
350
- tss.style.color = "#009900";
351
- } else {
352
- tss.textContent = "stopped";
353
- tss.style.color = "#666666";
627
+ ));
628
+
629
+ document.getElementById('autoUpdateApiTable').appendChild(createToggle(
630
+ 'Auto Update',
631
+ 'Manual', 'orange', // false state
632
+ 'Auto', 'green', // true state
633
+ (status) => {
634
+ const callback = () => { populateApiTable(); };
635
+ if (status) {
636
+ intervalTimer.subscribe(callback);
637
+ } else {
638
+ intervalTimer.unsubscribe(callback);
639
+ }
354
640
  }
355
- return tStatus.active;
356
- }
357
-
358
- document.getElementById('toggleTelemetry').addEventListener('change', function () {
359
- if (this.checked) {
360
- fetch('/telemetry/start').then(showTelemetryStatus());
361
- } else {
362
- fetch('/telemetry/stop').then(showTelemetryStatus());
641
+ ));
642
+
643
+ document.getElementById('heapAutoUpdate').appendChild(createToggle(
644
+ 'Auto Update',
645
+ 'Manual', 'orange', // false state
646
+ 'Auto', 'green', // true state
647
+ (status) => {
648
+ if (status) {
649
+ intervalTimer.subscribe(populateHeapStats);
650
+ } else {
651
+ intervalTimer.unsubscribe(populateHeapStats);
652
+ }
363
653
  }
364
- });
654
+ ));
365
655
 
366
656
  window.onload = async function () {
367
- document.getElementById('autoUpdate').checked = !intervalTimer.disabled; // Default to false
368
- const activeTelemetry = await showTelemetryStatus();
369
- document.getElementById('toggleTelemetry').checked = activeTelemetry; // Default to not toggled
370
- const apiSpec = await fetchSpec()
371
- populateOASTable(apiSpec);
657
+ document.getElementById('autoUpdateApiTable').querySelector('.toggle').classList.toggle('active', localStorageManager.get('autoUpdateApiTable'));
658
+ const activeTelemetry = await fetchTelemetryStatus();
659
+ document.getElementById('toggleTelemetry').querySelector('.toggle').classList.toggle('active', activeTelemetry);
660
+ populateApiTable();
661
+ populateHeapStats();
372
662
  intervalTimer.start();
373
663
  };
374
664
  </script>