Merge ticket branch t087
This commit is contained in:
@@ -9,13 +9,7 @@ import {
|
||||
type AlertRule,
|
||||
type CompareOp,
|
||||
} from '../lib/api'
|
||||
import {
|
||||
CollapseToggle,
|
||||
handleSectionHeaderKeyDown,
|
||||
sectionHeaderInteractiveClassName,
|
||||
sectionHeaderLeftClassName,
|
||||
useSectionCollapsed,
|
||||
} from './CollapsibleSection'
|
||||
import { FormFooter, SectionHeader, useSectionCollapsed } from './CollapsibleSection'
|
||||
import { HelpIcon } from './HelpIcon'
|
||||
import { RegexHelpIcon } from './RegexHelpIcon'
|
||||
import { queryInputClassName } from './FilterBar'
|
||||
@@ -238,22 +232,7 @@ export function Alerts() {
|
||||
|
||||
return (
|
||||
<div className="border-b border-border text-sm">
|
||||
<div
|
||||
className={sectionHeaderInteractiveClassName}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={!collapsed}
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
onKeyDown={handleSectionHeaderKeyDown(() => setCollapsed(!collapsed))}
|
||||
>
|
||||
<div className={sectionHeaderLeftClassName}>
|
||||
Alerts
|
||||
<HelpIcon sectionId="alerts" label="Help: Alerts" />
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CollapseToggle collapsed={collapsed} />
|
||||
</div>
|
||||
</div>
|
||||
<SectionHeader title="Alerts" helpSectionId="alerts" collapsed={collapsed} onToggle={() => setCollapsed(!collapsed)} />
|
||||
<div hidden={collapsed}>
|
||||
<p className="px-3 pt-3 text-xs text-text-faint">
|
||||
Every rule delivers to the ntfy sink configured in Settings.
|
||||
@@ -420,20 +399,13 @@ export function Alerts() {
|
||||
|
||||
{submitError && <div className="rounded border border-danger-border bg-danger-bg/50 px-2 py-1 text-xs text-danger-text">{submitError}</div>}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
{submitting ? 'Saving…' : editingId ? 'Save rule' : 'Create rule'}
|
||||
</button>
|
||||
{editingId && (
|
||||
<button type="button" onClick={startCreate} className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2">
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<FormFooter
|
||||
editing={editingId !== null}
|
||||
submitting={submitting}
|
||||
idleLabel="Create rule"
|
||||
editingLabel="Save rule"
|
||||
onCancel={startCreate}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import type { TimeRange, TimeRangePresetId } from '../lib/timeRange'
|
||||
import { HelpIcon } from './HelpIcon'
|
||||
|
||||
/// Shared look for a primary section's title bar: shaded so it reads as a
|
||||
/// distinct band rather than blending into the section body below it.
|
||||
@@ -95,6 +96,100 @@ export function handleSectionHeaderKeyDown(onToggle: () => void) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The header row every settings panel (Params/Metrics/Alerts/Dashboards) opens with: a click/keyboard-toggleable
|
||||
/// title bar pairing a title + `HelpIcon` on the left with `CollapseToggle` (and, for panels with a standalone
|
||||
/// popped-out view, a `PopOutButton`) on the right. Assembles `sectionHeaderInteractiveClassName` /
|
||||
/// `handleSectionHeaderKeyDown` / `sectionHeaderLeftClassName` so callers don't each re-wire the same
|
||||
/// `role="button"`/`tabIndex`/`aria-expanded` boilerplate by hand.
|
||||
export function SectionHeader({
|
||||
title,
|
||||
helpSectionId,
|
||||
helpLabel,
|
||||
collapsed,
|
||||
onToggle,
|
||||
popOut,
|
||||
}: {
|
||||
title: string
|
||||
helpSectionId: string
|
||||
helpLabel?: string
|
||||
collapsed: boolean
|
||||
onToggle: () => void
|
||||
popOut?: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={sectionHeaderInteractiveClassName}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={!collapsed}
|
||||
onClick={onToggle}
|
||||
onKeyDown={handleSectionHeaderKeyDown(onToggle)}
|
||||
>
|
||||
<div className={sectionHeaderLeftClassName}>
|
||||
{title}
|
||||
<HelpIcon sectionId={helpSectionId} label={helpLabel ?? `Help: ${title}`} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CollapseToggle collapsed={collapsed} />
|
||||
{popOut}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/// The declare/edit form footer shared by `Params.tsx`/`Metrics.tsx`/`Alerts.tsx`: a submit button whose label
|
||||
/// swaps between create/edit and idle/in-flight variants, plus a Cancel button that only appears while editing.
|
||||
/// Doesn't render the "Editing X" banner some callers show above this — that banner's placement relative to
|
||||
/// `submitError` differs per caller, so it stays theirs to render; this covers only the part that's identical
|
||||
/// everywhere. Label text stays a per-caller prop (e.g. "Declare param" vs "Declare metric") rather than
|
||||
/// hardcoded here, since that's the one part of this footer each panel's copy actually differs on.
|
||||
export function FormFooter({
|
||||
editing,
|
||||
submitting,
|
||||
idleLabel,
|
||||
editingLabel,
|
||||
submittingLabel = 'Saving…',
|
||||
editingSubmittingLabel,
|
||||
onCancel,
|
||||
}: {
|
||||
editing: boolean
|
||||
submitting: boolean
|
||||
idleLabel: string
|
||||
editingLabel: string
|
||||
submittingLabel?: string
|
||||
editingSubmittingLabel?: string
|
||||
onCancel: () => void
|
||||
}) {
|
||||
const label = submitting
|
||||
? editing
|
||||
? (editingSubmittingLabel ?? submittingLabel)
|
||||
: submittingLabel
|
||||
: editing
|
||||
? editingLabel
|
||||
: idleLabel
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
{editing && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
disabled={submitting}
|
||||
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/// The four primary sections poppable into their own tab; matches the `?section=` query param a standalone page reads.
|
||||
export type PopOutSectionId = 'log' | 'params' | 'metrics' | 'dashboards'
|
||||
|
||||
|
||||
@@ -15,15 +15,7 @@ import {
|
||||
type PromqlSeries,
|
||||
} from '../lib/api'
|
||||
import { POLL_MS } from '../lib/polling'
|
||||
import {
|
||||
CollapseToggle,
|
||||
handleSectionHeaderKeyDown,
|
||||
PopOutButton,
|
||||
sectionHeaderInteractiveClassName,
|
||||
sectionHeaderLeftClassName,
|
||||
useSectionCollapsed,
|
||||
} from './CollapsibleSection'
|
||||
import { HelpIcon } from './HelpIcon'
|
||||
import { PopOutButton, SectionHeader, useSectionCollapsed } from './CollapsibleSection'
|
||||
|
||||
const CHART_TYPES: ChartType[] = ['line', 'bar', 'pie']
|
||||
|
||||
@@ -282,23 +274,13 @@ export function Dashboards({ range, activePreset }: { range: TimeRange | null; a
|
||||
if (!activeProfile) {
|
||||
return (
|
||||
<div className="border-b border-border text-sm">
|
||||
<div
|
||||
className={sectionHeaderInteractiveClassName}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={!collapsed}
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
onKeyDown={handleSectionHeaderKeyDown(() => setCollapsed(!collapsed))}
|
||||
>
|
||||
<div className={sectionHeaderLeftClassName}>
|
||||
Dashboards
|
||||
<HelpIcon sectionId="dashboards" label="Help: Dashboards" />
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CollapseToggle collapsed={collapsed} />
|
||||
<PopOutButton section="dashboards" range={range} activePreset={activePreset} />
|
||||
</div>
|
||||
</div>
|
||||
<SectionHeader
|
||||
title="Dashboards"
|
||||
helpSectionId="dashboards"
|
||||
collapsed={collapsed}
|
||||
onToggle={() => setCollapsed(!collapsed)}
|
||||
popOut={<PopOutButton section="dashboards" range={range} activePreset={activePreset} />}
|
||||
/>
|
||||
<div className="p-3 text-xs text-text-faint">
|
||||
Activate a profile (see the settings menu) to create and save dashboards.
|
||||
</div>
|
||||
@@ -308,23 +290,13 @@ export function Dashboards({ range, activePreset }: { range: TimeRange | null; a
|
||||
|
||||
return (
|
||||
<div className="border-b border-border text-sm">
|
||||
<div
|
||||
className={sectionHeaderInteractiveClassName}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={!collapsed}
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
onKeyDown={handleSectionHeaderKeyDown(() => setCollapsed(!collapsed))}
|
||||
>
|
||||
<div className={sectionHeaderLeftClassName}>
|
||||
Dashboards
|
||||
<HelpIcon sectionId="dashboards" label="Help: Dashboards" />
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CollapseToggle collapsed={collapsed} />
|
||||
<PopOutButton section="dashboards" range={range} activePreset={activePreset} />
|
||||
</div>
|
||||
</div>
|
||||
<SectionHeader
|
||||
title="Dashboards"
|
||||
helpSectionId="dashboards"
|
||||
collapsed={collapsed}
|
||||
onToggle={() => setCollapsed(!collapsed)}
|
||||
popOut={<PopOutButton section="dashboards" range={range} activePreset={activePreset} />}
|
||||
/>
|
||||
{error && !collapsed && (
|
||||
<div className="mx-3 mt-3 rounded border border-danger-border bg-danger-bg/50 px-2 py-1 text-xs text-danger-text">{error}</div>
|
||||
)}
|
||||
|
||||
@@ -13,15 +13,7 @@ import {
|
||||
type MetricDeclaration,
|
||||
type ParamDeclaration,
|
||||
} from '../lib/api'
|
||||
import {
|
||||
CollapseToggle,
|
||||
handleSectionHeaderKeyDown,
|
||||
PopOutButton,
|
||||
sectionHeaderInteractiveClassName,
|
||||
sectionHeaderLeftClassName,
|
||||
useSectionCollapsed,
|
||||
} from './CollapsibleSection'
|
||||
import { HelpIcon } from './HelpIcon'
|
||||
import { FormFooter, PopOutButton, SectionHeader, useSectionCollapsed } from './CollapsibleSection'
|
||||
import { RegexHelpIcon } from './RegexHelpIcon'
|
||||
|
||||
// One row in the labels -> occurrence count table; `values` keeps the raw points for the sparkline rendering.
|
||||
@@ -207,23 +199,13 @@ export function Metrics({ range, activePreset }: { range: TimeRange | null; acti
|
||||
|
||||
return (
|
||||
<div className="border-b border-border text-sm">
|
||||
<div
|
||||
className={sectionHeaderInteractiveClassName}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={!collapsed}
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
onKeyDown={handleSectionHeaderKeyDown(() => setCollapsed(!collapsed))}
|
||||
>
|
||||
<div className={sectionHeaderLeftClassName}>
|
||||
Metrics
|
||||
<HelpIcon sectionId="metrics" label="Help: Metrics" />
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CollapseToggle collapsed={collapsed} />
|
||||
<PopOutButton section="metrics" range={range} activePreset={activePreset} />
|
||||
</div>
|
||||
</div>
|
||||
<SectionHeader
|
||||
title="Metrics"
|
||||
helpSectionId="metrics"
|
||||
collapsed={collapsed}
|
||||
onToggle={() => setCollapsed(!collapsed)}
|
||||
popOut={<PopOutButton section="metrics" range={range} activePreset={activePreset} />}
|
||||
/>
|
||||
<div className="flex gap-4 p-3" hidden={collapsed}>
|
||||
<form onSubmit={handleSubmit} className="w-80 shrink-0 space-y-2">
|
||||
<div>
|
||||
@@ -300,25 +282,15 @@ export function Metrics({ range, activePreset }: { range: TimeRange | null; acti
|
||||
</div>
|
||||
)}
|
||||
{submitError && <div className="rounded border border-danger-border bg-danger-bg/50 px-2 py-1 text-xs text-danger-text">{submitError}</div>}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
{submitting ? (editingName ? 'Saving…' : 'Declaring…') : editingName ? 'Save changes' : 'Declare metric'}
|
||||
</button>
|
||||
{editingName && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetForm}
|
||||
disabled={submitting}
|
||||
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<FormFooter
|
||||
editing={editingName !== null}
|
||||
submitting={submitting}
|
||||
idleLabel="Declare metric"
|
||||
editingLabel="Save changes"
|
||||
submittingLabel="Declaring…"
|
||||
editingSubmittingLabel="Saving…"
|
||||
onCancel={resetForm}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { ApiError, clearParams, declareParam, type ParamDeclaration } from '../lib/api'
|
||||
import {
|
||||
CollapseToggle,
|
||||
handleSectionHeaderKeyDown,
|
||||
PopOutButton,
|
||||
sectionHeaderInteractiveClassName,
|
||||
sectionHeaderLeftClassName,
|
||||
useSectionCollapsed,
|
||||
} from './CollapsibleSection'
|
||||
import { HelpIcon } from './HelpIcon'
|
||||
import { FormFooter, PopOutButton, SectionHeader, useSectionCollapsed } from './CollapsibleSection'
|
||||
import { RegexHelpIcon } from './RegexHelpIcon'
|
||||
|
||||
const EMPTY_KIND: ParamDeclaration['kind'] = 'string'
|
||||
@@ -155,23 +147,13 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
|
||||
|
||||
return (
|
||||
<div className="border-b border-border text-sm">
|
||||
<div
|
||||
className={sectionHeaderInteractiveClassName}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={!collapsed}
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
onKeyDown={handleSectionHeaderKeyDown(() => setCollapsed(!collapsed))}
|
||||
>
|
||||
<div className={sectionHeaderLeftClassName}>
|
||||
Params
|
||||
<HelpIcon sectionId="parameters" label="Help: Params" />
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CollapseToggle collapsed={collapsed} />
|
||||
<PopOutButton section="params" />
|
||||
</div>
|
||||
</div>
|
||||
<SectionHeader
|
||||
title="Params"
|
||||
helpSectionId="parameters"
|
||||
collapsed={collapsed}
|
||||
onToggle={() => setCollapsed(!collapsed)}
|
||||
popOut={<PopOutButton section="params" />}
|
||||
/>
|
||||
<div className="flex gap-4 p-3" hidden={collapsed}>
|
||||
<form onSubmit={handleSubmit} className="w-80 shrink-0 space-y-2">
|
||||
<div>
|
||||
@@ -276,25 +258,15 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
|
||||
</div>
|
||||
)}
|
||||
{submitError && <div className="rounded border border-danger-border bg-danger-bg/50 px-2 py-1 text-xs text-danger-text">{submitError}</div>}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
{submitting ? (editingName ? 'Saving…' : 'Declaring…') : editingName ? 'Save changes' : 'Declare param'}
|
||||
</button>
|
||||
{editingName && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetForm}
|
||||
disabled={submitting}
|
||||
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<FormFooter
|
||||
editing={editingName !== null}
|
||||
submitting={submitting}
|
||||
idleLabel="Declare param"
|
||||
editingLabel="Save changes"
|
||||
submittingLabel="Declaring…"
|
||||
editingSubmittingLabel="Saving…"
|
||||
onCancel={resetForm}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
|
||||
Reference in New Issue
Block a user