RoyAalekh commited on
Commit
c3aaa58
·
1 Parent(s): 4b4d918

chore: remove Granim and telemetry UI from frontend; switch Leaflet CSS to prefetch; git rm static/telemetry.html

Browse files
static/index.html CHANGED
@@ -9,13 +9,10 @@
9
  <title>TreeTrack - Professional Field Research</title>
10
  <link rel="preconnect" href="https://unpkg.com" crossorigin>
11
  <link rel="dns-prefetch" href="//unpkg.com">
12
- <link rel="preload" as="style" href="https://unpkg.com/[email protected]/dist/leaflet.css">
13
  <link rel="icon" type="image/png" href="/static/image/icons8-tree-96.png">
14
  <link rel="apple-touch-icon" href="/static/image/icons8-tree-96.png">
15
  <link rel="stylesheet" href="/static/css/design-system.css">
16
- <!-- Granim.js for gradient animations (deferred) -->
17
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/granim.min.js" defer></script>
18
- <style>
19
 
20
  :root {
21
  /* Colors */
@@ -93,18 +90,7 @@
93
  overflow: hidden;
94
  }
95
 
96
- /* Granim Canvas for Header */
97
- #header-canvas {
98
- position: absolute;
99
- top: 0;
100
- left: 0;
101
- width: 100vw;
102
- height: 100%;
103
- z-index: 1;
104
- background: linear-gradient(135deg, #0f172a, #1e293b, #16a085);
105
- }
106
-
107
- /* Ensure header content is above canvas */
108
  .tt-header-content {
109
  position: relative;
110
  z-index: 2;
@@ -920,7 +906,7 @@
920
  // Force refresh if we detect cached version
921
  (function() {
922
  const currentVersion = '5.1.1';
923
- const timestamp = '1755114945'; // Cache-busting bump
924
  const lastVersion = sessionStorage.getItem('treetrack_version');
925
  const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
926
 
@@ -1165,7 +1151,7 @@
1165
  </div>
1166
  </div>
1167
 
1168
- <script type="module" src="/static/js/tree-track-app.js?v=5.1.1&t=1755114945"></script>
1169
 
1170
  <script>
1171
  // Idle-time prefetch of map assets to speed up first navigation
@@ -1190,44 +1176,6 @@
1190
  link.addEventListener('mouseenter', prefetch, {once: true});
1191
  link.addEventListener('touchstart', prefetch, {once: true});
1192
  })();
1193
- // Initialize Granim background animation on page load
1194
- document.addEventListener('DOMContentLoaded', function() {
1195
- // Define initializer and defer to idle time for better performance
1196
- function startHeaderGranim() {
1197
- new Granim({
1198
- element: '#header-canvas',
1199
- direction: 'diagonal',
1200
- isPausedWhenNotInView: false,
1201
- image: {
1202
- // Forest path image
1203
- source: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&w=2560&auto=format&fit=crop&ixlib=rb-4.0.3',
1204
- position: ['center', 'center'],
1205
- stretchMode: ['stretch', 'stretch'],
1206
- blendingMode: 'multiply'
1207
- },
1208
- states: {
1209
- "default-state": {
1210
- gradients: [
1211
- ['#0f172a', '#1e293b', '#16a085'],
1212
- ['#1a202c', '#2d3748', '#27ae60'],
1213
- ['#2d3748', '#4a5568', '#2ecc71'],
1214
- ['#1a365d', '#2c5282', '#138d75'],
1215
- ['#0f172a', '#2d3748', '#16a085']
1216
- ],
1217
- transitionSpeed: 12000
1218
- }
1219
- }
1220
- });
1221
- }
1222
-
1223
- if (window.requestIdleCallback) {
1224
- requestIdleCallback(() => { startHeaderGranim(); });
1225
- } else {
1226
- setTimeout(() => { startHeaderGranim(); }, 0);
1227
- }
1228
-
1229
- console.log('Scheduled header Granim initialization');
1230
- });
1231
  </script>
1232
  </body>
1233
  </html>
 
9
  <title>TreeTrack - Professional Field Research</title>
10
  <link rel="preconnect" href="https://unpkg.com" crossorigin>
11
  <link rel="dns-prefetch" href="//unpkg.com">
12
+ <link rel="prefetch" href="https://unpkg.com/[email protected]/dist/leaflet.css">
13
  <link rel="icon" type="image/png" href="/static/image/icons8-tree-96.png">
14
  <link rel="apple-touch-icon" href="/static/image/icons8-tree-96.png">
15
  <link rel="stylesheet" href="/static/css/design-system.css">
 
 
 
16
 
17
  :root {
18
  /* Colors */
 
90
  overflow: hidden;
91
  }
92
 
93
+ /* Ensure header content is above background */
 
 
 
 
 
 
 
 
 
 
 
94
  .tt-header-content {
95
  position: relative;
96
  z-index: 2;
 
906
  // Force refresh if we detect cached version
907
  (function() {
908
  const currentVersion = '5.1.1';
909
+ const timestamp = '1755115883'; // Cache-busting bump
910
  const lastVersion = sessionStorage.getItem('treetrack_version');
911
  const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
912
 
 
1151
  </div>
1152
  </div>
1153
 
1154
+ <script type="module" src="/static/js/tree-track-app.js?v=5.1.1&t=1755115883"></script>
1155
 
1156
  <script>
1157
  // Idle-time prefetch of map assets to speed up first navigation
 
1176
  link.addEventListener('mouseenter', prefetch, {once: true});
1177
  link.addEventListener('touchstart', prefetch, {once: true});
1178
  })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1179
  </script>
1180
  </body>
1181
  </html>
static/js/modules/api-client.js CHANGED
@@ -178,70 +178,15 @@ export class ApiClient {
178
  });
179
 
180
  if (!response.ok) {
181
- const errText = await response.text().catch(() => '');
182
- await this.sendTelemetry({
183
- event_type: 'upload',
184
- status: 'error',
185
- metadata: {
186
- type,
187
- category,
188
- filename: file.name,
189
- size: file.size,
190
- content_type: file.type,
191
- response_status: response.status,
192
- response_text: errText
193
- }
194
- }).catch(() => {});
195
  throw new Error('Upload failed');
196
  }
197
  resJson = await response.json();
198
- await this.sendTelemetry({
199
- event_type: 'upload',
200
- status: 'success',
201
- metadata: {
202
- type,
203
- category,
204
- filename: file.name,
205
- size: file.size,
206
- content_type: file.type,
207
- server_filename: resJson.filename,
208
- bucket: resJson.bucket
209
- }
210
- }).catch(() => {});
211
  return resJson;
212
  } catch (e) {
213
  // Network or other errors
214
- if (!resJson) {
215
- await this.sendTelemetry({
216
- event_type: 'upload',
217
- status: 'error',
218
- metadata: {
219
- type,
220
- category,
221
- filename: file?.name,
222
- size: file?.size,
223
- content_type: file?.type,
224
- error: e.message
225
- }
226
- }).catch(() => {});
227
- }
228
  throw e;
229
  }
230
  }
231
 
232
- async sendTelemetry(payload) {
233
- try {
234
- const response = await this.authenticatedFetch('/api/telemetry', {
235
- method: 'POST',
236
- headers: {
237
- 'Content-Type': 'application/json'
238
- },
239
- body: JSON.stringify(payload)
240
- });
241
- if (!response) return;
242
- // Best-effort; ignore response body
243
- } catch (_) {
244
- // swallow telemetry errors
245
- }
246
- }
247
  }
 
178
  });
179
 
180
  if (!response.ok) {
181
+ // Avoid noisy telemetry; just throw error
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  throw new Error('Upload failed');
183
  }
184
  resJson = await response.json();
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  return resJson;
186
  } catch (e) {
187
  // Network or other errors
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  throw e;
189
  }
190
  }
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
static/js/modules/auth-manager.js CHANGED
@@ -38,22 +38,6 @@ export class AuthManager {
38
 
39
  async logout() {
40
  try {
41
- // Telemetry: logout initiated (best-effort)
42
- try {
43
- await fetch('/api/telemetry', {
44
- method: 'POST',
45
- headers: {
46
- 'Content-Type': 'application/json',
47
- 'Authorization': `Bearer ${this.authToken}`
48
- },
49
- body: JSON.stringify({
50
- event_type: 'auth',
51
- status: 'logout_initiated',
52
- metadata: { source: 'client' }
53
- })
54
- });
55
- } catch (_) { /* ignore */ }
56
-
57
  await fetch('/api/auth/logout', {
58
  method: 'POST',
59
  headers: {
 
38
 
39
  async logout() {
40
  try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  await fetch('/api/auth/logout', {
42
  method: 'POST',
43
  headers: {
static/js/tree-track-app.js CHANGED
@@ -38,12 +38,6 @@ export class TreeTrackApp {
38
  // Setup event listeners
39
  this.setupEventListeners();
40
 
41
- // Log UI load telemetry
42
- this.apiClient.sendTelemetry({
43
- event_type: 'ui',
44
- status: 'view',
45
- metadata: { page: 'index', action: 'load' }
46
- }).catch(() => {});
47
 
48
 
49
  // Load initial data
@@ -52,12 +46,6 @@ export class TreeTrackApp {
52
  } catch (error) {
53
  console.error('Error initializing TreeTrack app:', error);
54
  this.uiManager.showMessage('Error initializing application: ' + error.message, 'error');
55
- // Telemetry for init error
56
- this.apiClient?.sendTelemetry({
57
- event_type: 'ui',
58
- status: 'error',
59
- metadata: { page: 'index', action: 'init', error: error?.message }
60
- }).catch(() => {});
61
  }
62
  }
63
 
@@ -170,40 +158,16 @@ export class TreeTrackApp {
170
  let result;
171
  if (this.formManager.isInEditMode()) {
172
  const id = this.formManager.getCurrentEditId();
173
- // Telemetry: update start
174
- this.apiClient.sendTelemetry({
175
- event_type: 'tree_update',
176
- status: 'start',
177
- metadata: { tree_id: id }
178
- }).catch(() => {});
179
  result = await this.apiClient.updateTree(id, treeData);
180
  this.uiManager.showMessage(`Tree #${result.id} updated successfully!`, 'success');
181
- // Telemetry: update success
182
- this.apiClient.sendTelemetry({
183
- event_type: 'tree_update',
184
- status: 'success',
185
- metadata: { tree_id: result.id }
186
- }).catch(() => {});
187
  // Exit edit mode silently after successful update
188
  this.handleCancelEdit(false);
189
  } else {
190
- // Telemetry: create start
191
- this.apiClient.sendTelemetry({
192
- event_type: 'tree_create',
193
- status: 'start',
194
- metadata: { has_location_name: !!treeData.location_name }
195
- }).catch(() => {});
196
  result = await this.apiClient.saveTree(treeData);
197
  this.uiManager.showMessage(
198
  `Tree successfully added! Tree ID: ${result.id}. The form has been cleared for your next entry.`,
199
  'success'
200
  );
201
- // Telemetry: create success
202
- this.apiClient.sendTelemetry({
203
- event_type: 'tree_create',
204
- status: 'success',
205
- metadata: { tree_id: result.id }
206
- }).catch(() => {});
207
  this.formManager.resetForm(true);
208
  }
209
 
@@ -215,12 +179,6 @@ export class TreeTrackApp {
215
  console.error('Error submitting form:', error);
216
  this.uiManager.showMessage('Error saving tree: ' + error.message, 'error');
217
  this.uiManager.focusFirstError();
218
- // Telemetry: save/update error
219
- this.apiClient.sendTelemetry({
220
- event_type: this.formManager.isInEditMode() ? 'tree_update' : 'tree_create',
221
- status: 'error',
222
- metadata: { error: error?.message }
223
- }).catch(() => {});
224
  }
225
  }
226
 
@@ -254,30 +212,12 @@ export class TreeTrackApp {
254
  }
255
 
256
  try {
257
- // Telemetry: delete start
258
- this.apiClient.sendTelemetry({
259
- event_type: 'tree_delete',
260
- status: 'start',
261
- metadata: { tree_id: treeId }
262
- }).catch(() => {});
263
  await this.apiClient.deleteTree(treeId);
264
  this.uiManager.showMessage(`Tree #${treeId} deleted successfully.`, 'success');
265
- // Telemetry: delete success
266
- this.apiClient.sendTelemetry({
267
- event_type: 'tree_delete',
268
- status: 'success',
269
- metadata: { tree_id: treeId }
270
- }).catch(() => {});
271
  await this.loadTrees();
272
  } catch (error) {
273
  console.error('Error deleting tree:', error);
274
  this.uiManager.showMessage('Error deleting tree: ' + error.message, 'error');
275
- // Telemetry: delete error
276
- this.apiClient.sendTelemetry({
277
- event_type: 'tree_delete',
278
- status: 'error',
279
- metadata: { tree_id: treeId, error: error?.message }
280
- }).catch(() => {});
281
  }
282
  }
283
 
@@ -299,21 +239,9 @@ export class TreeTrackApp {
299
  trees = (trees || []).sort((a,b) => new Date(b.created_at) - new Date(a.created_at));
300
  } catch (_) {}
301
  this.uiManager.renderTreeList(trees);
302
- // Telemetry: list loaded
303
- this.apiClient.sendTelemetry({
304
- event_type: 'tree_list',
305
- status: 'success',
306
- metadata: { count: Array.isArray(trees) ? trees.length : null }
307
- }).catch(() => {});
308
  } catch (error) {
309
  console.error('Error loading trees:', error);
310
  this.uiManager.showErrorState('treeList', 'Error loading trees');
311
- // Telemetry: list error
312
- this.apiClient.sendTelemetry({
313
- event_type: 'tree_list',
314
- status: 'error',
315
- metadata: { error: error?.message }
316
- }).catch(() => {});
317
  }
318
  }
319
 
 
38
  // Setup event listeners
39
  this.setupEventListeners();
40
 
 
 
 
 
 
 
41
 
42
 
43
  // Load initial data
 
46
  } catch (error) {
47
  console.error('Error initializing TreeTrack app:', error);
48
  this.uiManager.showMessage('Error initializing application: ' + error.message, 'error');
 
 
 
 
 
 
49
  }
50
  }
51
 
 
158
  let result;
159
  if (this.formManager.isInEditMode()) {
160
  const id = this.formManager.getCurrentEditId();
 
 
 
 
 
 
161
  result = await this.apiClient.updateTree(id, treeData);
162
  this.uiManager.showMessage(`Tree #${result.id} updated successfully!`, 'success');
 
 
 
 
 
 
163
  // Exit edit mode silently after successful update
164
  this.handleCancelEdit(false);
165
  } else {
 
 
 
 
 
 
166
  result = await this.apiClient.saveTree(treeData);
167
  this.uiManager.showMessage(
168
  `Tree successfully added! Tree ID: ${result.id}. The form has been cleared for your next entry.`,
169
  'success'
170
  );
 
 
 
 
 
 
171
  this.formManager.resetForm(true);
172
  }
173
 
 
179
  console.error('Error submitting form:', error);
180
  this.uiManager.showMessage('Error saving tree: ' + error.message, 'error');
181
  this.uiManager.focusFirstError();
 
 
 
 
 
 
182
  }
183
  }
184
 
 
212
  }
213
 
214
  try {
 
 
 
 
 
 
215
  await this.apiClient.deleteTree(treeId);
216
  this.uiManager.showMessage(`Tree #${treeId} deleted successfully.`, 'success');
 
 
 
 
 
 
217
  await this.loadTrees();
218
  } catch (error) {
219
  console.error('Error deleting tree:', error);
220
  this.uiManager.showMessage('Error deleting tree: ' + error.message, 'error');
 
 
 
 
 
 
221
  }
222
  }
223
 
 
239
  trees = (trees || []).sort((a,b) => new Date(b.created_at) - new Date(a.created_at));
240
  } catch (_) {}
241
  this.uiManager.renderTreeList(trees);
 
 
 
 
 
 
242
  } catch (error) {
243
  console.error('Error loading trees:', error);
244
  this.uiManager.showErrorState('treeList', 'Error loading trees');
 
 
 
 
 
 
245
  }
246
  }
247
 
static/map.html CHANGED
@@ -13,9 +13,6 @@
13
  <!-- Leaflet CSS -->
14
  <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
15
  <link rel="stylesheet" href="/static/css/design-system.css">
16
- <!-- Granim.js for gradient animations -->
17
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/granim.min.js"></script>
18
-
19
  <style>
20
 
21
  :root {
@@ -102,18 +99,7 @@
102
  overflow: hidden;
103
  }
104
 
105
- /* Granim Canvas for Map Header */
106
- #map-header-canvas {
107
- position: absolute;
108
- top: 0;
109
- left: 0;
110
- width: 100vw;
111
- height: 100%;
112
- z-index: 1;
113
- background: linear-gradient(135deg, #f8fafc, #e2e8f0, #cbd5e1);
114
- }
115
-
116
- /* Ensure header content is above canvas */
117
  .tt-header-content {
118
  position: relative;
119
  z-index: 2;
@@ -813,7 +799,7 @@
813
  // Force refresh if we detect cached version
814
  (function() {
815
  const currentVersion = '5.1.1';
816
- const timestamp = '1755114945'; // Current timestamp for cache busting
817
  const lastVersion = sessionStorage.getItem('treetrack_version');
818
  const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
819
 
@@ -939,7 +925,7 @@ const timestamp = '1755114945'; // Current timestamp for cache busting
939
 
940
  <!-- Leaflet JS -->
941
  <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
942
- <script src="/static/map.js?v=5.1.1&t=1755114945">
943
 
944
  "default-state": {
945
  gradients: [
@@ -954,7 +940,6 @@ const timestamp = '1755114945'; // Current timestamp for cache busting
954
  }
955
  });
956
 
957
- console.log('Map header Granim with forest road background initialized synchronously');
958
  // Defer any heavy data fetch; ensure map shell is visible first
959
  if (window.requestAnimationFrame) {
960
  requestAnimationFrame(() => setTimeout(() => {
@@ -963,28 +948,5 @@ const timestamp = '1755114945'; // Current timestamp for cache busting
963
  }
964
  });
965
  </script>
966
- <script>
967
- // Emit telemetry for map page view (best-effort)
968
- (function() {
969
- try {
970
- document.addEventListener('DOMContentLoaded', function() {
971
- var token = localStorage.getItem('auth_token');
972
- if (!token) return;
973
- fetch('/api/telemetry', {
974
- method: 'POST',
975
- headers: {
976
- 'Content-Type': 'application/json',
977
- 'Authorization': 'Bearer ' + token
978
- },
979
- body: JSON.stringify({
980
- event_type: 'ui',
981
- status: 'view',
982
- metadata: { page: 'map', action: 'load' }
983
- })
984
- }).catch(function(){});
985
- });
986
- } catch(e) { /* ignore */ }
987
- })();
988
- </script>
989
  </body>
990
  </html>
 
13
  <!-- Leaflet CSS -->
14
  <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
15
  <link rel="stylesheet" href="/static/css/design-system.css">
 
 
 
16
  <style>
17
 
18
  :root {
 
99
  overflow: hidden;
100
  }
101
 
102
+ /* Ensure header content is above background */
 
 
 
 
 
 
 
 
 
 
 
103
  .tt-header-content {
104
  position: relative;
105
  z-index: 2;
 
799
  // Force refresh if we detect cached version
800
  (function() {
801
  const currentVersion = '5.1.1';
802
+ const timestamp = '1755115883'; // Current timestamp for cache busting
803
  const lastVersion = sessionStorage.getItem('treetrack_version');
804
  const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
805
 
 
925
 
926
  <!-- Leaflet JS -->
927
  <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
928
+ <script src="/static/map.js?v=5.1.1&t=1755115883">
929
 
930
  "default-state": {
931
  gradients: [
 
940
  }
941
  });
942
 
 
943
  // Defer any heavy data fetch; ensure map shell is visible first
944
  if (window.requestAnimationFrame) {
945
  requestAnimationFrame(() => setTimeout(() => {
 
948
  }
949
  });
950
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
  </body>
952
  </html>
static/sw.js CHANGED
@@ -1,5 +1,5 @@
1
  // TreeTrack Service Worker - PWA and Offline Support
2
- const VERSION = 1755114945; // Cache busting bump - force clients to fetch new static assets and header image change
3
  const CACHE_NAME = `treetrack-v${VERSION}`;
4
  const STATIC_CACHE = `static-v${VERSION}`;
5
  const API_CACHE = `api-v${VERSION}`;
 
1
  // TreeTrack Service Worker - PWA and Offline Support
2
+ const VERSION = 1755115883; // Cache busting bump - force clients to fetch new static assets and header image change
3
  const CACHE_NAME = `treetrack-v${VERSION}`;
4
  const STATIC_CACHE = `static-v${VERSION}`;
5
  const API_CACHE = `api-v${VERSION}`;
static/telemetry.html CHANGED
@@ -1,179 +1 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Telemetry Viewer - Admin</title>
7
- <link rel="stylesheet" href="/static/css/design-system.css" />
8
- <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
9
- <meta http-equiv="Pragma" content="no-cache">
10
- <meta http-equiv="Expires" content="0">
11
- <style>
12
- body { font-family: Inter, system-ui, -apple-system, Segoe UI, sans-serif; background: var(--gray-50); color: var(--gray-800); }
13
- .container { max-width: 1200px; margin: 0 auto; padding: 1rem; }
14
- .header { display: flex; justify-content: space-between; align-items: center; margin: 1rem 0; }
15
- .title { font-size: 1.25rem; font-weight: 700; }
16
- .controls { display: flex; gap: .5rem; align-items: center; }
17
- .filter-input { padding: .5rem .75rem; border: 1px solid var(--gray-300); border-radius: .5rem; font-size: .9rem; }
18
- .btn { padding: .5rem .75rem; border: 1px solid var(--gray-300); background: white; border-radius: .5rem; cursor: pointer; }
19
- .btn-primary { background: var(--primary-600); color: white; border-color: var(--primary-600); }
20
- .btn:disabled { opacity: .5; cursor: not-allowed; }
21
- .meta { color: var(--gray-500); font-size: .85rem; }
22
- .table { width: 100%; border-collapse: collapse; background: white; border: 1px solid var(--gray-200); border-radius: .75rem; overflow: hidden; }
23
- .table th, .table td { padding: .5rem .75rem; border-bottom: 1px solid var(--gray-100); text-align: left; vertical-align: top; font-size: .9rem; }
24
- .table th { background: var(--gray-50); font-weight: 600; }
25
- .pill { display: inline-block; padding: .15rem .4rem; border-radius: .5rem; font-size: .75rem; border: 1px solid var(--gray-300); }
26
- .pill-success { background: var(--green-50); color: var(--green-700); border-color: var(--green-300); }
27
- .pill-error { background: var(--red-50); color: var(--red-700); border-color: var(--red-300); }
28
- .pill-start { background: var(--orange-50); color: var(--orange-700); border-color: var(--orange-300); }
29
- .code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .8rem; background: var(--gray-50); padding: .35rem .5rem; border-radius: .35rem; border: 1px solid var(--gray-200); display: inline-block; max-width: 520px; overflow: auto; }
30
- .row { transition: background .2s ease; }
31
- .row:hover { background: var(--gray-50); }
32
- .footer { display: flex; justify-content: space-between; align-items: center; margin-top: .75rem; }
33
- </style>
34
- </head>
35
- <body>
36
- <div class="container">
37
- <div class="header">
38
- <div>
39
- <div class="title">Telemetry Viewer (Admin)</div>
40
- <div class="meta">Inspect recent telemetry events to monitor app health and user actions.</div>
41
- </div>
42
- <div class="controls">
43
- <input id="limit" class="filter-input" type="number" min="1" max="1000" value="200" title="Max events" />
44
- <input id="search" class="filter-input" placeholder="Search text (type, status, user, etc.)" />
45
- <button id="refresh" class="btn btn-primary">Refresh</button>
46
- <a class="btn" href="/">Back</a>
47
- </div>
48
- </div>
49
-
50
- <div id="meta" class="meta" style="margin-bottom: .5rem;">&nbsp;</div>
51
-
52
- <table class="table">
53
- <thead>
54
- <tr>
55
- <th style="width: 10rem;">Timestamp</th>
56
- <th style="width: 8rem;">Type</th>
57
- <th style="width: 8rem;">Status</th>
58
- <th style="width: 10rem;">User</th>
59
- <th>Metadata</th>
60
- </tr>
61
- </thead>
62
- <tbody id="tbody"></tbody>
63
- </table>
64
-
65
- <div class="footer">
66
- <div id="source" class="meta"></div>
67
- <div class="meta">Only accessible to admin users</div>
68
- </div>
69
- </div>
70
-
71
- <script type="module">
72
- async function fetchTelemetry(limit) {
73
- const params = new URLSearchParams({ limit: String(limit) });
74
- const res = await fetch(`/api/telemetry?${params.toString()}`, { headers: authHeaders() });
75
- if (res.status === 401 || res.status === 403) {
76
- alert('Unauthorized. You must be an admin to view telemetry.');
77
- window.location.href = '/login';
78
- return null;
79
- }
80
- if (!res.ok) {
81
- throw new Error('Failed to fetch telemetry');
82
- }
83
- return res.json();
84
- }
85
-
86
- function authHeaders() {
87
- const token = localStorage.getItem('auth_token');
88
- return token ? { 'Authorization': `Bearer ${token}` } : {};
89
- }
90
-
91
- function pill(status) {
92
- if (!status) return '';
93
- const cls = status === 'success' ? 'pill-success' : status === 'error' ? 'pill-error' : status === 'start' ? 'pill-start' : '';
94
- return `<span class="pill ${cls}">${status}</span>`;
95
- }
96
-
97
- function escapeHtml(s) { return s.replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])); }
98
-
99
- function renderRows(events) {
100
- const tbody = document.getElementById('tbody');
101
- const q = (document.getElementById('search').value || '').toLowerCase().trim();
102
- tbody.innerHTML = '';
103
- for (const evt of events) {
104
- const ts = evt.timestamp || '';
105
- const type = evt.event_type || '';
106
- const status = evt.status || '';
107
- const user = (evt.user && evt.user.username) ? `${evt.user.username} (${evt.user.role || ''})` : '';
108
- const meta = evt.metadata ? escapeHtml(JSON.stringify(evt.metadata)) : '';
109
- const line = `${ts} ${type} ${status} ${user} ${meta}`.toLowerCase();
110
- if (q && !line.includes(q)) continue;
111
- const tr = document.createElement('tr');
112
- tr.className = 'row';
113
- tr.innerHTML = `
114
- <td>${escapeHtml(ts)}</td>
115
- <td><span class="pill">${escapeHtml(type)}</span></td>
116
- <td>${pill(status)}</td>
117
- <td>${escapeHtml(user)}</td>
118
- <td><span class="code">${meta}</span></td>
119
- `;
120
- tbody.appendChild(tr);
121
- }
122
- }
123
-
124
- async function refresh() {
125
- const limit = Math.max(1, Math.min(1000, parseInt(document.getElementById('limit').value || '200', 10)));
126
- document.getElementById('refresh').disabled = true;
127
- try {
128
- const data = await fetchTelemetry(limit);
129
- if (!data) return;
130
- document.getElementById('meta').textContent = `Loaded ${data.count} events`;
131
- document.getElementById('source').textContent = `Source: ${data.source}`;
132
- renderRows(data.events || []);
133
- } catch (e) {
134
- alert(e.message || 'Failed to load telemetry');
135
- } finally {
136
- document.getElementById('refresh').disabled = false;
137
- }
138
- }
139
-
140
- document.getElementById('refresh').addEventListener('click', refresh);
141
- document.getElementById('search').addEventListener('input', () => {
142
- // quick re-filter without fetching again
143
- const meta = document.getElementById('meta').textContent || '';
144
- // no-op; filtering reads from table content
145
- const rows = Array.from(document.querySelectorAll('#tbody tr'));
146
- const q = (document.getElementById('search').value || '').toLowerCase().trim();
147
- rows.forEach(row => {
148
- const text = row.textContent.toLowerCase();
149
- row.style.display = q && !text.includes(q) ? 'none' : '';
150
- });
151
- });
152
-
153
- // On load: validate auth and fetch
154
- (async () => {
155
- try {
156
- const token = localStorage.getItem('auth_token');
157
- if (!token) {
158
- window.location.href = '/login';
159
- return;
160
- }
161
- // Validate user and ensure admin
162
- const res = await fetch('/api/auth/validate', { headers: authHeaders() });
163
- if (!res.ok) { window.location.href = '/login'; return; }
164
- const info = await res.json();
165
- const perms = (info.user && info.user.permissions) || [];
166
- if (!perms.includes('admin')) {
167
- alert('Admin access required');
168
- window.location.href = '/';
169
- return;
170
- }
171
- await refresh();
172
- } catch {
173
- window.location.href = '/login';
174
- }
175
- })();
176
- </script>
177
- </body>
178
- </html>
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
version.json CHANGED
@@ -1,4 +1,4 @@
1
  {
2
  "version": "5.1.1",
3
- "timestamp": 1755114945
4
  }
 
1
  {
2
  "version": "5.1.1",
3
+ "timestamp": 1755115883
4
  }