Skip to content

feat: implement maintenance mode frontend#950

Merged
NathaelB merged 4 commits into
mainfrom
feat/implement-maintenance-mode-frontend
Apr 14, 2026
Merged

feat: implement maintenance mode frontend#950
NathaelB merged 4 commits into
mainfrom
feat/implement-maintenance-mode-frontend

Conversation

@LeadcodeDev

@LeadcodeDev LeadcodeDev commented Apr 14, 2026

Copy link
Copy Markdown
Contributor
Capture d’écran 2026-04-14 à 23 14 46 Capture d’écran 2026-04-14 à 23 15 01 image image

Summary by CodeRabbit

  • New Features

    • Maintenance mode UI for clients with configurable reason and session strategy
    • Whitelist management for clients and realms (add/remove users and roles)
    • Maintenance tabs and routes added to client and realm settings
    • New webhook triggers for client maintenance enable/disable events
  • Bug Fixes

    • Improved maintenance-related alert on login with amber styling and icon

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds client- and realm-scoped maintenance mode UI and API hooks: toggle maintenance, manage client/realm whitelists, new WhitelistPicker component, maintenance pages and tabs, login error render for maintenance messages, and webhook trigger identifiers for maintenance enable/disable.

Changes

Cohort / File(s) Summary
API & Data Integration
front/src/api/maintenance.api.ts
New React Query hooks calling window.tanstackApi for client/realm whitelist GET/POST/DELETE and a maintenance toggle PUT. Hooks return TanStack options and perform query invalidation and toasts on success.
Client UI & Routes
front/src/pages/client/components/whitelist-picker.tsx, front/src/pages/client/feature/page-client-maintenance-feature.tsx, front/src/pages/client/layout/client-layout.tsx, front/src/pages/client/page-client.tsx
Added WhitelistPicker component and client maintenance feature page. Fetches client/users/roles/whitelists, manages local overrides (reason/strategy), toggles maintenance via mutation, and wires add/remove whitelist actions. Adds maintenance tab and route.
Realm UI & Routes
front/src/pages/realm/feature/page-realm-settings-maintenance-feature.tsx, front/src/pages/realm/layouts/realm-settings-layout.tsx, front/src/pages/realm/page-realm.tsx, front/src/pages/realm/ui/page-realm-settings.tsx
Added realm maintenance settings page using two WhitelistPicker instances (users, roles). Updates valid tabs and routing to include maintenance.
Login UX
front/src/pages/authentication/ui/page-login.tsx
Detects "under maintenance" in login errors and renders an amber-styled alert with Wrench icon and trimmed message; other errors unchanged.
Webhook Catalog
front/src/utils/webhook-utils.ts
Added client.maintenance.enabled and client.maintenance.disabled triggers and corresponding labels/descriptions to webhook maps.

Sequence Diagram(s)

sequenceDiagram
  participant User as rgba(66,133,244,0.5) User
  participant UI as rgba(0,150,136,0.5) Frontend UI
  participant TanStack as rgba(255,193,7,0.5) window.tanstackApi
  participant Server as rgba(156,39,176,0.5) Backend API
  participant Cache as rgba(3,169,244,0.5) QueryCache
  participant Toast as rgba(233,30,99,0.5) Toast

  User->>UI: Click toggle / add/remove whitelist
  UI->>TanStack: call mutation/query options
  TanStack->>Server: HTTP PUT/POST/DELETE/GET
  Server-->>TanStack: response
  TanStack-->>Cache: invalidate/update relevant queryKey(s)
  TanStack-->>UI: resolve promise (data/error)
  UI->>Toast: show success/error toast
  UI-->>User: update UI state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • NathaelB
  • jorisvilardell

Poem

🐇🔧 In tunnels of code I softly tread,
I plant a wrench where alerts are fed.
Whitelists bloom and toggles gleam,
Webhooks whisper through the stream.
The rabbit hops—maintenance is fed.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: implement maintenance mode frontend' accurately describes the primary change—implementing a complete frontend system for maintenance mode.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/implement-maintenance-mode-frontend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
front/src/pages/client/feature/page-client-maintenance-feature.tsx (3)

132-144: Local overrides not cleared after saving.

After handleSaveSettings succeeds, reasonOverride and strategyOverride remain set. While the query invalidation will update the server values, the hasSettingsChanges check (line 130) will still be true, keeping the FloatingActionBar visible until the component re-renders with updated data from the server.

Consider clearing the overrides optimistically or in the mutation's onSuccess callback.

♻️ Suggested fix to clear overrides after save
   const handleSaveSettings = () => {
     toggleMaintenance({
       body: {
         enabled: client.maintenance_enabled ?? false,
         reason: reason || undefined,
         session_strategy: strategy,
       },
       path: {
         client_id: client.id,
         realm_name: realmName,
       },
-    })
+    }, {
+      onSuccess: () => {
+        setReasonOverride(undefined)
+        setStrategyOverride(undefined)
+      },
+    })
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/client/feature/page-client-maintenance-feature.tsx` around
lines 132 - 144, The local overrides (reasonOverride and strategyOverride) are
not cleared after calling handleSaveSettings, so hasSettingsChanges stays true
and the FloatingActionBar remains visible; update handleSaveSettings to clear
those overrides optimistically or, better, clear them in the toggleMaintenance
mutation's onSuccess callback: after toggleMaintenance completes, reset
reasonOverride and strategyOverride (and any local state tied to
hasSettingsChanges) so the UI reflects the saved server state; reference
handleSaveSettings, toggleMaintenance, reasonOverride, strategyOverride and
hasSettingsChanges when making the change.

65-65: Consider showing a loading skeleton instead of returning null.

Returning null while client is loading results in a blank page flash. A loading skeleton or spinner would improve the user experience.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/client/feature/page-client-maintenance-feature.tsx` at line
65, The early-return "if (!client) return null" causes a blank flash; replace it
with a loading UI so users see progress. Update the render logic in the
Page/Component where the "client" variable is checked (the block with "if
(!client) return null") to render a skeleton or spinner component (e.g., a
<LoadingSkeleton> or <Spinner>) while client is undefined, and keep the existing
UI render path unchanged once client is available; ensure the placeholder
matches the layout so there’s no layout shift.

54-83: Heavy type casting suggests API typing gaps.

The extensive as unknown as ... and as any casts for whitelistResponse, realmWhitelistResponse, usersResponse, and rolesResponse indicate the generated API client may be missing types for these endpoints. This is acceptable for now but should be addressed when regenerating the API client.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/client/feature/page-client-maintenance-feature.tsx` around
lines 54 - 83, The code uses heavy unsafe casts for whitelistResponse,
realmWhitelistResponse, usersResponse, and rolesResponse which hides API typing
gaps; replace the ad-hoc casts in the variables whitelist, realmWhitelist,
users, and roles by using proper typed response shapes (create or import
interfaces for the endpoint payloads or use the API client's response generics)
and apply those concrete types instead of `any`/`unknown`/double-casts; if the
API client is missing types, add explicit DTOs (e.g., WhitelistItem,
RealmWhitelistItem, UserItem, RoleItem) and type the responses or update the
generated client so you can rely on typed responses in the expressions that
assign whitelist, realmWhitelist, users, and roles.
front/src/api/maintenance.api.ts (1)

90-98: Consider adding explicit enabled option for consistency.

Unlike useGetClientWhitelist, this hook doesn't explicitly control when the query is enabled. While the default realm = 'master' ensures a truthy value, adding an explicit enabled option would improve consistency and make the behavior clearer.

♻️ Suggested change for consistency
 export const useGetRealmWhitelist = ({ realm = 'master' }: BaseQuery) => {
   return useQuery(
-    // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-    (window.tanstackApi.get as any)(
-      '/realms/{realm_name}/settings/maintenance/whitelist',
-      { path: { realm_name: realm } }
-    ).queryOptions
+    {
+      // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
+      ...(window.tanstackApi.get as any)(
+        '/realms/{realm_name}/settings/maintenance/whitelist',
+        { path: { realm_name: realm } }
+      ).queryOptions,
+      enabled: !!realm,
+    }
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/api/maintenance.api.ts` around lines 90 - 98, The hook
useGetRealmWhitelist should explicitly pass an enabled flag to the query options
like useGetClientWhitelist does; update the return value where you call
(window.tanstackApi.get as
any)('/realms/{realm_name}/settings/maintenance/whitelist', { path: {
realm_name: realm } }).queryOptions to merge or set an enabled property (e.g.,
enabled: Boolean(realm) or enabled: realm !== undefined) so the query only runs
when a valid realm is present and to keep behavior consistent with
useGetClientWhitelist.
front/src/pages/client/components/whitelist-picker.tsx (1)

148-157: Potential undefined access in entryIdMap.

When calling onRemove(entryIdMap[entry.id]), if entryIdMap doesn't contain entry.id, this will pass undefined to onRemove. While this should only happen for client-sourced entries (which should always be in the map), adding a guard would make the code more defensive.

🛡️ Suggested defensive check
               {entry.source === 'client' && (
                 <Button
                   type='button'
                   variant='ghost'
                   size='sm'
-                  onClick={() => onRemove(entryIdMap[entry.id])}
+                  onClick={() => {
+                    const entryId = entryIdMap[entry.id]
+                    if (entryId) onRemove(entryId)
+                  }}
                 >
                   <Trash2 className='h-4 w-4 text-destructive' />
                 </Button>
               )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/client/components/whitelist-picker.tsx` around lines 148 -
157, The click handler may pass undefined to onRemove when entryIdMap lacks
entry.id; update the onClick in the WhitelistPicker component to guard against
missing mapping by resolving const removeId = entryIdMap[entry.id] and only
calling onRemove(removeId) when removeId is defined (or alternatively
disable/hide the Button when mapping is missing) so onRemove never receives
undefined; reference entryIdMap, entry.id and onRemove to locate and change the
handler.
front/src/pages/client/components/client-maintenance-section.tsx (1)

166-194: Raw ID inputs for whitelist entries is poor UX.

This component uses raw text inputs for user/role IDs instead of the searchable picker used in PageClientMaintenanceFeature. Users would need to know and manually enter UUIDs, which is error-prone and unfriendly.

If this component is still needed, consider using WhitelistPicker for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/client/components/client-maintenance-section.tsx` around
lines 166 - 194, The UI currently asks for raw UUIDs via InputText (newUserId,
newRoleId) and handlers handleAddUser/handleAddRole, which is poor UX; replace
those raw text fields with the searchable WhitelistPicker used by
PageClientMaintenanceFeature so users can lookup/select users and roles instead
of typing IDs. Swap the InputText components for WhitelistPicker instances bound
to the same state (or adapt state to accept the picker output), update the
onChange to setNewUserId/setNewRoleId from the picker's selection, and ensure
handleAddUser/handleAddRole consume the selected object/ID format the picker
returns. Ensure labels/name props remain appropriate and remove/disable
free-text entry so UUIDs are not required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@front/src/api/maintenance.api.ts`:
- Around line 12-19: Remove the unnecessary "as any" casts in
maintenance.api.ts: stop casting window.tanstackApi.get/put to any and use the
generated client types (e.g., get_Get_client_whitelist, put_Toggle_maintenance)
directly so queryOptions keeps full type-safety; replace lines like
"(window.tanstackApi.get as any)(...)." with "window.tanstackApi.get(...)" (and
the same for put) and remove the eslint-disable comment, and scan the file for
other "as any" occurrences and delete them so the generated types are used
end-to-end.

In `@front/src/pages/authentication/ui/page-login.tsx`:
- Around line 97-106: Normalize maintenance detection and parsing by using a
single case-insensitive regex against errorMessage (e.g., /under
maintenance(?:[:\s-]*)?(.*)$/i) to both detect the maintenance condition and
capture the optional reason; replace the current includes('under maintenance')
check with this regex test, use the captured group as the banner text but fall
back to a default like "Service under maintenance" if the capture is empty or
whitespace, and keep the existing banner structure (the Wrench icon and styling)
while removing the separate case-sensitive includes and the replace(/^.*under
maintenance:\s*/i, '') call.

In `@front/src/pages/client/components/client-maintenance-section.tsx`:
- Around line 33-63: Delete the unused ClientMaintenanceSection component and
all related dead code: remove the entire client-maintenance-section.tsx file
(including the ClientMaintenanceSection React component, its state hooks
reason/strategy/newUserId/newRoleId, and usages of useToggleMaintenance,
useGetClientWhitelist, useAddClientWhitelistEntry, useRemoveClientWhitelistEntry
and the local whitelist variable/handleToggle function) since
PageClientMaintenanceFeature replaces its behavior; ensure no imports reference
ClientMaintenanceSection remain elsewhere.

In `@front/src/pages/realm/feature/page-realm-settings-maintenance-feature.tsx`:
- Line 14: The current default assignment const realmName = realm_name ??
'master' can silently route writes to the wrong realm; change this to fail fast
when realm_name is missing by validating realm_name (the param) before use and
returning an error/early render instead of defaulting to 'master'. Locate the
realmName assignment in page-realm-settings-maintenance-feature (look for
realm_name and realmName) and replace the fallback with a guard that throws or
renders an error/placeholder UI when realm_name is undefined or empty, ensuring
any subsequent mutations always use an explicitly provided realmName.

---

Nitpick comments:
In `@front/src/api/maintenance.api.ts`:
- Around line 90-98: The hook useGetRealmWhitelist should explicitly pass an
enabled flag to the query options like useGetClientWhitelist does; update the
return value where you call (window.tanstackApi.get as
any)('/realms/{realm_name}/settings/maintenance/whitelist', { path: {
realm_name: realm } }).queryOptions to merge or set an enabled property (e.g.,
enabled: Boolean(realm) or enabled: realm !== undefined) so the query only runs
when a valid realm is present and to keep behavior consistent with
useGetClientWhitelist.

In `@front/src/pages/client/components/client-maintenance-section.tsx`:
- Around line 166-194: The UI currently asks for raw UUIDs via InputText
(newUserId, newRoleId) and handlers handleAddUser/handleAddRole, which is poor
UX; replace those raw text fields with the searchable WhitelistPicker used by
PageClientMaintenanceFeature so users can lookup/select users and roles instead
of typing IDs. Swap the InputText components for WhitelistPicker instances bound
to the same state (or adapt state to accept the picker output), update the
onChange to setNewUserId/setNewRoleId from the picker's selection, and ensure
handleAddUser/handleAddRole consume the selected object/ID format the picker
returns. Ensure labels/name props remain appropriate and remove/disable
free-text entry so UUIDs are not required.

In `@front/src/pages/client/components/whitelist-picker.tsx`:
- Around line 148-157: The click handler may pass undefined to onRemove when
entryIdMap lacks entry.id; update the onClick in the WhitelistPicker component
to guard against missing mapping by resolving const removeId =
entryIdMap[entry.id] and only calling onRemove(removeId) when removeId is
defined (or alternatively disable/hide the Button when mapping is missing) so
onRemove never receives undefined; reference entryIdMap, entry.id and onRemove
to locate and change the handler.

In `@front/src/pages/client/feature/page-client-maintenance-feature.tsx`:
- Around line 132-144: The local overrides (reasonOverride and strategyOverride)
are not cleared after calling handleSaveSettings, so hasSettingsChanges stays
true and the FloatingActionBar remains visible; update handleSaveSettings to
clear those overrides optimistically or, better, clear them in the
toggleMaintenance mutation's onSuccess callback: after toggleMaintenance
completes, reset reasonOverride and strategyOverride (and any local state tied
to hasSettingsChanges) so the UI reflects the saved server state; reference
handleSaveSettings, toggleMaintenance, reasonOverride, strategyOverride and
hasSettingsChanges when making the change.
- Line 65: The early-return "if (!client) return null" causes a blank flash;
replace it with a loading UI so users see progress. Update the render logic in
the Page/Component where the "client" variable is checked (the block with "if
(!client) return null") to render a skeleton or spinner component (e.g., a
<LoadingSkeleton> or <Spinner>) while client is undefined, and keep the existing
UI render path unchanged once client is available; ensure the placeholder
matches the layout so there’s no layout shift.
- Around line 54-83: The code uses heavy unsafe casts for whitelistResponse,
realmWhitelistResponse, usersResponse, and rolesResponse which hides API typing
gaps; replace the ad-hoc casts in the variables whitelist, realmWhitelist,
users, and roles by using proper typed response shapes (create or import
interfaces for the endpoint payloads or use the API client's response generics)
and apply those concrete types instead of `any`/`unknown`/double-casts; if the
API client is missing types, add explicit DTOs (e.g., WhitelistItem,
RealmWhitelistItem, UserItem, RoleItem) and type the responses or update the
generated client so you can rely on typed responses in the expressions that
assign whitelist, realmWhitelist, users, and roles.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4557d33-88fb-413f-b672-acdd64e1a87a

📥 Commits

Reviewing files that changed from the base of the PR and between 861a522 and 07aff74.

📒 Files selected for processing (13)
  • front/src/api/api.client.ts
  • front/src/api/maintenance.api.ts
  • front/src/pages/authentication/ui/page-login.tsx
  • front/src/pages/client/components/client-maintenance-section.tsx
  • front/src/pages/client/components/whitelist-picker.tsx
  • front/src/pages/client/feature/page-client-maintenance-feature.tsx
  • front/src/pages/client/layout/client-layout.tsx
  • front/src/pages/client/page-client.tsx
  • front/src/pages/realm/feature/page-realm-settings-maintenance-feature.tsx
  • front/src/pages/realm/layouts/realm-settings-layout.tsx
  • front/src/pages/realm/page-realm.tsx
  • front/src/pages/realm/ui/page-realm-settings.tsx
  • front/src/utils/webhook-utils.ts

Comment thread front/src/api/maintenance.api.ts Outdated
Comment on lines +97 to +106
errorMessage.includes('under maintenance') ? (
<div className='rounded-md border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-700 dark:text-amber-400 flex items-start gap-2'>
<Wrench className='h-4 w-4 mt-0.5 shrink-0' />
<span>{errorMessage.replace(/^.*under maintenance:\s*/i, '')}</span>
</div>
) : (
<div className='rounded-md border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive'>
{errorMessage}
</div>
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Harden maintenance-message parsing to avoid false negatives and empty banners.

On Line 97, includes('under maintenance') is case-sensitive, while Line 100 strips the prefix case-insensitively. Also, if no reason follows the prefix, the <span> can render empty text. Please normalize detection/parsing once and provide a fallback message.

💡 Proposed fix
   const providers = loginSettings.identity_providers ?? []
+  const isMaintenanceError = /under maintenance/i.test(errorMessage ?? '')
+  const maintenanceReason =
+    errorMessage?.match(/under maintenance:\s*(.*)$/i)?.[1]?.trim() ||
+    'This service is currently under maintenance.'

 ...
-                        {errorMessage && (
-                          errorMessage.includes('under maintenance') ? (
-                            <div className='rounded-md border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-700 dark:text-amber-400 flex items-start gap-2'>
+                        {errorMessage && (
+                          isMaintenanceError ? (
+                            <div
+                              role='alert'
+                              className='rounded-md border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-700 dark:text-amber-400 flex items-start gap-2'
+                            >
                               <Wrench className='h-4 w-4 mt-0.5 shrink-0' />
-                              <span>{errorMessage.replace(/^.*under maintenance:\s*/i, '')}</span>
+                              <span>{maintenanceReason}</span>
                             </div>
                           ) : (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
errorMessage.includes('under maintenance') ? (
<div className='rounded-md border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-700 dark:text-amber-400 flex items-start gap-2'>
<Wrench className='h-4 w-4 mt-0.5 shrink-0' />
<span>{errorMessage.replace(/^.*under maintenance:\s*/i, '')}</span>
</div>
) : (
<div className='rounded-md border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive'>
{errorMessage}
</div>
)
const providers = loginSettings.identity_providers ?? []
const isMaintenanceError = /under maintenance/i.test(errorMessage ?? '')
const maintenanceReason =
errorMessage?.match(/under maintenance:\s*(.*)$/i)?.[1]?.trim() ||
'This service is currently under maintenance.'
// ... other code ...
{errorMessage && (
isMaintenanceError ? (
<div
role='alert'
className='rounded-md border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-700 dark:text-amber-400 flex items-start gap-2'
>
<Wrench className='h-4 w-4 mt-0.5 shrink-0' />
<span>{maintenanceReason}</span>
</div>
) : (
<div className='rounded-md border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive'>
{errorMessage}
</div>
)
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/authentication/ui/page-login.tsx` around lines 97 - 106,
Normalize maintenance detection and parsing by using a single case-insensitive
regex against errorMessage (e.g., /under maintenance(?:[:\s-]*)?(.*)$/i) to both
detect the maintenance condition and capture the optional reason; replace the
current includes('under maintenance') check with this regex test, use the
captured group as the banner text but fall back to a default like "Service under
maintenance" if the capture is empty or whitespace, and keep the existing banner
structure (the Wrench icon and styling) while removing the separate
case-sensitive includes and the replace(/^.*under maintenance:\s*/i, '') call.

Comment thread front/src/pages/client/components/client-maintenance-section.tsx Outdated

export default function PageRealmSettingsMaintenanceFeature() {
const { realm_name } = useParams<RouterParams>()
const realmName = realm_name ?? 'master'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid defaulting to master when realm_name is missing.

Line 14 can silently redirect mutations to the wrong realm. If params are missing, fail fast (or render an error) instead of writing to master.

💡 Safer guard
 export default function PageRealmSettingsMaintenanceFeature() {
   const { realm_name } = useParams<RouterParams>()
-  const realmName = realm_name ?? 'master'
+  if (!realm_name) {
+    return <div className='text-sm text-destructive'>Missing realm context.</div>
+  }
+  const realmName = realm_name
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/realm/feature/page-realm-settings-maintenance-feature.tsx` at
line 14, The current default assignment const realmName = realm_name ?? 'master'
can silently route writes to the wrong realm; change this to fail fast when
realm_name is missing by validating realm_name (the param) before use and
returning an error/early render instead of defaulting to 'master'. Locate the
realmName assignment in page-realm-settings-maintenance-feature (look for
realm_name and realmName) and replace the fallback with a guard that throws or
renders an error/placeholder UI when realm_name is undefined or empty, ensuring
any subsequent mutations always use an explicitly provided realmName.

@NathaelB NathaelB left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove any

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
front/src/pages/client/feature/page-client-maintenance-feature.tsx (1)

57-69: Use react-hook-form + Zod for the maintenance settings form.

This page has field state, dirty tracking, save, and reset behavior, but it is implemented with ad-hoc useState. Moving reason/strategy into a schema-backed form would centralize defaults, validation, and reset semantics and reduce the state drift already showing up here. As per coding guidelines, "Frontend: Use react-hook-form with Zod validation for form handling" and "Validate frontend forms with react-hook-form + Zod validation schemas."

Also applies to: 198-228, 279-290

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/client/feature/page-client-maintenance-feature.tsx` around
lines 57 - 69, Replace the ad-hoc useState tracking (reasonOverride,
setReasonOverride, strategyOverride, setStrategyOverride) and computed values
(serverReason, serverStrategy, reason, strategy) with a react-hook-form form
using a Zod schema: create a Zod schema for maintenance_reason and
maintenance_session_strategy, initialize useForm with zodResolver and
defaultValues set from client (serverReason/serverStrategy), use
formState.isDirty to track edits, use handleSubmit for save, and call
reset(formDefaults) whenever the client prop changes to restore server defaults;
update UI bindings to use register/watch instead of the old state setters and
remove the manual reason/strategy variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@front/src/pages/client/feature/page-client-maintenance-feature.tsx`:
- Around line 105-132: handleToggle is persisting unsaved draft fields
(reason/strategy) and handleSaveSettings reads the stale
client.maintenance_enabled; change handleToggle to only send the enabled flag
(no reason/session_strategy) so flipping the switch doesn't save drafts, and
change handleSaveSettings to send the current draft payload (reason or
reasonOverride, strategy or strategyOverride) plus the current UI enabled state
(track a local enabledDraft or use the enabled passed into handleToggle/save)
instead of reading client.maintenance_enabled from query state; update
occurrences in handleToggle and handleSaveSettings (and the similar block at
lines ~178-183) to reference these local draft values and avoid relying on
client.maintenance_enabled.
- Around line 57-69: The dirty-tracking currently uses whether an override was
set (reasonOverride/strategyOverride) rather than whether the effective values
differ from the server; change the change-detection to compare the effective
values (reason = reasonOverride ?? serverReason, strategy = strategyOverride ??
serverStrategy) against serverReason and serverStrategy (e.g. set
hasSettingsChanges = reason !== serverReason || strategy !== serverStrategy),
and after a successful save clear the overrides (setReasonOverride(undefined),
setStrategyOverride(undefined)) so the action bar closes; apply the same
comparison fix to the other occurrences that compute effective values (the other
blocks referencing reasonOverride/strategyOverride at the noted sections).

---

Nitpick comments:
In `@front/src/pages/client/feature/page-client-maintenance-feature.tsx`:
- Around line 57-69: Replace the ad-hoc useState tracking (reasonOverride,
setReasonOverride, strategyOverride, setStrategyOverride) and computed values
(serverReason, serverStrategy, reason, strategy) with a react-hook-form form
using a Zod schema: create a Zod schema for maintenance_reason and
maintenance_session_strategy, initialize useForm with zodResolver and
defaultValues set from client (serverReason/serverStrategy), use
formState.isDirty to track edits, use handleSubmit for save, and call
reset(formDefaults) whenever the client prop changes to restore server defaults;
update UI bindings to use register/watch instead of the old state setters and
remove the manual reason/strategy variables.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1a46e0a-a879-4f96-9dd1-70bffc7c6d7b

📥 Commits

Reviewing files that changed from the base of the PR and between 07aff74 and 1400163.

📒 Files selected for processing (4)
  • front/src/api/maintenance.api.ts
  • front/src/pages/client/components/whitelist-picker.tsx
  • front/src/pages/client/feature/page-client-maintenance-feature.tsx
  • front/src/pages/realm/feature/page-realm-settings-maintenance-feature.tsx
✅ Files skipped from review due to trivial changes (1)
  • front/src/pages/client/components/whitelist-picker.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • front/src/pages/realm/feature/page-realm-settings-maintenance-feature.tsx
  • front/src/api/maintenance.api.ts

Comment on lines +57 to +69
// Track user edits — undefined means "not edited, use server value"
const [reasonOverride, setReasonOverride] = useState<string | undefined>(undefined)
const [strategyOverride, setStrategyOverride] = useState<'terminate' | 'expire' | undefined>(
undefined
)

if (!client) return null

const serverReason = client.maintenance_reason ?? ''
const serverStrategy =
(client.maintenance_session_strategy as 'terminate' | 'expire') ?? 'expire'
const reason = reasonOverride ?? serverReason
const strategy = strategyOverride ?? serverStrategy

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Dirty tracking is based on “was edited” instead of “actually changed”.

hasSettingsChanges stays true once an override is set, even if the user restores the original value. It also keeps the action bar open after a successful save until they manually cancel. Compare the effective values against the server values instead.

♻️ Minimal fix
-  const hasSettingsChanges = reasonOverride !== undefined || strategyOverride !== undefined
+  const hasSettingsChanges = reason !== serverReason || strategy !== serverStrategy

Also applies to: 119-138, 279-290

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/client/feature/page-client-maintenance-feature.tsx` around
lines 57 - 69, The dirty-tracking currently uses whether an override was set
(reasonOverride/strategyOverride) rather than whether the effective values
differ from the server; change the change-detection to compare the effective
values (reason = reasonOverride ?? serverReason, strategy = strategyOverride ??
serverStrategy) against serverReason and serverStrategy (e.g. set
hasSettingsChanges = reason !== serverReason || strategy !== serverStrategy),
and after a successful save clear the overrides (setReasonOverride(undefined),
setStrategyOverride(undefined)) so the action bar closes; apply the same
comparison fix to the other occurrences that compute effective values (the other
blocks referencing reasonOverride/strategyOverride at the noted sections).

Comment on lines +105 to +132
const handleToggle = (enabled: boolean) => {
toggleMaintenance({
body: {
enabled,
reason: reason || undefined,
session_strategy: strategy,
},
path: {
client_id: client.id,
realm_name: realmName,
},
})
}

const hasSettingsChanges = reasonOverride !== undefined || strategyOverride !== undefined

const handleSaveSettings = () => {
toggleMaintenance({
body: {
enabled: client.maintenance_enabled ?? false,
reason: reason || undefined,
session_strategy: strategy,
},
path: {
client_id: client.id,
realm_name: realmName,
},
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Separate the immediate toggle from the draft settings payload.

handleToggle currently persists reason and strategy, so flipping the switch also saves unsaved edits even though the page still shows “Save Changes”. Then handleSaveSettings reads client.maintenance_enabled back from query state, so a toggle+save flow can send the old flag back before the refetch lands and leave maintenance in the wrong state.

Also applies to: 178-183

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/pages/client/feature/page-client-maintenance-feature.tsx` around
lines 105 - 132, handleToggle is persisting unsaved draft fields
(reason/strategy) and handleSaveSettings reads the stale
client.maintenance_enabled; change handleToggle to only send the enabled flag
(no reason/session_strategy) so flipping the switch doesn't save drafts, and
change handleSaveSettings to send the current draft payload (reason or
reasonOverride, strategy or strategyOverride) plus the current UI enabled state
(track a local enabledDraft or use the enabled passed into handleToggle/save)
instead of reading client.maintenance_enabled from query state; update
occurrences in handleToggle and handleSaveSettings (and the similar block at
lines ~178-183) to reference these local draft values and avoid relying on
client.maintenance_enabled.

@NathaelB NathaelB self-requested a review April 14, 2026 22:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@front/src/api/maintenance.api.ts`:
- Around line 5-14: The hook useGetClientWhitelist incorrectly defaults
realm='master' causing requests to silently target master; remove the default so
realm is optional (do not set realm = 'master'), require callers to pass an
explicit realm, and change the query enabled gating to check for an explicit
realm value (e.g., enabled: !!clientId && realm != null && realm !== '') so it
does not run when realm is unresolved; apply the same change to the other
maintenance whitelist hook referenced around lines 76-81 so both align with
useGetClient's explicit-realm behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94282172-3188-4253-980b-1da0aa58990e

📥 Commits

Reviewing files that changed from the base of the PR and between 1400163 and e8e53ea.

📒 Files selected for processing (1)
  • front/src/api/maintenance.api.ts

Comment on lines +5 to +14
export function useGetClientWhitelist({
realm = 'master',
clientId,
}: BaseQuery & { clientId?: string }) {
return useQuery({
...window.tanstackApi.get('/realms/{realm_name}/clients/{client_id}/maintenance/whitelist', {
path: { realm_name: realm, client_id: clientId! },
}).queryOptions,
enabled: !!clientId && !!realm,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't silently fall back to the master realm.

Both query hooks will hit /realms/master/... whenever the caller hasn't resolved realm context yet. In useGetClientWhitelist, Line 13 no longer protects that case because !!realm is always true after the fallback. That can cache and render another realm's whitelist in multi-realm flows. Please align these with useGetClient and require an explicit realm plus enabled gating.

🛠️ Proposed fix
 export function useGetClientWhitelist({
-  realm = 'master',
+  realm,
   clientId,
 }: BaseQuery & { clientId?: string }) {
   return useQuery({
     ...window.tanstackApi.get('/realms/{realm_name}/clients/{client_id}/maintenance/whitelist', {
-      path: { realm_name: realm, client_id: clientId! },
+      path: { realm_name: realm!, client_id: clientId! },
     }).queryOptions,
     enabled: !!clientId && !!realm,
   })
 }
@@
-export function useGetRealmWhitelist({ realm = 'master' }: BaseQuery) {
-  return useQuery(
-    window.tanstackApi.get('/realms/{realm_name}/clients/settings/maintenance/whitelist', {
-      path: { realm_name: realm },
-    }).queryOptions
-  )
+export function useGetRealmWhitelist({ realm }: BaseQuery) {
+  return useQuery({
+    ...window.tanstackApi.get('/realms/{realm_name}/clients/settings/maintenance/whitelist', {
+      path: { realm_name: realm! },
+    }).queryOptions,
+    enabled: !!realm,
+  })
 }

Also applies to: 76-81

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front/src/api/maintenance.api.ts` around lines 5 - 14, The hook
useGetClientWhitelist incorrectly defaults realm='master' causing requests to
silently target master; remove the default so realm is optional (do not set
realm = 'master'), require callers to pass an explicit realm, and change the
query enabled gating to check for an explicit realm value (e.g., enabled:
!!clientId && realm != null && realm !== '') so it does not run when realm is
unresolved; apply the same change to the other maintenance whitelist hook
referenced around lines 76-81 so both align with useGetClient's explicit-realm
behavior.

@NathaelB NathaelB merged commit ea43b0d into main Apr 14, 2026
9 checks passed
@NathaelB NathaelB deleted the feat/implement-maintenance-mode-frontend branch April 14, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants