feat: add formatLastSeen and buildOfflineTileHTML functions

- Add formatLastSeen(ms) to format millisecond timestamps into relative
  strings (Xs ago, Xm ago, Xh ago, Xd ago, Never for null)
- Add buildOfflineTileHTML(source) to render unreachable device tiles
  with Offline badge and last-seen timestamp
- Both functions added after buildAuthTileHTML in app.js
- Both exported via module.exports
- 7 new tests covering export, CSS class, device name, badge, last-seen,
  HTML escaping, and null handling
This commit is contained in:
Brian Krabach
2026-03-31 02:45:36 -07:00
parent 83e5318ba1
commit e07cf196c6
2 changed files with 73 additions and 0 deletions
+33
View File
@@ -541,6 +541,37 @@ function buildAuthTileHTML(source) {
);
}
/**
* Format a millisecond timestamp into a relative 'last seen' string.
* @param {number|null} ms - Millisecond timestamp
* @returns {string}
*/
function formatLastSeen(ms) {
if (ms == null) return 'Never';
var diff = Math.floor((Date.now() - ms) / 1000);
if (diff < 60) return diff + 's ago';
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
return Math.floor(diff / 86400) + 'd ago';
}
/**
* Build the HTML string for an offline (unreachable) source tile.
* @param {{ name: string, url: string, lastSeenAt: number|null }} source
* @returns {string}
*/
function buildOfflineTileHTML(source) {
var escapedName = escapeHtml(source.name || '');
var lastSeen = formatLastSeen(source.lastSeenAt);
return (
'<article class="source-tile source-tile--offline">' +
'<span class="source-tile__name">' + escapedName + '</span>' +
'<span class="source-tile__badge">Offline</span>' +
'<span class="source-tile__last-seen">Last seen ' + escapeHtml(lastSeen) + '</span>' +
'</article>'
);
}
/**
* Returns sessions with hidden session names removed.
* Only hides LOCAL sessions (those with empty/absent sourceUrl) matching the
@@ -2219,6 +2250,8 @@ if (typeof module !== 'undefined' && module.exports) {
renderFilterBar,
// Federation tiles
buildAuthTileHTML,
buildOfflineTileHTML,
formatLastSeen,
// Test-only helpers
_setCurrentSessions,
_setViewMode,
+40
View File
@@ -2864,3 +2864,43 @@ test('buildAuthTileHTML escapes HTML in device name', () => {
assert.ok(html.includes('&lt;script&gt;'), 'escaped script tag should appear in html');
});
// --- buildOfflineTileHTML ---
test('buildOfflineTileHTML is exported as a function', () => {
assert.strictEqual(typeof app.buildOfflineTileHTML, 'function');
});
test('buildOfflineTileHTML returns article with source-tile--offline class', () => {
const html = app.buildOfflineTileHTML({ name: 'Dev Server', url: 'http://dev:8088', lastSeenAt: null });
assert.ok(html.startsWith('<article'), 'html should start with <article');
assert.ok(html.includes('source-tile--offline'), 'html should include source-tile--offline class');
});
test('buildOfflineTileHTML includes device name', () => {
const html = app.buildOfflineTileHTML({ name: 'Dev Server', url: 'http://dev:8088', lastSeenAt: null });
assert.ok(html.includes('Dev Server'), 'html should include the device name');
});
test('buildOfflineTileHTML includes Offline badge', () => {
const html = app.buildOfflineTileHTML({ name: 'Dev Server', url: 'http://dev:8088', lastSeenAt: null });
assert.ok(html.includes('Offline'), 'html should include Offline text');
assert.ok(html.includes('source-tile__badge'), 'html should include source-tile__badge class');
});
test('buildOfflineTileHTML shows relative last-seen time', () => {
const fiveMinAgo = Date.now() - 5 * 60 * 1000;
const html = app.buildOfflineTileHTML({ name: 'Dev Server', url: 'http://dev:8088', lastSeenAt: fiveMinAgo });
assert.ok(html.includes('Last seen'), 'html should include Last seen text');
});
test('buildOfflineTileHTML escapes device name', () => {
const html = app.buildOfflineTileHTML({ name: '<b>bad</b>', url: '', lastSeenAt: null });
assert.ok(!html.includes('<b>bad</b>'), 'raw <b>bad</b> must not appear in html');
assert.ok(html.includes('&lt;b&gt;bad&lt;/b&gt;'), 'device name should be HTML-escaped');
});
test('buildOfflineTileHTML shows "Never" when lastSeenAt is null', () => {
const html = app.buildOfflineTileHTML({ name: 'Dev Server', url: '', lastSeenAt: null });
assert.ok(html.includes('Never'), 'html should include Never when lastSeenAt is null');
});