Skip to content

feat: add context menu to service cards#4219

Merged
Siumauricio merged 2 commits intocanaryfrom
feat/service-cards-context-menu
Apr 14, 2026
Merged

feat: add context menu to service cards#4219
Siumauricio merged 2 commits intocanaryfrom
feat/service-cards-context-menu

Conversation

@Siumauricio
Copy link
Copy Markdown
Contributor

@Siumauricio Siumauricio commented Apr 14, 2026

Summary

  • Right-click context menu on service cards (Start, Deploy, Stop, Delete)
  • Uses shadcn/ui ContextMenu component (@radix-ui/react-context-menu)
  • Delete action shows confirmation dialog
  • LibSQL services excluded (no standard mutation endpoints)

🤖 Generated with Claude Code

Greptile Summary

This PR adds a right-click context menu to service cards on the environment page, offering Start, Deploy, Stop, and Delete actions. The implementation uses the standard shadcn/ui ContextMenu component over @radix-ui/react-context-menu and a new serviceToDelete confirmation dialog.

  • Compose delete is broken: handleServiceDelete calls the compose delete mutation with only { composeId }, but the backend schema (apiDeleteCompose) declares deleteVolumes: z.boolean() as a required field. The as any cast hides the TypeScript error. Every compose service delete triggered from this context menu will fail with a tRPC validation error at runtime. The fix is to add a "Delete volumes" checkbox to the single-delete dialog (matching the bulk-delete UX) and pass the value explicitly.

Confidence Score: 4/5

  • Not safe to merge as-is: compose service deletes via the context menu will always fail due to a missing required field.
  • There is one clear P1 defect: the compose delete mutation requires deleteVolumes: z.boolean() per the Zod schema, but handleServiceDelete omits it (masked by as any). This means every compose-service delete from the new context menu throws a tRPC validation error. All other service types (application, postgres, mysql, mariadb, redis, mongo) are unaffected since their delete schemas only need the ID.
  • apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx — specifically the handleServiceDelete function and the single-service delete dialog.

Reviews (1): Last reviewed commit: "feat: add context menu to service cards" | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Right-click on service cards to quickly Start, Deploy, Stop, or Delete
a service without navigating into it. Uses shadcn/ui ContextMenu
component built on @radix-ui/react-context-menu. Delete action shows
a confirmation dialog. LibSQL services are excluded since they lack
standard mutation endpoints.
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. enhancement New feature or request labels Apr 14, 2026
Use toast.promise for loading/success/error states and invalidate
environment query after actions complete to update service status.
Comment on lines +898 to +900
await actions.delete.mutateAsync({
[idKey]: service.id,
} as any);
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.

P1 Compose delete always fails — deleteVolumes is required

handleServiceDelete passes only { [idKey]: service.id } to every mutation. For compose services the backend schema is apiDeleteCompose = z.object({ composeId: z.string().min(1), deleteVolumes: z.boolean() })deleteVolumes is required (no .optional()), so the tRPC input validation will reject the call and the delete will always error. The as any cast hides the TypeScript mismatch.

The single-service delete dialog also has no "Delete volumes" checkbox, so this can't be inferred from user input. Fix by adding the checkbox to the dialog (mirroring the bulk-delete UX) and forwarding the value:

await actions.delete.mutateAsync(
  service.type === "compose"
    ? { composeId: service.id, deleteVolumes: deleteVolumes }
    : ({ [idKey]: service.id } as any),
);

Comment on lines +1700 to +1703
<ContextMenuItem
className="flex items-center gap-2 text-orange-500 focus:text-orange-500"
onClick={() =>
handleServiceAction(service, "stop")
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.

P2 LibSQL cards suppress browser context menu without showing custom one

When service.type === "libsql", no ContextMenuContent is rendered but the ContextMenuTrigger still intercepts the contextmenu event, preventing the browser's native context menu from appearing. Users right-clicking a LibSQL card will silently get nothing. Consider skipping the ContextMenu wrapper entirely for libsql services so the native browser context menu remains accessible.

@Siumauricio Siumauricio merged commit d234558 into canary Apr 14, 2026
4 checks passed
@Siumauricio Siumauricio deleted the feat/service-cards-context-menu branch April 14, 2026 02:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant