@@ -275,7 +275,8 @@ fn handleApiDashboard(request: *std.http.Server.Request, db: *sqlite.Database) v
275275 // Parse period from query params (default: 24h)
276276 const target = request .head .target ;
277277 const period = parseDashboardPeriod (target );
278- const offset = periodToOffset (period );
278+ var offset_buf : [48 ]u8 = undefined ;
279+ const offset = periodToOffset (period , & offset_buf );
279280 const project = parseDashboardProject (target );
280281
281282 var response = std .ArrayList (u8 ){};
@@ -291,6 +292,16 @@ fn handleApiDashboard(request: *std.http.Server.Request, db: *sqlite.Database) v
291292 sendJsonResponse (request , .ok , response .items ) catch {};
292293}
293294
295+ /// Parse a period string like "30s", "5m", "1h", "24h", "7d" into (number, unit).
296+ fn parsePeriod (p : []const u8 ) ? struct { n : u32 , unit : u8 } {
297+ if (p .len < 2 or p .len > 10 ) return null ;
298+ const unit = p [p .len - 1 ];
299+ if (unit != 's' and unit != 'm' and unit != 'h' and unit != 'd' ) return null ;
300+ const n = std .fmt .parseInt (u32 , p [0 .. p .len - 1 ], 10 ) catch return null ;
301+ if (n == 0 ) return null ;
302+ return .{ .n = n , .unit = unit };
303+ }
304+
294305fn parseDashboardPeriod (target : []const u8 ) []const u8 {
295306 const query_start = std .mem .indexOf (u8 , target , "?" ) orelse return "24h" ;
296307 const query_string = target [query_start + 1 .. ];
@@ -300,12 +311,7 @@ fn parseDashboardPeriod(target: []const u8) []const u8 {
300311 const key = pair [0.. eq_pos ];
301312 const value = pair [eq_pos + 1 .. ];
302313 if (std .mem .eql (u8 , key , "period" )) {
303- if (std .mem .eql (u8 , value , "1m" ) or std .mem .eql (u8 , value , "5m" ) or
304- std .mem .eql (u8 , value , "1h" ) or std .mem .eql (u8 , value , "24h" ) or
305- std .mem .eql (u8 , value , "7d" ) or std .mem .eql (u8 , value , "30d" ))
306- {
307- return value ;
308- }
314+ if (parsePeriod (value ) != null ) return value ;
309315 }
310316 }
311317 return "24h" ;
@@ -326,14 +332,29 @@ fn parseDashboardProject(target: []const u8) ?[]const u8 {
326332 return null ;
327333}
328334
329- fn periodToOffset (period : []const u8 ) []const u8 {
330- if (std .mem .eql (u8 , period , "1m" )) return "-1 minutes" ;
331- if (std .mem .eql (u8 , period , "5m" )) return "-5 minutes" ;
332- if (std .mem .eql (u8 , period , "1h" )) return "-1 hours" ;
333- if (std .mem .eql (u8 , period , "24h" )) return "-24 hours" ;
334- if (std .mem .eql (u8 , period , "7d" )) return "-7 days" ;
335- if (std .mem .eql (u8 , period , "30d" )) return "-30 days" ;
336- return "-24 hours" ;
335+ fn periodToOffset (period : []const u8 , buf : * [48 ]u8 ) []const u8 {
336+ const parsed = parsePeriod (period ) orelse {
337+ const fb = "-24 hours" ;
338+ @memcpy (buf [0.. fb .len ], fb );
339+ return buf [0.. fb .len ];
340+ };
341+ const unit_word : []const u8 = switch (parsed .unit ) {
342+ 's' = > " seconds" ,
343+ 'm' = > " minutes" ,
344+ 'h' = > " hours" ,
345+ 'd' = > " days" ,
346+ else = > " hours" ,
347+ };
348+ const dash = "-" ;
349+ @memcpy (buf [0.. 1], dash );
350+ const num_slice = std .fmt .bufPrint (buf [1.. ], "{d}" , .{parsed .n }) catch {
351+ const fb = "-24 hours" ;
352+ @memcpy (buf [0.. fb .len ], fb );
353+ return buf [0.. fb .len ];
354+ };
355+ const num_end = 1 + num_slice .len ;
356+ @memcpy (buf [num_end .. num_end + unit_word .len ], unit_word );
357+ return buf [0 .. num_end + unit_word .len ];
337358}
338359
339360fn buildDashboardJson (
@@ -552,11 +573,18 @@ fn buildDashboardJson(
552573 // For each vital, bucket by time and compute approximate p75
553574 try writer .writeAll (", \" timeseries\" : [" );
554575 {
555- // Determine bucket format based on period
556- const bucket_fmt = if (std .mem .eql (u8 , period , "1h" ) or std .mem .eql (u8 , period , "24h" ))
557- "%Y-%m-%dT%H:%M:00Z"
558- else
559- "%Y-%m-%dT%H:00:00Z" ;
576+ // Determine bucket format based on period (minute buckets for <=24h)
577+ const bucket_fmt = blk : {
578+ const pp = parsePeriod (period ) orelse break :blk "%Y-%m-%dT%H:%M:00Z" ;
579+ const total_s : u64 = switch (pp .unit ) {
580+ 's' = > pp .n ,
581+ 'm' = > @as (u64 , pp .n ) * 60 ,
582+ 'h' = > @as (u64 , pp .n ) * 3600 ,
583+ 'd' = > @as (u64 , pp .n ) * 86400 ,
584+ else = > 86400 ,
585+ };
586+ break :blk if (total_s <= 86400 ) "%Y-%m-%dT%H:%M:00Z" else "%Y-%m-%dT%H:00:00Z" ;
587+ };
560588
561589 var ts_buf : [896 ]u8 = undefined ;
562590 const ts_p1 = "SELECT strftime('" ;
0 commit comments