1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 07:49:41 +02:00

feat(config): separate configmaps and secrets [EE-5078] (#9029)

This commit is contained in:
Ali 2023-06-12 09:46:48 +12:00 committed by GitHub
parent 4a331b71e1
commit d7fc2046d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
102 changed files with 2845 additions and 665 deletions

View file

@ -0,0 +1,22 @@
import { ExternalLink } from 'lucide-react';
import { Icon } from '@@/Icon';
interface Props {
to: string;
text: string;
}
export function ExternalIPLink({ to, text }: Props) {
return (
<a
href={to}
target="_blank"
rel="noreferrer"
className="flex items-center gap-1"
>
<Icon icon={ExternalLink} />
<span>{text}</span>
</a>
);
}

View file

@ -0,0 +1,39 @@
import { CellContext } from '@tanstack/react-table';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { Link } from '@@/Link';
import { Service } from '../../../types';
import { columnHelper } from './helper';
export const application = columnHelper.accessor(
(row) => (row.Applications ? row.Applications[0].Name : ''),
{
header: 'Application',
id: 'application',
cell: Cell,
}
);
function Cell({ row, getValue }: CellContext<Service, string>) {
const appName = getValue();
const environmentId = useEnvironmentId();
return appName ? (
<Link
to="kubernetes.applications.application"
params={{
endpointId: environmentId,
namespace: row.original.Namespace,
name: appName,
}}
title={appName}
>
{appName}
</Link>
) : (
'-'
);
}

View file

@ -0,0 +1,41 @@
import { columnHelper } from './helper';
export const clusterIP = columnHelper.accessor(
(row) => row.ClusterIPs?.join(','),
{
header: 'Cluster IP',
id: 'clusterIP',
cell: ({ row }) => {
const clusterIPs = row.original.ClusterIPs;
if (!clusterIPs?.length) {
return '-';
}
return clusterIPs.map((ip) => <div key={ip}>{ip}</div>);
},
sortingFn: (rowA, rowB) => {
const a = rowA.original.ClusterIPs;
const b = rowB.original.ClusterIPs;
const ipA = a?.[0];
const ipB = b?.[0];
// no ip's at top, followed by 'None', then ordered by ip
if (!ipA) return 1;
if (!ipB) return -1;
if (ipA === ipB) return 0;
if (ipA === 'None') return 1;
if (ipB === 'None') return -1;
// natural sort of the ip
return ipA.localeCompare(
ipB,
navigator.languages[0] || navigator.language,
{
numeric: true,
ignorePunctuation: true,
}
);
},
}
);

View file

@ -0,0 +1,23 @@
import { formatDate } from '@/portainer/filters/filters';
import { columnHelper } from './helper';
export const created = columnHelper.accessor(
(row) => {
const owner = row.Labels?.['io.portainer.kubernetes.application.owner'];
const date = formatDate(row.CreationTimestamp);
return owner ? `${date} by ${owner}` : date;
},
{
header: 'Created',
id: 'created',
cell: ({ row }) => {
const date = formatDate(row.original.CreationTimestamp);
const owner =
row.original.Labels?.['io.portainer.kubernetes.application.owner'];
return owner ? `${date} by ${owner}` : date;
},
}
);

View file

@ -0,0 +1,139 @@
import { CellContext } from '@tanstack/react-table';
import { Service } from '../../../types';
import { ExternalIPLink } from './ExternalIPLink';
import { columnHelper } from './helper';
export const externalIP = columnHelper.accessor(
(row) => {
if (row.Type === 'ExternalName') {
return row.ExternalName || '';
}
if (row.ExternalIPs?.length) {
return row.ExternalIPs?.join(',') || '';
}
return row.IngressStatus?.map((status) => status.IP).join(',') || '';
},
{
header: 'External IP',
id: 'externalIP',
cell: Cell,
sortingFn: (rowA, rowB) => {
const a = rowA.original.IngressStatus;
const b = rowB.original.IngressStatus;
const aExternal = rowA.original.ExternalIPs;
const bExternal = rowB.original.ExternalIPs;
const ipA = a?.[0].IP || aExternal?.[0] || rowA.original.ExternalName;
const ipB = b?.[0].IP || bExternal?.[0] || rowA.original.ExternalName;
if (!ipA) return 1;
if (!ipB) return -1;
// use a nat sort order for ip addresses
return ipA.localeCompare(
ipB,
navigator.languages[0] || navigator.language,
{
numeric: true,
ignorePunctuation: true,
}
);
},
}
);
function Cell({ row }: CellContext<Service, string>) {
if (row.original.Type === 'ExternalName') {
if (row.original.ExternalName) {
const linkTo = `http://${row.original.ExternalName}`;
return <ExternalIPLink to={linkTo} text={row.original.ExternalName} />;
}
return '-';
}
const [scheme, port] = getSchemeAndPort(row.original);
if (row.original.ExternalIPs?.length) {
return row.original.ExternalIPs?.map((ip, index) => {
// some ips come through blank
if (ip.length === 0) {
return '-';
}
if (scheme) {
let linkTo = `${scheme}://${ip}`;
if (port !== 80 && port !== 443) {
linkTo = `${linkTo}:${port}`;
}
return (
<div key={index}>
<ExternalIPLink to={linkTo} text={ip} />
</div>
);
}
return <div key={index}>{ip}</div>;
});
}
const status = row.original.IngressStatus;
if (status) {
return status?.map((status, index) => {
// some ips come through blank
if (status.IP.length === 0) {
return '-';
}
if (scheme) {
let linkTo = `${scheme}://${status.IP}`;
if (port !== 80 && port !== 443) {
linkTo = `${linkTo}:${port}`;
}
return (
<div key={index}>
<ExternalIPLink to={linkTo} text={status.IP} />
</div>
);
}
return <div key={index}>{status.IP}</div>;
});
}
return '-';
}
// calculate the scheme based on the ports of the service
// favour https over http.
function getSchemeAndPort(svc: Service): [string, number] {
let scheme = '';
let servicePort = 0;
svc.Ports?.forEach((port) => {
if (port.Protocol === 'TCP') {
switch (port.TargetPort) {
case '443':
case '8443':
case 'https':
scheme = 'https';
servicePort = port.Port;
break;
case '80':
case '8080':
case 'http':
if (scheme !== 'https') {
scheme = 'http';
servicePort = port.Port;
}
break;
default:
break;
}
}
});
return [scheme, servicePort];
}

View file

@ -0,0 +1,5 @@
import { createColumnHelper } from '@tanstack/react-table';
import { Service } from '../../../types';
export const columnHelper = createColumnHelper<Service>();

View file

@ -0,0 +1,21 @@
import { name } from './name';
import { type } from './type';
import { namespace } from './namespace';
import { ports } from './ports';
import { clusterIP } from './clusterIP';
import { externalIP } from './externalIP';
import { targetPorts } from './targetPorts';
import { application } from './application';
import { created } from './created';
export const columns = [
name,
application,
namespace,
type,
ports,
targetPorts,
clusterIP,
externalIP,
created,
];

View file

@ -0,0 +1,53 @@
import { Authorized } from '@/react/hooks/useUser';
import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils';
import { columnHelper } from './helper';
export const name = columnHelper.accessor(
(row) => {
let name = row.Name;
const isExternal =
!row.Labels || !row.Labels['io.portainer.kubernetes.application.owner'];
const isSystem = isSystemNamespace(row.Namespace);
if (isExternal && !isSystem) {
name = `${name} external`;
}
if (isSystem) {
name = `${name} system`;
}
return name;
},
{
header: 'Name',
id: 'name',
cell: ({ row }) => {
const name = row.original.Name;
const isSystem = isSystemNamespace(row.original.Namespace);
const isExternal =
!row.original.Labels ||
!row.original.Labels['io.portainer.kubernetes.application.owner'];
return (
<Authorized authorizations="K8sServiceW" childrenUnauthorized={name}>
{name}
{isSystem && (
<span className="label label-info image-tag label-margins">
system
</span>
)}
{isExternal && !isSystem && (
<span className="label label-primary image-tag label-margins">
external
</span>
)}
</Authorized>
);
},
}
);

View file

@ -0,0 +1,35 @@
import { Row } from '@tanstack/react-table';
import { filterHOC } from '@/react/components/datatables/Filter';
import { Link } from '@@/Link';
import { Service } from '../../../types';
import { columnHelper } from './helper';
export const namespace = columnHelper.accessor('Namespace', {
header: 'Namespace',
id: 'namespace',
cell: ({ getValue }) => {
const namespace = getValue();
return (
<Link
to="kubernetes.resourcePools.resourcePool"
params={{
id: namespace,
}}
title={namespace}
>
{namespace}
</Link>
);
},
meta: {
filter: filterHOC('Filter by namespace'),
},
enableColumnFilter: true,
filterFn: (row: Row<Service>, columnId: string, filterValue: string[]) =>
filterValue.length === 0 || filterValue.includes(row.original.Namespace),
});

View file

@ -0,0 +1,73 @@
import { Tooltip } from '@@/Tip/Tooltip';
import { columnHelper } from './helper';
export const ports = columnHelper.accessor(
(row) =>
row.Ports.map(
(port) =>
`${port.Port}${port.NodePort !== 0 ? `:${port.NodePort}` : ''}/${
port.Protocol
}`
).join(',') || '-',
{
header: () => (
<>
Ports
<Tooltip message="The format of Ports is port[:nodePort]/protocol. Protocol is either TCP, UDP or SCTP." />
</>
),
id: 'ports',
cell: ({ row }) => {
if (!row.original.Ports.length) {
return '-';
}
return (
<>
{row.original.Ports.map((port, index) => {
if (port.NodePort !== 0) {
return (
<div key={index}>
{port.Port}:{port.NodePort}/{port.Protocol}
</div>
);
}
return (
<div key={index}>
{port.Port}/{port.Protocol}
</div>
);
})}
</>
);
},
sortingFn: (rowA, rowB) => {
const a = rowA.original.Ports;
const b = rowB.original.Ports;
if (!a.length && !b.length) return 0;
if (!a.length) return 1;
if (!b.length) return -1;
// sort order based on first port
const portA = a[0].Port;
const portB = b[0].Port;
if (portA === portB) {
// longer list of ports is considered "greater"
if (a.length < b.length) return -1;
if (a.length > b.length) return 1;
return 0;
}
// now do a regular number sort
if (portA < portB) return -1;
if (portA > portB) return 1;
return 0;
},
}
);

View file

@ -0,0 +1,45 @@
import { columnHelper } from './helper';
export const targetPorts = columnHelper.accessor(
(row) => row.Ports.map((port) => port.TargetPort).join(','),
{
header: 'Target Ports',
id: 'targetPorts',
cell: ({ row }) => {
const ports = row.original.Ports.map((port) => port.TargetPort);
if (!ports.length) {
return '-';
}
return ports.map((port, index) => <div key={index}>{port}</div>);
},
sortingFn: (rowA, rowB) => {
const a = rowA.original.Ports;
const b = rowB.original.Ports;
if (!a.length && !b.length) return 0;
if (!a.length) return 1;
if (!b.length) return -1;
const portA = a[0].TargetPort;
const portB = b[0].TargetPort;
if (portA === portB) {
if (a.length < b.length) return -1;
if (a.length > b.length) return 1;
return 0;
}
// natural sort of the port
return portA.localeCompare(
portB,
navigator.languages[0] || navigator.language,
{
numeric: true,
ignorePunctuation: true,
}
);
},
}
);

View file

@ -0,0 +1,18 @@
import { Row } from '@tanstack/react-table';
import { filterHOC } from '@@/datatables/Filter';
import { Service } from '../../../types';
import { columnHelper } from './helper';
export const type = columnHelper.accessor('Type', {
header: 'Type',
id: 'type',
meta: {
filter: filterHOC('Filter by type'),
},
enableColumnFilter: true,
filterFn: (row: Row<Service>, columnId: string, filterValue: string[]) =>
filterValue.length === 0 || filterValue.includes(row.original.Type),
});