search for standard sites pub-search.waow.tech
search zig blog atproto
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

replace uptime with persistent service age

- store service_started_at timestamp in stats table (set once, never changes)
- dashboard shows "service age" calculated from that epoch
- survives deploys/restarts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

zzstoatzz 420ceb12 da6875ac

+36 -27
+17 -18
backend/src/dashboard.zig
··· 3 3 /// Generate dashboard HTML with live stats 4 4 pub fn render( 5 5 alloc: std.mem.Allocator, 6 - uptime_secs: i64, 6 + started_at: i64, 7 7 searches: u64, 8 8 errors: u64, 9 9 documents: i64, ··· 80 80 \\ 81 81 \\ <div class="stats-grid"> 82 82 \\ <div class="stat"> 83 - \\ <div class="stat-value uptime" id="uptime">--</div> 84 - \\ <div class="stat-label">uptime</div> 83 + \\ <div class="stat-value uptime" id="age">--</div> 84 + \\ <div class="stat-label">service age</div> 85 85 \\ </div> 86 86 \\ <div class="stat"> 87 87 \\ <div class="stat-value"> ··· 130 130 \\ </div> 131 131 \\ 132 132 \\ <script> 133 - \\ const startUptime = 133 + \\ const startedAt = 134 134 ); 135 135 136 - try w.print("{d}", .{uptime_secs}); 136 + try w.print("{d}", .{started_at}); 137 137 try w.writeAll( 138 - \\; 139 - \\ const startTime = Date.now(); 138 + \\ * 1000; 140 139 \\ 141 - \\ function formatUptime(secs) { 140 + \\ function formatAge(ms) { 141 + \\ const secs = Math.floor(ms / 1000); 142 142 \\ const d = Math.floor(secs / 86400); 143 143 \\ const h = Math.floor((secs % 86400) / 3600); 144 144 \\ const m = Math.floor((secs % 3600) / 60); 145 - \\ const s = secs % 60; 146 - \\ if (d > 0) return `${d}d ${h}h ${m}m`; 147 - \\ if (h > 0) return `${h}h ${m}m ${s}s`; 148 - \\ if (m > 0) return `${m}m ${s}s`; 149 - \\ return `${s}s`; 145 + \\ if (d > 0) return `${d}d ${h}h`; 146 + \\ if (h > 0) return `${h}h ${m}m`; 147 + \\ if (m > 0) return `${m}m`; 148 + \\ return `${secs}s`; 150 149 \\ } 151 150 \\ 152 - \\ function updateUptime() { 153 - \\ const elapsed = Math.floor((Date.now() - startTime) / 1000); 154 - \\ document.getElementById('uptime').textContent = formatUptime(startUptime + elapsed); 151 + \\ function updateAge() { 152 + \\ const age = Date.now() - startedAt; 153 + \\ document.getElementById('age').textContent = formatAge(age); 155 154 \\ } 156 155 \\ 157 - \\ updateUptime(); 158 - \\ setInterval(updateUptime, 1000); 156 + \\ updateAge(); 157 + \\ setInterval(updateAge, 60000); 159 158 \\ 160 159 \\ const tags = 161 160 );
+7 -6
backend/src/db/mod.zig
··· 307 307 return try output.toOwnedSlice(); 308 308 } 309 309 310 - pub fn getStats() struct { documents: i64, publications: i64, searches: i64, errors: i64 } { 311 - var c = &(client orelse return .{ .documents = 0, .publications = 0, .searches = 0, .errors = 0 }); 310 + pub fn getStats() struct { documents: i64, publications: i64, searches: i64, errors: i64, started_at: i64 } { 311 + var c = &(client orelse return .{ .documents = 0, .publications = 0, .searches = 0, .errors = 0, .started_at = 0 }); 312 312 313 313 var res = c.query( 314 314 \\SELECT 315 315 \\ (SELECT COUNT(*) FROM documents) as docs, 316 316 \\ (SELECT COUNT(*) FROM publications) as pubs, 317 317 \\ (SELECT total_searches FROM stats WHERE id = 1) as searches, 318 - \\ (SELECT total_errors FROM stats WHERE id = 1) as errors 319 - , &.{}) catch return .{ .documents = 0, .publications = 0, .searches = 0, .errors = 0 }; 318 + \\ (SELECT total_errors FROM stats WHERE id = 1) as errors, 319 + \\ (SELECT service_started_at FROM stats WHERE id = 1) as started_at 320 + , &.{}) catch return .{ .documents = 0, .publications = 0, .searches = 0, .errors = 0, .started_at = 0 }; 320 321 defer res.deinit(); 321 322 322 - const row = res.first() orelse return .{ .documents = 0, .publications = 0, .searches = 0, .errors = 0 }; 323 - return .{ .documents = row.int(0), .publications = row.int(1), .searches = row.int(2), .errors = row.int(3) }; 323 + const row = res.first() orelse return .{ .documents = 0, .publications = 0, .searches = 0, .errors = 0, .started_at = 0 }; 324 + return .{ .documents = row.int(0), .publications = row.int(1), .searches = row.int(2), .errors = row.int(3), .started_at = row.int(4) }; 324 325 } 325 326 326 327 pub fn recordSearch(query: []const u8) void {
+11 -1
backend/src/db/schema.zig
··· 66 66 \\CREATE TABLE IF NOT EXISTS stats ( 67 67 \\ id INTEGER PRIMARY KEY CHECK (id = 1), 68 68 \\ total_searches INTEGER DEFAULT 0, 69 - \\ total_errors INTEGER DEFAULT 0 69 + \\ total_errors INTEGER DEFAULT 0, 70 + \\ service_started_at INTEGER 70 71 \\) 71 72 , &.{}); 72 73 73 74 // ensure the single row exists 74 75 client.exec("INSERT OR IGNORE INTO stats (id) VALUES (1)", &.{}) catch {}; 75 76 77 + // set service_started_at if not already set (first run ever) 78 + var ts_buf: [20]u8 = undefined; 79 + const ts_str = std.fmt.bufPrint(&ts_buf, "{d}", .{std.time.timestamp()}) catch "0"; 80 + client.exec( 81 + "UPDATE stats SET service_started_at = ? WHERE id = 1 AND service_started_at IS NULL", 82 + &.{ts_str}, 83 + ) catch {}; 84 + 76 85 // popular searches tracking 77 86 try client.exec( 78 87 \\CREATE TABLE IF NOT EXISTS popular_searches ( ··· 86 95 // these may fail if columns already exist - that's fine 87 96 client.exec("ALTER TABLE documents ADD COLUMN publication_uri TEXT", &.{}) catch {}; 88 97 client.exec("ALTER TABLE publications ADD COLUMN base_path TEXT", &.{}) catch {}; 98 + client.exec("ALTER TABLE stats ADD COLUMN service_started_at INTEGER", &.{}) catch {}; 89 99 }
+1 -2
backend/src/http.zig
··· 173 173 defer arena.deinit(); 174 174 const alloc = arena.allocator(); 175 175 176 - const s = stats.get(); 177 176 const db_stats = db.getStats(); 178 177 const tags_json = db.getTags(alloc) catch "[]"; 179 178 180 179 const html = dashboard.render( 181 180 alloc, 182 - s.getUptime(), 181 + db_stats.started_at, 183 182 @intCast(db_stats.searches), 184 183 @intCast(db_stats.errors), 185 184 db_stats.documents,