320 lines
23 KiB
TypeScript
320 lines
23 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import { TableCell, TableRow } from '@/components/ui/table';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import {
|
|
Boxes, Container, Network, FileText, Shield, Activity, Clock,
|
|
FileCode, Pencil, Terminal, HardDrive, Trash2, Eye,
|
|
} from 'lucide-react';
|
|
import i18n from '@/i18n';
|
|
|
|
export type ResourceType =
|
|
| 'deployments' | 'pods' | 'services' | 'ingresses'
|
|
| 'configmaps' | 'secrets' | 'hpas' | 'cronjobs'
|
|
| 'pvcs' | 'statefulsets' | 'daemonsets'
|
|
| 'serviceaccounts' | 'roles' | 'rolebindings';
|
|
|
|
// 支持可视化编辑/新建的资源类型 // Logan.Li
|
|
export const VISUAL_RESOURCE_TYPES: ResourceType[] = ['services', 'ingresses', 'configmaps', 'secrets', 'pvcs'];
|
|
/** Deployment 列表:新建 + YAML 导入,不做批量导出 // Logan.Li */
|
|
export const DEPLOYMENT_LIST_TYPES: ResourceType[] = ['deployments'];
|
|
/** 支持命名空间 YAML 批量导出(对齐集群导出)// Logan.Li */
|
|
export const EXPORT_YAML_TYPES: ResourceType[] = ['services', 'ingresses', 'configmaps', 'secrets'];
|
|
/** 列表页保留 CSV/JSON 导出的配置类资源 // Logan.Li */
|
|
export const BULK_EXPORT_TYPES: ResourceType[] = ['services', 'ingresses', 'configmaps', 'secrets', 'pvcs'];
|
|
|
|
export interface ResourceRowOpts {
|
|
clusterCode: string;
|
|
namespace: string;
|
|
onDetail?: (item: any) => void;
|
|
onYaml?: (name: string) => void;
|
|
onEdit?: (name: string) => void;
|
|
onTerminal?: (item: any) => void;
|
|
onFileBrowser?: (item: any) => void;
|
|
onDeletePod?: (item: any) => void;
|
|
onPodDetail?: (item: any) => void;
|
|
}
|
|
|
|
export const VisualActions = ({ name, opts }: { name: string; opts: { onYaml?: (n: string) => void; onEdit?: (n: string) => void } }) => (
|
|
<div className="flex items-center gap-0.5">
|
|
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-green)] hover:text-[var(--dg-green)]" title={i18n.t('k8s.resource.visualEditor')}
|
|
onClick={() => opts.onEdit?.(name)}><Pencil className="h-3.5 w-3.5 mr-1" /> {i18n.t('common.edit')} </Button>
|
|
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-secondary)]" title="YAML"
|
|
onClick={() => opts.onYaml?.(name)}><FileCode className="h-3.5 w-3.5 mr-1" />YAML</Button>
|
|
</div>
|
|
);
|
|
|
|
export const RESOURCE_CONFIG: Record<ResourceType, {
|
|
title: string;
|
|
subtitle: string;
|
|
icon: React.ComponentType<any>;
|
|
columns: string[];
|
|
renderRow: (item: any, opts: ResourceRowOpts) => React.ReactNode;
|
|
}> = {
|
|
deployments: {
|
|
title: 'Deployments', subtitle: i18n.t('k8s.nsResource.deployDesc'), icon: Boxes,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('kubernetes.container.image')],
|
|
renderRow: (d, opts) => (
|
|
<TableRow key={d.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30 cursor-pointer" onClick={() => opts.onDetail?.(d)}>
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{d.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{d.namespace}</Badge></TableCell>
|
|
<TableCell>
|
|
{(() => {
|
|
const ready = d.ready_replicas || 0; const total = d.replicas || 0;
|
|
const pct = total > 0 ? (ready / total) * 100 : 0; const done = ready === total;
|
|
return (
|
|
<div className="flex items-center gap-2 min-w-[100px]">
|
|
<div className="flex-1 h-1.5 rounded-full bg-[var(--dg-bg-tertiary)]/50 overflow-hidden">
|
|
<div className={`h-full rounded-full ${done ? 'bg-[var(--dg-green)]' : 'bg-[var(--dg-orange)]'}`} style={{ width: `${pct}%` }} />
|
|
</div>
|
|
<span className={`text-xs ${done ? 'text-[var(--dg-green)]' : 'text-[var(--dg-orange)]'}`}>{ready}/{total}</span>
|
|
</div>
|
|
);
|
|
})()}
|
|
</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)] max-w-[200px] truncate">{d.image || '-'}</TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
pods: {
|
|
title: 'Pods', subtitle: i18n.t('k8s.nsResource.podDesc'), icon: Container,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('system.terminalStatus'), i18n.t('kubernetes.node.title'), 'CPU', i18n.t('kubernetes.pod.memory'), i18n.t('agent.agentServiceRestart'), i18n.t('kubernetes.container.image'), i18n.t('system.auditAction')],
|
|
renderRow: (p, opts) => {
|
|
const statusMap: Record<string, string> = { Running: 'bg-[var(--dg-green)]/20 text-[var(--dg-green)] border-[var(--dg-green)]/30', Pending: 'bg-[var(--dg-orange)]/20 text-[var(--dg-orange)] border-[var(--dg-orange)]/30', Failed: 'bg-[var(--dg-red)]/20 text-[var(--dg-red)] border-[var(--dg-red)]/30' };
|
|
return (
|
|
<TableRow key={p.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)] cursor-pointer hover:text-[var(--dg-blue)]" onClick={() => opts.onPodDetail?.(p)}>
|
|
<div className="flex items-center gap-1.5">
|
|
<Eye className="h-3.5 w-3.5 text-[var(--dg-text-muted)] shrink-0" />
|
|
<span className="truncate">{p.name}</span>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{p.namespace}</Badge></TableCell>
|
|
<TableCell><Badge className={statusMap[p.status] || 'bg-[var(--dg-text-muted)]/20 text-[var(--dg-text-secondary)]'}>{p.status || 'Unknown'}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{p.node_name || '-'}</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-cyan)]">{p.cpu_usage_m || '-'}</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-orange)]">{p.mem_usage_mi || '-'}</TableCell>
|
|
<TableCell className={(p.restart_count > 0 ? 'text-[var(--dg-orange)]' : 'text-[var(--dg-text-muted)]')}>{p.restart_count || 0}</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)] max-w-[200px] truncate">{p.image || '-'}</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center gap-0.5">
|
|
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-green)]" title={i18n.t('kubernetes.pod.terminal')}
|
|
onClick={() => opts.onTerminal?.(p)}><Terminal className="h-3.5 w-3.5" /></Button>
|
|
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-orange)]" title={i18n.t('k8s.pods.action.file')}
|
|
onClick={() => opts.onFileBrowser?.(p)}><HardDrive className="h-3.5 w-3.5" /></Button>
|
|
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-blue)]" title="YAML"
|
|
onClick={() => opts.onYaml?.(p.name)}><FileCode className="h-3.5 w-3.5" /></Button>
|
|
<Button variant="ghost" size="sm" className="h-7 w-7 p-0 shrink-0 text-[var(--dg-red)] hover:text-[var(--dg-red)]" title={i18n.t('kubernetes.storage.delete2')}
|
|
onClick={() => opts.onDeletePod?.(p)}><Trash2 className="h-3.5 w-3.5" /></Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
},
|
|
},
|
|
services: {
|
|
title: 'Services', subtitle: i18n.t('k8s.nsResource.serviceDesc'), icon: Network,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('system.credentialType'), 'Cluster IP', i18n.t('system.ldapPort'), i18n.t('system.auditAction')],
|
|
renderRow: (s, opts) => (
|
|
<TableRow key={s.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{s.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{s.namespace}</Badge></TableCell>
|
|
<TableCell><Badge className="bg-[var(--dg-blue)]/20 text-[var(--dg-blue)] border-[var(--dg-blue)]/30">{s.type || '-'}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{s.cluster_ip || '-'}</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{s.ports || '-'}</TableCell>
|
|
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={s.name} opts={opts} /></TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
ingresses: {
|
|
title: 'Ingresses', subtitle: i18n.t('k8s.nsResource.ingressDesc'), icon: Network,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), 'Hosts', 'TLS', i18n.t('system.auditAction')],
|
|
renderRow: (i, opts) => {
|
|
const hosts = i.hosts || [];
|
|
return (
|
|
<TableRow key={i.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{i.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{i.namespace}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)] max-w-[250px] truncate">{hosts.length > 0 ? hosts.join(', ') : '-'}</TableCell>
|
|
<TableCell><Badge className={i.tls ? 'bg-[var(--dg-green)]/20 text-[var(--dg-green)]' : 'bg-[var(--dg-text-muted)]/20 text-[var(--dg-text-secondary)]'}>{i.tls ? 'HTTPS' : 'HTTP'}</Badge></TableCell>
|
|
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={i.name} opts={opts} /></TableCell>
|
|
</TableRow>
|
|
);
|
|
},
|
|
},
|
|
configmaps: {
|
|
title: 'ConfigMaps', subtitle: i18n.t('k8s.nsResource.configMapDesc'), icon: FileText,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('system.auditAction')],
|
|
renderRow: (c, opts) => (
|
|
<TableRow key={c.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{c.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{c.namespace}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{c.key_count || '-'}</TableCell>
|
|
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={c.name} opts={opts} /></TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
secrets: {
|
|
title: 'Secrets', subtitle: i18n.t('k8s.nsResource.secretDesc'), icon: Shield,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('system.credentialType'), i18n.t('system.auditAction')],
|
|
renderRow: (s, opts) => (
|
|
<TableRow key={s.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{s.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{s.namespace}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{s.type || '-'}</TableCell>
|
|
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={s.name} opts={opts} /></TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
hpas: {
|
|
title: 'HPAs', subtitle: i18n.t('k8s.nsResource.hpaDesc'), icon: Activity,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('monitor.alertEvents.col.target'), i18n.t('k8s.hpaCronHpa.col.minMax'), 'CPU%', 'Mem%'],
|
|
renderRow: (h, _opts) => (
|
|
<TableRow key={h.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{h.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{h.namespace}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{h.target_kind}/{h.target_name}</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{h.min_replicas}/{h.max_replicas}</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{h.cpu_percent ?? '-'}%</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{h.mem_percent ?? '-'}%</TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
cronjobs: {
|
|
title: 'CronJobs', subtitle: i18n.t('automation.cronJobs'), icon: Clock,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('kubernetes.workload.scheduling'), i18n.t('system.terminalStatus'), i18n.t('automation.scriptLastExec')],
|
|
renderRow: (cj) => (
|
|
<TableRow key={cj.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{cj.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{cj.namespace}</Badge></TableCell>
|
|
<TableCell className="text-sm font-mono text-[var(--dg-text-secondary)]">{cj.schedule || '-'}</TableCell>
|
|
<TableCell><Badge className={cj.suspend ? 'bg-[var(--dg-orange)]/20 text-[var(--dg-orange)]' : 'bg-[var(--dg-green)]/20 text-[var(--dg-green)]'}>{cj.suspend ? i18n.t('kubernetes.deployment.paused') : i18n.t('agent.agentServiceRunning')}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{cj.last_schedule || '-'}</TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
pvcs: {
|
|
title: i18n.t('k8s.nsResource.pvcDesc'), subtitle: 'PersistentVolumeClaim', icon: Boxes,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('system.terminalStatus'), i18n.t('kubernetes.storage.pvcSize'), 'StorageClass', i18n.t('system.auditAction')],
|
|
renderRow: (p, opts) => (
|
|
<TableRow key={p.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{p.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{p.namespace}</Badge></TableCell>
|
|
<TableCell><Badge className={p.phase === 'Bound' ? 'bg-[var(--dg-green)]/20 text-[var(--dg-green)]' : 'bg-[var(--dg-orange)]/20 text-[var(--dg-orange)]'}>{p.phase || '-'}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{p.size || '-'}</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{p.storage_class || '-'}</TableCell>
|
|
<TableCell onClick={(e) => e.stopPropagation()}><VisualActions name={p.name} opts={opts} /></TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
statefulsets: {
|
|
title: 'StatefulSets', subtitle: i18n.t('k8s.nsResource.stsDesc'), icon: Boxes,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('kubernetes.pod.ready'), i18n.t('kubernetes.container.image')],
|
|
renderRow: (s, opts) => (
|
|
<TableRow key={s.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30 cursor-pointer" onClick={() => opts.onDetail?.(s)}>
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{s.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{s.namespace}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{s.replicas || 0}</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{s.ready || 0}</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)] max-w-[200px] truncate">{s.image || '-'}</TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
daemonsets: {
|
|
title: 'DaemonSets', subtitle: i18n.t('k8s.nsResource.dsDesc'), icon: Boxes,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('monitor.siteProtocol.http.expectedPrefix'), i18n.t('cloud.current'), i18n.t('kubernetes.pod.ready'), i18n.t('kubernetes.container.image')],
|
|
renderRow: (d, opts) => (
|
|
<TableRow key={d.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30 cursor-pointer" onClick={() => opts.onDetail?.(d)}>
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{d.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{d.namespace}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{d.desired || 0}</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{d.current || 0}</TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{d.ready || 0}</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)] max-w-[200px] truncate">{d.image || '-'}</TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
serviceaccounts: {
|
|
title: 'ServiceAccounts', subtitle: i18n.t('k8s.nsResource.saDesc'), icon: Shield,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('common.namespace'), i18n.t('system.userCreatedAt'), i18n.t('system.auditAction')],
|
|
renderRow: (sa, opts) => (
|
|
<TableRow key={sa.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{sa.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{sa.namespace}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{sa.secret_count ?? 0}</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)] max-w-[200px] truncate">
|
|
{sa.image_pull_secrets && sa.image_pull_secrets.length > 0 ? sa.image_pull_secrets.join(', ') : '-'}
|
|
</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)]">{sa.age || '-'}</TableCell>
|
|
<TableCell onClick={(e) => e.stopPropagation()}>
|
|
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title={i18n.t('k8s.nsResource.viewYaml')}
|
|
onClick={() => opts.onYaml?.(sa.name)}><FileCode className="h-3.5 w-3.5" /></Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
),
|
|
},
|
|
roles: {
|
|
title: 'Roles', subtitle: i18n.t('k8s.nsResource.roleDesc'), icon: Shield,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('system.apiKeyScope'), i18n.t('system.userCreatedAt'), i18n.t('system.auditAction')],
|
|
renderRow: (r, opts) => {
|
|
const verbs = r.verbs || [];
|
|
const resources = r.resources || [];
|
|
const verbsStr = verbs.length > 4 ? verbs.slice(0, 4).join(', ') + ` +${verbs.length - 4}` : verbs.join(', ');
|
|
const resStr = resources.length > 4 ? resources.slice(0, 4).join(', ') + ` +${resources.length - 4}` : resources.join(', ');
|
|
return (
|
|
<TableRow key={r.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{r.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{r.namespace}</Badge></TableCell>
|
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{r.rule_count ?? 0}</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)] max-w-[280px]">
|
|
<div className="flex flex-col gap-0.5">
|
|
<span className="text-[var(--dg-cyan)] truncate" title={verbs.join(', ')}>verbs: {verbsStr || '-'}</span>
|
|
<span className="text-[var(--dg-orange)] truncate" title={resources.join(', ')}>resources: {resStr || '-'}</span>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)]">{r.age || '-'}</TableCell>
|
|
<TableCell onClick={(e) => e.stopPropagation()}>
|
|
<div className="flex items-center gap-0.5">
|
|
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title={i18n.t('k8s.nsResource.viewYaml')}
|
|
onClick={() => opts.onYaml?.(r.name)}><FileCode className="h-3.5 w-3.5" /></Button>
|
|
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-green)] hover:text-[var(--dg-green)]" title={i18n.t('common.edit')}
|
|
onClick={() => opts.onEdit?.(r.name)}><Pencil className="h-3.5 w-3.5" /></Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
},
|
|
},
|
|
rolebindings: {
|
|
title: 'RoleBindings', subtitle: i18n.t('k88s.nsResource.rbDesc'), icon: Shield,
|
|
columns: [i18n.t('kubernetes.secret.secretName'), i18n.t('kubernetes.namespace.title'), i18n.t('common.name'), i18n.t('common.namespace'), i18n.t('system.userCreatedAt'), i18n.t('system.auditAction')],
|
|
renderRow: (rb, opts) => {
|
|
const subjects = rb.subjects || [];
|
|
const subjStr = subjects.length > 3 ? subjects.slice(0, 3).join(', ') + ` +${subjects.length - 3}` : subjects.join(', ');
|
|
return (
|
|
<TableRow key={rb.name} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
|
<TableCell className="font-medium text-[var(--dg-text-primary)]">{rb.name}</TableCell>
|
|
<TableCell><Badge variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)]">{rb.namespace}</Badge></TableCell>
|
|
<TableCell>
|
|
<Badge className="bg-[var(--dg-purple)]/20 text-[var(--dg-purple)] border-[var(--dg-purple)]/30">
|
|
{rb.role_ref_kind || '-'}/{rb.role_ref_name || '-'}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)] max-w-[200px] truncate" title={subjects.join(', ')}>
|
|
{subjStr || '-'}
|
|
</TableCell>
|
|
<TableCell className="text-xs text-[var(--dg-text-muted)]">{rb.age || '-'}</TableCell>
|
|
<TableCell onClick={(e) => e.stopPropagation()}>
|
|
<div className="flex items-center gap-0.5">
|
|
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-text-secondary)] hover:text-[var(--dg-blue)]" title={i18n.t('k8s.nsResource.viewYaml')}
|
|
onClick={() => opts.onYaml?.(rb.name)}><FileCode className="h-3.5 w-3.5" /></Button>
|
|
<Button variant="ghost" size="icon" className="h-7 w-7 text-[var(--dg-green)] hover:text-[var(--dg-green)]" title={i18n.t('common.edit')}
|
|
onClick={() => opts.onEdit?.(rb.name)}><Pencil className="h-3.5 w-3.5" /></Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
},
|
|
},
|
|
};
|