Skip to content

[4.x] QoL & Polish#1145

Merged
RichardAnderson merged 2 commits into
vitodeploy:4.xfrom
RichardAnderson:fix/qol-and-polish
Jun 5, 2026
Merged

[4.x] QoL & Polish#1145
RichardAnderson merged 2 commits into
vitodeploy:4.xfrom
RichardAnderson:fix/qol-and-polish

Conversation

@RichardAnderson

@RichardAnderson RichardAnderson commented Jun 5, 2026

Copy link
Copy Markdown
Member
  • Resolved an issue with Scripts rendering as a ServerPage and not top level
  • Introduced a new BreadcrumbHeader component for consistent breadcrumb navigation with a back button, and updated pages such as commands/show.tsx and domains/show.tsx to use it.
  • Removed the old Breadcrumbs component and refactored layout components (app/layout.tsx, settings/layout.tsx, admin/layout.tsx) to no longer require or pass breadcrumb props, simplifying their interfaces.
  • Improved the design of the GitHub App integration page by using CardHeader, CardTitle, and CardDescription for better structure, and added separators between installation rows for clarity.
  • Added a hasHandler() method to the Service model and updated log retrieval logic to skip services without a handler, making service log handling more robust.
  • Standardized SSH user retrieval by replacing calls to sshLoginUsers() with getSshUsers() in resources and controllers, ensuring consistency in how SSH users are fetched and exposed.
  • Consolidated several table like controls to be consistent with the backgrounds of tables to improve user experience.
  • Added various docs buttons to be consistent with other areas
  • Updated vite configuration for v8 to remove npm run dev errors and warnings

Summary by CodeRabbit

  • New Features

    • Added breadcrumb navigation to command history, domain details, workflow runs, and scripts pages for better navigation clarity.
    • Added documentation links to site tooling and statistics pages.
  • Bug Fixes

    • Services without configured handlers are now properly excluded from service logs.
    • SSH user selection now retrieves data from the correct source.
  • UI/Style

    • Improved card styling consistency across settings, features, and dashboard pages.
    • Refactored table displays for monitoring details and statistics to use consistent formatting.
    • Enhanced page header layouts with improved breadcrumb support.
  • Tests

    • Added test coverage for service handler validation.
  • Chores

    • Optimized dependency bundling for improved performance.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors breadcrumb navigation by replacing a shared Breadcrumbs component with a new BreadcrumbHeader component rendered per-page, refactors SSH users to a top-level Inertia prop, adds service handler validation, standardizes Card styling across the application with overflow-hidden and bg-background classes, and adopts DataTable for metrics rendering.

Changes

Breadcrumb Navigation and Layout Refactoring

Layer / File(s) Summary
Service handler validation and logs filtering
app/Models/Service.php, app/Actions/Server/GetServiceLogs.php, tests/Feature/ServiceLogsTest.php
Adds Service::hasHandler() method and guards GetServiceLogs iteration to skip services lacking handlers, preventing null handler exceptions. Feature test verifies excluded services.
SSH users data flow refactoring
app/Http/Resources/ServerResource.php, app/Http/Controllers/ConsoleController.php, resources/js/pages/servers/console.tsx
SSH users moved from nested server.ssh_users to top-level Inertia prop. ServerResource calls getSshUsers(), ConsoleController populates via sshLoginUsers(), console page reads from new location.
Breadcrumb component and layout contract refactoring
resources/js/components/breadcrumb-header.tsx, resources/js/components/breadcrumbs.tsx (removed), resources/js/layouts/app/layout.tsx, resources/js/layouts/admin/layout.tsx, resources/js/layouts/settings/layout.tsx
Old Breadcrumbs component removed; new BreadcrumbHeader added with conditional parent-crumb navigation. Layout components remove optional breadcrumbs prop, consolidating navigation into individual pages.
Commands show page breadcrumb integration
resources/js/pages/commands/show.tsx
Adds command prop, constructs breadcrumbs from Commands + current command, renders BreadcrumbHeader, updates heading to "History of {command}".
Domains show page breadcrumb header
resources/js/pages/domains/show.tsx
Builds breadcrumbs from domain hierarchy, restructures header using HeaderContainer and BreadcrumbHeader while preserving sync control and record actions.
Scripts pages layout and scope refactoring
resources/js/pages/scripts/index.tsx, resources/js/pages/scripts/show.tsx
Index page switches from ServerLayout to Layout, removes server/site from props, adds Docs button. Show page adds script prop, breadcrumbs, and BreadcrumbHeader.
Workflow runs pages breadcrumb navigation
resources/js/pages/workflow-runs/index.tsx, resources/js/pages/workflow-runs/show.tsx
Replaces breadcrumbs Layout prop with BreadcrumbHeader inside HeaderContainer. Show page updates breadcrumb titles to "History of {workflow}" and repositions status badge.
Card styling standardization
resources/js/pages/server-features/index.tsx, resources/js/pages/plugins/index.tsx, resources/js/pages/server-logs/services.tsx, resources/js/pages/server-settings/index.tsx, resources/js/pages/site-features/index.tsx, resources/js/pages/site-settings/index.tsx
Adds overflow-hidden to Card and bg-background to CardContent across multiple pages for consistent visual appearance.
Data table refactoring
resources/js/pages/monitoring/components/memory-disk-details.tsx, resources/js/pages/sites/stats.tsx
Memory-disk metrics and stats pages switch from card-based manual table rendering to DataTable with ColumnDef configurations.
GitHub App UI component restructuring
resources/js/pages/github-app/index.tsx
Connected installations card refactored with CardHeader/CardContent and Separator-delimited items. ConfiguredCard header restructured from CardRow to CardHeader with CardTitle/CardDescription.
Miscellaneous refinements and config
app/Http/Controllers/CronJobController.php, resources/js/pages/plugins/components/official.tsx, resources/js/pages/security/index.tsx, resources/js/pages/site-tooling/components/tooling-table.tsx, resources/js/pages/site-tooling/index.tsx, vite.config.ts
CronJob adds sites collection; plugins separator styling updated; security page reformatted; site-tooling and stats add Docs buttons; tooling-table replaces Card with div; Vite adds recharts pre-bundling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • vitodeploy/vito#1143: Both PRs modify SSH user serialization in ServerResource, overlapping on the same data transformation path.

Suggested reviewers

  • saeedvaziry

Poem

🐰 A breadcrumb trail once scattered through the app,
Now gathered in each page—a tidy map!
SSH users float free, cards stand aligned,
With overflow and background so refined.
DataTables chart the metrics with grace,
As navigation finds its perfect place.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title '[4.x] QoL & Polish' is vague and generic, using non-descriptive terms that don't convey specific information about the substantial changes in this pull request. Consider using a more specific title that highlights the main changes, such as 'Refactor breadcrumb navigation and add BreadcrumbHeader component' or split into multiple focused PRs.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (4)
tests/Feature/ServiceLogsTest.php (1)

54-58: ⚡ Quick win

Make the new test deterministic by setting service status explicitly.

This test currently relies on factory defaults to include the new service in the READY-only query. Set status to ServiceStatus::READY in the factory payload so the test always exercises the hasHandler() skip path directly.

Proposed change
 use App\Facades\SSH;
+use App\Enums\ServiceStatus;
 use App\Models\Service;
@@
         Service::factory()->create([
             'server_id' => $this->server->id,
             'type' => 'vpn',
             'name' => 'wireguard',
+            'status' => ServiceStatus::READY,
         ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/ServiceLogsTest.php` around lines 54 - 58, The test is
non-deterministic because the Service factory relies on defaults; update the
Service::factory()->create call to explicitly set the service status to
ServiceStatus::READY (e.g., include 'status' => ServiceStatus::READY in the
payload) so the created record will always match the READY-only query and
exercise the hasHandler() skip path; ensure the ServiceStatus symbol is imported
or fully qualified in the test file.
resources/js/pages/monitoring/components/memory-disk-details.tsx (1)

35-49: 💤 Low value

Consider memoizing the column definition.

The columns array is recreated on every render. For small datasets this has negligible impact, but you can optimize by moving the column factory outside or wrapping in useMemo:

♻️ Optional optimization
+const createDetailsColumns = (title: string): ColumnDef<DetailRow>[] => [
+  {
+    accessorKey: 'label',
+    header: title,
+  },
+  {
+    accessorKey: 'value',
+    header: '',
+    cell: ({ row }) => <div className="text-right">{row.original.value}</div>,
+  },
+];
+
 function DetailsTable({ title, rows }: { title: string; rows: DetailRow[] }) {
-  const columns: ColumnDef<DetailRow>[] = [
-    {
-      accessorKey: 'label',
-      header: title,
-    },
-    {
-      accessorKey: 'value',
-      header: '',
-      cell: ({ row }) => <div className="text-right">{row.original.value}</div>,
-    },
-  ];
+  const columns = useMemo(() => createDetailsColumns(title), [title]);

   return <DataTable columns={columns} data={rows} />;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/js/pages/monitoring/components/memory-disk-details.tsx` around
lines 35 - 49, The columns array in DetailsTable is recreated on every render;
wrap the columns definition in React.useMemo (or move the factory outside the
component) so it's stable across renders — e.g., in the DetailsTable function
memoize the columns used by DataTable with useMemo and include the proper
dependency(s) (at minimum the title prop) to avoid unnecessary re-creation;
reference the columns variable and the DetailsTable component and ensure the
cell renderer remains unchanged.
resources/js/pages/sites/stats.tsx (2)

326-344: 💤 Low value

Consider memoizing or extracting the column definition.

The columns array is recreated on every render. While the performance impact is negligible for these small datasets, you can optimize by extracting the definition:

♻️ Optional optimization
+const panelColumns: ColumnDef<SiteStatsPanelRow>[] = [
+  {
+    accessorKey: 'name',
+    header: 'Page',
+    cell: ({ row }) => <div className="max-w-[280px] truncate">{row.original.name}</div>,
+  },
+  {
+    accessorKey: 'hits',
+    header: () => <div className="text-right">Hits</div>,
+    cell: ({ row }) => <div className="text-right">{row.original.hits.toLocaleString()}</div>,
+  },
+  {
+    accessorKey: 'visitors',
+    header: () => <div className="text-right">Visitors</div>,
+    cell: ({ row }) => <div className="text-right">{row.original.visitors.toLocaleString()}</div>,
+  },
+];
+
+function PanelCard({ title, rows }: { title: string; rows: SiteStatsPanelRow[] }) {
+  const columns = useMemo(
+    (): ColumnDef<SiteStatsPanelRow>[] => [
+      { ...panelColumns[0], header: title },
+      panelColumns[1],
+      panelColumns[2],
+    ],
+    [title],
+  );
-  const columns: ColumnDef<SiteStatsPanelRow>[] = [
-    {
-      accessorKey: 'name',
-      header: title,
-      cell: ({ row }) => <div className="max-w-[280px] truncate">{row.original.name}</div>,
-    },
-    {
-      accessorKey: 'hits',
-      header: () => <div className="text-right">Hits</div>,
-      cell: ({ row }) => <div className="text-right">{row.original.hits.toLocaleString()}</div>,
-    },
-    {
-      accessorKey: 'visitors',
-      header: () => <div className="text-right">Visitors</div>,
-      cell: ({ row }) => <div className="text-right">{row.original.visitors.toLocaleString()}</div>,
-    },
-  ];

   return <DataTable columns={columns} data={rows} />;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/js/pages/sites/stats.tsx` around lines 326 - 344, The columns array
(ColumnDef<SiteStatsPanelRow>[] named columns) is recreated on every render
which can be avoided; wrap the columns definition in a useMemo (or extract it
outside the component) so it is only recomputed when its dependencies change
(e.g., title), e.g., memoize the columns used by DataTable to improve render
stability and avoid re-creating cell/header functions on each render; ensure
useMemo is imported and include title (and any other props used in the column
definitions) in the dependency array.

348-360: 💤 Low value

Consider extracting the column definition outside the component.

The columns array is recreated on every render. For a minor performance improvement, extract it as a constant:

♻️ Optional optimization
+const statusCodeColumns: ColumnDef<SiteStatsStatusCode>[] = [
+  {
+    accessorKey: 'name',
+    header: 'Status code',
+  },
+  {
+    accessorKey: 'hits',
+    header: () => <div className="text-right">Hits</div>,
+    cell: ({ row }) => <div className="text-right">{row.original.hits.toLocaleString()}</div>,
+  },
+];
+
 function StatusCodesCard({ rows }: { rows: SiteStatsStatusCode[] }) {
-  const columns: ColumnDef<SiteStatsStatusCode>[] = [
-    {
-      accessorKey: 'name',
-      header: 'Status code',
-    },
-    {
-      accessorKey: 'hits',
-      header: () => <div className="text-right">Hits</div>,
-      cell: ({ row }) => <div className="text-right">{row.original.hits.toLocaleString()}</div>,
-    },
-  ];

-  return <DataTable columns={columns} data={rows} />;
+  return <DataTable columns={statusCodeColumns} data={rows} />;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/js/pages/sites/stats.tsx` around lines 348 - 360, The columns array
(const columns: ColumnDef<SiteStatsStatusCode>[]) is being recreated on every
render; move that definition out of the component and into a module-level
constant (e.g., COLUMNS or SITE_STATS_COLUMNS) typed as
ColumnDef<SiteStatsStatusCode>[] and then reference it in the component when
rendering <DataTable columns={COLUMNS} data={rows} />; ensure any JSX
header/cell callbacks remain valid at module scope (or wrap dynamic parts with
useMemo if they depend on props/state).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Http/Resources/ServerResource.php`:
- Line 25: ServerResource is exposing unfiltered SSH users by calling
Server::getSshUsers() (which always includes root); change
ServerResource::ssh_users to call Server::sshLoginUsers() (or a shared helper
that applies the same root-login gating as sshLoginUsers()) so the
Inertia-shared page.props.server no longer exposes root when root login is
disabled. Also update backend validation and actions that currently use
$server->getSshUsers() — specifically replace Rule::in($server->getSshUsers())
in RunCommand and the default/assignment of $users in ExecuteScript to use
$server->sshLoginUsers() (or the same shared helper) so input validation and
script execution respect the root-login feature flag.

In `@resources/js/pages/site-tooling/index.tsx`:
- Around line 66-71: The anchor element wrapping the Button (the external link
to "https://vitodeploy.com/docs/sites/tooling") uses target="_blank" but lacks
the rel attribute; update that <a> element to include rel="noopener noreferrer"
so the external page cannot access window.opener and to follow security best
practices while leaving the existing Button and BookOpenIcon usage unchanged.

In `@resources/js/pages/sites/stats.tsx`:
- Around line 303-312: In DocsButton, avoid nesting interactive elements by
rendering the Button as the anchor using the Button's asChild prop (replace the
outer <a> + inner <Button> with a single Button asChild that contains the
BookOpenIcon and label), keep target="_blank" on the anchor element produced and
add rel="noopener noreferrer" for security; update the DocsButton function to
return a Button rendered as the link (using asChild) with the external link
attributes and existing content.

In `@resources/js/pages/workflow-runs/show.tsx`:
- Line 42: Breadcrumb title uses `History of ${page.props.workflow.name}` but
the <Head> title and the rendered <Heading> still use the old `"Workflow
[${workflow.name}]"`; update the <Head> element's title and the Heading's title
to match the breadcrumb (use `History of ${page.props.workflow.name}`) so all
three display the same string, locating the strings in the component that
references page.props.workflow and the Heading component rendering the page
title.

---

Nitpick comments:
In `@resources/js/pages/monitoring/components/memory-disk-details.tsx`:
- Around line 35-49: The columns array in DetailsTable is recreated on every
render; wrap the columns definition in React.useMemo (or move the factory
outside the component) so it's stable across renders — e.g., in the DetailsTable
function memoize the columns used by DataTable with useMemo and include the
proper dependency(s) (at minimum the title prop) to avoid unnecessary
re-creation; reference the columns variable and the DetailsTable component and
ensure the cell renderer remains unchanged.

In `@resources/js/pages/sites/stats.tsx`:
- Around line 326-344: The columns array (ColumnDef<SiteStatsPanelRow>[] named
columns) is recreated on every render which can be avoided; wrap the columns
definition in a useMemo (or extract it outside the component) so it is only
recomputed when its dependencies change (e.g., title), e.g., memoize the columns
used by DataTable to improve render stability and avoid re-creating cell/header
functions on each render; ensure useMemo is imported and include title (and any
other props used in the column definitions) in the dependency array.
- Around line 348-360: The columns array (const columns:
ColumnDef<SiteStatsStatusCode>[]) is being recreated on every render; move that
definition out of the component and into a module-level constant (e.g., COLUMNS
or SITE_STATS_COLUMNS) typed as ColumnDef<SiteStatsStatusCode>[] and then
reference it in the component when rendering <DataTable columns={COLUMNS}
data={rows} />; ensure any JSX header/cell callbacks remain valid at module
scope (or wrap dynamic parts with useMemo if they depend on props/state).

In `@tests/Feature/ServiceLogsTest.php`:
- Around line 54-58: The test is non-deterministic because the Service factory
relies on defaults; update the Service::factory()->create call to explicitly set
the service status to ServiceStatus::READY (e.g., include 'status' =>
ServiceStatus::READY in the payload) so the created record will always match the
READY-only query and exercise the hasHandler() skip path; ensure the
ServiceStatus symbol is imported or fully qualified in the test file.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0b0be647-25aa-4e5c-b669-8c0f878857e6

📥 Commits

Reviewing files that changed from the base of the PR and between 3b8f2f2 and 46043f1.

📒 Files selected for processing (32)
  • app/Actions/Server/GetServiceLogs.php
  • app/Http/Controllers/ConsoleController.php
  • app/Http/Controllers/CronJobController.php
  • app/Http/Resources/ServerResource.php
  • app/Models/Service.php
  • resources/js/components/breadcrumb-header.tsx
  • resources/js/components/breadcrumbs.tsx
  • resources/js/layouts/admin/layout.tsx
  • resources/js/layouts/app/layout.tsx
  • resources/js/layouts/settings/layout.tsx
  • resources/js/pages/commands/show.tsx
  • resources/js/pages/domains/show.tsx
  • resources/js/pages/github-app/index.tsx
  • resources/js/pages/monitoring/components/memory-disk-details.tsx
  • resources/js/pages/plugins/components/official.tsx
  • resources/js/pages/plugins/index.tsx
  • resources/js/pages/scripts/index.tsx
  • resources/js/pages/scripts/show.tsx
  • resources/js/pages/security/index.tsx
  • resources/js/pages/server-features/index.tsx
  • resources/js/pages/server-logs/services.tsx
  • resources/js/pages/server-settings/index.tsx
  • resources/js/pages/servers/console.tsx
  • resources/js/pages/site-features/index.tsx
  • resources/js/pages/site-settings/index.tsx
  • resources/js/pages/site-tooling/components/tooling-table.tsx
  • resources/js/pages/site-tooling/index.tsx
  • resources/js/pages/sites/stats.tsx
  • resources/js/pages/workflow-runs/index.tsx
  • resources/js/pages/workflow-runs/show.tsx
  • tests/Feature/ServiceLogsTest.php
  • vite.config.ts
💤 Files with no reviewable changes (2)
  • resources/js/components/breadcrumbs.tsx
  • app/Http/Controllers/CronJobController.php

Comment thread app/Http/Resources/ServerResource.php
Comment thread resources/js/pages/site-tooling/index.tsx
Comment thread resources/js/pages/sites/stats.tsx
Comment thread resources/js/pages/workflow-runs/show.tsx
@RichardAnderson RichardAnderson merged commit ac4cca0 into vitodeploy:4.x Jun 5, 2026
4 checks passed
@RichardAnderson RichardAnderson deleted the fix/qol-and-polish branch June 5, 2026 20:30
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.

2 participants