diff --git a/packages/refine/src/App.tsx b/packages/refine/src/App.tsx index 542d4fde..5d8eed55 100644 --- a/packages/refine/src/App.tsx +++ b/packages/refine/src/App.tsx @@ -5,6 +5,7 @@ import { Route, Router } from 'react-router-dom'; import { Layout } from './components'; import { Dovetail } from './Dovetail'; import { ConfigMapConfig } from './pages/configmaps'; +import { NetworkPolicyConfig } from './pages/networkpolicies'; import { CronJobForm, CronJobList, CronJobShow } from './pages/cronjobs'; import { DaemonSetForm, DaemonSetList, DaemonSetShow } from './pages/daemonsets'; import { DeploymentForm, DeploymentList, DeploymentShow } from './pages/deployments'; @@ -66,6 +67,7 @@ function App() { ConfigMapConfig, SecretsConfig, ServicesConfig, + NetworkPolicyConfig, ]; }, []); diff --git a/packages/refine/src/components/ShowContent/fields.tsx b/packages/refine/src/components/ShowContent/fields.tsx index ce7ea255..fae32142 100644 --- a/packages/refine/src/components/ShowContent/fields.tsx +++ b/packages/refine/src/components/ShowContent/fields.tsx @@ -164,3 +164,92 @@ export const ServicePodsField = (_: i18n): ShowField => { }, }; }; +import { List, Tabs, Tag } from 'antd'; +import { get } from 'lodash'; + +const { TabPane } = Tabs; + +// Helper component to display key, operator, and value +const SelectorList: React.FC<{ + data?: { + matchLabels?: Record; + matchExpressions?: Array<{ key: string; operator: string; values: Array }>; + }; +}> = ({ data }) => { + // Process matchLabels into an array of label strings + const labels = get(data, 'matchLabels', {}) + ? Object.entries(data.matchLabels).map(([key, value]) => `${key}=${value}`) + : []; + // Process matchExpressions into an array of expression strings + const expressions = get(data, 'matchExpressions', []).map( + expr => `${expr.key} ${expr.operator} ${expr.values.join(', ')}` + ); + // Combine and map to ListItem + const items = [...labels, ...expressions]; + return ( + {item}} + /> + ); +}; + +// Create a field renderer for Ingress +export const IngressField = (i18n: i18n): ShowField => ({ + key: 'ingress', + title: i18n.t('Ingress'), + path: ['rawYaml', 'spec', 'ingress'], + render: ingressRules => { + if (!ingressRules) return null; + + return ( + {}}> + {ingressRules.map((rule, index) => ( + +

{i18n.t('Source')}

+

CIDR: {get(rule, 'from[0].ipBlock.cidr')}

+

Exceptions: {get(rule, 'from[0].ipBlock.except', []).join(', ')}

+ // TODO: NamespaceSelector and PodSelector // TODO: Implement table or list + with labels +

{i18n.t('Ports')}

+ {get(rule, 'ports', []).map((port, portIndex) => ( +

+ {i18n.t('Port')}: {port.port}, {i18n.t('Protocol')}: {port.protocol} +

+ ))} +
+ ))} +
+ ); + }, +}); + +// Create a field renderer for Egress +export const EgressField = (i18n: i18n): ShowField => ({ + key: 'egress', + title: i18n.t('Egress'), + path: ['rawYaml', 'spec', 'egress'], + render: egressRules => { + if (!egressRules) return null; + + return ( + {}}> + {egressRules.map((rule, index) => ( + +

{i18n.t('Destination')}

+

CIDR: {get(rule, 'to[0].ipBlock.cidr')}

+

Exceptions: {get(rule, 'to[0].ipBlock.except', []).join(', ')}

+ // TODO: Implement table or list with labels +

{i18n.t('Ports')}

+ {get(rule, 'ports', []).map((port, portIndex) => ( +

+ {i18n.t('Port')}: {port.port}, {i18n.t('Protocol')}: {port.protocol} +

+ ))} +
+ ))} +
+ ); + }, +}); diff --git a/packages/refine/src/hooks/useEagleTable/columns.tsx b/packages/refine/src/hooks/useEagleTable/columns.tsx index 85b37fa6..fd524745 100644 --- a/packages/refine/src/hooks/useEagleTable/columns.tsx +++ b/packages/refine/src/hooks/useEagleTable/columns.tsx @@ -219,3 +219,37 @@ export const ServiceTypeColumnRenderer = ( sorter: CommonSorter(dataIndex), }; }; +import { NetworkPolicy } from 'kubernetes-types/networking/v1'; + +export const RulesCountColumnRenderer = (i18n: i18n): Column => { + return { + key: 'rulesCount', + display: true, + dataIndex: ['rawYaml'], + title: i18n.t('rules_count'), + sortable: true, + render: (value: NetworkPolicy) => { + const ingressRules = get(value, 'spec.ingress.length', 0); + const egressRules = get(value, 'spec.egress.length', 0); + const totalRules = ingressRules + egressRules; + return {totalRules}; + }, + }; +}; + +export const PodSelectorColumnRenderer = (i18n: i18n): Column => { + return { + key: 'podSelector', + display: true, + dataIndex: ['rawYaml'], + title: i18n.t('pod_selector'), + sortable: true, + render: (value: NetworkPolicy) => { + const podSelector = get(value, 'spec.podSelector.matchLabels', {}); + const podSelectorRules = Object.entries(podSelector) + .map(([key, val]) => `${key}=${val}`) + .join(', '); + return {podSelectorRules}; + }, + }; +}; diff --git a/packages/refine/src/pages/networkpolicies/index.tsx b/packages/refine/src/pages/networkpolicies/index.tsx new file mode 100644 index 00000000..4d562455 --- /dev/null +++ b/packages/refine/src/pages/networkpolicies/index.tsx @@ -0,0 +1,46 @@ +import { RulesCountColumnRenderer } from '../../hooks/useEagleTable/columns'; +import { PodSelectorColumnRenderer } from '../../hooks/useEagleTable/columns'; +import { IngressField } from '../../components/ShowContent/fields'; +import { EgressField } from '../../components/ShowContent/fields'; +import { i18n } from 'i18next'; +import { RESOURCE_GROUP, ResourceConfig, WithId } from '../../types'; +import { ResourceModel } from '../../model'; +import { NetworkPolicy } from 'kubernetes-types/networking/v1'; + +// a example k8s yaml for the network policy resource +const NETWORKPOLICY_INIT_VALUE = { + apiVersion: 'networking.k8s.io/v1', + kind: 'NetworkPolicy', + metadata: { + name: '', + namespace: 'default', + annotations: {}, + labels: {}, + }, + spec: { + podSelector: {}, + policyTypes: [], + }, +}; + +export const NetworkPolicyConfig: ResourceConfig, ResourceModel> = { + name: 'networkpolicies', + kind: 'NetworkPolicy', + basePath: '/apis/networking.k8s.io/v1', + apiVersion: 'v1', + parent: RESOURCE_GROUP.NETWORK, + label: 'Network Policies', + columns: (i18n: i18n) => [ + RulesCountColumnRenderer(i18n), + PodSelectorColumnRenderer(i18n), + ], + showFields: (i18n: i18n) => [ + // define the fields to show in the first line area as the first array + [], + // define the fields to show in the second line area as the second array + [], + // define the fields to show in the tabs area as the third array + [IngressField(i18n), EgressField(i18n)], + ], + initValue: NETWORKPOLICY_INIT_VALUE, +};