Skip to content

Commit f893419

Browse files
authored
Merge PR #476: Activity feed prefs + errors filter
Activity feed: persist filters + Errors-only toggle
2 parents ee5adfa + 447bd50 commit f893419

2 files changed

Lines changed: 68 additions & 4 deletions

File tree

client/activity-feed.js

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,44 @@ class ActivityFeedPanel {
55
this.eventIds = new Set();
66
this.socket = null;
77
this.serverUrl = window.location.origin;
8-
this.filterText = '';
9-
this.groupFilter = 'all';
10-
this.paused = false;
8+
this.filterText = this.loadPref('activityFeed.filterText', '');
9+
this.groupFilter = this.loadPref('activityFeed.groupFilter', 'all');
10+
this.paused = this.loadPrefBool('activityFeed.paused', false);
11+
this.errorsOnly = this.loadPrefBool('activityFeed.errorsOnly', false);
1112
this.unseenCount = 0;
1213

1314
this._dismissPointerHandler = null;
1415
this._dismissKeyHandler = null;
1516
this._socketHandler = null;
1617
}
1718

19+
loadPref(key, fallback) {
20+
try {
21+
const v = localStorage.getItem(key);
22+
return v === null ? fallback : v;
23+
} catch {
24+
return fallback;
25+
}
26+
}
27+
28+
loadPrefBool(key, fallback) {
29+
try {
30+
const v = localStorage.getItem(key);
31+
if (v === null) return fallback;
32+
return v === 'true';
33+
} catch {
34+
return fallback;
35+
}
36+
}
37+
38+
savePref(key, value) {
39+
try {
40+
localStorage.setItem(key, String(value));
41+
} catch {
42+
// ignore
43+
}
44+
}
45+
1846
isOpen() {
1947
const modal = document.getElementById('activity-feed-modal');
2048
return !!modal && !modal.classList.contains('hidden');
@@ -105,6 +133,11 @@ class ActivityFeedPanel {
105133
Pause live
106134
</label>
107135
136+
<label class="option-toggle" title="Show only failing/error events">
137+
<input type="checkbox" id="activity-errors-only">
138+
Errors only
139+
</label>
140+
108141
<button class="btn-secondary" id="activity-refresh-btn">Refresh</button>
109142
<button class="btn-secondary" id="activity-clear-btn" title="Clear only this UI list (does not delete server history)">Clear</button>
110143
</div>
@@ -126,6 +159,7 @@ class ActivityFeedPanel {
126159
textInput.value = this.filterText;
127160
textInput.oninput = () => {
128161
this.filterText = String(textInput.value || '');
162+
this.savePref('activityFeed.filterText', this.filterText);
129163
this.renderList();
130164
};
131165
}
@@ -135,6 +169,7 @@ class ActivityFeedPanel {
135169
groupSelect.value = this.groupFilter;
136170
groupSelect.onchange = () => {
137171
this.groupFilter = String(groupSelect.value || 'all');
172+
this.savePref('activityFeed.groupFilter', this.groupFilter);
138173
this.renderList();
139174
};
140175
}
@@ -144,10 +179,21 @@ class ActivityFeedPanel {
144179
pauseCb.checked = !!this.paused;
145180
pauseCb.onchange = () => {
146181
this.paused = !!pauseCb.checked;
182+
this.savePref('activityFeed.paused', this.paused ? 'true' : 'false');
147183
this.renderStats();
148184
};
149185
}
150186

187+
const errorsCb = document.getElementById('activity-errors-only');
188+
if (errorsCb) {
189+
errorsCb.checked = !!this.errorsOnly;
190+
errorsCb.onchange = () => {
191+
this.errorsOnly = !!errorsCb.checked;
192+
this.savePref('activityFeed.errorsOnly', this.errorsOnly ? 'true' : 'false');
193+
this.renderList();
194+
};
195+
}
196+
151197
document.getElementById('activity-refresh-btn')?.addEventListener('click', () => this.refresh());
152198
document.getElementById('activity-clear-btn')?.addEventListener('click', () => {
153199
this.events = [];
@@ -260,12 +306,24 @@ class ActivityFeedPanel {
260306
const kind = String(ev?.kind || '');
261307
const groupOk = group === 'all' ? true : this.getGroup(kind) === group;
262308
if (!groupOk) return false;
309+
if (this.errorsOnly && !this.isErrorEvent(ev)) return false;
263310
if (!text) return true;
264311
const hay = `${kind} ${JSON.stringify(ev?.data || {})}`.toLowerCase();
265312
return hay.includes(text);
266313
});
267314
}
268315

316+
isErrorEvent(ev) {
317+
const kind = String(ev?.kind || '');
318+
const data = ev?.data && typeof ev.data === 'object' ? ev.data : {};
319+
if (data.ok === false) return true;
320+
if (kind.includes('failed')) return true;
321+
if (kind.includes('.error')) return true;
322+
if (kind.endsWith('.failed')) return true;
323+
if (kind.includes('close.failed')) return true;
324+
return false;
325+
}
326+
269327
getGroup(kind) {
270328
const k = String(kind || '');
271329
const head = k.split('.')[0] || '';
@@ -281,9 +339,10 @@ class ActivityFeedPanel {
281339
const dataJson = this.escapeHtml(this.compactJson(ev?.data));
282340
const group = this.getGroup(kind);
283341
const actions = this.renderEventActions(ev);
342+
const failedClass = this.isErrorEvent(ev) ? ' activity-failed' : '';
284343

285344
return `
286-
<div class="activity-event">
345+
<div class="activity-event${failedClass}">
287346
<div class="activity-meta">
288347
<span class="activity-time">${this.escapeHtml(time)}</span>
289348
<span class="activity-kind activity-kind-${this.escapeHtml(group)}">${this.escapeHtml(kind)}</span>

client/styles.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9924,6 +9924,11 @@ header h1 {
99249924
margin-bottom: var(--space-sm);
99259925
}
99269926

9927+
.activity-event.activity-failed {
9928+
border-color: rgba(248, 81, 73, 0.55);
9929+
box-shadow: 0 0 0 1px rgba(248, 81, 73, 0.18);
9930+
}
9931+
99279932
.activity-meta {
99289933
display: flex;
99299934
gap: var(--space-sm);

0 commit comments

Comments
 (0)