From 0dbbdd6c9f6cdd35e8527059fc6da481eee084ed Mon Sep 17 00:00:00 2001 From: Bob Date: Fri, 20 Feb 2026 14:16:39 +0000 Subject: [PATCH 1/2] fix(timeline): truncate long synced bucket names in sidebar (#682) Synced bucket names can exceed 137+ characters, making the timeline sidebar unusable on smaller screens. Three fixes: - CSS text-overflow ellipsis as fallback for any long label - Abbreviate synced names: show 'watcher-name (synced from host)' instead of full 'watcher_localhost-synced-from-remotehost' - Add title tooltip showing full bucket ID on hover - Fix: use group.id instead of group.content when looking up bucket ID for event editing (content now contains display HTML, not raw ID) --- src/visualizations/VisTimeline.vue | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/visualizations/VisTimeline.vue b/src/visualizations/VisTimeline.vue index cf9c435f..3b87998b 100644 --- a/src/visualizations/VisTimeline.vue +++ b/src/visualizations/VisTimeline.vue @@ -28,6 +28,13 @@ div#visualization { pointer-events: none; } + .vis-labelset .vis-label .vis-inner { + max-width: 250px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .timeline-timeline { font-family: sans-serif !important; @@ -224,6 +231,16 @@ export default { alert('selected multiple items: ' + JSON.stringify(properties.items)); } }, + abbreviateBucketName(bucketId: string): string { + // Abbreviate synced bucket names which can be extremely long (#682) + // e.g. "aw-watcher-window_host-synced-from-remotehost" -> "aw-watcher-window (synced from remotehost)" + const syncMatch = bucketId.match(/^([^_]+)_.*-synced-from-(.+)$/); + if (syncMatch) { + const escaped = bucketId.replace(/"/g, '"'); + return `${syncMatch[1]} (synced from ${syncMatch[2]})`; + } + return `${bucketId}`; + }, ensureUpdate() { // Will only run update() if data available and never ran before if (!this.updateHasRun) { @@ -248,7 +265,8 @@ export default { ); } } - return { id: bucket.id, content: this.showRowLabels ? bucket.id : '' }; + const label = this.showRowLabels ? this.abbreviateBucketName(bucket.id) : ''; + return { id: bucket.id, content: label }; }); // Build items From c81046cefa297b26c279062eb878349d42c0592e Mon Sep 17 00:00:00 2001 From: Bob Date: Fri, 20 Feb 2026 14:21:30 +0000 Subject: [PATCH 2/2] fix(timeline): add proper HTML escaping in abbreviateBucketName to prevent XSS --- src/visualizations/VisTimeline.vue | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/visualizations/VisTimeline.vue b/src/visualizations/VisTimeline.vue index 3b87998b..c7e56e7b 100644 --- a/src/visualizations/VisTimeline.vue +++ b/src/visualizations/VisTimeline.vue @@ -231,15 +231,25 @@ export default { alert('selected multiple items: ' + JSON.stringify(properties.items)); } }, + escapeHtml(str: string): string { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + }, abbreviateBucketName(bucketId: string): string { // Abbreviate synced bucket names which can be extremely long (#682) // e.g. "aw-watcher-window_host-synced-from-remotehost" -> "aw-watcher-window (synced from remotehost)" + const escaped = this.escapeHtml(bucketId); const syncMatch = bucketId.match(/^([^_]+)_.*-synced-from-(.+)$/); if (syncMatch) { - const escaped = bucketId.replace(/"/g, '"'); - return `${syncMatch[1]} (synced from ${syncMatch[2]})`; + return `${this.escapeHtml( + syncMatch[1] + )} (synced from ${this.escapeHtml(syncMatch[2])})`; } - return `${bucketId}`; + return `${escaped}`; }, ensureUpdate() { // Will only run update() if data available and never ran before