@@ -10,7 +10,7 @@ import { hostname } from 'os'
1010
1111import { loadPricing } from './models.js'
1212import { buildMenubarPayloadForRange } from './usage-aggregator.js'
13- import { getDateRange , parseDateRangeFlags , formatDateRangeLabel , toPeriod } from './cli-date.js'
13+ import { periodInfoFromQuery , UsageQueryError } from './cli-date.js'
1414import { pullDevices , linkRemote } from './sharing/host.js'
1515import { browse } from './sharing/discovery.js'
1616import { loadOrCreateIdentity } from './sharing/identity.js'
@@ -31,6 +31,11 @@ function readBody(req: import('http').IncomingMessage): Promise<string> {
3131 } )
3232}
3333
34+ function writeJsonError ( res : import ( 'http' ) . ServerResponse , status : number , error : string ) : void {
35+ res . writeHead ( status , { 'content-type' : 'application/json; charset=utf-8' } )
36+ res . end ( JSON . stringify ( { error } ) )
37+ }
38+
3439const HERE = dirname ( fileURLToPath ( import . meta. url ) )
3540
3641// Locate the built React dashboard (dist/dash). Works both when running from a
@@ -94,10 +99,7 @@ export async function runWebDashboard(opts: {
9499 // Sharing this device serves the SANITIZED aggregate (no project names/paths
95100 // or per-session detail), unlike the local /api/usage which shows everything.
96101 const shareGetUsage = async ( q : { period ?: string ; from ?: string ; to ?: string } ) => {
97- const customRange = parseDateRangeFlags ( q . from , q . to )
98- const periodInfo = customRange
99- ? { range : customRange , label : formatDateRangeLabel ( q . from , q . to ) }
100- : getDateRange ( toPeriod ( q . period ?? opts . period ) )
102+ const periodInfo = periodInfoFromQuery ( q , opts . period )
101103 return sanitizeForSharing ( await buildMenubarPayloadForRange ( periodInfo , { provider : 'all' , optimize : false } ) )
102104 }
103105 const share = new ShareController ( shareGetUsage )
@@ -126,10 +128,14 @@ export async function runWebDashboard(opts: {
126128 const provider = url . searchParams . get ( 'provider' ) ?? opts . provider
127129 const from = url . searchParams . get ( 'from' ) ?? opts . from
128130 const to = url . searchParams . get ( 'to' ) ?? opts . to
129- const customRange = parseDateRangeFlags ( from , to )
130- const periodInfo = customRange
131- ? { range : customRange , label : formatDateRangeLabel ( from , to ) }
132- : getDateRange ( toPeriod ( period ) )
131+ let periodInfo
132+ try {
133+ periodInfo = periodInfoFromQuery ( { period, from, to } , opts . period )
134+ } catch ( err ) {
135+ if ( ! ( err instanceof UsageQueryError ) ) throw err
136+ writeJsonError ( res , 400 , err instanceof Error ? err . message : String ( err ) )
137+ return
138+ }
133139 const payload = await buildMenubarPayloadForRange ( periodInfo , {
134140 provider,
135141 project : opts . project ,
@@ -148,11 +154,15 @@ export async function runWebDashboard(opts: {
148154 const provider = url . searchParams . get ( 'provider' ) ?? opts . provider
149155 const from = url . searchParams . get ( 'from' ) ?? opts . from
150156 const to = url . searchParams . get ( 'to' ) ?? opts . to
157+ try {
158+ periodInfoFromQuery ( { period, from, to } , opts . period )
159+ } catch ( err ) {
160+ if ( ! ( err instanceof UsageQueryError ) ) throw err
161+ writeJsonError ( res , 400 , err instanceof Error ? err . message : String ( err ) )
162+ return
163+ }
151164 const localGetUsage = async ( q : { period ?: string ; from ?: string ; to ?: string } ) => {
152- const customRange = parseDateRangeFlags ( q . from , q . to )
153- const periodInfo = customRange
154- ? { range : customRange , label : formatDateRangeLabel ( q . from , q . to ) }
155- : getDateRange ( toPeriod ( q . period ?? period ) )
165+ const periodInfo = periodInfoFromQuery ( q , period )
156166 return buildMenubarPayloadForRange ( periodInfo , { provider, project : opts . project , exclude : opts . exclude , optimize : false } )
157167 }
158168 const results = await pullDevices ( localGetUsage , { period, from, to } , hostname ( ) , { } )
0 commit comments