owlservable 0.2.9 → 0.2.11

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/auto.cjs CHANGED
@@ -1 +1 @@
1
- require('./index.cjs').init()
1
+ require('./index.cjs').init({ save: true })
package/auto.js CHANGED
@@ -1,2 +1,2 @@
1
1
  import { init } from './index.js'
2
- init()
2
+ init({ save: true })
package/dashboard.html CHANGED
@@ -227,20 +227,21 @@ function detailHtml(r){
227
227
  }
228
228
 
229
229
  function addRow(r,prepend){
230
- total++;if(!r.status||r.error)errs++;sumMs+=r.latency||0;
230
+ if(!r.pending){total++;if(!r.status||r.error)errs++;sumMs+=r.latency||0;}
231
231
  if(r.tokens)totalTok+=r.tokens.total||0;
232
232
  storeSize=Math.min(storeSize+1,500);
233
233
  nReq.textContent=total;nErr.textContent=errs;
234
- nLat.textContent=fms(Math.round(sumMs/total));
234
+ nLat.textContent=total?fms(Math.round(sumMs/total)):'—';
235
235
  nTok.textContent=fn(totalTok);nStore.textContent=storeSize+'/500';
236
236
  var method=r.method||'GET',parts=splitUrl(r.url||'');
237
- var sTxt=r.status||(r.error?'ERR':'—');
237
+ var statusHtml=r.pending?'<span class="pending" title="in-flight"></span>':'<span class="badge '+sc(r.status)+'">'+esc(r.status||(r.error?'ERR':'—'))+'</span>';
238
+ var latHtml=r.pending?'<span class="muted">…</span>':fms(r.latency||0);
238
239
  var tr=document.createElement('tr');tr.className='req new';tr.dataset.id=r.id;
239
240
  tr.innerHTML=
240
241
  '<td class="c-time muted">'+ftime(r.timestamp)+'</td>'+
241
242
  '<td class="c-method"><span class="'+esc(method)+'">'+esc(method)+'</span></td>'+
242
- '<td class="c-status"><span class="badge '+sc(r.status)+'">'+esc(sTxt)+'</span></td>'+
243
- '<td class="c-lat muted">'+fms(r.latency||0)+'</td>'+
243
+ '<td class="c-status">'+statusHtml+'</td>'+
244
+ '<td class="c-lat muted">'+latHtml+'</td>'+
244
245
  '<td class="c-tok">'+tokHtml(r)+'</td>'+
245
246
  '<td class="c-url" title="'+esc(r.url||'')+'"><span class="muted">'+esc(parts.host)+'</span>'+esc(parts.path)+'</td>';
246
247
  var dtd=document.createElement('td');dtd.colSpan=6;dtd.innerHTML=detailHtml(r);
@@ -257,9 +258,18 @@ function addRow(r,prepend){
257
258
 
258
259
  function applyUpdate(id,patch){
259
260
  var e=byId[id];if(!e)return;
261
+ var prevTok=(e.data.tokens&&e.data.tokens.total)||0;
262
+ var wasPending=e.data.pending;
260
263
  Object.assign(e.data,patch);
264
+ if(wasPending&&!e.data.pending){
265
+ total++;if(!e.data.status||e.data.error)errs++;sumMs+=e.data.latency||0;
266
+ nReq.textContent=total;nErr.textContent=errs;
267
+ nLat.textContent=total?fms(Math.round(sumMs/total)):'—';
268
+ e.tr.querySelector('.c-status').innerHTML='<span class="badge '+sc(e.data.status)+'">'+esc(e.data.status||(e.data.error?'ERR':'—'))+'</span>';
269
+ e.tr.querySelector('.c-lat').innerHTML=fms(e.data.latency||0);
270
+ }
261
271
  if(patch.tokens){
262
- totalTok+=patch.tokens.total||0;
272
+ totalTok+=(patch.tokens.total||0)-prevTok;
263
273
  nTok.textContent=fn(totalTok);
264
274
  e.tr.querySelector('.c-tok').innerHTML=tokHtml(e.data);
265
275
  }else if('metaPending' in patch&&!patch.metaPending){
@@ -307,7 +317,7 @@ function handleMsg(msg){
307
317
  msg.requests.slice().reverse().forEach(function(r){addRow(r,false);});
308
318
  renderSave(msg.save);
309
319
  }else if(msg.type==='request'){
310
- addRow(msg.record,true);
320
+ if(!byId[msg.record.id])addRow(msg.record,true);
311
321
  }else if(msg.type==='update'){
312
322
  applyUpdate(msg.id,msg.patch);
313
323
  }else if(msg.type==='saveConfig'){
@@ -352,6 +362,23 @@ function connect(){
352
362
  try{handleMsg(msg);}catch(_){}
353
363
  });
354
364
  }
365
+ var pollGen=1;
366
+ function poll(){
367
+ fetch('/api/requests').then(function(r){return r.json();}).then(function(d){
368
+ var reqs=d.requests||[],gen=d.generation||1;
369
+ if(gen!==pollGen){
370
+ pollGen=gen;reset();
371
+ reqs.slice().reverse().forEach(function(r){addRow(r,false);});
372
+ renderSave(d.save);return;
373
+ }
374
+ reqs.forEach(function(r){
375
+ if(!byId[r.id]){addRow(r,true);}
376
+ else{var e=byId[r.id];if((e.data.pending&&!r.pending)||(e.data.metaPending&&!r.metaPending)){applyUpdate(r.id,r);}}
377
+ });
378
+ }).catch(function(){});
379
+ }
380
+ setInterval(poll,2000);
381
+ setTimeout(poll,500);
355
382
  connect();
356
383
  })();
357
384
  </script>
package/index.cjs CHANGED
@@ -17,6 +17,7 @@ emitter.setMaxListeners(100)
17
17
 
18
18
  const requests = []
19
19
  let nextId = 1
20
+ let generation = 1
20
21
 
21
22
  function addRequest(entry) {
22
23
  try {
@@ -43,6 +44,7 @@ function updateRequest(id, patch) {
43
44
  function getRequests() { try { return requests.slice() } catch (_) { return [] } }
44
45
 
45
46
  function clearRequests() {
47
+ generation++
46
48
  requests.splice(0)
47
49
  if (saveState.enabled) try { fs.writeFileSync(saveState.filePath, '') } catch (_) {}
48
50
  emitter.emit('reload', { requests: [], save: getSaveInfo() })
@@ -163,18 +165,19 @@ function patchFetch() {
163
165
  reqBody = bodyFromInit(init?.body ?? null)
164
166
  } catch (_) {}
165
167
 
168
+ const r = addRequest({ url, method, status: null, latency: null, reqBody, metaPending: false, pending: true })
166
169
  const start = Date.now()
167
170
  try {
168
171
  const res = await orig.call(this, input, init)
169
172
  const ct = res.headers.get('content-type') || ''
170
173
  const isJson = ct.includes('application/json')
171
174
  const isSse = ct.includes('text/event-stream')
172
- const r = addRequest({ url, method, status: res.status, latency: Date.now() - start, reqBody, metaPending: isJson || isSse })
175
+ updateRequest(r.id, { status: res.status, latency: Date.now() - start, pending: false, metaPending: isJson || isSse })
173
176
  if (r && isJson) handleJsonResponse(r, () => res.clone().text())
174
177
  if (r && isSse) handleSseResponse(r, () => res.clone().text())
175
178
  return res
176
179
  } catch (err) {
177
- try { addRequest({ url, method, status: 0, latency: Date.now() - start, reqBody, error: err.message }) } catch (_) {}
180
+ try { updateRequest(r.id, { status: 0, latency: Date.now() - start, pending: false, error: err.message }) } catch (_) {}
178
181
  throw err
179
182
  }
180
183
  }
@@ -196,6 +199,7 @@ function patchHttpModule(mod, protocol) {
196
199
  } catch (_) {}
197
200
 
198
201
  const req = orig.apply(mod, args)
202
+ const r = addRequest({ url, method: method.toUpperCase(), status: null, latency: null, reqBody: null, metaPending: false, pending: true })
199
203
  const reqChunks = []; let reqSize = 0
200
204
  const origWrite = req.write
201
205
  req.write = function(chunk, encoding, cb) {
@@ -213,8 +217,7 @@ function patchHttpModule(mod, protocol) {
213
217
  const reqBody = reqSize > 0 ? reqChunks.join('').slice(0, MAX_REQ_BODY) : null
214
218
  const ct = res.headers['content-type'] || ''
215
219
  const isJson = ct.includes('application/json')
216
- const isSse = ct.includes('text/event-stream')
217
- const r = addRequest({ url, method: method.toUpperCase(), status: res.statusCode, latency: Date.now() - start, reqBody, metaPending: isJson })
220
+ updateRequest(r.id, { status: res.statusCode, latency: Date.now() - start, reqBody, pending: false, metaPending: isJson })
218
221
  if (r && isJson) {
219
222
  try {
220
223
  const chunks = []; let size = 0
@@ -225,12 +228,11 @@ function patchHttpModule(mod, protocol) {
225
228
  res.once('error', reject)
226
229
  })
227
230
  if (isJson) handleJsonResponse(r, getText)
228
- if (isSse) handleSseResponse(r, getText)
229
231
  } catch (_) { updateRequest(r.id, { metaPending: false }) }
230
232
  }
231
233
  })
232
234
  req.once('error', err => {
233
- try { addRequest({ url, method: method.toUpperCase(), status: 0, latency: Date.now() - start, error: err.message }) } catch (_) {}
235
+ try { updateRequest(r.id, { status: 0, latency: Date.now() - start, pending: false, error: err.message }) } catch (_) {}
234
236
  })
235
237
  } catch (_) {}
236
238
 
@@ -383,6 +385,12 @@ function startDashboard(port) {
383
385
  return
384
386
  }
385
387
 
388
+ if (urlPath === '/api/requests' && req.method === 'GET') {
389
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' })
390
+ res.end(JSON.stringify({ requests: getRequests(), save: getSaveInfo(), generation }))
391
+ return
392
+ }
393
+
386
394
  if (urlPath === '/owl.png') {
387
395
  try {
388
396
  const data = fs.readFileSync(path.join(__dir, 'owl.png'))
package/index.js CHANGED
@@ -17,6 +17,7 @@ emitter.setMaxListeners(100)
17
17
 
18
18
  const requests = []
19
19
  let nextId = 1
20
+ let generation = 1
20
21
 
21
22
  function addRequest(entry) {
22
23
  try {
@@ -43,6 +44,7 @@ function updateRequest(id, patch) {
43
44
  function getRequests() { try { return requests.slice() } catch (_) { return [] } }
44
45
 
45
46
  function clearRequests() {
47
+ generation++
46
48
  requests.splice(0)
47
49
  if (saveState.enabled) try { fs.writeFileSync(saveState.filePath, '') } catch (_) {}
48
50
  emitter.emit('reload', { requests: [], save: getSaveInfo() })
@@ -163,18 +165,19 @@ function patchFetch() {
163
165
  reqBody = bodyFromInit(init?.body ?? null)
164
166
  } catch (_) {}
165
167
 
168
+ const r = addRequest({ url, method, status: null, latency: null, reqBody, metaPending: false, pending: true })
166
169
  const start = Date.now()
167
170
  try {
168
171
  const res = await orig.call(this, input, init)
169
172
  const ct = res.headers.get('content-type') || ''
170
173
  const isJson = ct.includes('application/json')
171
174
  const isSse = ct.includes('text/event-stream')
172
- const r = addRequest({ url, method, status: res.status, latency: Date.now() - start, reqBody, metaPending: isJson || isSse })
175
+ updateRequest(r.id, { status: res.status, latency: Date.now() - start, pending: false, metaPending: isJson || isSse })
173
176
  if (r && isJson) handleJsonResponse(r, () => res.clone().text())
174
177
  if (r && isSse) handleSseResponse(r, () => res.clone().text())
175
178
  return res
176
179
  } catch (err) {
177
- try { addRequest({ url, method, status: 0, latency: Date.now() - start, reqBody, error: err.message }) } catch (_) {}
180
+ try { updateRequest(r.id, { status: 0, latency: Date.now() - start, pending: false, error: err.message }) } catch (_) {}
178
181
  throw err
179
182
  }
180
183
  }
@@ -196,6 +199,7 @@ function patchHttpModule(mod, protocol) {
196
199
  } catch (_) {}
197
200
 
198
201
  const req = orig.apply(mod, args)
202
+ const r = addRequest({ url, method: method.toUpperCase(), status: null, latency: null, reqBody: null, metaPending: false, pending: true })
199
203
  const reqChunks = []; let reqSize = 0
200
204
  const origWrite = req.write
201
205
  req.write = function(chunk, encoding, cb) {
@@ -213,8 +217,7 @@ function patchHttpModule(mod, protocol) {
213
217
  const reqBody = reqSize > 0 ? reqChunks.join('').slice(0, MAX_REQ_BODY) : null
214
218
  const ct = res.headers['content-type'] || ''
215
219
  const isJson = ct.includes('application/json')
216
- const isSse = ct.includes('text/event-stream')
217
- const r = addRequest({ url, method: method.toUpperCase(), status: res.statusCode, latency: Date.now() - start, reqBody, metaPending: isJson })
220
+ updateRequest(r.id, { status: res.statusCode, latency: Date.now() - start, reqBody, pending: false, metaPending: isJson })
218
221
  if (r && isJson) {
219
222
  try {
220
223
  const chunks = []; let size = 0
@@ -225,12 +228,11 @@ function patchHttpModule(mod, protocol) {
225
228
  res.once('error', reject)
226
229
  })
227
230
  if (isJson) handleJsonResponse(r, getText)
228
- if (isSse) handleSseResponse(r, getText)
229
231
  } catch (_) { updateRequest(r.id, { metaPending: false }) }
230
232
  }
231
233
  })
232
234
  req.once('error', err => {
233
- try { addRequest({ url, method: method.toUpperCase(), status: 0, latency: Date.now() - start, error: err.message }) } catch (_) {}
235
+ try { updateRequest(r.id, { status: 0, latency: Date.now() - start, pending: false, error: err.message }) } catch (_) {}
234
236
  })
235
237
  } catch (_) {}
236
238
 
@@ -383,6 +385,12 @@ function startDashboard(port) {
383
385
  return
384
386
  }
385
387
 
388
+ if (urlPath === '/api/requests' && req.method === 'GET') {
389
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' })
390
+ res.end(JSON.stringify({ requests: getRequests(), save: getSaveInfo(), generation }))
391
+ return
392
+ }
393
+
386
394
  if (urlPath === '/owl.png') {
387
395
  try {
388
396
  const data = fs.readFileSync(path.join(__dir, 'owl.png'))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "owlservable",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Minimalist Observability Platform. Zero config, zero dependencies.",
5
5
  "type": "module",
6
6
  "main": "index.js",