mirror of
https://github.com/portainer/portainer.git
synced 2025-07-23 15:29:42 +02:00
refactor(kube/apps): migrate table to react [EE-4685] (#11028)
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
This commit is contained in:
parent
e9ebef15a0
commit
76e49ed9a8
41 changed files with 756 additions and 903 deletions
|
@ -0,0 +1,166 @@
|
|||
import { useEffect } from 'react';
|
||||
import { BoxIcon } from 'lucide-react';
|
||||
|
||||
import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
|
||||
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
|
||||
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
|
||||
import { CreateFromManifestButton } from '@/react/kubernetes/components/CreateFromManifestButton';
|
||||
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment';
|
||||
import { useAuthorizations } from '@/react/hooks/useUser';
|
||||
|
||||
import { TableSettingsMenu } from '@@/datatables';
|
||||
import { useRepeater } from '@@/datatables/useRepeater';
|
||||
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||
import { AddButton } from '@@/buttons';
|
||||
import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable';
|
||||
|
||||
import { NamespaceFilter } from '../ApplicationsStacksDatatable/NamespaceFilter';
|
||||
import { Namespace } from '../ApplicationsStacksDatatable/types';
|
||||
|
||||
import { Application, ConfigKind } from './types';
|
||||
import { useColumns } from './useColumns';
|
||||
import { getPublishedUrls } from './PublishedPorts';
|
||||
import { SubRow } from './SubRow';
|
||||
import { HelmInsightsBox } from './HelmInsightsBox';
|
||||
|
||||
export function ApplicationsDatatable({
|
||||
dataset,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
onRemove,
|
||||
namespace = '',
|
||||
namespaces,
|
||||
onNamespaceChange,
|
||||
showSystem,
|
||||
onShowSystemChange,
|
||||
hideStacks,
|
||||
}: {
|
||||
dataset: Array<Application>;
|
||||
onRefresh: () => void;
|
||||
isLoading: boolean;
|
||||
onRemove: (selectedItems: Application[]) => void;
|
||||
namespace?: string;
|
||||
namespaces: Array<Namespace>;
|
||||
onNamespaceChange(namespace: string): void;
|
||||
showSystem?: boolean;
|
||||
onShowSystemChange(showSystem: boolean): void;
|
||||
hideStacks: boolean;
|
||||
}) {
|
||||
const envId = useEnvironmentId();
|
||||
const envQuery = useCurrentEnvironment();
|
||||
const namespaceMetaListQuery = useNamespacesQuery(envId);
|
||||
|
||||
const tableState = useKubeStore('kubernetes.applications', 'Name');
|
||||
useRepeater(tableState.autoRefreshRate, onRefresh);
|
||||
|
||||
const hasWriteAuthQuery = useAuthorizations(
|
||||
'K8sApplicationsW',
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
const { setShowSystemResources } = tableState;
|
||||
|
||||
useEffect(() => {
|
||||
setShowSystemResources(showSystem || false);
|
||||
}, [showSystem, setShowSystemResources]);
|
||||
|
||||
const columns = useColumns(hideStacks);
|
||||
|
||||
const filteredDataset = !showSystem
|
||||
? dataset.filter(
|
||||
(item) => !namespaceMetaListQuery.data?.[item.ResourcePool]?.IsSystem
|
||||
)
|
||||
: dataset;
|
||||
|
||||
return (
|
||||
<ExpandableDatatable
|
||||
data-cy="k8sApp-appTable"
|
||||
noWidget
|
||||
dataset={filteredDataset}
|
||||
settingsManager={tableState}
|
||||
columns={columns}
|
||||
title="Applications"
|
||||
titleIcon={BoxIcon}
|
||||
isLoading={isLoading}
|
||||
disableSelect={!hasWriteAuthQuery.authorized}
|
||||
isRowSelectable={(row) =>
|
||||
!namespaceMetaListQuery.data?.[row.original.ResourcePool]?.IsSystem
|
||||
}
|
||||
getRowCanExpand={(row) => isExpandable(row.original)}
|
||||
renderSubRow={(row) => (
|
||||
<SubRow
|
||||
item={row.original}
|
||||
hideStacks={hideStacks}
|
||||
areSecretsRestricted={
|
||||
envQuery.data?.Kubernetes.Configuration.RestrictSecrets || false
|
||||
}
|
||||
/>
|
||||
)}
|
||||
renderTableActions={(selectedItems) =>
|
||||
hasWriteAuthQuery.authorized && (
|
||||
<>
|
||||
<DeleteButton
|
||||
data-cy="k8sApp-removeAppButton"
|
||||
disabled={selectedItems.length === 0}
|
||||
confirmMessage="Do you want to remove the selected application(s)?"
|
||||
onConfirmed={() => onRemove(selectedItems)}
|
||||
/>
|
||||
|
||||
<AddButton data-cy="k8sApp-addApplicationButton" color="secondary">
|
||||
Add with form
|
||||
</AddButton>
|
||||
|
||||
<CreateFromManifestButton data-cy="k8sApp-deployFromManifestButton" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
renderTableSettings={() => (
|
||||
<TableSettingsMenu>
|
||||
<DefaultDatatableSettings
|
||||
settings={tableState}
|
||||
onShowSystemChange={onShowSystemChange}
|
||||
/>
|
||||
</TableSettingsMenu>
|
||||
)}
|
||||
description={
|
||||
<div className="w-full">
|
||||
<div className="min-w-[140px] float-right">
|
||||
<NamespaceFilter
|
||||
namespaces={namespaces}
|
||||
value={namespace}
|
||||
onChange={onNamespaceChange}
|
||||
showSystem={tableState.showSystemResources}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<SystemResourceDescription
|
||||
showSystemResources={tableState.showSystemResources}
|
||||
/>
|
||||
|
||||
<div className="w-fit">
|
||||
<HelmInsightsBox />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function isExpandable(item: Application) {
|
||||
return (
|
||||
!!item.KubernetesApplications ||
|
||||
!!getPublishedUrls(item).length ||
|
||||
hasConfigurationSecrets(item)
|
||||
);
|
||||
}
|
||||
|
||||
function hasConfigurationSecrets(item: Application) {
|
||||
return !!item.Configurations?.some(
|
||||
(config) => config.Data && config.Kind === ConfigKind.Secret
|
||||
);
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import { useAuthorizations } from '@/react/hooks/useUser';
|
||||
|
||||
import { SensitiveDetails } from './SensitiveDetails';
|
||||
import { Application, ConfigKind } from './types';
|
||||
|
||||
export function ConfigurationDetails({
|
||||
item,
|
||||
areSecretsRestricted,
|
||||
username,
|
||||
}: {
|
||||
item: Application;
|
||||
areSecretsRestricted: boolean;
|
||||
username: string;
|
||||
}) {
|
||||
const isEnvironmentAdminQuery = useAuthorizations(['K8sResourcePoolsW']);
|
||||
|
||||
const secrets = item.Configurations?.filter(
|
||||
(config) => config.Data && config.Kind === ConfigKind.Secret
|
||||
);
|
||||
|
||||
if (isEnvironmentAdminQuery.isLoading || !secrets || secrets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="col-xs-12 !px-0 !py-1 text-[13px]"> Secrets </div>
|
||||
<table className="w-1/2">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{secrets.map((secret) =>
|
||||
Object.entries(secret.Data || {}).map(([key, value]) => (
|
||||
<SensitiveDetails
|
||||
key={key}
|
||||
name={key}
|
||||
value={value}
|
||||
canSeeValue={canSeeValue(secret)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
|
||||
function canSeeValue(secret: { ConfigurationOwner: string }) {
|
||||
return (
|
||||
!areSecretsRestricted ||
|
||||
isEnvironmentAdminQuery.authorized ||
|
||||
secret.ConfigurationOwner === username
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { NestedDatatable } from '@@/datatables/NestedDatatable';
|
||||
|
||||
import { Application } from './types';
|
||||
import { useBaseColumns } from './useColumns';
|
||||
|
||||
export function InnerTable({
|
||||
dataset,
|
||||
hideStacks,
|
||||
}: {
|
||||
dataset: Array<Application>;
|
||||
hideStacks: boolean;
|
||||
}) {
|
||||
const columns = useBaseColumns(hideStacks);
|
||||
|
||||
return (
|
||||
<NestedDatatable
|
||||
dataset={dataset}
|
||||
columns={columns}
|
||||
data-cy="applications-nested-datatable"
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import { ExternalLinkIcon } from 'lucide-react';
|
||||
|
||||
import { getSchemeFromPort } from '@/react/common/network-utils';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import { Application } from './types';
|
||||
|
||||
export function PublishedPorts({ item }: { item: Application }) {
|
||||
const urls = getPublishedUrls(item);
|
||||
|
||||
if (urls.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="published-url-container">
|
||||
<div>
|
||||
<div className="text-muted"> Published URL(s) </div>
|
||||
</div>
|
||||
<div>
|
||||
{urls.map((url) => (
|
||||
<div key={url}>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
className="publish-url-link vertical-center"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Icon icon={ExternalLinkIcon} />
|
||||
{url}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getPublishedUrls(item: Application) {
|
||||
// Map all ingress rules in published ports to their respective URLs
|
||||
const ingressUrls =
|
||||
item.PublishedPorts?.flatMap((pp) => pp.IngressRules)
|
||||
.filter(({ Host, IP }) => Host || IP)
|
||||
.map(({ Host, IP, Path, TLS }) => {
|
||||
const scheme =
|
||||
TLS &&
|
||||
TLS.filter((tls) => tls.hosts && tls.hosts.includes(Host)).length > 0
|
||||
? 'https'
|
||||
: 'http';
|
||||
return `${scheme}://${Host || IP}${Path}`;
|
||||
}) || [];
|
||||
|
||||
// Map all load balancer service ports to ip address
|
||||
const loadBalancerURLs =
|
||||
(item.LoadBalancerIPAddress &&
|
||||
item.PublishedPorts?.map(
|
||||
(pp) =>
|
||||
`${getSchemeFromPort(pp.Port)}://${item.LoadBalancerIPAddress}:${
|
||||
pp.Port
|
||||
}`
|
||||
)) ||
|
||||
[];
|
||||
|
||||
// combine ingress urls
|
||||
const publishedUrls = [...ingressUrls, ...loadBalancerURLs];
|
||||
|
||||
// Return the first URL - priority given to ingress urls, then services (load balancers)
|
||||
return publishedUrls.length > 0 ? publishedUrls : [];
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
.sensitive-details-container {
|
||||
display: grid;
|
||||
grid-template-columns: 25ch 25ch auto;
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.show-hide-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import { useState } from 'react';
|
||||
import { EyeIcon, EyeOffIcon } from 'lucide-react';
|
||||
|
||||
import { RestrictedSecretBadge } from '@/react/kubernetes/configs/RestrictedSecretBadge';
|
||||
|
||||
import { Button, CopyButton } from '@@/buttons';
|
||||
|
||||
import styles from './SensitiveDetails.module.css';
|
||||
|
||||
export function SensitiveDetails({
|
||||
name,
|
||||
value,
|
||||
canSeeValue,
|
||||
}: {
|
||||
name: string;
|
||||
value: string;
|
||||
canSeeValue?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.sensitiveDetailsContainer}>
|
||||
<div className="text-wrap">{name}</div>
|
||||
{canSeeValue ? (
|
||||
<>
|
||||
<ShowHide value={value} useAsterisk />
|
||||
<CopyButton copyText={value} data-cy="copy-button" />
|
||||
</>
|
||||
) : (
|
||||
<RestrictedSecretBadge />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ShowHide({
|
||||
value,
|
||||
useAsterisk,
|
||||
}: {
|
||||
value: string;
|
||||
useAsterisk: boolean;
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={styles.showHideContainer}>
|
||||
<div className="small text-muted text-wrap">
|
||||
{show ? value : useAsterisk && '********'}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
color="link"
|
||||
type="button"
|
||||
onClick={() => setShow((show) => !show)}
|
||||
title="Show/Hide value"
|
||||
icon={show ? EyeIcon : EyeOffIcon}
|
||||
data-cy="show-hide-button"
|
||||
>
|
||||
{show ? 'Hide' : 'Show'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { useCurrentUser } from '@/react/hooks/useUser';
|
||||
|
||||
import { ConfigurationDetails } from './ConfigurationDetails';
|
||||
import { InnerTable } from './InnerTable';
|
||||
import { PublishedPorts } from './PublishedPorts';
|
||||
import { Application } from './types';
|
||||
|
||||
export function SubRow({
|
||||
item,
|
||||
hideStacks,
|
||||
areSecretsRestricted,
|
||||
}: {
|
||||
item: Application;
|
||||
hideStacks: boolean;
|
||||
areSecretsRestricted: boolean;
|
||||
}) {
|
||||
const {
|
||||
user: { Username: username },
|
||||
} = useCurrentUser();
|
||||
|
||||
return (
|
||||
<tr className={clsx({ 'secondary-body': !item.KubernetesApplications })}>
|
||||
<td />
|
||||
<td colSpan={8} className="datatable-padding-vertical">
|
||||
{item.KubernetesApplications ? (
|
||||
<InnerTable
|
||||
dataset={item.KubernetesApplications}
|
||||
hideStacks={hideStacks}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<PublishedPorts item={item} />
|
||||
<ConfigurationDetails
|
||||
item={item}
|
||||
areSecretsRestricted={areSecretsRestricted}
|
||||
username={username}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
|
||||
import { Application } from './types';
|
||||
|
||||
export const helper = createColumnHelper<Application>();
|
|
@ -0,0 +1,47 @@
|
|||
import { CellContext } from '@tanstack/react-table';
|
||||
|
||||
import { useIsSystemNamespace } from '@/react/kubernetes/namespaces/queries/useIsSystemNamespace';
|
||||
|
||||
import { Link } from '@@/Link';
|
||||
import { SystemBadge } from '@@/Badge/SystemBadge';
|
||||
import { ExternalBadge } from '@@/Badge/ExternalBadge';
|
||||
|
||||
import { helper } from './columns.helper';
|
||||
import { Application } from './types';
|
||||
|
||||
export const name = helper.accessor('Name', {
|
||||
header: 'Name',
|
||||
cell: Cell,
|
||||
});
|
||||
|
||||
function Cell({ row: { original: item } }: CellContext<Application, string>) {
|
||||
const isSystem = useIsSystemNamespace(item.ResourcePool);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{item.KubernetesApplications ? (
|
||||
<Link
|
||||
data-cy="application-helm-link"
|
||||
to="kubernetes.helm"
|
||||
params={{ name: item.Name, namespace: item.ResourcePool }}
|
||||
>
|
||||
{item.Name}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
data-cy="application-link"
|
||||
to="kubernetes.applications.application"
|
||||
params={{
|
||||
name: item.Name,
|
||||
namespace: item.ResourcePool,
|
||||
'resource-type': item.ApplicationType,
|
||||
}}
|
||||
>
|
||||
{item.Name}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{isSystem ? <SystemBadge /> : !item.ApplicationOwner && <ExternalBadge />}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
.status-indicator {
|
||||
padding: 0 !important;
|
||||
margin-right: 1ch;
|
||||
border-radius: 50%;
|
||||
background-color: var(--red-3);
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-indicator.ok {
|
||||
background-color: var(--green-3);
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import { CellContext } from '@tanstack/react-table';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import {
|
||||
KubernetesApplicationDeploymentTypes,
|
||||
KubernetesApplicationTypes,
|
||||
} from '@/kubernetes/models/application/models/appConstants';
|
||||
|
||||
import styles from './columns.status.module.css';
|
||||
import { helper } from './columns.helper';
|
||||
import { Application } from './types';
|
||||
|
||||
export const status = helper.accessor('Status', {
|
||||
header: 'Status',
|
||||
cell: Cell,
|
||||
enableSorting: false,
|
||||
});
|
||||
|
||||
function Cell({ row: { original: item } }: CellContext<Application, string>) {
|
||||
if (
|
||||
item.ApplicationType === KubernetesApplicationTypes.Pod &&
|
||||
item.Pods &&
|
||||
item.Pods.length > 0
|
||||
) {
|
||||
return item.Pods[0].Status;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={clsx([
|
||||
styles.statusIndicator,
|
||||
{
|
||||
[styles.ok]:
|
||||
(item.TotalPodsCount > 0 &&
|
||||
item.TotalPodsCount === item.RunningPodsCount) ||
|
||||
item.Status === 'Ready',
|
||||
},
|
||||
])}
|
||||
/>
|
||||
{item.DeploymentType ===
|
||||
KubernetesApplicationDeploymentTypes.Replicated && (
|
||||
<span>Replicated</span>
|
||||
)}
|
||||
{item.DeploymentType === KubernetesApplicationDeploymentTypes.Global && (
|
||||
<span>Global</span>
|
||||
)}
|
||||
{item.RunningPodsCount >= 0 && item.TotalPodsCount >= 0 && (
|
||||
<span>
|
||||
<code aria-label="Running Pods" title="Running Pods">
|
||||
{item.RunningPodsCount}
|
||||
</code>{' '}
|
||||
/{' '}
|
||||
<code aria-label="Total Pods" title="Total Pods">
|
||||
{item.TotalPodsCount}
|
||||
</code>
|
||||
</span>
|
||||
)}
|
||||
{item.KubernetesApplications && <span>{item.Status}</span>}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import { isoDate, truncate } from '@/portainer/filters/filters';
|
||||
|
||||
import { helper } from './columns.helper';
|
||||
|
||||
export const stackName = helper.accessor('StackName', {
|
||||
header: 'Stack',
|
||||
cell: ({ getValue }) => getValue() || '-',
|
||||
});
|
||||
|
||||
export const namespace = helper.accessor('ResourcePool', {
|
||||
header: 'Namespace',
|
||||
cell: ({ getValue }) => getValue() || '-',
|
||||
});
|
||||
|
||||
export const image = helper.accessor('Image', {
|
||||
header: 'Image',
|
||||
cell: ({ row: { original: item } }) => (
|
||||
<>
|
||||
{truncate(item.Image, 64)}
|
||||
{item.Containers && item.Containers?.length > 1 && (
|
||||
<>+ {item.Containers.length - 1}</>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
export const appType = helper.accessor('ApplicationType', {
|
||||
header: 'Application Type',
|
||||
});
|
||||
|
||||
export const published = helper.accessor('Services', {
|
||||
header: 'Published',
|
||||
cell: ({ row: { original: item } }) =>
|
||||
item.Services?.length === 0 ? 'No' : 'Yes',
|
||||
enableSorting: false,
|
||||
});
|
||||
|
||||
export const created = helper.accessor('CreationDate', {
|
||||
header: 'Created',
|
||||
cell({ row: { original: item } }) {
|
||||
return (
|
||||
<>
|
||||
{isoDate(item.CreationDate)}{' '}
|
||||
{item.ApplicationOwner ? ` by ${item.ApplicationOwner}` : ''}
|
||||
</>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
import { AppType, DeploymentType } from '../../types';
|
||||
|
||||
export interface Application {
|
||||
Id: string;
|
||||
Name: string;
|
||||
Image: string;
|
||||
Containers?: Array<unknown>;
|
||||
Services?: Array<unknown>;
|
||||
CreationDate: string;
|
||||
ApplicationOwner?: string;
|
||||
StackName?: string;
|
||||
ResourcePool: string;
|
||||
ApplicationType: AppType;
|
||||
KubernetesApplications?: Array<Application>;
|
||||
Metadata?: {
|
||||
labels: Record<string, string>;
|
||||
};
|
||||
Status: 'Ready' | string;
|
||||
TotalPodsCount: number;
|
||||
RunningPodsCount: number;
|
||||
DeploymentType: DeploymentType;
|
||||
Pods?: Array<{
|
||||
Status: string;
|
||||
}>;
|
||||
Configurations?: Array<{
|
||||
Data?: object;
|
||||
Kind: ConfigKind;
|
||||
ConfigurationOwner: string;
|
||||
}>;
|
||||
LoadBalancerIPAddress?: string;
|
||||
PublishedPorts?: Array<{
|
||||
IngressRules: Array<{
|
||||
Host: string;
|
||||
IP: string;
|
||||
Path: string;
|
||||
TLS: Array<{
|
||||
hosts: Array<string>;
|
||||
}>;
|
||||
}>;
|
||||
Port: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export enum ConfigKind {
|
||||
ConfigMap = 1,
|
||||
Secret,
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { buildExpandColumn } from '@@/datatables/expand-column';
|
||||
|
||||
import { name } from './columns.name';
|
||||
import { status } from './columns.status';
|
||||
import {
|
||||
appType,
|
||||
created,
|
||||
image,
|
||||
namespace,
|
||||
published,
|
||||
stackName,
|
||||
} from './columns';
|
||||
import { Application } from './types';
|
||||
|
||||
export function useColumns(hideStacks: boolean) {
|
||||
const baseColumns = useBaseColumns(hideStacks);
|
||||
|
||||
return _.compact([buildExpandColumn<Application>(), ...baseColumns]);
|
||||
}
|
||||
|
||||
export function useBaseColumns(hideStacks: boolean) {
|
||||
return _.compact([
|
||||
name,
|
||||
!hideStacks && stackName,
|
||||
namespace,
|
||||
image,
|
||||
appType,
|
||||
status,
|
||||
published,
|
||||
created,
|
||||
]);
|
||||
}
|
|
@ -47,10 +47,11 @@ export function ApplicationsStacksDatatable({
|
|||
}: Props) {
|
||||
const tableState = useTableState(settingsStore, storageKey);
|
||||
|
||||
const { setShowSystemResources } = tableState;
|
||||
|
||||
useEffect(() => {
|
||||
tableState.setShowSystemResources(showSystem || false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [showSystem]);
|
||||
setShowSystemResources(showSystem || false);
|
||||
}, [showSystem, setShowSystemResources]);
|
||||
|
||||
const { authorized } = useAuthorizations('K8sApplicationsW');
|
||||
useRepeater(tableState.autoRefreshRate, onRefresh);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue