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