Skip to content

Commit e4a3408

Browse files
thjaeckleclaude
andcommitted
Add optional "Include context headers" checkbox to Thing Updates
Adds a checkbox to the Thing Updates section that optionally includes _context/headers in SSE events. In Live mode, toggling it reconnects the SSE stream immediately. In Historical mode, only the useful _context/headers/historical-headers field is requested. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 022e4ab commit e4a3408

3 files changed

Lines changed: 56 additions & 8 deletions

File tree

ui/modules/things/thingUpdates.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ <h5 data-bs-toggle="collapse" data-bs-target="#collapseThingUpdates">
3131
data-bs-toggle="tooltip" title="Reset the updates table">
3232
<i class="bi bi-x-circle"></i> Reset
3333
</button>
34+
<div class="form-check form-check-inline ms-2 mb-0 d-flex align-items-center">
35+
<input class="form-check-input mt-0" type="checkbox" id="checkboxIncludeHeaders">
36+
<label class="form-check-label ms-1" for="checkboxIncludeHeaders"
37+
data-bs-toggle="tooltip" title="Include _context/headers in SSE events (reconnects live stream)">
38+
Include context headers</label>
39+
</div>
3440
</div>
3541
<div id="historicalUpdateControls" hidden>
3642
<div class="input-group input-group-sm mb-1">

ui/modules/things/thingUpdates.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import thingUpdatesHTML from './thingUpdates.html';
2121
import * as Things from './things.js';
2222
import * as ThingsSSE from './thingsSSE.js';
2323

24+
const BASE_CONTEXT_FIELDS = '_context/topic,_context/path,_context/value';
25+
const EXTRA_FIELDS = 'thingId,policyId,definition,attributes,features,_revision,_created,_modified,_metadata';
26+
2427
const MAX_MESSAGES = 5000;
2528

2629
enum ThingUpdateContent {
@@ -53,6 +56,7 @@ type DomElements = {
5356
inputHistoricalRqlFilter: HTMLInputElement,
5457
buttonFetchHistorical: HTMLButtonElement,
5558
buttonStopHistorical: HTMLButtonElement,
59+
checkboxIncludeHeaders: HTMLInputElement,
5660
selectThingUpdateContent: HTMLSelectElement,
5761
tbodyThingUpdates: HTMLTableElement,
5862
tableFilterThingUpdates: TableFilter,
@@ -81,6 +85,7 @@ let dom: DomElements = {
8185
inputHistoricalRqlFilter: null,
8286
buttonFetchHistorical: null,
8387
buttonStopHistorical: null,
88+
checkboxIncludeHeaders: null,
8489
selectThingUpdateContent: null,
8590
tbodyThingUpdates: null,
8691
tableFilterThingUpdates: null,
@@ -119,6 +124,7 @@ export function ready() {
119124
dom.tableFilterThingUpdates.addEventListener('filterChange', onFilterChange);
120125
dom.tableFilterThingUpdates.filterOptions = createFilterOptions();
121126

127+
dom.checkboxIncludeHeaders.onchange = onIncludeHeadersChange;
122128
dom.thingUpdatesModeLive.onchange = onModeChange;
123129
dom.thingUpdatesModeHistorical.onchange = onModeChange;
124130
dom.historicalRangeModeRevision.onchange = onRangeModeChange;
@@ -159,6 +165,23 @@ export function ready() {
159165
};
160166
}
161167

168+
/**
169+
* Returns the SSE fields parameter string, optionally including context headers.
170+
* In historical mode only `_context/headers/historical-headers` is useful;
171+
* in live mode the full `_context/headers` is requested.
172+
*/
173+
export function buildFieldsParam(historical = false): string {
174+
const headerField = historical ? '_context/headers/historical-headers' : '_context/headers';
175+
const contextFields = dom.checkboxIncludeHeaders?.checked
176+
? `${BASE_CONTEXT_FIELDS},${headerField}`
177+
: BASE_CONTEXT_FIELDS;
178+
return `fields=${EXTRA_FIELDS},${contextFields}&extraFields=${EXTRA_FIELDS}`;
179+
}
180+
181+
function onIncludeHeadersChange() {
182+
ThingsSSE.reconnectSelectedThing(buildFieldsParam());
183+
}
184+
162185
function onEnvironmentChanged(modifiedField) {
163186
if (!['pinnedThings', 'filterList', 'messageTemplates', 'recentPolicyIds'].includes(modifiedField)) {
164187
cancelActiveProbe();
@@ -436,8 +459,7 @@ function onFetchHistorical() {
436459
lastReceivedRevision = null;
437460
requestedToRevision = null;
438461

439-
const fields = 'fields=thingId,policyId,definition,attributes,features,_revision,_created,_modified,_metadata,_context/topic,_context/path,_context/value' +
440-
'&extraFields=thingId,policyId,definition,attributes,features,_revision,_created,_modified,_metadata';
462+
const fields = buildFieldsParam(true);
441463

442464
let rangeParams = '';
443465
if (dom.historicalRangeModeRevision.checked) {

ui/modules/things/thingsSSE.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ import * as Environments from '../environments/environments.js';
1919
import * as Things from './things.js';
2020
import * as ThingsSearch from './thingsSearch.js';
2121

22+
const DEFAULT_SELECTED_FIELDS =
23+
'fields=thingId,policyId,definition,attributes,features,_revision,_created,_modified,_metadata,_context/topic,_context/path,_context/value' +
24+
'&extraFields=thingId,policyId,definition,attributes,features,_revision,_created,_modified,_metadata';
25+
2226
let selectedThingEventSource;
2327
let thingsTableEventSource;
28+
let currentSelectedFields = DEFAULT_SELECTED_FIELDS;
2429

2530

2631
/**
@@ -53,12 +58,27 @@ function onSelectedThingChanged(newThingJson, isNewThingId) {
5358
if (!newThingJson) {
5459
stopSSE(selectedThingEventSource);
5560
} else if (isNewThingId) {
56-
selectedThingEventSource && selectedThingEventSource.close();
57-
// console.log('SSE Start: SELECTED THING : ' + newThingJson.thingId);
58-
selectedThingEventSource = API.getEventSource(newThingJson.thingId,
59-
'fields=thingId,policyId,definition,attributes,features,_revision,_created,_modified,_metadata,_context/topic,_context/path,_context/value' +
60-
'&extraFields=thingId,policyId,definition,attributes,features,_revision,_created,_modified,_metadata');
61-
selectedThingEventSource.onmessage = onMessageSelectedThing;
61+
connectSelectedThing(newThingJson.thingId);
62+
}
63+
}
64+
65+
function connectSelectedThing(thingId: string) {
66+
if (selectedThingEventSource) {
67+
selectedThingEventSource.close();
68+
}
69+
selectedThingEventSource = API.getEventSource(thingId, currentSelectedFields);
70+
selectedThingEventSource.onmessage = onMessageSelectedThing;
71+
}
72+
73+
/**
74+
* Reconnects the live SSE for the currently selected thing with updated
75+
* field parameters (e.g. after toggling "Include headers").
76+
*/
77+
export function reconnectSelectedThing(fields: string) {
78+
currentSelectedFields = fields;
79+
const thingId = Things.theThing?.thingId;
80+
if (thingId && selectedThingEventSource) {
81+
connectSelectedThing(thingId);
6282
}
6383
}
6484

0 commit comments

Comments
 (0)