Skip to content

feat(widgets): persist Beszel and Docker table layouts - #6490

Open
ajnart wants to merge 10 commits into
devfrom
feat/beszel-table-downloads-ux
Open

feat(widgets): persist Beszel and Docker table layouts#6490
ajnart wants to merge 10 commits into
devfrom
feat/beszel-table-downloads-ux

Conversation

@ajnart

@ajnart ajnart commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary

  • match the Beszel and Docker tables to the Downloads widget's draggable, resizable, persistent column UX
  • add Docker container action menus on the actions button and row right-click, including logs, lifecycle actions, guarded removal, and Add to Homarr
  • preserve existing visibility, filtering, sorting, modal, footer, and edit-mode behavior
  • document the interactions and handle malformed saved layouts safely

Validation

  • pnpm exec vitest run packages/widgets/src/beszel-system-table/component.spec.ts packages/widgets/src/docker/component.spec.ts packages/widgets/src/test/translation.spec.ts --coverage.enabled=false (558 tests passed)
  • pnpm turbo typecheck --filter=@homarr/widgets --filter=@homarr/docs
  • pnpm turbo build --filter=@homarr/docs
  • pnpm --filter @homarr/widgets lint
  • pnpm --filter @homarr/docs lint

Summary by CodeRabbit

  • New Features

    • Customize visible columns in the Docker containers widget.
    • Drag and resize table columns; layouts are saved automatically per widget.
    • Sort system and Docker tables, with system rows opening detailed statistics.
    • Access Docker actions through row menus or right-click menus.
    • View disk mount details by hovering over system table entries.
  • Improvements

    • Docker container removal now requires a second confirmation.
    • Tables provide clearer empty states and save-error notifications.
    • Table layouts remain consistent across supported widgets and sessions.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Widget table layout and interaction updates

Layer / File(s) Summary
Shared table layout persistence
packages/widgets/src/common/use-persisted-table-layout.ts, packages/widgets/src/common/homarr-data-table.*, packages/widgets/src/*/component.spec.ts
Adds validated, debounced column layout persistence and a shared data-table wrapper with common styling and edit-mode behavior.
Docker table and container actions
packages/widgets/src/docker/*, apps/docs/docs/widgets/docker-containers/*, packages/translation/src/lang/en.json
Adds selectable columns, persisted layouts, controlled sorting, lifecycle menus, context-menu actions, and confirmed removal.
Beszel table layout integration
packages/widgets/src/beszel-system-table/*, packages/translation/src/lang/en.json, apps/docs/docs/widgets/beszel-system-table/index.mdx
Adds persisted layouts, permission-gated saving, fixed column widths, synchronized sorting, and interaction documentation.
Downloads table integration
packages/widgets/src/downloads/*, apps/docs/docs/widgets/downloads/index.mdx
Migrates the downloads table to shared layout persistence and rendering, with remote saving restricted by board permissions.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant WidgetTable
  participant usePersistedTableLayout
  participant WidgetOptionsMutation
  User->>WidgetTable: Reorders or resizes columns
  WidgetTable->>usePersistedTableLayout: Reports layout change
  usePersistedTableLayout->>WidgetOptionsMutation: Saves layout after debounce
  WidgetOptionsMutation-->>WidgetTable: Returns save success or failure
Loading

Possibly related PRs

Suggested reviewers: manuel-rw

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: persistent table layouts for the Beszel and Docker widgets.
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.
Yagni / Over-Engineering ✅ Passed The diff implements the requested table UX and Docker menus, shares persistence and table wrappers across three widgets, adds malformed-layout tests, and adds no dependency.
Docs Are Up To Date ✅ Passed The Beszel, Docker, and Downloads docs pages exist and document the new table interactions, persistence, Docker actions, and widget configuration shown in the source.
✨ 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/beszel-table-downloads-ux

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.

ajnart commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

Triggered by Codex (OpenAI) to obtain the requested preview review for this draft PR.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

@ajnart I’ll review the draft PR, focusing on the persisted table-layout behavior, Docker actions, and compatibility with the existing widget interactions.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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)
packages/widgets/src/docker/component.tsx (1)

364-400: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Every drag/resize step triggers a setOptions + server mutation.

columnsWidth updates while resizing produce one saveItemOptions call per change. Consider debouncing persistLayout (e.g. 300–500ms trailing) to avoid a burst of writes per interaction.

🤖 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 `@packages/widgets/src/docker/component.tsx` around lines 364 - 400, Debounce
the persistLayout call in the layout-change useEffect so rapid columnsWidth
updates during resizing, and order updates during dragging, produce only a
trailing save after roughly 300–500ms of inactivity. Preserve the existing
layout-building and change-detection logic, and ensure the debounce is cancelled
or cleaned up when dependencies change or the component unmounts.
🤖 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 `@packages/widgets/src/docker/component.tsx`:
- Around line 57-89: Extract the duplicated table-layout parsing, hydration, and
persistence logic into a shared module/hook parameterized by accessors,
store-key prefix, columns, options, and setOptions. In
packages/widgets/src/docker/component.tsx lines 57-89, move parseColumnOrder and
parseColumnWidths into the shared module; in
packages/widgets/src/beszel-system-table/component.tsx lines 92-124, import
those shared parsers and retain local re-exports only if required by specs.
Replace the effects at packages/widgets/src/beszel-system-table/component.tsx
lines 395-451 and packages/widgets/src/docker/component.tsx lines 344-400 with
the shared hook.
- Around line 149-176: Update the column definitions for the cpuUsage and
memoryUsage accessors in the container columns configuration to include
sortable: sortingEnabled, matching the existing name, state, and host columns so
their headers support sorting and display sort indicators.

---

Nitpick comments:
In `@packages/widgets/src/docker/component.tsx`:
- Around line 364-400: Debounce the persistLayout call in the layout-change
useEffect so rapid columnsWidth updates during resizing, and order updates
during dragging, produce only a trailing save after roughly 300–500ms of
inactivity. Preserve the existing layout-building and change-detection logic,
and ensure the debounce is cancelled or cleaned up when dependencies change or
the component unmounts.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6a5ed5e-70fb-4924-8778-c5e3fbcc61d8

📥 Commits

Reviewing files that changed from the base of the PR and between 49d0f1a and b75f983.

📒 Files selected for processing (11)
  • apps/docs/docs/widgets/beszel-system-table/index.mdx
  • apps/docs/docs/widgets/docker-containers/index.mdx
  • apps/docs/docs/widgets/docker-containers/index.ts
  • packages/translation/src/lang/en.json
  • packages/widgets/src/beszel-system-table/component.spec.ts
  • packages/widgets/src/beszel-system-table/component.tsx
  • packages/widgets/src/beszel-system-table/index.ts
  • packages/widgets/src/docker/component.spec.ts
  • packages/widgets/src/docker/component.tsx
  • packages/widgets/src/docker/index.ts
  • packages/widgets/src/docker/styles.css

Comment thread packages/widgets/src/docker/component.tsx Outdated
Comment thread packages/widgets/src/docker/component.tsx
@ajnart
ajnart marked this pull request as ready for review July 31, 2026 03:52
@ajnart
ajnart requested a review from a team as a code owner July 31, 2026 03:52

@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

🧹 Nitpick comments (2)
packages/widgets/src/beszel-system-table/component.tsx (1)

341-341: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Narrow the columns memo dependencies to the flags it reads.

The memo depends on the whole options object. columns feeds visibleAccessors, visibleAccessorSet, and restoredOrder inside usePersistedTableLayout. If options receives a new identity on each render, those derived values change identity too, and the hydration effect in packages/widgets/src/common/use-persisted-table-layout.ts at Lines 87-104 never completes. Listing the individual flags removes that coupling.

♻️ Proposed dependency change
-  }, [options, t, size]);
+  }, [
+    options.showCpu,
+    options.showMemory,
+    options.showDisk,
+    options.showGpu,
+    options.showLoadAvg,
+    options.showNet,
+    options.showTemp,
+    options.showBattery,
+    options.showServices,
+    options.showUptime,
+    options.showAgent,
+    t,
+    size,
+  ]);
🤖 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 `@packages/widgets/src/beszel-system-table/component.tsx` at line 341, Update
the columns useMemo dependency list to remove the whole options object and
include only the individual option flags read by the columns computation. Keep t
and size, and ensure every referenced options flag remains represented so
derived layout values retain stable identity when unrelated options change.
packages/widgets/src/common/use-persisted-table-layout.ts (1)

136-146: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Handle all accessor keys in each columnsWidth entry.

columnsWidth entries are typed as { [x: string]: string | number; }, and reading only Object.keys(entry)[0] silently drops any additional accessor keys. Use Object.entries(entry) so every valid accessor preserves its width.

🤖 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 `@packages/widgets/src/common/use-persisted-table-layout.ts` around lines 136 -
146, Update the columnsWidth processing loop to iterate over every
accessor-width pair with Object.entries(entry), rather than selecting only
Object.keys(entry)[0]. Preserve the existing visible-accessor filtering and
numeric/px width validation while assigning each valid width to widthMap.
🤖 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 `@packages/widgets/src/common/use-persisted-table-layout.ts`:
- Around line 87-104: Update the hydration logic in the useEffect to determine
completion by value rather than relying on the requestAnimationFrame callback.
Compare the current table column order and visible widths with restoredOrder and
savedWidths, using value-based comparisons for both order and widths, and set
hydrated.current accordingly while preserving storeKey reset behavior.

---

Nitpick comments:
In `@packages/widgets/src/beszel-system-table/component.tsx`:
- Line 341: Update the columns useMemo dependency list to remove the whole
options object and include only the individual option flags read by the columns
computation. Keep t and size, and ensure every referenced options flag remains
represented so derived layout values retain stable identity when unrelated
options change.

In `@packages/widgets/src/common/use-persisted-table-layout.ts`:
- Around line 136-146: Update the columnsWidth processing loop to iterate over
every accessor-width pair with Object.entries(entry), rather than selecting only
Object.keys(entry)[0]. Preserve the existing visible-accessor filtering and
numeric/px width validation while assigning each valid width to widthMap.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 687ac5ed-1d9a-4dd9-b53a-0adbbbbfb5f0

📥 Commits

Reviewing files that changed from the base of the PR and between b75f983 and 6f59a55.

📒 Files selected for processing (5)
  • packages/widgets/src/beszel-system-table/component.spec.ts
  • packages/widgets/src/beszel-system-table/component.tsx
  • packages/widgets/src/common/use-persisted-table-layout.ts
  • packages/widgets/src/docker/component.spec.ts
  • packages/widgets/src/docker/component.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/widgets/src/docker/component.spec.ts
  • packages/widgets/src/beszel-system-table/component.spec.ts

Comment thread packages/widgets/src/common/use-persisted-table-layout.ts Outdated

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/widgets/src/common/use-persisted-table-layout.ts (1)

148-186: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Flush the pending layout on unmount instead of discarding it.

The unmount cleanup at lines 150-155 only clears saveTimeout.current. If the component unmounts (e.g., the widget is removed, the board is navigated away from) within the 350 ms debounce window after a column reorder or resize, pendingLayout.current is never sent to onLayoutChange and the user's last layout edit is silently lost.

Call onLayoutChange(pendingLayout.current) in the cleanup when there is a pending layout, before clearing the timeout.

🛠️ Proposed fix to flush pending layout on unmount
   useEffect(
-    () => () => {
-      window.clearTimeout(saveTimeout.current);
-    },
+    () => () => {
+      window.clearTimeout(saveTimeout.current);
+      if (Object.keys(pendingLayout.current).length > 0) {
+        onLayoutChange(pendingLayout.current);
+        pendingLayout.current = {};
+      }
+    },
     [],
   );

Note: if onLayoutChange is not stable across renders, add it to this effect's dependency array or capture it in a ref to avoid a stale closure.

🤖 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 `@packages/widgets/src/common/use-persisted-table-layout.ts` around lines 148 -
186, Update the unmount cleanup associated with pendingLayout and saveTimeout to
call onLayoutChange(pendingLayout.current) when a pending layout exists, then
clear the timeout and reset the pending layout. Ensure the cleanup uses the
current onLayoutChange callback by including it in the effect dependencies or
capturing it in a ref.
🤖 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.

Outside diff comments:
In `@packages/widgets/src/common/use-persisted-table-layout.ts`:
- Around line 148-186: Update the unmount cleanup associated with pendingLayout
and saveTimeout to call onLayoutChange(pendingLayout.current) when a pending
layout exists, then clear the timeout and reset the pending layout. Ensure the
cleanup uses the current onLayoutChange callback by including it in the effect
dependencies or capturing it in a ref.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e41d3187-94a6-4edc-badc-eb26dd296aa3

📥 Commits

Reviewing files that changed from the base of the PR and between 6f59a55 and ce75171.

📒 Files selected for processing (2)
  • packages/widgets/src/beszel-system-table/component.tsx
  • packages/widgets/src/common/use-persisted-table-layout.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/widgets/src/beszel-system-table/component.tsx

@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.

🧹 Nitpick comments (1)
packages/widgets/src/docker/component.tsx (1)

238-251: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the permission-gated layout-persist callback into a shared helper. Both widgets duplicate the identical setOptions + permission-gated saveItemOptions glue that feeds usePersistedTableLayout's onLayoutChange. The shared hook already consolidated the parsing/hydration/debounce logic; this remaining glue should move alongside it (e.g., accept setOptions, saveItemOptions, hasChangeAccess, boardId, itemId as hook inputs, or expose a small usePersistLayoutOptions helper) so a future fix to the gating logic lands once instead of per widget.

  • packages/widgets/src/docker/component.tsx#L238-L251: replace the local persistLayout useCallback with the shared helper.
  • packages/widgets/src/downloads/component.tsx#L341-L347: replace the local persistOption useCallback with the same shared helper.
🤖 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 `@packages/widgets/src/docker/component.tsx` around lines 238 - 251, Extract
the duplicated permission-gated layout persistence logic into a shared helper
alongside usePersistedTableLayout, accepting the existing setOptions,
saveItemOptions, hasChangeAccess, boardId, and itemId inputs as needed. Replace
the local persistLayout callback in packages/widgets/src/docker/component.tsx
lines 238-251 and the local persistOption callback in
packages/widgets/src/downloads/component.tsx lines 341-347 with this helper,
preserving both widgets’ current option updates and gated saves.
🤖 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.

Nitpick comments:
In `@packages/widgets/src/docker/component.tsx`:
- Around line 238-251: Extract the duplicated permission-gated layout
persistence logic into a shared helper alongside usePersistedTableLayout,
accepting the existing setOptions, saveItemOptions, hasChangeAccess, boardId,
and itemId inputs as needed. Replace the local persistLayout callback in
packages/widgets/src/docker/component.tsx lines 238-251 and the local
persistOption callback in packages/widgets/src/downloads/component.tsx lines
341-347 with this helper, preserving both widgets’ current option updates and
gated saves.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d2927172-a74c-4547-af89-c19410be24b3

📥 Commits

Reviewing files that changed from the base of the PR and between ce75171 and e63618d.

📒 Files selected for processing (10)
  • apps/docs/docs/widgets/beszel-system-table/index.mdx
  • apps/docs/docs/widgets/docker-containers/index.mdx
  • apps/docs/docs/widgets/downloads/index.mdx
  • packages/widgets/src/beszel-system-table/component.tsx
  • packages/widgets/src/beszel-system-table/styles.css
  • packages/widgets/src/common/homarr-data-table.css
  • packages/widgets/src/common/homarr-data-table.tsx
  • packages/widgets/src/docker/component.tsx
  • packages/widgets/src/downloads/component.tsx
  • packages/widgets/src/downloads/styles.css
💤 Files with no reviewable changes (2)
  • packages/widgets/src/downloads/styles.css
  • packages/widgets/src/beszel-system-table/styles.css
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/docs/docs/widgets/docker-containers/index.mdx
  • apps/docs/docs/widgets/beszel-system-table/index.mdx
  • packages/widgets/src/beszel-system-table/component.tsx

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/widgets/src/common/use-persisted-table-layout.ts`:
- Around line 173-181: Update the pending-layout state and flush logic around
flushPendingLayout so each debounced batch retains the originating
onLayoutChange callback and storeKey. When itemId/storeKey changes, flush the
existing batch through its stored callback before replacing it, and ensure the
timeout callback uses the batch’s stored callback rather than the latest
onLayoutChangeRef.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e2057c4-ead9-4f2c-91dd-93d32091f726

📥 Commits

Reviewing files that changed from the base of the PR and between e63618d and 0dad7ca.

📒 Files selected for processing (5)
  • apps/docs/docs/widgets/docker-containers/index.mdx
  • packages/widgets/src/beszel-system-table/component.tsx
  • packages/widgets/src/common/use-persisted-table-layout.ts
  • packages/widgets/src/docker/component.tsx
  • packages/widgets/src/downloads/component.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/docs/docs/widgets/docker-containers/index.mdx
  • packages/widgets/src/docker/component.tsx
  • packages/widgets/src/beszel-system-table/component.tsx
  • packages/widgets/src/downloads/component.tsx

Comment thread packages/widgets/src/common/use-persisted-table-layout.ts Outdated
@ajnart
ajnart force-pushed the feat/beszel-table-downloads-ux branch from 0242496 to d4c66ce Compare July 31, 2026 11:56
ajnart added 2 commits July 31, 2026 20:18
# Conflicts:
#	packages/integrations/src/navidrome/test/navidrome-integration.spec.ts
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.

1 participant