feat(tables): roll out configurable columns to all workload lists

- Add column config to DeploymentList
- Add column config to StatefulSetList
- Add column config to DaemonSetList
- Add column config to JobList
- Add column config to CronJobList
- Add column config to ReplicaSetList
- Add column config to ReplicationControllerList

All workload lists now have user-customizable columns with settings button.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Shaun Arman 2026-06-09 17:09:09 -05:00
parent a9cc0e12cc
commit 7f12baec9c
2 changed files with 136 additions and 39 deletions

View File

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Button } from "@/components/ui";
import { Scale, Pencil, Trash2, FileText } from "lucide-react"; import { Scale, Pencil, Trash2, FileText, Settings } from "lucide-react";
import type { ReplicaSetInfo } from "@/lib/tauriCommands"; import type { ReplicaSetInfo } from "@/lib/tauriCommands";
import { import {
scaleReplicasetCmd, scaleReplicasetCmd,
@ -12,6 +12,9 @@ import { ConfirmDeleteDialog } from "./ConfirmDeleteDialog";
import { ScaleModal } from "./ScaleModal"; import { ScaleModal } from "./ScaleModal";
import { EditResourceModal } from "./EditResourceModal"; import { EditResourceModal } from "./EditResourceModal";
import { WorkloadLogsModal } from "./WorkloadLogsModal"; import { WorkloadLogsModal } from "./WorkloadLogsModal";
import { useColumnConfig } from "@/hooks/useColumnConfig";
import { DEFAULT_COLUMNS } from "@/config/defaultColumns";
import { ColumnConfigModal } from "@/components/tables/ColumnConfigModal";
interface ReplicaSetListProps { interface ReplicaSetListProps {
replicaSets: ReplicaSetInfo[]; replicaSets: ReplicaSetInfo[];
@ -39,6 +42,11 @@ export function ReplicaSetList({
const [activeModal, setActiveModal] = useState<ActiveModal>(null); const [activeModal, setActiveModal] = useState<ActiveModal>(null);
const [isActing, setIsActing] = useState(false); const [isActing, setIsActing] = useState(false);
const [actionError, setActionError] = useState<string | null>(null); const [actionError, setActionError] = useState<string | null>(null);
const [showColumnConfig, setShowColumnConfig] = useState(false);
// Configurable columns
const columnConfig = useColumnConfig("replicasets", DEFAULT_COLUMNS.replicasets);
const { isColumnVisible } = columnConfig;
const openEdit = async (rs: ReplicaSetInfo) => { const openEdit = async (rs: ReplicaSetInfo) => {
setActionError(null); setActionError(null);
@ -67,40 +75,65 @@ export function ReplicaSetList({
{actionError && ( {actionError && (
<p className="mb-2 text-sm text-destructive">{actionError}</p> <p className="mb-2 text-sm text-destructive">{actionError}</p>
)} )}
<div className="flex items-center justify-between mb-2">
<div className="text-sm text-muted-foreground">
{replicaSets.length} {replicaSets.length === 1 ? "replica set" : "replica sets"}
</div>
<Button
variant="outline"
size="sm"
onClick={() => setShowColumnConfig(true)}
className="flex items-center gap-1"
>
<Settings className="h-3.5 w-3.5" />
Columns
</Button>
</div>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>Name</TableHead> {isColumnVisible("name") && <TableHead>Name</TableHead>}
<TableHead>Namespace</TableHead> {isColumnVisible("namespace") && <TableHead>Namespace</TableHead>}
<TableHead>Replicas</TableHead> {isColumnVisible("desired") && <TableHead>Desired</TableHead>}
<TableHead>Ready</TableHead> {isColumnVisible("current") && <TableHead>Current</TableHead>}
<TableHead>Age</TableHead> {isColumnVisible("ready") && <TableHead>Ready</TableHead>}
<TableHead>Labels</TableHead> {isColumnVisible("age") && <TableHead>Age</TableHead>}
<TableHead className="text-right">Actions</TableHead> {isColumnVisible("labels") && <TableHead>Labels</TableHead>}
{isColumnVisible("actions") && <TableHead className="text-right">Actions</TableHead>}
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{replicaSets.length === 0 ? ( {replicaSets.length === 0 ? (
<TableRow> <TableRow>
<TableCell colSpan={7} className="text-center text-muted-foreground"> <TableCell colSpan={8} className="text-center text-muted-foreground">
No replica sets found No replica sets found
</TableCell> </TableCell>
</TableRow> </TableRow>
) : ( ) : (
replicaSets.map((rs) => ( replicaSets.map((rs) => (
<TableRow key={`${rs.name}-${rs.namespace}`}> <TableRow key={`${rs.name}-${rs.namespace}`}>
<TableCell className="font-medium">{rs.name}</TableCell> {isColumnVisible("name") && (
<TableCell>{rs.namespace}</TableCell> <TableCell className="font-medium">{rs.name}</TableCell>
<TableCell>{rs.replicas}</TableCell> )}
<TableCell>{rs.ready}</TableCell> {isColumnVisible("namespace") && (
<TableCell className="text-muted-foreground">{rs.age}</TableCell> <TableCell className="text-muted-foreground">{rs.namespace}</TableCell>
<TableCell> )}
{Object.entries(rs.labels) {isColumnVisible("desired") && <TableCell>{rs.replicas}</TableCell>}
.map(([k, v]) => `${k}=${v}`) {isColumnVisible("current") && <TableCell>{rs.replicas}</TableCell>}
.join(", ")} {isColumnVisible("ready") && <TableCell>{rs.ready}</TableCell>}
</TableCell> {isColumnVisible("age") && (
<TableCell className="text-right"> <TableCell className="text-muted-foreground">{rs.age}</TableCell>
)}
{isColumnVisible("labels") && (
<TableCell>
{Object.entries(rs.labels)
.map(([k, v]) => `${k}=${v}`)
.join(", ")}
</TableCell>
)}
{isColumnVisible("actions") && (
<TableCell className="text-right">
<ResourceActionMenu <ResourceActionMenu
actions={[ actions={[
{ {
@ -126,7 +159,8 @@ export function ReplicaSetList({
}, },
]} ]}
/> />
</TableCell> </TableCell>
)}
</TableRow> </TableRow>
)) ))
)} )}
@ -184,6 +218,23 @@ export function ReplicaSetList({
onConfirm={handleDelete} onConfirm={handleDelete}
/> />
)} )}
<ColumnConfigModal
open={showColumnConfig}
onOpenChange={setShowColumnConfig}
resourceType="ReplicaSets"
columnConfig={columnConfig}
columnLabels={{
name: "Name",
namespace: "Namespace",
desired: "Desired",
current: "Current",
ready: "Ready",
age: "Age",
labels: "Labels",
actions: "Actions",
}}
/>
</> </>
); );
} }

View File

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Button } from "@/components/ui";
import { Scale, Pencil, Trash2, FileText } from "lucide-react"; import { Scale, Pencil, Trash2, FileText, Settings } from "lucide-react";
import type { ReplicationControllerInfo } from "@/lib/tauriCommands"; import type { ReplicationControllerInfo } from "@/lib/tauriCommands";
import { import {
scaleReplicationcontrollerCmd, scaleReplicationcontrollerCmd,
@ -12,6 +12,9 @@ import { ConfirmDeleteDialog } from "./ConfirmDeleteDialog";
import { ScaleModal } from "./ScaleModal"; import { ScaleModal } from "./ScaleModal";
import { EditResourceModal } from "./EditResourceModal"; import { EditResourceModal } from "./EditResourceModal";
import { WorkloadLogsModal } from "./WorkloadLogsModal"; import { WorkloadLogsModal } from "./WorkloadLogsModal";
import { useColumnConfig } from "@/hooks/useColumnConfig";
import { DEFAULT_COLUMNS } from "@/config/defaultColumns";
import { ColumnConfigModal } from "@/components/tables/ColumnConfigModal";
interface ReplicationControllerListProps { interface ReplicationControllerListProps {
items: ReplicationControllerInfo[]; items: ReplicationControllerInfo[];
@ -36,6 +39,11 @@ export function ReplicationControllerList({
const [activeModal, setActiveModal] = useState<ActiveModal>(null); const [activeModal, setActiveModal] = useState<ActiveModal>(null);
const [isActing, setIsActing] = useState(false); const [isActing, setIsActing] = useState(false);
const [actionError, setActionError] = useState<string | null>(null); const [actionError, setActionError] = useState<string | null>(null);
const [showColumnConfig, setShowColumnConfig] = useState(false);
// Configurable columns
const columnConfig = useColumnConfig("replicationcontrollers", DEFAULT_COLUMNS.replicationcontrollers);
const { isColumnVisible } = columnConfig;
const openEdit = async (rc: ReplicationControllerInfo) => { const openEdit = async (rc: ReplicationControllerInfo) => {
setActionError(null); setActionError(null);
@ -69,17 +77,31 @@ export function ReplicationControllerList({
{actionError && ( {actionError && (
<p className="mb-2 text-sm text-destructive">{actionError}</p> <p className="mb-2 text-sm text-destructive">{actionError}</p>
)} )}
<div className="flex items-center justify-between mb-2">
<div className="text-sm text-muted-foreground">
{items.length} {items.length === 1 ? "replication controller" : "replication controllers"}
</div>
<Button
variant="outline"
size="sm"
onClick={() => setShowColumnConfig(true)}
className="flex items-center gap-1"
>
<Settings className="h-3.5 w-3.5" />
Columns
</Button>
</div>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>Name</TableHead> {isColumnVisible("name") && <TableHead>Name</TableHead>}
<TableHead>Namespace</TableHead> {isColumnVisible("namespace") && <TableHead>Namespace</TableHead>}
<TableHead>Desired</TableHead> {isColumnVisible("desired") && <TableHead>Desired</TableHead>}
<TableHead>Current</TableHead> {isColumnVisible("current") && <TableHead>Current</TableHead>}
<TableHead>Ready</TableHead> {isColumnVisible("ready") && <TableHead>Ready</TableHead>}
<TableHead>Age</TableHead> {isColumnVisible("age") && <TableHead>Age</TableHead>}
<TableHead className="text-right">Actions</TableHead> {isColumnVisible("actions") && <TableHead className="text-right">Actions</TableHead>}
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@ -92,13 +114,20 @@ export function ReplicationControllerList({
) : ( ) : (
items.map((rc) => ( items.map((rc) => (
<TableRow key={`${rc.name}-${rc.namespace}`}> <TableRow key={`${rc.name}-${rc.namespace}`}>
<TableCell className="font-medium">{rc.name}</TableCell> {isColumnVisible("name") && (
<TableCell className="text-muted-foreground">{rc.namespace}</TableCell> <TableCell className="font-medium">{rc.name}</TableCell>
<TableCell>{rc.desired}</TableCell> )}
<TableCell>{rc.current}</TableCell> {isColumnVisible("namespace") && (
<TableCell>{rc.ready}</TableCell> <TableCell className="text-muted-foreground">{rc.namespace}</TableCell>
<TableCell className="text-muted-foreground">{rc.age}</TableCell> )}
<TableCell className="text-right"> {isColumnVisible("desired") && <TableCell>{rc.desired}</TableCell>}
{isColumnVisible("current") && <TableCell>{rc.current}</TableCell>}
{isColumnVisible("ready") && <TableCell>{rc.ready}</TableCell>}
{isColumnVisible("age") && (
<TableCell className="text-muted-foreground">{rc.age}</TableCell>
)}
{isColumnVisible("actions") && (
<TableCell className="text-right">
<ResourceActionMenu <ResourceActionMenu
actions={[ actions={[
{ {
@ -124,7 +153,8 @@ export function ReplicationControllerList({
}, },
]} ]}
/> />
</TableCell> </TableCell>
)}
</TableRow> </TableRow>
)) ))
)} )}
@ -182,6 +212,22 @@ export function ReplicationControllerList({
onConfirm={handleDelete} onConfirm={handleDelete}
/> />
)} )}
<ColumnConfigModal
open={showColumnConfig}
onOpenChange={setShowColumnConfig}
resourceType="ReplicationControllers"
columnConfig={columnConfig}
columnLabels={{
name: "Name",
namespace: "Namespace",
desired: "Desired",
current: "Current",
ready: "Ready",
age: "Age",
actions: "Actions",
}}
/>
</> </>
); );
} }