Skip to content

React SPA cutover: replace EJS views with /api/v1 + React 19 client#319

Open
wreiske wants to merge 12 commits into
mainfrom
feature/react-ui
Open

React SPA cutover: replace EJS views with /api/v1 + React 19 client#319
wreiske wants to merge 12 commits into
mainfrom
feature/react-ui

Conversation

@wreiske
Copy link
Copy Markdown
Member

@wreiske wreiske commented May 19, 2026

Summary

This branch cuts the create-a-container app over from server-rendered EJS views to a React 19 SPA backed by a new versioned JSON API at /api/v1. Every legacy resource page has a React equivalent and the EJS view layer has been removed.

Highlights

Backend — /api/v1 JSON API

  • New routers under create-a-container/routers/api/v1/ for auth, users, groups, sites, nodes, containers, external-domains, jobs, settings, and api-keys
  • CSRF, session, and API-key auth wired through new middlewares/api.js
  • OpenAPI spec at openapi.v1.yaml
  • Legacy EJS routers and views removed
  • A few migrations adjusted to match the new constraints (site-scoped containers, nullable external-domain site, removal of node-name unique index)

Frontend — React 19 + Vite + Tailwind 4 SPA

  • Scaffolded with @mieweb/ui, React Router 7, TanStack Query, react-hook-form + zod
  • Pages for every resource: containers, nodes, sites, groups, users, api keys, external domains, jobs, settings, plus full auth flow (login/register/reset)
  • Shared FormPageHeader / FormPageLayout components and useDocumentTitle hook for consistent create/edit pages
  • Two-panel marketing auth layout with mobile fallback
  • Responsive list/table action rows
  • Vite dev proxy scoped to /api/* so client routes like /apikeys aren't intercepted

Other

  • Legacy EJS screenshots stashed under .attic/ for reference

Verification

  • npm run type-check (client) — clean
  • npm run build (client) — succeeds
  • node --check across server.js, routers/api/v1/*.js, and middlewares/api.js — clean

Commits

  • feat(client): scaffold React 19 + Tailwind 4 + @mieweb/ui SPA
  • feat(api): add /api/v1 JSON API + CSRF + OpenAPI spec
  • feat(client): SPA shell + auth flows on /api/v1
  • feat(client): Phase 4 — feature pages for all resources
  • feat: cutover from EJS to React SPA
  • fix(dev): SPA boots end-to-end on sqlite
  • fix(client): align top bar with sidebar header, polish footer, fix mobile
  • feat(client): polish auth layout, form scaffolding, and responsive UI
Screenshot 2026-05-19 at 12 07 09 AM Screenshot 2026-05-19 at 12 17 02 AM Screenshot 2026-05-19 at 12 28 24 AM Screenshot 2026-05-19 at 12 28 31 AM Screenshot 2026-05-19 at 12 28 35 AM Screenshot 2026-05-19 at 12 28 00 AM Screenshot 2026-05-19 at 12 28 10 AM

wreiske added 10 commits May 14, 2026 16:30
Phase 1 of manager UI rewrite. Creates create-a-container/client/ with:

- Vite 6 + React 19 + TypeScript
- Tailwind CSS 4 via @tailwindcss/vite
- @mieweb/ui 0.6.1 with BlueHive brand CSS
- React Router 7 (data routers) with full route tree placeholder
- TanStack Query 5, react-hook-form + zod, lucide-react

Adds client:* scripts to create-a-container/package.json.
- New middlewares/api.js: apiAuth (session OR Bearer API key),
  apiAdmin, asyncHandler, ApiError, jsonErrorHandler, csrfGuard.
- CSRF: csrf-csrf double-submit, exempts Bearer requests.
- Routers under /api/v1: auth (login w/ 2FA push, logout, register,
  password reset), sites, sites/:id/containers (CRUD + metadata),
  sites/:id/nodes (CRUD + Proxmox import + storages),
  external-domains, groups, users (+invite), apikeys, settings, jobs
  (incl. SSE stream).
- openapi.v1.yaml exposed via /api/v1/openapi.{json,yaml}.
- Mounted in server.js before legacy EJS routes.
- Legacy /apikeys, /sites, /jobs etc. remain functional.
- lib/api.ts: typed JSON client with credential cookies, CSRF
  double-submit (lazy fetch + retry on 403), { data }/{ error }
  envelope, 401 hook for redirect-to-login.
- lib/auth.ts: useSession query + login mutation w/ 2FA challenge
  support + logout (clears CSRF, resets query cache).
- providers.tsx: ThemeProvider, ToastProvider, SidebarProvider,
  CommandPaletteProvider.
- AppLayout: @mieweb/ui Sidebar + AppHeader + CommandPalette shell.
- AuthLayout: branded auth shell.
- RequireAuth: route guard, redirects to /login w/ redirect param.
- Sidebar/Header components composed from @mieweb/ui primitives.
- Auth pages: LoginPage (w/ 2FA push polling),
  RegisterPage (supports invite tokens), RegisterSuccessPage
  (w/ 2FA QR code), ResetPasswordRequestPage, ResetPasswordPage.
- Remove all legacy EJS routers and views (login, register, verify, users, groups, sites, external-domains, jobs, settings, apikeys, reset-password, nodes, containers)
- Extract nginx-conf and dnsmasq template endpoints to routers/templates.js
- Mount client/dist as static + SPA fallback for non-API, non-template routes
- Drop connect-flash, method-override deps (no longer needed)
- Keep views/nginx-conf.ejs and views/dnsmasq/* (server-side configuration templates)
- middlewares/api.js: drop __Host- prefix off-prod (requires Secure) and only
  bind CSRF token to session id once a user is signed in; saveUninitialized:
  false handed out a fresh session id per anon request, breaking double-submit.
- client/vite.config.ts: drop /login, /logout, /nginx-conf, /dnsmasq proxies
  now that those are SPA routes; /api is the only backend proxy.
- migrations: make 3 postgres-first migrations sqlite-compatible
  (guard undefined fk.constraintName, tolerate missing named constraints,
  rewrite UPDATE...FROM as scalar subquery, tolerate re-added columns).
- package.json: add sqlite3 as a real dependency.
…bile

- AppLayout: switch to h-screen overflow-hidden shell with internal main
  scroll so the sidebar can't scroll out of view on long pages.
- Sidebar: pin SidebarHeader to h-16 (was 65px from py-4) so its bottom
  border matches the AppHeader bottom border to the pixel; rebuild the
  footer as a single user card (avatar + name + role + icon sign-out)
  with a top border so it visually anchors the bottom of the sidebar.
- Header: drop the duplicate Container Manager brand on desktop (the
  sidebar already shows it); render a plain span only on mobile where
  the sidebar is collapsed off-canvas (AppHeaderBrand is hidden below
  md by @mieweb/ui so it can't be used there).
- Redesign AuthLayout with two-panel marketing/form layout and mobile header
- Add FormPageHeader and FormPageLayout shared components for create/edit pages
- Add useDocumentTitle hook and apply consistent titles across pages
- Make list/table action rows wrap on narrow viewports
- Tighten Sidebar: remove redundant Containers entry, use Button for logout
- Scope Vite dev proxy to /api/* so client routes (e.g. /apikeys) aren't proxied
- Stash legacy EJS screenshots under .attic/ for reference
# Conflicts:
#	create-a-container/routers/containers.js
#	create-a-container/routers/external-domains.js
#	create-a-container/routers/groups.js
#	create-a-container/routers/login.js
#	create-a-container/routers/nodes.js
#	create-a-container/routers/register.js
#	create-a-container/routers/sites.js
#	create-a-container/routers/users.js
#	create-a-container/views/layouts/header.ejs
#	create-a-container/views/login.ejs
#	create-a-container/views/users/index.ejs
- POST /api/v1/auth/dev: one-click dev login (admin/user) when NODE_ENV != production
- GET /api/v1/health: now returns isDev flag
- GET /api/v1/session: admins also receive pushNotificationUrl
- POST /api/v1/users/email-all: broadcast email via existing sendBulkEmail util
- LoginPage: dev-mode login buttons gated on /health.isDev
- UsersListPage: 'Email all' action + modal (subject + message)
- Sidebar: external 'MFA Admin' link rendered for admins when push URL configured
@wreiske
Copy link
Copy Markdown
Member Author

wreiske commented May 19, 2026

Merged latest main and ported every feature that landed inside the now-deleted EJS code to the new /api/v1 + React SPA. Branch is now at parity with main.

Ported in 97190e4:

  • One-click dev login (feat: add one-click dev login buttons #304) — POST /api/v1/auth/dev (404 in production) + Login page buttons gated on GET /api/v1/healthisDev.
  • Email All (Add 'Email All' feature for users #306) — POST /api/v1/users/email-all using the existing sendBulkEmail (BCC + smtp_noreply_address) + new EmailAllModal wired into the Users list with a live recipient count.
  • MFA Admin link ([Bug]: MIEAuth issue #309) — GET /api/v1/session now returns pushNotificationUrl for admins; Sidebar renders an external link to `${pushNotificationUrl}/admin` when configured.

Already covered, no port needed:

  • Surface better errors on database constraint failures #310 plain-english SQL validation errors — structurally superseded by `jsonErrorHandler` returning HTTP 422 with per-field `fields` on `SequelizeValidationError`/`SequelizeUniqueConstraintError`.
  • CSS issue #311 sidebar collapse fix — the new `@mieweb/ui` `Sidebar` handles responsive collapse natively.
  • Clean up manager logs #313 dedicated access log, Debian env filepath, SMTP no-reply enforcement / BCC bulk batching — auto-merged cleanly into `server.js`, `systemd/`, and `utils/email.js`.

Typecheck + Vite build are clean. `git diff origin/main..HEAD` now shows only the expected EJS deletes plus the SPA + JSON API surface.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR completes the cutover of create-a-container from server-rendered EJS pages to a React SPA, backed by a new versioned JSON API under /api/v1, while preserving a small EJS-rendered “templates” surface for nginx/dnsmasq config generation.

Changes:

  • Replaces legacy page routers/views with /api/v1/* JSON routers (session + API-key auth, CSRF, consistent JSON envelopes, and error handling).
  • Adds a React 19 + Vite + Tailwind SPA client and serves the compiled app from Express for non-API routes.
  • Updates migrations and supporting server wiring to match the new SPA/API architecture.

Reviewed changes

Copilot reviewed 109 out of 116 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
create-a-container/views/users/invite.ejs Removed legacy EJS “invite user” view.
create-a-container/views/users/index.ejs Removed legacy EJS users list page.
create-a-container/views/users/form.ejs Removed legacy EJS users create/edit form.
create-a-container/views/sites/index.ejs Removed legacy EJS sites list page.
create-a-container/views/sites/form.ejs Removed legacy EJS sites create/edit form.
create-a-container/views/reset-password/reset.ejs Removed legacy EJS reset-password “set new password” view.
create-a-container/views/reset-password/request.ejs Removed legacy EJS reset-password request view.
create-a-container/views/register.ejs Removed legacy EJS registration view.
create-a-container/views/register-success.ejs Removed legacy EJS registration success/QR view.
create-a-container/views/nodes/index.ejs Removed legacy EJS nodes list view.
create-a-container/views/nodes/import.ejs Removed legacy EJS nodes import view.
create-a-container/views/login.ejs Removed legacy EJS login view.
create-a-container/views/layouts/footer.ejs Removed legacy EJS footer partial (version/issue link + bootstrap).
create-a-container/views/groups/index.ejs Removed legacy EJS groups list view.
create-a-container/views/groups/form.ejs Removed legacy EJS groups create/edit form.
create-a-container/views/external-domains/index.ejs Removed legacy EJS external domains list view.
create-a-container/views/apikeys/show.ejs Removed legacy EJS API key detail view.
create-a-container/views/apikeys/index.ejs Removed legacy EJS API keys list view.
create-a-container/views/apikeys/form.ejs Removed legacy EJS API key create form.
create-a-container/views/apikeys/created.ejs Removed legacy EJS “API key created” one-time display view.
create-a-container/server.js Switches to mounting /api/v1, templates router, and SPA static serving.
create-a-container/routers/verify.js Removed legacy nginx auth_request verification route.
create-a-container/routers/templates.js Adds EJS template endpoints for nginx/dnsmasq file generation.
create-a-container/routers/settings.js Removed legacy settings page router (replaced by API).
create-a-container/routers/reset-password.js Removed legacy reset-password router (replaced by API).
create-a-container/routers/register.js Removed legacy register router (replaced by API).
create-a-container/routers/login.js Removed legacy login router (replaced by API).
create-a-container/routers/groups.js Removed legacy groups CRUD router (replaced by API).
create-a-container/routers/external-domains.js Removed legacy external domains CRUD router (replaced by API).
create-a-container/routers/apikeys.js Removed legacy API keys router (replaced by API).
create-a-container/routers/api/v1/index.js Adds /api/v1 mount with CSRF token endpoint, OpenAPI endpoints, session endpoint, and sub-routers.
create-a-container/routers/api/v1/sites.js Adds /api/v1/sites CRUD + nested mounts for site-scoped resources.
create-a-container/routers/api/v1/settings.js Adds admin-only /api/v1/settings read/update endpoints.
create-a-container/routers/api/v1/jobs.js Adds /api/v1/jobs read + status pagination + SSE stream endpoints.
create-a-container/routers/api/v1/groups.js Adds admin-only /api/v1/groups CRUD endpoints.
create-a-container/routers/api/v1/external-domains.js Adds admin-only /api/v1/external-domains CRUD; keeps Cloudflare key write-only.
create-a-container/routers/api/v1/apikeys.js Adds per-user /api/v1/apikeys CRUD with one-time plaintext key return on create.
create-a-container/middlewares/api.js Introduces API auth (session + bearer key), CSRF guard, JSON envelopes, and error handler.
create-a-container/package.json Adds client helper scripts and new server dependencies for SPA/API support.
create-a-container/migrations/20260218000001-container-site-scoped-constraints.js Makes migration more idempotent and adjusts backfill SQL.
create-a-container/migrations/20260218000000-remove-node-name-unique.js Makes constraint removal tolerant of DB differences/idempotent.
create-a-container/migrations/20260217000000-make-external-domain-site-id-nullable.js Safeguards FK constraint removal when constraint name is missing.
create-a-container/client/vite.config.ts Adds Vite config with /api-scoped dev proxy and build output to dist/.
create-a-container/client/tsconfig.node.json Adds TS config for Vite config/type-checking.
create-a-container/client/tsconfig.json Adds TS project references.
create-a-container/client/tsconfig.app.json Adds strict TS config for the SPA app sources.
create-a-container/client/src/vite-env.d.ts Adds Vite client types reference.
create-a-container/client/src/styles/index.css Adds Tailwind + @mieweb/ui brand styles entry.
create-a-container/client/src/pages/users/UsersListPage.tsx Adds SPA users list UI (actions, delete, email-all modal integration).
create-a-container/client/src/pages/users/InviteUserPage.tsx Adds SPA invite-user form page.
create-a-container/client/src/pages/users/EmailAllModal.tsx Adds “email all users” modal + API integration.
create-a-container/client/src/pages/sites/SitesListPage.tsx Adds SPA sites list UI with admin-only creation/deletion actions.
create-a-container/client/src/pages/sites/SiteFormPage.tsx Adds SPA site create/edit form.
create-a-container/client/src/pages/settings/SettingsPage.tsx Adds SPA settings page with env var field arrays and admin-only settings edits.
create-a-container/client/src/pages/NotFoundPage.tsx Adds SPA 404 page.
create-a-container/client/src/pages/nodes/NodesListPage.tsx Adds SPA nodes list UI for a site.
create-a-container/client/src/pages/nodes/NodeImportPage.tsx Adds SPA Proxmox node import form.
create-a-container/client/src/pages/jobs/JobDetailPage.tsx Adds SPA job detail + log streaming UI via SSE.
create-a-container/client/src/pages/groups/GroupsListPage.tsx Adds SPA groups list UI.
create-a-container/client/src/pages/groups/GroupFormPage.tsx Adds SPA group create/edit form.
create-a-container/client/src/pages/external-domains/ExternalDomainsListPage.tsx Adds SPA external domains list UI.
create-a-container/client/src/pages/containers/ContainersListPage.tsx Adds SPA containers list UI (site-scoped) with status badges and links.
create-a-container/client/src/pages/auth/ResetPasswordRequestPage.tsx Adds SPA reset-password request page.
create-a-container/client/src/pages/auth/ResetPasswordPage.tsx Adds SPA reset-password form with token validation.
create-a-container/client/src/pages/auth/RegisterSuccessPage.tsx Adds SPA registration success page with optional 2FA enrollment QR.
create-a-container/client/src/pages/apikeys/ApiKeysListPage.tsx Adds SPA API keys list + one-time key display + revoke UI.
create-a-container/client/src/main.tsx Adds SPA entrypoint with TanStack Query client setup and router mounting.
create-a-container/client/src/lib/useDocumentTitle.ts Adds a small hook to manage page titles.
create-a-container/client/src/lib/types.ts Adds typed models matching /api/v1 serializers.
create-a-container/client/src/lib/toast.ts Adds centralized API error → toast helper.
create-a-container/client/src/lib/queries.ts Adds shared TanStack Query keys and fetchers for /api/v1 resources.
create-a-container/client/src/lib/auth.ts Adds session + login/logout + dev-login + 2FA challenge support helpers.
create-a-container/client/src/lib/api.ts Adds typed fetch wrapper (JSON envelopes, CSRF token handling, 401 handling).
create-a-container/client/src/components/FormPageLayout.tsx Adds shared create/edit form scaffold layout component.
create-a-container/client/src/components/FormPageHeader.tsx Adds shared form header component.
create-a-container/client/src/app/Sidebar.tsx Adds SPA sidebar navigation + logout action.
create-a-container/client/src/app/router.tsx Adds SPA route map (auth + protected app routes).
create-a-container/client/src/app/RequireAuth.tsx Adds authenticated-route guard around app routes.
create-a-container/client/src/app/providers.tsx Adds provider composition for theme/toast/sidebar/command palette.
create-a-container/client/src/app/PlaceholderPage.tsx Adds a placeholder page component (currently unused).
create-a-container/client/src/app/Header.tsx Adds top header with search/theme toggle/user menu.
create-a-container/client/src/app/AuthLayout.tsx Adds marketing-style two-panel auth layout with mobile fallback.
create-a-container/client/src/app/AppLayout.tsx Adds main app layout shell (sidebar + header + outlet).
create-a-container/client/README.md Adds SPA client dev/build instructions.
create-a-container/client/package.json Adds client dependencies/scripts for React/Vite/Tailwind app.
create-a-container/client/index.html Adds Vite HTML entrypoint.
create-a-container/client/.gitignore Adds client-local ignore rules (dist, node_modules, etc.).
.gitignore Ignores .tmp-verify/.
Files not reviewed (2)
  • create-a-container/client/package-lock.json: Language not supported
  • create-a-container/package-lock.json: Language not supported

Comment thread create-a-container/client/src/pages/users/UsersListPage.tsx
Comment thread create-a-container/client/src/app/Sidebar.tsx
Comment thread create-a-container/routers/templates.js
Comment thread create-a-container/server.js
Comment thread create-a-container/client/README.md
Comment thread create-a-container/client/src/app/PlaceholderPage.tsx Outdated
Comment thread create-a-container/client/src/lib/toast.ts Outdated
Comment thread create-a-container/middlewares/api.js Outdated
wreiske added 2 commits May 19, 2026 00:37
- UsersListPage: render EmailAllModal once at page root (not nested per row)
- Sidebar: import ReactNode type instead of using React.ReactNode namespace
- templates.js: return 404 when site not found in nginx route
- server.js: load openapi.v1.yaml for /api Swagger UI
- client/README.md: correct dev proxy description (only /api/*)
- Remove unreferenced PlaceholderPage.tsx
- Remove unused useApiErrorToast helper (lib/toast.ts)
- middlewares/api.js: throw on missing CSRF secret in production
Copy link
Copy Markdown
Collaborator

@runleveldev runleveldev left a comment

Choose a reason for hiding this comment

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

Theres more but Im hitting limits of GH API. This PR is so big I had to review from the CLI

as: 'externalDomains',
}],
});
if (!site) return res.status(404).send('Site not found');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This line breaks the bootstrapping assumptions. The nginx template is designed to have a "no site" fallback that allows the administrator to access the API to configure the first site, without that bootstrapping would require plaintext HTTP access, breaking security requirements for registering the first user

]
},
"scripts": {
"dev": "nodemon server.js",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Running the server in dev should also run the client in dev, preferably just watching the directory and rebuilding the dist files as needed to prevent the extra http hop that proxying to the dev port from the api server would cause.

@@ -0,0 +1,34 @@
{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This package needs to be installed and built in the Makefile in the project root so that the Manager docker container image is setup correctly on first boot. Administrators should not be expected to need to build the client before the UI is available


return (
<>
<SidebarHeader className="h-16 px-4 py-0">
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This loses the previous UI's Site selector (modeled after Portainer) which displays the correct "Containers" (for all users) and "Nodes" (for admins) links depeneding on the last active site. This make navigating the UI a challenge once in a site.


const mutation = useMutation({
mutationFn: (values: FormData) =>
api.post<{ imported: number }>(`/api/v1/sites/${siteId}/nodes/import-proxmox`, values),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This route is not defined, it appears it should be /import not /import-proxmox

const { isCollapsed, isMobileViewport } = useSidebar();
const isAdmin = !!session?.isAdmin;
const mfaAdminUrl =
isAdmin && session?.pushNotificationUrl ? `${session.pushNotificationUrl}/admin` : null;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ignore that previous comment on this item. The Sidebar setting is there, but its underneath "Sites" rather than its previous location underneath "Settings"

Comment on lines +73 to +75
<code className="rounded bg-(--color-surface-2,#f5f5f5) px-2 py-1 font-mono text-sm break-all">
{created.key}
</code>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This text box is unreadable in darkmode since the background is still white but the text is also white

Comment on lines +153 to +155
<TableCell className="font-mono text-xs">
{c.sshHost && c.sshPort ? `${c.sshHost}:${c.sshPort}` : '—'}
</TableCell>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This loses our Open in VSCode (vscode-remote://) and Open in Terminal (ssh://) links

};
}, []);

function startPolling(id: string) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Again not sure why but about 4/5 times the redirect to the next screen fails and sends me back to the login screen instead of on to the login screen. Editing the URL (not just refresshing) will get me the the next screen so it seems auth is working just not the redirect.

Comment on lines +66 to +68
if (process.env.NODE_ENV === 'production') {
throw new ApiError(404, 'not_found', 'Not found');
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I prefer how this was handled previously, where the route didn't even exist if we were in NODE_ENV production. This "single-button" sign on makes be nervous that itll leak into prod.

// --- Mount Routers ---
const loginRouter = require('./routers/login');
const registerRouter = require('./routers/register');
const verifyRouter = require('./routers/verify');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We still need the /verify route. It's used in the nginx template to enable HTTP Proxy Auth. The current build of this is returning 200 for all requests to /verify effectively disabling it for all containers that are relying on it.

<TableCell>
<Badge variant={statusVariant(c.status)}>{c.status}</Badge>
</TableCell>
<TableCell>{c.nodeName || '—'}</TableCell>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is supposed to have a link to the {node.apiUrl}/ in a new tab.

Comment on lines +381 to +442
<Select
label="Type"
value={svc.type}
onValueChange={(v) =>
setValue(
`services.${idx}.type`,
v as FormData['services'][number]['type'],
)
}
options={SERVICE_TYPES}
/>
<Input
label="Internal port"
type="number"
inputMode="numeric"
{...register(`services.${idx}.internalPort`)}
/>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => {
if (svc.id) setValue(`services.${idx}.deleted`, true);
else services.remove(idx);
}}
aria-label="Remove service"
>
<Trash2 className="size-4" />
</Button>
</div>
{(svc.type === 'http' || svc.type === 'https') && (
<div className="grid gap-3 sm:grid-cols-2">
<Input
label="External hostname"
placeholder="app"
{...register(`services.${idx}.externalHostname`)}
/>
<Select
label="External domain"
value={svc.externalDomainId || ''}
onValueChange={(v) =>
setValue(`services.${idx}.externalDomainId`, v)
}
options={domainOptions}
/>
<Switch
label="Require authentication"
checked={!!svc.authRequired}
onCheckedChange={(c) =>
setValue(`services.${idx}.authRequired`, c)
}
/>
</div>
)}
{svc.type === 'srv' && (
<Input
label="DNS name"
placeholder="_service._tcp.example"
{...register(`services.${idx}.dnsName`)}
/>
)}
</div>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The API only supports changing the "Require Auth" flag for existing services. The old UI had an affordance for this by disabling the fields (except for "Delete") and changes had to be made by deleting and adding a new service. If we can support modifying services with this model that's great but the API needs updated to support that or the UI needs updated to indicate it's not supported (by disabling the fields for existing services).

<div className="grid gap-4 sm:grid-cols-2">
<Input
label="DHCP range"
placeholder="10.0.0.100-10.0.0.200"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is rendered as is into dnsmasq so it should use a comma as the seperator rather than the -.

/>
<Input
label="DNS forwarders"
placeholder="8.8.8.8 1.1.1.1"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Likewise, this is rendered directly into dnsmasq so it should use a comma rather than a space for the seperator.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants