Skip to content

Conversation

@Divkix
Copy link
Contributor

@Divkix Divkix commented Dec 15, 2025

Summary

  • Add view toggle button (LayoutGrid/LayoutList icons) to switch between Grid and Table views
  • Implement Table view with sortable columns using @tanstack/react-table
  • Add search/filter by hostname functionality
  • Add column visibility dropdown
  • Add pagination controls (Previous/Next)
  • Persist view preference in localStorage
  • Preserve all existing Grid view functionality as default

Table View Columns

Column Description
Host Sortable, clickable external link to domain
Service Service name badge (for compose)
Path URL path
Port Container port
Protocol HTTP/HTTPS badge
Certificate Certificate type badge (none/letsencrypt/custom)
DNS Status Interactive validation badge (click to validate)
Actions Dropdown with DNS Helper, Edit, Delete

Files Changed

  • Created: apps/dokploy/components/dashboard/application/domains/columns.tsx
  • Modified: apps/dokploy/components/dashboard/application/domains/show-domains.tsx

Test Plan

  • Verify Grid view works as before (default)
  • Verify Table view displays all 8 columns correctly
  • Verify sorting works on Host column
  • Verify filter by hostname works
  • Verify column visibility toggle works
  • Verify pagination works (Previous/Next)
  • Verify DNS validation clickable badge works in table view
  • Verify Edit/Delete actions work via Actions dropdown
  • Verify view preference persists in localStorage
  • Verify table has horizontal scroll on mobile devices

Fixes #3158

Add a view toggle button that allows switching between Grid view
(current default) and Table view for better data density and
comparison of domain configurations.

Table view features:
- Sortable Host column with external link
- Service, Path, Port, Protocol, Certificate columns
- DNS Status column with interactive validation badge
- Actions column with DNS helper, Edit, and Delete
- Search/filter by hostname
- Column visibility dropdown
- Pagination controls (Previous/Next)
- View preference persisted in localStorage

Grid view preserved as default for backward compatibility.

Fixes Dokploy#3158
@Divkix Divkix requested a review from Siumauricio as a code owner December 15, 2025 06:32
Copilot AI review requested due to automatic review settings December 15, 2025 06:32
Copy link

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 adds a table view option to the domains page alongside the existing grid view, with view preference persistence via localStorage. Users can toggle between views using LayoutGrid/LayoutList icons.

Key changes:

  • New table view with sortable Host column, filtering, pagination, and column visibility controls
  • Added columns.tsx file with column definitions using @tanstack/react-table
  • View toggle button with localStorage persistence for user preference
  • DNS validation badge clickable in table view with actions dropdown for Edit/Delete

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
apps/dokploy/components/dashboard/application/domains/columns.tsx New file defining 8 table columns (Host, Service, Path, Port, Protocol, Certificate, DNS Status, Actions) with interactive elements and dropdown menu for actions
apps/dokploy/components/dashboard/application/domains/show-domains.tsx Modified to add view toggle button, table implementation with filters/pagination, localStorage hook integration, and refactored delete logic into reusable handler function

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

https: domain.https,
path: domain.path || undefined,
}}
serverIp={serverIp}
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The DnsHelperModal component renders a Dialog with a DialogTrigger that contains a Button. When placed directly inside DropdownMenuContent, this creates nested interactive elements which can cause accessibility issues and unpredictable behavior. The button inside the DialogTrigger may not function properly within the dropdown menu context, as it conflicts with the dropdown's own focus management and click-away behavior.

Consider wrapping the DnsHelperModal in a DropdownMenuItem or restructuring it to work properly within a dropdown menu context.

Suggested change
serverIp={serverIp}
serverIp={serverIp}
trigger={
<Button
variant="ghost"
className="w-full justify-start font-normal h-9 px-2"
>
<Server className="size-4 mr-2" />
DNS Helper
</Button>
}

Copilot uses AI. Check for mistakes.
header: "Port",
cell: ({ row }) => {
const port = row.getValue("port") as number | null;
return <span className="text-sm">{port ?? 3000}</span>;
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The port value falls back to 3000 when null, but this default value might not accurately represent the actual port configuration. In the grid view (line 380), the port is displayed directly without a fallback, showing the actual port value. This inconsistency could mislead users about the actual port being used.

Consider displaying the port as-is (potentially showing null/empty state) or ensure the fallback matches the actual default behavior in the system.

Suggested change
return <span className="text-sm">{port ?? 3000}</span>;
return <span className="text-sm">{port != null ? port : "-"}</span>;

Copilot uses AI. Check for mistakes.
Comment on lines +188 to +196
const columns = createColumns({
id,
type,
validationStates,
handleValidateDomain,
handleDeleteDomain,
isRemoving,
serverIp,
});
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The columns are being recreated on every render because createColumns is called directly in the component body. Since validationStates can change frequently (during DNS validation), and handleValidateDomain, handleDeleteDomain, and isRemoving are recreated on each render, this causes the entire column definition array to be recreated unnecessarily, potentially impacting performance.

Consider wrapping createColumns call with useMemo and including only the necessary dependencies, or memoize the handler functions with useCallback.

Copilot uses AI. Check for mistakes.
column.toggleVisibility(!!value)
}
>
{column.id}
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The column visibility dropdown displays column IDs directly (line 522: {column.id}), which will show technical names like "serviceName", "certificateType", "dnsStatus", and "https" instead of user-friendly labels like "Service Name", "Certificate Type", "DNS Status", and "Protocol". This creates a poor user experience as users need to map technical identifiers to the actual column headers they see in the table.

Consider creating a mapping function or using the column's header property to display proper labels in the visibility dropdown.

Suggested change
{column.id}
{flexRender(column.columnDef.header, column.getContext())}

Copilot uses AI. Check for mistakes.
- Fix DnsHelperModal nested button issue by adding `asChild` to
  DialogTrigger and accepting `children` prop for custom triggers
- Fix port display inconsistency: show "-" when null in both grid
  and table views instead of fallback to 3000
- Add column label mapping for visibility dropdown to show proper
  labels (Service, Protocol, Certificate, DNS Status) instead of
  technical IDs (serviceName, https, certificateType, dnsStatus)
@Divkix
Copy link
Contributor Author

Divkix commented Dec 15, 2025

Addressed Copilot Review Comments

Comment Status
1. DnsHelperModal nested in dropdown ✅ Fixed
2. Port fallback inconsistency ✅ Fixed
3. Columns recreated every render ⏭️ Intentionally skipped
4. Column ID vs friendly label ✅ Fixed

Why Comment 3 (useMemo optimization) was skipped:

  1. Columns must update when validationStates changes - The DNS Status cell renders validation results directly from that state. Memoizing with validationStates as a dependency means columns still get recreated on every validation anyway.

  2. Non-trivial refactor required - Proper optimization would need:

    • useCallback for handlers
    • useMemo for columns
    • Moving validation state lookup out of column definitions into cell render via context/props
    • ~30 lines of boilerplate for marginal gain
  3. React Table handles this efficiently - The library doesn't re-render all cells just because the columns array reference changed.

  4. Scale doesn't justify it - A domains list is typically <50 items. Recreating ~8 column definition objects per render costs microseconds.

  5. Premature optimization - No evidence of actual performance issues. Adding useMemo/useCallback everywhere adds cognitive overhead and can introduce stale closure bugs without measurable benefit.

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.

Add Grid/Table view toggle for Domains page with detailed table columns

1 participant