Skip to content
This repository was archived by the owner on May 12, 2026. It is now read-only.

Commit 34cbfa1

Browse files
committed
Metrics and page width
1 parent cb65675 commit 34cbfa1

3 files changed

Lines changed: 183 additions & 118 deletions

File tree

src/lib/components/metrics/MetricsQueryForm.svelte

Lines changed: 92 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -65,69 +65,113 @@
6565
</script>
6666

6767
<form onsubmit={handleSubmit} class="qf">
68-
{#if filtersExpanded}
68+
{#if !isGrpc}
69+
<!-- Row 1: time range, pagination, sort (REST only) -->
6970
<div class="qf-bar qf-filters">
70-
<label class="qf-inline qf-sm"><span>Verb</span>
71-
<select bind:value={queryForm.verb} onchange={handleFieldChange} name="verb">
72-
<option value="">All</option>
73-
<option value="GET">GET</option>
74-
<option value="POST">POST</option>
75-
<option value="PUT">PUT</option>
76-
<option value="DELETE">DEL</option>
77-
<option value="PATCH">PATCH</option>
78-
</select>
71+
<label class="qf-inline"><span>From</span>
72+
<input type="datetime-local" bind:value={queryForm.from_date} onblur={handleFieldChange} onchange={handleFieldChange} step="1" name="from_date" />
7973
</label>
80-
<label class="qf-inline qf-sm"><span>Code</span>
81-
<select bind:value={queryForm.http_status_code} onchange={handleFieldChange} name="http_status_code">
82-
<option value="">All</option>
83-
<option value="200">200</option>
84-
<option value="201">201</option>
85-
<option value="204">204</option>
86-
<option value="400">400</option>
87-
<option value="401">401</option>
88-
<option value="403">403</option>
89-
<option value="404">404</option>
90-
<option value="500">500</option>
91-
<option value="502">502</option>
92-
<option value="503">503</option>
93-
</select>
74+
<label class="qf-inline"><span>To</span>
75+
<input type="datetime-local" bind:value={queryForm.to_date} onblur={handleFieldChange} onchange={handleFieldChange} step="1" name="to_date" />
9476
</label>
95-
<label class="qf-inline"><span>Consumer</span>
96-
<input type="text" bind:value={queryForm.consumer_id} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="ID" name="consumer_id" />
77+
<label class="qf-inline qf-xs"><span>Limit</span>
78+
<input type="number" bind:value={queryForm.limit} min="1" max="10000" onblur={handleFieldChange} onchange={handleFieldChange} name="limit" />
9779
</label>
98-
<label class="qf-inline"><span>App</span>
99-
<input type="text" bind:value={queryForm.app_name} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="name" name="app_name" />
80+
<label class="qf-inline qf-xs"><span>Offset</span>
81+
<input type="number" bind:value={queryForm.offset} min="0" onblur={handleFieldChange} onchange={handleFieldChange} name="offset" />
10082
</label>
101-
<label class="qf-inline" title={isGrpc ? grpcUnsupported : ""}><span>Apps</span>
102-
<input type="text" bind:value={queryForm.include_app_names} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="csv" name="include_app_names" disabled={isGrpc} />
83+
<label class="qf-inline qf-sm"><span>Sort</span>
84+
<select bind:value={queryForm.sort_by} onchange={handleFieldChange} name="sort_by">
85+
<option value="date">Date</option>
86+
<option value="url">URL</option>
87+
<option value="username">User</option>
88+
<option value="app_name">App</option>
89+
<option value="verb">Method</option>
90+
<option value="developer_email">Dev Email</option>
91+
<option value="consumer_id">Consumer</option>
92+
<option value="implemented_by_partial_function">Fn</option>
93+
<option value="implemented_in_version">Ver</option>
94+
</select>
10395
</label>
104-
<label class="qf-inline" title={isGrpc ? grpcUnsupported : ""}><span>User</span>
105-
<input type="text" bind:value={queryForm.username} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="ID" name="username" disabled={isGrpc} />
96+
<label class="qf-inline qf-xs"><span>Dir</span>
97+
<select bind:value={queryForm.direction} onchange={handleFieldChange} name="direction">
98+
<option value="desc">Desc</option>
99+
<option value="asc">Asc</option>
100+
</select>
106101
</label>
107-
<label class="qf-inline"><span>Fn</span>
108-
<input type="text" bind:value={queryForm.implemented_by_partial_function} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="partial fn" name="implemented_by_partial_function" />
102+
</div>
103+
{/if}
104+
105+
<!-- Row 2: match filters (server-side) -->
106+
<div class="qf-bar qf-filters">
107+
<label class="qf-inline qf-sm"><span>Verb</span>
108+
<select bind:value={queryForm.verb} onchange={handleFieldChange} name="verb">
109+
<option value="">All</option>
110+
<option value="GET">GET</option>
111+
<option value="POST">POST</option>
112+
<option value="PUT">PUT</option>
113+
<option value="DELETE">DEL</option>
114+
<option value="PATCH">PATCH</option>
115+
</select>
116+
</label>
117+
<label class="qf-inline qf-sm"><span>Code</span>
118+
<select bind:value={queryForm.http_status_code} onchange={handleFieldChange} name="http_status_code">
119+
<option value="">All</option>
120+
<option value="200">200</option>
121+
<option value="201">201</option>
122+
<option value="204">204</option>
123+
<option value="400">400</option>
124+
<option value="401">401</option>
125+
<option value="403">403</option>
126+
<option value="404">404</option>
127+
<option value="500">500</option>
128+
<option value="502">502</option>
129+
<option value="503">503</option>
130+
</select>
131+
</label>
132+
<label class="qf-inline"><span>Consumer</span>
133+
<input type="text" bind:value={queryForm.consumer_id} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="ID" name="consumer_id" />
134+
</label>
135+
<label class="qf-inline"><span>App</span>
136+
<input type="text" bind:value={queryForm.app_name} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="name" name="app_name" />
137+
</label>
138+
<label class="qf-inline"><span>Fn</span>
139+
<input type="text" bind:value={queryForm.implemented_by_partial_function} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="partial fn" name="implemented_by_partial_function" />
140+
</label>
141+
</div>
142+
143+
<!-- Row 3: extra filters -->
144+
<div class="qf-bar qf-filters">
145+
{#if !isGrpc}
146+
<label class="qf-inline"><span>Apps</span>
147+
<input type="text" bind:value={queryForm.include_app_names} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="csv" name="include_app_names" />
109148
</label>
110-
<label class="qf-inline qf-sm" title={isGrpc ? grpcUnsupported : ""}><span>Ver</span>
111-
<input type="text" bind:value={queryForm.implemented_in_version} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="ver" name="implemented_in_version" disabled={isGrpc} />
149+
<label class="qf-inline"><span>User</span>
150+
<input type="text" bind:value={queryForm.username} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="ID" name="username" />
112151
</label>
113-
<label class="qf-inline qf-sm"><span>Min Duration</span>
114-
<input type="number" bind:value={queryForm.duration} min="0" onblur={handleFieldChange} onchange={handleFieldChange} placeholder="ms" name="duration" />
152+
<label class="qf-inline qf-sm"><span>Ver</span>
153+
<input type="text" bind:value={queryForm.implemented_in_version} onblur={handleFieldChange} onchange={handleFieldChange} placeholder="ver" name="implemented_in_version" />
115154
</label>
116-
<label class="qf-inline qf-sm" title={isGrpc ? grpcUnsupported : ""}><span>Anon</span>
117-
<select bind:value={queryForm.anon} onchange={handleFieldChange} name="anon" disabled={isGrpc}>
155+
{/if}
156+
<label class="qf-inline qf-sm"><span>Min Duration</span>
157+
<input type="number" bind:value={queryForm.duration} min="0" onblur={handleFieldChange} onchange={handleFieldChange} placeholder="ms" name="duration" />
158+
</label>
159+
{#if !isGrpc}
160+
<label class="qf-inline qf-sm"><span>Anon</span>
161+
<select bind:value={queryForm.anon} onchange={handleFieldChange} name="anon">
118162
<option value="">All</option>
119163
<option value="true">Yes</option>
120164
<option value="false">No</option>
121165
</select>
122166
</label>
123-
<button type="submit" hidden>Submit</button>
124-
{#if showClearButton}
125-
<div class="qf-actions">
126-
<button type="button" class="qf-btn" onclick={onClear} title="Clear form">🗑️ Clear</button>
127-
</div>
128-
{/if}
129-
</div>
130-
{/if}
167+
{/if}
168+
<button type="submit" hidden>Submit</button>
169+
{#if showClearButton}
170+
<div class="qf-actions">
171+
<button type="button" class="qf-btn" onclick={onClear} title="Clear form">🗑️ Clear</button>
172+
</div>
173+
{/if}
174+
</div>
131175
</form>
132176

133177
<style>

src/routes/(protected)/metrics/+page.svelte

Lines changed: 90 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@
6767
let timeUpdateInterval: number | undefined = undefined;
6868
let currentTime = $state(new Date().toLocaleString());
6969
let lastRefreshTime = $state(new Date().toLocaleString());
70+
const REFRESH_OPTIONS = [
71+
{ value: 5, label: "5s" },
72+
{ value: 10, label: "10s" },
73+
{ value: 30, label: "30s" },
74+
{ value: 60, label: "60s" },
75+
{ value: 0, label: "Off" },
76+
];
77+
let refreshSeconds = $state(5);
7078
let countdown = $state(5);
7179
let isCountingDown = $state(false);
7280
let timestampColorIndex = $state(0);
@@ -269,27 +277,34 @@
269277
}
270278
271279
function startAutoRefresh() {
272-
// Start 5-second auto-refresh cycle
273-
countdown = 5;
274-
isCountingDown = true;
275-
276280
if (refreshInterval) clearInterval(refreshInterval);
277281
if (countdownInterval) clearInterval(countdownInterval);
278282
279-
console.log("Starting auto-refresh countdown from 5");
283+
// "Off" — keep the page, just don't auto-refresh.
284+
if (refreshSeconds <= 0) {
285+
isCountingDown = false;
286+
countdown = 0;
287+
return;
288+
}
289+
290+
countdown = refreshSeconds;
291+
isCountingDown = true;
292+
280293
countdownInterval = setInterval(() => {
281294
countdown--;
282-
console.log("Countdown:", countdown);
283295
if (countdown <= 0) {
284-
console.log("Countdown reached 0, refreshing...");
285296
lastRefreshTime = new Date().toLocaleString();
286297
timestampColorIndex = (timestampColorIndex + 1) % 2;
287298
invalidate("app:metrics");
288-
countdown = 5;
299+
countdown = refreshSeconds;
289300
}
290301
}, 1000);
291302
}
292303
304+
function handleRefreshIntervalChange() {
305+
if (transport === "rest") startAutoRefresh();
306+
}
307+
293308
function stopAutoRefresh() {
294309
if (countdownInterval) {
295310
clearInterval(countdownInterval);
@@ -525,46 +540,7 @@
525540
<div class="panel-header-compact">
526541
<div class="panel-header-row">
527542
<h2 class="panel-title">Metrics Query</h2>
528-
<div class="panel-meta header-fields" oninput={() => queryStatus = "dirty"} data-transport={transport}>
529-
<label class="hf" title={transport === "grpc" ? "Not applicable on gRPC — stream emits events from connect time" : ""}><span>From</span>
530-
<input type="datetime-local" bind:value={queryForm.from_date} onblur={handleFieldChange} onchange={handleFieldChange} step="1" name="from_date" disabled={transport === "grpc"} />
531-
</label>
532-
<label class="hf" title={transport === "grpc" ? "Not applicable on gRPC — stream emits events from connect time" : ""}><span>To</span>
533-
<input type="datetime-local" bind:value={queryForm.to_date} onblur={handleFieldChange} onchange={handleFieldChange} step="1" name="to_date" disabled={transport === "grpc"} />
534-
</label>
535-
<label class="hf hf-tiny" title={transport === "grpc" ? "Not applicable on gRPC — streams can't paginate" : ""}><span>Limit</span>
536-
<input type="number" bind:value={queryForm.limit} min="1" max="10000" onblur={handleFieldChange} onchange={handleFieldChange} name="limit" disabled={transport === "grpc"} />
537-
</label>
538-
<label class="hf hf-tiny" title={transport === "grpc" ? "Not applicable on gRPC — streams can't paginate" : ""}><span>Offset</span>
539-
<input type="number" bind:value={queryForm.offset} min="0" onblur={handleFieldChange} onchange={handleFieldChange} name="offset" disabled={transport === "grpc"} />
540-
</label>
541-
<label class="hf hf-sm" title={transport === "grpc" ? "Not applicable on gRPC — events arrive in real-time order" : ""}><span>Sort</span>
542-
<select bind:value={queryForm.sort_by} onchange={handleFieldChange} name="sort_by" disabled={transport === "grpc"}>
543-
<option value="date">Date</option>
544-
<option value="url">URL</option>
545-
<option value="username">User</option>
546-
<option value="app_name">App</option>
547-
<option value="verb">Method</option>
548-
<option value="developer_email">Developer Email</option>
549-
<option value="consumer_id">Consumer ID</option>
550-
<option value="implemented_by_partial_function">Partial Function</option>
551-
<option value="implemented_in_version">Version</option>
552-
</select>
553-
</label>
554-
<label class="hf hf-xs" title={transport === "grpc" ? "Not applicable on gRPC — events arrive in real-time order" : ""}><span>Dir</span>
555-
<select bind:value={queryForm.direction} onchange={handleFieldChange} name="direction" disabled={transport === "grpc"}>
556-
<option value="desc">Desc</option>
557-
<option value="asc">Asc</option>
558-
</select>
559-
</label>
560-
<button
561-
class="header-btn"
562-
onclick={() => filtersExpanded = !filtersExpanded}
563-
data-testid="filters-toggle"
564-
data-state={filtersExpanded ? "expanded" : "collapsed"}
565-
>
566-
{filtersExpanded ? "" : ""} More
567-
</button>
543+
<div class="panel-meta">
568544
<button
569545
class="header-btn query-btn"
570546
onclick={submitQuery}
@@ -576,21 +552,19 @@
576552
</div>
577553
</div>
578554

579-
{#if filtersExpanded}
580-
<div class="panel-content" oninput={() => queryStatus = "dirty"}>
581-
<MetricsQueryForm
582-
bind:queryForm
583-
bind:filtersExpanded
584-
onFieldChange={handleFieldChange}
585-
onClear={clearQuery}
586-
onSubmit={submitQuery}
587-
showAutoRefresh={false}
588-
showClearButton={false}
589-
showRefreshButton={false}
590-
{transport}
591-
/>
592-
</div>
593-
{/if}
555+
<div class="panel-content" oninput={() => queryStatus = "dirty"}>
556+
<MetricsQueryForm
557+
bind:queryForm
558+
bind:filtersExpanded
559+
onFieldChange={handleFieldChange}
560+
onClear={clearQuery}
561+
onSubmit={submitQuery}
562+
showAutoRefresh={false}
563+
showClearButton={false}
564+
showRefreshButton={false}
565+
{transport}
566+
/>
567+
</div>
594568
</div>
595569

596570
<!-- Panel 2: Results -->
@@ -648,10 +622,24 @@
648622
<span class="meta-separator">•</span>
649623
<span class="timestamp-color-{timestampColorIndex}">{lastRefreshTime}</span>
650624
<span class="meta-separator">•</span>
651-
{#if isCountingDown}
652-
<span class="countdown">{countdown}s</span>
653-
{:else}
654-
<span class="countdown-idle">{countdown}s</span>
625+
<label class="refresh-interval">
626+
<span>Every</span>
627+
<select
628+
bind:value={refreshSeconds}
629+
onchange={handleRefreshIntervalChange}
630+
data-testid="refresh-interval"
631+
>
632+
{#each REFRESH_OPTIONS as opt}
633+
<option value={opt.value}>{opt.label}</option>
634+
{/each}
635+
</select>
636+
</label>
637+
{#if refreshSeconds > 0}
638+
{#if isCountingDown}
639+
<span class="countdown">{countdown}s</span>
640+
{:else}
641+
<span class="countdown-idle">{countdown}s</span>
642+
{/if}
655643
{/if}
656644
<button
657645
class="refresh-btn-inline"
@@ -862,6 +850,9 @@
862850
863851
.container {
864852
max-width: 1400px;
853+
/* Allow shrinking below intrinsic min-width so the metrics table's
854+
min-width: 800px doesn't expand the outer grid track. */
855+
min-width: 0;
865856
}
866857
867858
.alert {
@@ -896,10 +887,14 @@
896887
}
897888
898889
.full-width-panel {
899-
/* override .panel's overflow:hidden so the header can position:sticky */
900-
overflow: visible;
890+
/* overflow-x: clip prevents the metrics table from pushing a page-level
891+
horizontal scrollbar. overflow-y: visible lets position:sticky on the
892+
header work relative to the viewport. */
893+
overflow-x: clip;
894+
overflow-y: visible;
901895
margin-bottom: 1.5rem;
902896
width: 100%;
897+
min-width: 0;
903898
}
904899
905900
.panel-header {
@@ -949,6 +944,8 @@
949944
display: flex;
950945
align-items: center;
951946
gap: 0.5rem;
947+
flex-wrap: wrap;
948+
min-width: 0;
952949
font-size: 0.875rem;
953950
color: var(--color-surface-600);
954951
}
@@ -1148,6 +1145,30 @@
11481145
color: var(--color-surface-300);
11491146
}
11501147
1148+
.refresh-interval {
1149+
display: inline-flex;
1150+
align-items: center;
1151+
gap: 0.25rem;
1152+
font-size: 0.75rem;
1153+
color: #6b7280;
1154+
}
1155+
1156+
.refresh-interval select {
1157+
padding: 0.125rem 0.25rem;
1158+
font-size: 0.75rem;
1159+
border: 1px solid #d1d5db;
1160+
border-radius: 0.25rem;
1161+
background: white;
1162+
color: #374151;
1163+
cursor: pointer;
1164+
}
1165+
1166+
:global([data-mode="dark"]) .refresh-interval select {
1167+
background: rgb(var(--color-surface-700));
1168+
border-color: rgb(var(--color-surface-600));
1169+
color: var(--color-surface-200);
1170+
}
1171+
11511172
.refresh-btn-inline {
11521173
background: none;
11531174
border: none;

0 commit comments

Comments
 (0)