Skip to content

feat(ui): add admin namespace management#6105

Open
luizhf42 wants to merge 3 commits intomasterfrom
feat/ui-react/admin-namespace-management
Open

feat(ui): add admin namespace management#6105
luizhf42 wants to merge 3 commits intomasterfrom
feat/ui-react/admin-namespace-management

Conversation

@luizhf42
Copy link
Copy Markdown
Member

@luizhf42 luizhf42 commented Apr 1, 2026

What

Admin namespace management for the React UI — paginated list, detail page, inline edit, and delete with cascade warning.

Why

Part of the admin panel migration from Vue to React (shellhub-io/team#93, feature #6). Depends on shellhub-io/cloud#2328 for the backend /admin/api/namespaces endpoints.

Changes

  • Hooks: useAdminNamespaces (paginated list with base64-encoded name filter), useAdminNamespace (single fetch), useEditNamespace and useDeleteNamespace mutations with cache invalidation — all mirroring the admin user hooks pattern
  • List page: Sortable table with Name, Owner, Devices, Max Devices, Created At columns; 300ms debounced search; row-click navigation to detail
  • Detail page: Two-column layout with properties card (name, tenant ID with copy, owner link, created at) and settings card (max devices, session recording, connection announcement, device count breakdown); members table with links to admin user detail
  • Edit drawer: Name, max devices (with NaN guard for 0 vs -1), session recording toggle; handles 409 conflict errors
  • Delete dialog: Cascade warning listing what gets removed (devices, sessions, keys, firewall rules, members)
  • Routes: /admin/namespaces and /admin/namespaces/:id under LicenseGuard

Testing

52 tests covering EditNamespaceDrawer (form pre-filling, validation, submit payload structure, 409/generic errors, state reset on reopen) and DeleteNamespaceDialog (cascade warning, mutation calls, callbacks, error handling).

Backend dependency: requires shellhub-io/cloud#2328 merged for /admin/api/namespaces to return 200 instead of 404.

luizhf42 added 3 commits April 1, 2026 18:08
Add query hooks (useAdminNamespaces, useAdminNamespace)
and mutation hooks (useEditNamespace, useDeleteNamespace)
for admin namespace CRUD, mirroring the admin user hooks.
Add paginated namespace list with search, detail page
with properties/settings/members, edit drawer, delete
dialog with cascade warning, and routes under
LicenseGuard.
Add 52 tests covering EditNamespaceDrawer (form
pre-filling, validation, submit payload, 409/generic
errors, state reset) and DeleteNamespaceDialog (cascade
warning, mutation calls, callbacks, error handling).
@luizhf42 luizhf42 requested a review from a team as a code owner April 1, 2026 21:14
@luizhf42 luizhf42 self-assigned this Apr 1, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 1, 2026

Claude finished @luizhf42's task in 1m 1s —— View job


Code Review Complete

Reviewed 10 files across code quality, security, testing, TypeScript/React patterns, and architecture. 3 inline issues posted (1 critical, 1 high, 1 medium).

Severity File Issue
critical __tests__/EditNamespaceDrawer.test.tsx:17-19 Imports MockDrawer from non-existent users/__tests__/mocks — all 26 tests fail at import
high hooks/useAdminNamespaceMutations.ts:8 useEditNamespace / useDeleteNamespace name collision with non-admin useNamespaceMutations.ts
medium pages/admin/namespaces/NamespaceDetails.tsx:62 ownerMember?.type reads non-existent field — type lives on Namespace, not Member

Additional notes

The routes are correctly guarded (ProtectedRouteAdminRouteLicenseGuard), hooks follow the established admin pattern, and the security review found no issues. The cross-repo dependency on shellhub-io/cloud#2328 is expected — the generated SDK client types won't exist until those backend endpoints are merged.


If you've addressed the feedback and want a new review, tag @shellhub-io/admin and a team member can trigger it.

@luizhf42 luizhf42 added kind/feature New feature or request area/ui status/work-in-progress product/enterprise Issue/PR related to Enterprise Edition labels Apr 1, 2026
Comment on lines +17 to +19
vi.mock("../../../../components/common/Drawer", async () => ({
default: (await import("../../users/__tests__/mocks")).MockDrawer,
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical: This imports MockDrawer from ../../users/__tests__/mocks, but that file does not exist anywhere in the codebase — no pages/admin/users/ directory exists, and MockDrawer is never exported from any module. The entire test suite (all 26 tests) will fail at import resolution.

Either create the shared mock at the expected path, or inline the mock directly in this file (following the pattern used in DeleteNamespaceDialog.test.tsx for ConfirmDialog):

Suggested change
vi.mock("../../../../components/common/Drawer", async () => ({
default: (await import("../../users/__tests__/mocks")).MockDrawer,
}));
vi.mock("../../../../components/common/Drawer", () => ({
default: ({
open,
onClose,
title,
subtitle,
children,
footer,
}: {
open: boolean;
onClose: () => void;
title: string;
subtitle?: React.ReactNode;
children: React.ReactNode;
footer?: React.ReactNode;
}) => {
if (!open) return null;
return (
<div>
<h2>{title}</h2>
{subtitle && <div>{subtitle}</div>}
<div>{children}</div>
{footer && <div>{footer}</div>}
</div>
);
},
}));

} from "../client/@tanstack/react-query.gen";
import { useInvalidateByIds } from "./useInvalidateQueries";

export function useEditNamespace() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high: useEditNamespace and useDeleteNamespace are also exported from hooks/useNamespaceMutations.ts with identical names but very different behavior — the non-admin useDeleteNamespace logs the user out and redirects to /login, while this admin version invalidates admin query caches. IDE auto-import could silently suggest the wrong one.

Consider prefixing with Admin to make the scope explicit:

Suggested change
export function useEditNamespace() {
export function useAdminEditNamespace() {

Same applies to useDeleteNamespace on line 19 → useAdminDeleteNamespace. The three import sites (EditNamespaceDrawer.tsx, DeleteNamespaceDialog.tsx, and their tests) would need updating accordingly.


const ownerMember = namespace.members?.find((m) => m.id === namespace.owner);
const ownerLabel = ownerMember?.email || namespace.owner;
const namespaceType = ownerMember?.type;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium: ownerMember?.type reads a field that doesn't exist on members. The backend Member struct has id, email, role, and added_at — no type field. The type field (personal/team) lives on the Namespace struct itself. This means namespaceType is always undefined and the badge on lines 97–101 never renders (dead code).

If you want to show the namespace type:

Suggested change
const namespaceType = ownerMember?.type;
const namespaceType = (namespace as Record<string, unknown>).type as string | undefined;

Or use namespace.type directly if the generated client type includes it. If the field isn't available yet in the OpenAPI spec, remove lines 62 and 97–101 until it is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ui kind/feature New feature or request product/enterprise Issue/PR related to Enterprise Edition status/work-in-progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant