10160 Commits
Author SHA1 Message Date
Alexander Holliday 1f0ea6cedc Merge pull request #3914 from eegrok/fix/maintenance-window-timezone-copy
fix Start Time description to accurately reflect that the time zone is local not UTC
2026-09-08 10:07:10 -07:00
Alexander Holliday 07a38b9c2c Merge pull request #3925 from egeoztass/fix/create-monitor-return-document
fix: return the created monitor from createMonitor
2026-09-08 10:03:34 -07:00
egeoztassandClaude Fable 5.1 67806c94b1 fix: return the created monitor from createMonitor
The controller writes the service's return value into the response, but
createMonitor was typed Promise<void> and returned nothing, so every
successful POST /monitors answered data: null and callers had no way to
read the new monitor's id.

Fixes #3917

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D7eViZ2Np5ZpWUYNoRhmAt
2026-09-07 12:31:07 +02:00
Alexander Holliday 26c0628f34 Merge pull request #3923 from bluewave-labs/feat/reorder-sidebar-options
feat(sidebar): reorder Docker and Pagespeed menu items
2026-09-06 07:58:54 -07:00
Malena AguiarandClaude Sonnet 4.6 4bbc84ac48 feat(sidebar): reorder Docker and Pagespeed menu items
Swap Docker and Pagespeed positions in the monitoring section of the
sidebar so Docker appears before Infrastructure and Pagespeed comes last.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 08:41:41 -06:00
Ali Shan Ahmad 2eeb088b61 [FIX]: rbac by adding the missing isAllowed(["admin", "superadmin"]) authorization middleware to mutating API endpoints for maintenance windows, notifications, and monitor check history. 2026-09-06 08:35:43 +00:00
Gorkem Cetin 7862b3c6f9 Update image in README.md
Replaced the old image with a new image in the README.
2026-09-06 04:14:17 +03:00
Gorkem Cetin 02b0daa37d Update README.md 2026-09-06 04:11:51 +03:00
Gorkem Cetin 1492569b73 Remove code owners from CODEOWNERS file 2026-09-06 04:10:05 +03:00
Alexander Holliday a545b81569 Merge pull request #3921 from bluewave-labs/fix/docker-permissions
fix: update docker permissions
2026-09-04 14:56:37 -07:00
Alex Holliday ce9dce48b3 update docker permissions 2026-09-04 14:55:07 -07:00
Alexander Holliday 7fcdcc0568 Merge pull request #3920 from bluewave-labs/feat/docker-container-logs
feat: docker container logs
2026-09-04 14:42:27 -07:00
Alex Holliday 9eea53aa5f add docker redirect 2026-09-04 14:41:10 -07:00
Alex Holliday bd7b6038f5 add empty view for no logs 2026-09-04 14:36:26 -07:00
Alex Holliday e2362ca809 add empty view if logs disabled 2026-09-04 14:34:24 -07:00
Alex Holliday 9a541dac21 mock missing dependency 2026-09-04 14:33:13 -07:00
Alex Holliday 71fed33ecd purge ssh 2026-09-04 14:33:13 -07:00
Alex Holliday 074b94b061 fix stale docker strings 2026-09-04 14:09:41 -07:00
Alex Holliday c1af8995e4 add opt in docker logs 2026-09-04 14:07:10 -07:00
Alex Holliday 431cada08e add opt in docker logs flag 2026-09-04 14:06:46 -07:00
Alex Holliday e012910332 add loading older logs 2026-09-04 13:16:22 -07:00
Alex Holliday 7dbc7b3064 wrap showToast in useMemo 2026-09-04 13:16:11 -07:00
Alex Holliday 1b97539eae export API response interface 2026-09-04 12:06:35 -07:00
Alex Holliday 6e0afe223e add after to docker logs query 2026-09-04 12:06:17 -07:00
Alex Holliday 37e1005479 add comment 2026-09-04 11:54:29 -07:00
Alex Holliday 795e1caca4 log display 2026-09-04 11:42:11 -07:00
Alex Holliday a5bb186814 wrap basebox in fowardRef 2026-09-04 11:28:10 -07:00
Alex Holliday bee3d4221e implement display of log head in logs tab 2026-09-03 12:57:06 -07:00
Alex Holliday f7e1793bac update tests 2026-09-03 12:56:44 -07:00
Alexander Holliday 9a8203b0d8 Merge pull request #3918 from bluewave-labs/fix/improve-code-readability
fix: improve code readability on uptime page components
2026-09-03 12:30:44 -07:00
Malena Aguiar 24d4595bb1 fix: improve code readability on uptime components 2026-09-03 12:55:43 -06:00
Alex Holliday 6587ddf389 wire up services/repositories 2026-09-03 11:32:48 -07:00
Alexander Holliday 9b939f2d94 Merge pull request #3915 from bluewave-labs/feat/theme-contrast-and-type
feat(client): define the neutral palette, switch to Geist, and standardize controls
2026-09-03 10:15:52 -07:00
Alex Holliday 530eeb0110 purge comments 2026-09-03 09:57:05 -07:00
Alex Holliday da0d2a47b4 use sx props 2026-09-03 09:47:38 -07:00
Alex Holliday 700baf2061 add compile script 2026-09-03 09:19:08 -07:00
gorkem-bwl de8eb53dd4 feat(client): one control height of 32px
Buttons, inputs, selects, pickers, toggles and tabs all declared 34 while
IconButton and the PageHeader action sat at 32, so anything mixing them
in a row was 2px out of alignment.

Everything now reads CONTROL_HEIGHT (32) from constants, so the number
lives in one place instead of nine.

Two content-box fixes were needed to make 32 safe, both measured in the
browser rather than assumed:

- MUI's button line-height is 1.75 - 22.75px at our 13px base - which
  with 6px padding needs 34.75px. It was already over its own box at 34
  and would have clipped at 32. Now 1.2, which leaves headroom for the
  taller scripts we ship (ar, th, vi, ja, zh).
- MUI pads the inner input 8.5px top and bottom, so an input's content
  needed 35.7px inside a 32px shell and was being cut off by
  overflow:hidden. Padding is zeroed in the theme for both the input and
  the Select's div, which centres instead.

Verified in the DOM: button, select and input all render at exactly 32
with scrollHeight equal to height, so nothing is clipped.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 01:38:51 -04:00
gorkem-bwl dbe0e2f4e3 fix(client): sidebar selection ordering, and clear second review pass
The sidebar regression is the real fix here. NavItem paints the selected
item with action.selected but hovers unselected items with MUI's default
action.hover. Once the sidebar moved onto `paper`, selected composited to
rgb(31,38,41) - darker than the ground - while hover came out lighter at
rgb(46,46,51), so hovering any other item made it read as more selected
than the actual selection. Both states now come from the same family
(selectedHover / rowHover), so selected always reads stronger.

Also from review:

- Dropped "Inter" from the status page SANS_STACK. Its webfont went with
  the Google Fonts import, so on Windows and Linux the stack was silently
  falling through to Segoe UI / Roboto on pages users publish.
- Button focus rings now use the same dark-mode green as input focus
  rings. They were still on primary.main at 2.86:1, which is the exact
  problem primary.light was added to solve.
- Removed 13 size={16} props made redundant by the Icon default change.
  The load-bearing pins stay: size={20} on the public status page, and
  the header pins that were relying on the old default.
- Documented that alpha() replaces rather than multiplies the alpha
  channel in StatusBox, so the grid is coupled to `divider` being opaque.
- Noted on the isRowSelected JSDoc that the small-screen card branch does
  not style selection, same limitation getRowSx already had.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 01:30:02 -04:00
gorkem-bwl 3ddc6ef1cb fix(client): one border story for every input
Inputs disagreed on both hover and focus. The theme's MuiOutlinedInput
hovered to text.primary - near-black in light mode - while Select,
TextInput and DatePicker each overrode hover back to `divider`, which is
the resting colour, so those had no hover cue at all. Four behaviours
across four components.

Now defined once in the theme and inherited by every outlined input:

  rest   divider           1.55:1 light / 1.73:1 dark
  hover  text.disabled     3.64:1 light / 4.66:1 dark
  focus  primary, 2px      5.93:1 light / 6.36:1 dark
  error  error.main

The per-component overrides in Select, TextInput and DatePicker are gone,
so there is one place to change this next time.

Focus in dark mode takes primary.light. The brand green is only 2.86:1 on
the dark ground, which made a focused input dimmer than a hovered one.
`main` is deliberately left alone: it is the fill for contained buttons,
and lightening it would flip their label to dark text across 31 sites.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 01:27:42 -04:00
gorkem-bwl ac94ae0fdd fix(client): make 16 the Icon default and size the stragglers
Third time this bug has surfaced, so this fixes the cause. Icon defaulted
to 20px, but 17 call sites already asked for 16 explicitly against 2 for
20, and the sidebar clamps to 16 in CSS - so 16 was the house size and
the 20 default was one nobody chose. Anything bare rendered above the cap
height of the 13px text beside it.

The default is now 16. Five sites that were relying on the old default
and sit next to labels are pinned explicitly anyway (team header, status
page controls, status page delete, theme toggle), and the public status
page's warning icon is pinned at 20 so the default change does not alter
a page users publish.

Sidebar icons are unaffected either way - their CSS already forced 16.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 01:24:44 -04:00
gorkem-bwl acbf26b03d fix(client): solid red offline banner, and lighter incident icons
The offline banner used error.main, which in dark mode is light enough
that MUI derives near-black contrastText - a pale salmon bar with dark
text on the one surface where urgency is the point. It now uses
error.dark with explicit white text: 11.12:1 in light mode, 5.04:1 in
dark, and a solid red bar in both.

Darkening error.main itself was the other option, but roughly 30 places
use it as text or a chart mark on a dark ground, where the lighter value
is correct - it would have cost all of them to fix one banner.

The incident summary icons rendered at the shared Icon default of 20px
in lucide's currentColor, so they sat larger and darker than the 13px
label beside them. Now 16px, matching the sidebar and the uptime header
buttons, and coloured from SummaryItem so the icon is a supporting mark
rather than competing with its own label. The 32px status icon on the
active-incidents card keeps its size and semantic colour; it is a focal
indicator, not a label decoration.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 01:22:12 -04:00
gorkem-bwl f931954997 fix(client): make row selection visible, and clear review findings
Selection never showed. getRowSx set a background on the <tr>, but every
<td> paints background.paper over it, and the hover rule targets cells -
so hover won and selection was invisible. Selection and hover are now
both cell-level rules in DataTable, with selection first so hover layers
on top, and rows carry an `is-selected` class from a new isRowSelected
prop. Uptime, Docker and Infrastructure tables all had the same broken
copy of this; they now share the one implementation, and any future table
gets it for free.

action.selected/selectedHover are defined explicitly and tinted with the
brand, so a selected row reads as chosen rather than dirty. Kept low
(6-11%) so the highlight does not swamp the row, especially in dark mode.

From code review:

- Dropped the Google Fonts @import at the top of index.css. Nothing has
  used Inter since the switch to Geist, and it was a render-blocking
  third-party request on every load - exactly what self-hosting the font
  was meant to avoid on air-gapped installs.
- TextInput's autofill override painted background.default, which used
  to equal paper and no longer does, so Chrome-autofilled fields showed
  a grey block on white cards. Now paper.
- Sidebar background moved from sx to the bgcolor prop (convention rule
  1, and it was a new lint warning), and the backdropFilter dropped -
  the background is opaque, so the blur only cost a compositing layer.
- Tab no longer bolds the selected label. That is the same reflow the
  ToggleButton change removed, and worse here because the indicator
  animates toward a target that moves as the label widens.
- Corrected the StatBox gradient comment: contrastText is white in light
  mode but near-black in dark, so the constraint is staying near `main`,
  not staying dark.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 01:14:49 -04:00
gorkem-bwl 2cfc2915ce fix(client): let page descriptions hold one line
PageHeader capped its description at maxWidth: 640, which wraps every
page description well before the text runs out - the uptime one is 100
characters and needs roughly 700px at 14px.

Now 108ch, sized against the longest description in en.json (113 chars,
the notifications page). Still a cap, so the line wraps on narrow
viewports and does not run the full width of a large display, and `ch`
tracks the font size rather than assuming 14px forever.

The empty-state description keeps its 360px cap - that copy is centred
and reads better narrow.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 01:10:57 -04:00
gorkem-bwl d965a82cc9 feat(client): put the sidebar on paper
The sidebar set no background, so it inherited background.default. Once
that became an off-white ground rather than plain white, the nav read as
a grey slab against the content.

It now sits on background.paper, which also gives the layout a real
figure/ground: white nav, slightly darker content area behind it. The
existing right border and the mobile Backdrop still do their jobs, and
an opaque panel is the correct behaviour over that backdrop.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 01:03:08 -04:00
gorkem-bwl 62fb17f64c fix(client): match header button icons and toggle weight to the rest of the UI
Two mismatches in the monitor detail header:

- The action button icons rendered at the shared Icon default of 20px,
  while the sidebar clamps the same lucide icons to 16px in CSS. The
  buttons were therefore 25% larger than every other icon in the app,
  and taller than the cap height of the 13px label beside them. Now 16px
  at all eight call sites. Left at full opacity - the sidebar dims to
  0.81 because nav is recessive, but these are active controls.

- ToggleButton went to fontWeight 500 when selected while Button is a
  flat 400, so the time range group sat heavier than the button row
  above it. Weight now stays at 400 and the tinted fill plus text.primary
  ink carry the selected state; that is a 2.9x difference in ink against
  the unselected label, so selection is still obvious. It also stops the
  label reflowing as you switch range.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 01:00:23 -04:00
gorkem-bwl 8582958ad5 fix(client): rebuild the stat box gradient from main
The coloured stat box ran its gradient from `<palette>.light` to
`<palette>.main`. With Material's stock warning that was #ff9800 ->
#ed6c02, a bright orange. The new warning.main is a dark amber chosen so
the "Paused" chip text clears 4.5:1, and MUI derives `light` from it, so
the gradient became brown mud.

The two roles were never compatible: `main` has to be dark enough for
small text on white, and the same value was being used as a large filled
background. The gradient now runs from lighten(main, 0.12) to main,
which keeps the box in the status colour, restores the depth that the
flat brown lost, and holds white contrastText at 4.64:1 or better at the
lightest point for all four palettes.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 00:56:29 -04:00
gorkem-bwl 1372e66749 fix(client): calm the stat box grid and firm up borders
Three fixes, all fallout from giving `divider` a real value:

- The graph-paper texture behind the status counts is drawn with
  `divider`. That used to be rgba(0,0,0,.12) and near-invisible; now it
  is opaque, so the grid jumped forward and read as the loudest thing on
  the page. It is texture, not a rule, so it now takes divider at 40%.

- GradientBox called lighten() on background.paper to build its
  gradient. paper is white in light mode and lightening white is a
  no-op, so the gradient collapsed flat. Darkens slightly toward the
  page ground instead.

- Borders were too faint at #D9E1DD (1.33:1 against paper). Now #C7D2CC
  (1.55:1), and dark mode #2C3532 -> #3A4642 (1.73:1), so tables and
  cards have a visible edge without turning into hard rules.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 00:52:41 -04:00
gorkem-bwl bdad949170 fix(client): put the tab bar back on the 13px base
Tabs.tsx hardcoded fontSize: 14, so the tab bar sat a pixel above the
13px base the rest of the chrome uses. Now typographyLevels.m, which
also satisfies the no-hardcoded-literals rule.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 00:48:02 -04:00
gorkem-bwl e69a2f05af feat(client): define the neutral palette and switch to Geist
The palette declared a brand green and six chart hues but never defined
the neutrals, so MUI's defaults filled the gap: body text at 60% black,
dividers at 12%, and background.default equal to background.paper. That
is the "washed out" everything sat on - table cells render through
text.secondary, and cards had no ground to sit on.

- Opaque neutral ramp, biased slightly green so it sits with the brand.
  background.default is now off-white so paper separates from the page.
- Semantic success/error/warning derived from the brand instead of
  Material's stock red/green/orange. Only `main` is set: `light` is
  consumed as a saturated colour by status dots, histogram bars and
  heatmap cells, so MUI must keep deriving it.
- Dark mode gets the same treatment; it defined background and secondary
  but its text and divider were still MUI defaults.
- Geist Variable, self-hosted via @fontsource-variable so air-gapped
  installs keep the typeface. Base size stays at 13px.
- h1 700 / h2 600 against 400 body, and table headers 500 -> 600. Two
  weights was why everything read at the same visual weight.

All contrast pairs pass WCAG AA in both themes (lowest 5.98:1).
No component markup changed.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 00:45:40 -04:00
gorkem-bwl 9c796edbde refactor(client): drop the copy button, keep the selection fix
The selection guard alone closes #3907: drag-selecting a check's error
message no longer navigates away, so the text can be copied. The copy
button was a second, larger answer to the same problem, and it brought a
reusable component, an HTTP clipboard fallback, four translation keys and
a timer to maintain.

Reverting it leaves the fix at ~29 lines in one file, and keeps the
behaviour change to the thing that was actually broken. CopyButton is
worth having for monitor URLs, tokens and incident ids, but it should
land on its own merits rather than inside a bug fix.

Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
2026-09-03 00:24:07 -04:00