Commit Graph
9891 Commits
Author SHA1 Message Date
Alex Holliday ca896fa22f implement docker host details page 2026-08-31 12:02:40 -07:00
Alex Holliday a7ee90e1ee merge develop 2026-08-31 10:26:38 -07:00
Alexander Holliday 06190a885a Merge pull request #3902 from bluewave-labs/feat/settings-tabs
feat(client): group settings into five tabs
2026-08-31 10:23:16 -07:00
Alexander Holliday 5f07860217 Merge pull request #3905 from eralpozcan/feat/maintenance-window-monitor-tags
feat(maintenance): show monitor tags in maintenance selector
2026-08-31 10:20:27 -07:00
Alex Holliday 073635d786 add localizations, exclude ref from recursion 2026-08-31 10:17:00 -07:00
Alexander Holliday af1d964e44 Merge pull request #3901 from bluewave-labs/feat/sidebar-groups
feat(client): group sidebar navigation into four labelled sections
2026-08-31 10:01:05 -07:00
Alex Holliday 3b6b7a1e5d add header icon divider 2026-08-31 09:59:26 -07:00
Alex Holliday bc44a3baeb retain header size on collapse 2026-08-31 09:56:59 -07:00
Alexander Holliday 4e4bc5f319 Merge pull request #3900 from bluewave-labs/fix/hover-states
fix(client): add hover states to tables, cards, stat boxes and icon buttons
2026-08-31 09:49:14 -07:00
Alex Holliday 2fec09df62 apply hover states to components directly rather than to parent 2026-08-31 09:47:20 -07:00
Alex Holliday 13e49c518d remove dead code, fix mobile regeression 2026-08-31 09:42:04 -07:00
Alexander Holliday a1f9c0304f Merge pull request #3897 from bluewave-labs/fix/network-notice-gating
fix(infrastructure): show the container-namespace notice when it applies
2026-08-31 09:36:15 -07:00
Alex Holliday d158bcdce1 pruge comment, fix busy connection regression 2026-08-31 09:34:59 -07:00
Alexander Holliday c45a21b4c8 Merge pull request #3896 from bluewave-labs/fix/gauge-arc-clamp-and-key
fix(infrastructure): clamp gauge arc and fix disk key fallback
2026-08-31 09:30:45 -07:00
Alex Holliday a0738dff63 purge narrative comment 2026-08-31 09:29:17 -07:00
Alexander Holliday 3cc4db9af5 Merge pull request #3895 from bluewave-labs/fix/infrastructure-gauges-overflow
fix(infrastructure): wrap gauge row so every disk stays reachable
2026-08-31 09:26:28 -07:00
Alex Holliday 94ecbfe365 add missing props 2026-08-31 09:24:56 -07:00
Alex Holliday 5343241036 add a flex basis prop instead of one-off fix 2026-08-31 09:21:35 -07:00
eralpozcan 261fdd08f1 feat(maintenance): show monitor tags in selector 2026-08-30 02:35:36 +03:00
gorkem-bwl 5470f44ebb fix(client): align sidebar group labels with the nav icons
The group labels sat 11px left of the icon column and rendered at the
same 13px as the nav items, so the groups read as indented blocks rather
than headings over a list.

The label used paddingLeft LAYOUT.LG (16px) while NavItem uses
theme.spacing(5) (10px); matching NavItem puts the label flush with the
icons. The eyebrow variant is 13px, so the size is overridden to
typographyLevels.s (11px) to sit below the item text. Top padding goes
from XS to SM for a little more separation above each group.

Measured in the browser: label and first icon both start at x=12, label
renders 11px against the items' 13px.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 23:40:41 -04:00
gorkem-bwl b24e42e516 fix(client): align the validation alert with the save button
Browser testing showed the alert stretching the full width of the save
bar while Save was pushed against the right edge, and the two sat on
different vertical centres.

The alert carried flexGrow: 1, so it consumed all the free space in the
row, plus mb: 2 and mr: 2, which offset it against a button that had
neither. The row itself set no alignItems, so the items aligned by
stretch rather than centre.

The row now centres its items and uses gap for spacing, and the alert
drops its sx entirely: growing was what stretched it, and the margins
are the gap's job. Measured: the alert sizes to its content (297px, was
1168px), 16px from Save, centres within 2px.

Removing the sx also clears two of the four pre-existing lint warnings
on this file.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 23:06:27 -04:00
gorkem-bwl 09d20c1bc7 fix(client): show usable field names in the settings error list
Second review pass on the tab grouping.

The save bar listed raw schema keys, and react-hook-form nests errors by
path, so a threshold error arrived as { globalThresholds: { cpu: {...} } }.
Iterating only the top level produced an entry whose own message was
undefined, rendering "globalThresholds: Invalid value" -- naming none of
the four sliders and carrying no reason. Errors are now flattened to
their leaves, so the row names the field that actually failed.

Rows also showed untranslated identifiers such as
systemEmailRejectUnauthorized. Every field already owns a translated
label, so those are reused via FIELD_LABEL_KEY; no new strings. Labels
that append an explanation after a dash are trimmed to the name.

Both defects predate this branch, but the tab work promoted these rows
into clickable links, which made unusable names more prominent.

Also guards visibleTabs[0], which is safe only because "general" is
currently ungated: gating it later, or adding a second role, would throw
on an empty list and blank the page. And a "tab" parameter that does not
resolve -- an unknown key, or an admin tab followed by a non-admin -- is
now dropped, so the URL stops claiming a tab the page is not showing.
Valid deep links are untouched.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 21:45:56 -04:00
gorkem-bwl d59bdf6c0c fix(client): render settings tab panels conditionally
Code review caught that the tab panels never hid.

Each panel was a `<Stack hidden={activeTab !== key}>`. The `hidden`
attribute reaches the DOM, but the only rule acting on it is the user
agent's `[hidden] { display: none }`, and MUI's Stack sets
`display: flex` in an author-level emotion class
(@mui/system/Stack/createStack.js:70). Author origin beats user agent
regardless of specificity, so every panel stayed visible: all five tabs
rendered stacked down the page and clicking a tab changed nothing.

Panels are now conditionally rendered, matching Pages/Account. This also
fixes a second defect: BasePage wraps children in `Stack spacing`, whose
sibling selector keys on DOM position rather than visibility, so the four
empty panels a non-admin never sees still collected a margin-top each and
left four blank gaps below "About".

My commit message for the previous commit argued panels had to stay
mounted or a partial tree would compute a stale `isValid` and lock
saving. That was wrong. No Form*Field passes `shouldUnregister`, and
react-hook-form defaults it to false, so unmounting a panel preserves its
values and validation state.

Also moves the tab-reveal off `onError`, which was unreachable: the save
button is `disabled={!isValid}` under `mode: "onChange"`, so
handleSubmit's error branch never fires. The validation Alert renders
straight from `formState.errors`, so a field on a hidden tab could be
named with no way to reach it. Each field in the Alert is now a button
that switches to its own tab.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 21:36:10 -04:00
gorkem-bwl 9d0f0116f3 feat(client): group settings into five tabs
The settings page rendered all 13 ConfigBox sections in one column, so
finding anything meant scrolling past everything else.

The sections are now grouped under five tabs by subject:

  General       timezone, appearance, status page URL, about
  Monitoring    thresholds, check retention, demo monitors
  Email         email settings
  Integrations  global proxy, PageSpeed API key
  Data          import/export, monitor history, system reset

No section changed. Every ConfigBox moved verbatim with its props,
children and isAdmin guard intact; the only edit inside one is Prettier
rewrapping an arrow function that shifted indentation level.

Tabs are derived from a filtered array rather than conditional JSX, so
indices stay correct when admin-only tabs drop out. Nine of the thirteen
sections are admin-gated, which leaves a non-admin four sections in
General alone -- the bar is hidden entirely in that case rather than
showing four empty tabs.

Panels stay mounted and toggle with `hidden`. Conditionally rendering
them would unmount inputs belonging to the shared react-hook-form, and
since Save is disabled on !isValid, a partial tree can compute a stale
isValid and lock saving.

The active tab lives in the URL as ?tab=<key> so reloads and shared
links land in the right place, following the useSearchParams pattern in
StatusPage/Status. An unknown tab, or an admin tab requested by a
non-admin, falls back to the first visible tab.

Both confirmation dialogs and the sticky save bar stay outside the
panels: the dialogs would unmount mid-request otherwise, and the save
bar serves the whole form.

Because the save bar lists validation errors by field name, an error
could name a field on a hidden tab. onError now switches to the tab
owning the first error before the alert renders.

Two titles are corrected to sentence case while their sections move:
"Global Thresholds" and "Monitor IP/URL on Status Page".

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 21:31:09 -04:00
gorkem-bwl 3e56dc63a9 fix(client): correct StatBox hover and separate nested-control hover
Addresses review findings on the hover-states change.

StatBox's hover was a no-op. `GradientBox` always paints an opaque
`linear-gradient` via the `background` shorthand, which populates
background-image and covers any `backgroundColor` beneath it, so the new hover
was invisible — a regression from the `opacity: 0.95` it replaced. The overlay
now layers onto the same gradient, and moves into `GradientBox` where `bg` is
in scope, behind an `interactive` prop.

The mobile card branch gated its cursor and hover on `onRowClick` alone, so
expandable-row tables below the md breakpoint rendered cards that expand on
click but showed no pointer and no hover — the exact inconsistency this change
set out to fix, reintroduced on mobile. Both branches now share one
`isInteractive` flag.

The row action gear used the same overlay as the row beneath it. Alpha
overlays compose, so it was not invisible as reported, but the resulting delta
was 6 levels — too faint for a nested control. Adds `HOVER.CONTROL` (0.05) and
an `action.controlHover` token for controls nested inside a hoverable row: the
gear and the pagination arrows.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 19:53:39 -04:00
gorkem-bwl 30881a0af1 fix(client): drop invalid li wrapper from sidebar groups
The group wrapper was rendering as `<li>`, but the enclosing `List` is
`component="nav"` and so renders a `<nav>`, not a `<ul>`. That put list items
directly inside a nav — invalid HTML, and a screen reader announces orphaned
list items with no enclosing list or item count.

`NavItem` renders a `div` via `ListItemButton`, so there is no list structure
for the group wrapper to join. Makes it a plain `Box` and drops the
now-redundant `listStyle: "none"`.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 19:28:29 -04:00
gorkem-bwl ebbf1a8b1e feat(client): group sidebar navigation into four labelled sections
The sidebar listed twelve items in a flat column ordered roughly by when each
feature was added, so nothing signalled that Uptime, Pagespeed and
Infrastructure are the same kind of thing, or that Tags and Proxies are
configuration rather than views. Adding a new monitor type made this worse:
the natural place for a new entry is the end of the list, three rows below the
item it most resembles.

Groups the menu into Monitoring, Alerting, Reporting and Configuration.
`getMenu` now returns groups of items rather than a flat array; the sidebar
renders an eyebrow label per group.

When collapsed to 64px there is no room for a label, so the labels are
replaced by dividers between groups — the grouping survives as rhythm.

Settings moves from the middle of the list to the end of Configuration.

Adds four `components.sidebar.menu.groups.*` keys across all 19 locales.

Also corrects the i18n note in CLAUDE.md, which claimed translations are
managed through PoEditor.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 19:25:38 -04:00
gorkem-bwl 9a6602b9e1 fix(client): add hover states to tables, cards, stat boxes and icon buttons
Clickable surfaces across the app set `cursor: pointer` but gave no visual
response on hover, so rows and cards read as static text.

Two separate causes:

- `DataTable` never passed MUI's opt-in `hover` prop on `TableRow`, which is
  `false` by default, so no table in the app shaded on hover. One change here
  covers all 16 page tables (Uptime, Incidents, Tags, Team, Notifications,
  Maintenance, Proxies, Status pages, Checks, Geo checks, Infrastructure,
  PageSpeed and the three Logs tables).
- `IconButton` gates its hover style behind `props => !props.disableRipple`,
  and the theme sets `disableRipple: true` globally, so the row action gear
  and the pagination arrows lost their background. `MenuItem` is unaffected —
  its hover sits on the base style — so action menu items already worked and
  are left alone.

Adds a `HOVER.ROW` (0.025) constant and an `action.rowHover` palette token,
defined in both light and dark palettes so the overlay direction is correct on
each surface. Kept separate from MUI's shared `action.hover` so menu items,
which read that token, are not restyled as a side effect.

Also fixes `cursor` on expandable rows, which showed `default` despite being
clickable to expand.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 19:13:33 -04:00
Alexander Holliday 46211ac61d Merge pull request #3899 from bluewave-labs/feat/first-class-docker-monitoring
feat: first class docker monitoring
2026-08-28 15:12:19 -07:00
Alex Holliday 791a0c8483 add docker monitors page, table, and details skeleton 2026-08-28 15:08:23 -07:00
Alex Holliday e964234f54 add tests and migration 2026-08-28 14:42:46 -07:00
Alex Holliday e60bfd18ff add route 2026-08-28 14:03:27 -07:00
Alex Holliday 560d64a482 add controller method for docker monitors 2026-08-28 14:00:34 -07:00
Alex Holliday c8826d90d5 update openapi 2026-08-28 13:59:54 -07:00
Alex Holliday cacedfd7d8 add validation 2026-08-28 13:59:24 -07:00
Alex Holliday 4ec03dfd04 fix return type, copy paste bug 2026-08-28 13:49:44 -07:00
Alex Holliday 78d3b23e93 handle docker checks 2026-08-28 13:48:37 -07:00
Alex Holliday 1a1dc7d807 add docker check aggregation 2026-08-28 13:30:32 -07:00
Alex Holliday 03174d98dd update monitors 2026-08-28 13:30:21 -07:00
Alex Holliday 686ded2137 update check and monitor domains 2026-08-28 13:02:38 -07:00
Alex Holliday 9c8bf4bb05 update tests 2026-08-28 12:39:56 -07:00
Alex Holliday 8f6c909acd add missing const, add key check 2026-08-28 12:28:26 -07:00
Alex Holliday 1ca38a31c9 add new docker payload types, promote docker type monitor to first class monitor 2026-08-28 12:24:44 -07:00
gorkem-bwl 9ffcbd1242 fix(infrastructure): show the container-namespace notice when it applies
The network tab warns when Capture can only see a container's own
network namespace, but the condition required EVERY reported interface
to be named lo, lo0 or docker0. A containerised Capture usually also
reports the container's veth under a host-looking name (eth0, ens3,
...), so the common case failed that test and the notice never
appeared - leaving those users with plausible-but-meaningless traffic
and nothing explaining why.

Widening it to "any container-only name present" would have been wrong
in the other direction: a host-level Capture on a machine running
Docker legitimately reports docker0 alongside its real NIC, and would
then have been warned incorrectly.

Interface names alone cannot separate the two cases. What does is that
in the container case nothing carries real traffic, while a genuine
host has a busy NIC. The notice now fires when a container-only
interface is present AND every interface is idle, using a 1 KB/s
threshold - real NICs sit orders of magnitude above it, a container's
veth sits at or near zero.

Checked against six scenarios: the reported case (lo + ens3 + docker0,
all near zero) now warns; a host with a busy NIC plus an idle docker0
does not; a fully namespaced container still warns as before; and a
quiet host with no container-only names stays silent.

The notice text is updated to match, since it previously described
only the all-container-names case and did not explain that a
host-looking interface can be the container's own.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 12:44:14 -04:00
gorkem-bwl 840e12eb9a fix(infrastructure): clamp gauge arc and fix disk key fallback
Two small pre-existing bugs in the infrastructure gauges.

Arc overdraw. Gauge computed strokeLength from the raw `progress` prop
while the label and the fill colour both used `progressWithinRange`,
the value clamped to 0-100. A reading above 100% therefore swept the
arc past a full circle - the offset (circumference - strokeLength)
went negative - while the label still read "100.0%". A negative
reading drew the arc backwards for the same reason. strokeLength now
uses the clamped value, so out-of-range input stops at exactly one
turn and matches what the label and colour already showed. Values in
range are unaffected.

The effect that animates the arc also listed `progress` in its
dependency array while only reading `strokeLength`, so it re-ran the
100ms animation on changes that could not alter the arc (105% to 110%,
say). Now that strokeLength is derived from the clamped value, the
stale dependency is dropped.

Disk key fallback. `disk?.device || 0 + idx` parses as
`disk?.device || (0 + idx)` because + binds tighter than ||, so the
fallback was just `idx` and the leading 0 did nothing. It happened to
produce unique keys, but by accident rather than intent, and a disk
whose device is the empty string collided with index 0. Replaced with
a `disk-${idx}` template so the intent is explicit.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 11:22:29 -04:00
gorkem-bwl 96b2266a9d fix(infrastructure): wrap gauge row so every disk stays reachable
The gauge row on the infrastructure details page laid out memory, CPU
and one gauge per disk in a single flex row with no wrapping and no
overflow handling. Each gauge is capped at 260px, so on a host with
enough disks the row ran past the content column.

RootLayout sets overflow: "hidden" on that column, so the excess was
clipped outright rather than becoming scrollable - the later disks
rendered but could not be reached by any means.

Three changes, all scoped to this row:

flexWrap moves the overflow onto additional rows.

useFlexGap is required alongside it. MUI's Stack implements `spacing`
as `& > :not(style) ~ :not(style) { marginLeft }`, and that selector is
DOM-order based rather than line-aware, so the first card of each
wrapped row would carry a spurious 16px left margin and misalign
against the row above. useFlexGap switches to a real flex gap.

flexBasis on the children is what actually makes the line break.
BaseChart sets `flex: 1`, which expands to `flex: 1 1 0%`; wrapping is
decided by the sum of items' hypothetical main sizes, so with a zero
basis that sum is 0 and the row never overflows. The cards would
instead shrink to min-content (roughly the 70px gauge plus the widest
label) and stay on one cramped line. Giving each child a 260px basis
makes the row wrap at the intended card width.

The basis is applied through the row's own `& > *` selector rather than
by changing BaseChart, which has eight other consumers that default to
maxWidth: "100%" and would have had their layout altered.

The repeated 260 literals are now a named constant.

Only affects viewports at or above the md breakpoint; below it the
stack is already a column, where flexWrap is inert.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-08-28 10:36:50 -04:00
Alexander Holliday 80073af80a Merge pull request #3894 from bluewave-labs/chore/monitor-list-refactor
chore: monitor list refactor
2026-08-27 14:41:09 -07:00
Alex Holliday 0c9334071f refactor monitor pages 2026-08-27 14:28:29 -07:00
Alex Holliday e444d5fbe6 decycle imports 2026-08-27 13:54:40 -07:00
Alex Holliday e9dca23664 add base monitor list page 2026-08-27 13:53:28 -07:00