fix(ingress): handle system resources [EE-4775] (#9972)

* fix(ingress): handle system resources [EE-4775]
pull/10090/head
Ali 2023-08-23 09:13:35 +12:00 committed by GitHub
parent 5586910e9d
commit 1e61f7e305
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 105 additions and 60 deletions

View File

@ -3,11 +3,7 @@ import { FileCode, Plus, Trash2 } from 'lucide-react';
import { ConfigMap } from 'kubernetes-types/core/v1'; import { ConfigMap } from 'kubernetes-types/core/v1';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
Authorized,
useAuthorizations,
useCurrentUser,
} from '@/react/hooks/useUser';
import { useNamespaces } from '@/react/kubernetes/namespaces/queries'; import { useNamespaces } from '@/react/kubernetes/namespaces/queries';
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
@ -39,7 +35,9 @@ const settingsStore = createStore(storageKey);
export function ConfigMapsDatatable() { export function ConfigMapsDatatable() {
const tableState = useTableState(settingsStore, storageKey); const tableState = useTableState(settingsStore, storageKey);
const readOnly = !useAuthorizations(['K8sConfigMapsW']); const readOnly = !useAuthorizations(['K8sConfigMapsW']);
const { isAdmin } = useCurrentUser(); const canAccessSystemResources = useAuthorizations(
'K8sAccessSystemNamespaces'
);
const environmentId = useEnvironmentId(); const environmentId = useEnvironmentId();
const { data: namespaces, ...namespacesQuery } = useNamespaces( const { data: namespaces, ...namespacesQuery } = useNamespaces(
@ -63,10 +61,10 @@ export function ConfigMapsDatatable() {
() => () =>
configMaps?.filter( configMaps?.filter(
(configMap) => (configMap) =>
(isAdmin && tableState.showSystemResources) || (canAccessSystemResources && tableState.showSystemResources) ||
!isSystemNamespace(configMap.metadata?.namespace ?? '') !isSystemNamespace(configMap.metadata?.namespace ?? '')
) || [], ) || [],
[configMaps, tableState, isAdmin] [configMaps, tableState, canAccessSystemResources]
); );
const configMapRowData = useConfigMapRowData( const configMapRowData = useConfigMapRowData(
filteredConfigMaps, filteredConfigMaps,
@ -95,13 +93,15 @@ export function ConfigMapsDatatable() {
<TableSettingsMenu> <TableSettingsMenu>
<DefaultDatatableSettings <DefaultDatatableSettings
settings={tableState} settings={tableState}
hideShowSystemResources={!isAdmin} hideShowSystemResources={!canAccessSystemResources}
/> />
</TableSettingsMenu> </TableSettingsMenu>
)} )}
description={ description={
<SystemResourceDescription <SystemResourceDescription
showSystemResources={tableState.showSystemResources || !isAdmin} showSystemResources={
tableState.showSystemResources || !canAccessSystemResources
}
/> />
} }
/> />

View File

@ -3,11 +3,7 @@ import { Lock, Plus, Trash2 } from 'lucide-react';
import { Secret } from 'kubernetes-types/core/v1'; import { Secret } from 'kubernetes-types/core/v1';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
Authorized,
useAuthorizations,
useCurrentUser,
} from '@/react/hooks/useUser';
import { useNamespaces } from '@/react/kubernetes/namespaces/queries'; import { useNamespaces } from '@/react/kubernetes/namespaces/queries';
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
@ -39,7 +35,9 @@ const settingsStore = createStore(storageKey);
export function SecretsDatatable() { export function SecretsDatatable() {
const tableState = useTableState(settingsStore, storageKey); const tableState = useTableState(settingsStore, storageKey);
const readOnly = !useAuthorizations(['K8sSecretsW']); const readOnly = !useAuthorizations(['K8sSecretsW']);
const { isAdmin } = useCurrentUser(); const canAccessSystemResources = useAuthorizations(
'K8sAccessSystemNamespaces'
);
const environmentId = useEnvironmentId(); const environmentId = useEnvironmentId();
const { data: namespaces, ...namespacesQuery } = useNamespaces( const { data: namespaces, ...namespacesQuery } = useNamespaces(
@ -63,10 +61,10 @@ export function SecretsDatatable() {
() => () =>
secrets?.filter( secrets?.filter(
(secret) => (secret) =>
(isAdmin && tableState.showSystemResources) || (canAccessSystemResources && tableState.showSystemResources) ||
!isSystemNamespace(secret.metadata?.namespace ?? '') !isSystemNamespace(secret.metadata?.namespace ?? '')
) || [], ) || [],
[secrets, tableState, isAdmin] [secrets, tableState, canAccessSystemResources]
); );
const secretRowData = useSecretRowData( const secretRowData = useSecretRowData(
filteredSecrets, filteredSecrets,
@ -95,13 +93,15 @@ export function SecretsDatatable() {
<TableSettingsMenu> <TableSettingsMenu>
<DefaultDatatableSettings <DefaultDatatableSettings
settings={tableState} settings={tableState}
hideShowSystemResources={!isAdmin} hideShowSystemResources={!canAccessSystemResources}
/> />
</TableSettingsMenu> </TableSettingsMenu>
)} )}
description={ description={
<SystemResourceDescription <SystemResourceDescription
showSystemResources={tableState.showSystemResources || !isAdmin} showSystemResources={
tableState.showSystemResources || !canAccessSystemResources
}
/> />
} }
/> />

View File

@ -1,16 +1,20 @@
import { Plus, Trash2 } from 'lucide-react'; import { Plus, Trash2 } from 'lucide-react';
import { useRouter } from '@uirouter/react'; import { useRouter } from '@uirouter/react';
import { useMemo } from 'react';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { useNamespaces } from '@/react/kubernetes/namespaces/queries'; import { useNamespaces } from '@/react/kubernetes/namespaces/queries';
import { useAuthorizations, Authorized } from '@/react/hooks/useUser'; import { useAuthorizations, Authorized } from '@/react/hooks/useUser';
import Route from '@/assets/ico/route.svg?c'; import Route from '@/assets/ico/route.svg?c';
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils';
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
import { confirmDelete } from '@@/modals/confirm'; import { confirmDelete } from '@@/modals/confirm';
import { Datatable } from '@@/datatables'; import { Datatable, TableSettingsMenu } from '@@/datatables';
import { Button } from '@@/buttons'; import { Button } from '@@/buttons';
import { Link } from '@@/Link'; import { Link } from '@@/Link';
import { createPersistedStore } from '@@/datatables/types';
import { useTableState } from '@@/datatables/useTableState'; import { useTableState } from '@@/datatables/useTableState';
import { DeleteIngressesRequest, Ingress } from '../types'; import { DeleteIngressesRequest, Ingress } from '../types';
@ -26,26 +30,42 @@ interface SelectedIngress {
} }
const storageKey = 'ingressClassesNameSpace'; const storageKey = 'ingressClassesNameSpace';
const settingsStore = createPersistedStore(storageKey); const settingsStore = createStore(storageKey);
export function IngressDatatable() { export function IngressDatatable() {
const tableState = useTableState(settingsStore, storageKey);
const environmentId = useEnvironmentId(); const environmentId = useEnvironmentId();
const canAccessSystemResources = useAuthorizations(
'K8sAccessSystemNamespaces'
);
const { data: namespaces, ...namespacesQuery } = useNamespaces(environmentId); const { data: namespaces, ...namespacesQuery } = useNamespaces(environmentId);
const ingressesQuery = useIngresses( const { data: ingresses, ...ingressesQuery } = useIngresses(
environmentId, environmentId,
Object.keys(namespaces || {}) Object.keys(namespaces || {}),
{
autoRefreshRate: tableState.autoRefreshRate * 1000,
}
);
const filteredIngresses = useMemo(
() =>
ingresses?.filter(
(ingress) =>
(canAccessSystemResources && tableState.showSystemResources) ||
!isSystemNamespace(ingress.Namespace ?? '')
) || [],
[ingresses, tableState, canAccessSystemResources]
); );
const deleteIngressesMutation = useDeleteIngresses(); const deleteIngressesMutation = useDeleteIngresses();
const tableState = useTableState(settingsStore, storageKey);
const router = useRouter(); const router = useRouter();
return ( return (
<Datatable <Datatable
settingsManager={tableState} settingsManager={tableState}
dataset={ingressesQuery.data || []} dataset={filteredIngresses}
columns={columns} columns={columns}
isLoading={ingressesQuery.isLoading || namespacesQuery.isLoading} isLoading={ingressesQuery.isLoading || namespacesQuery.isLoading}
emptyContentLabel="No supported ingresses found" emptyContentLabel="No supported ingresses found"
@ -53,6 +73,21 @@ export function IngressDatatable() {
titleIcon={Route} titleIcon={Route}
getRowId={(row) => row.Name + row.Type + row.Namespace} getRowId={(row) => row.Name + row.Type + row.Namespace}
renderTableActions={tableActions} renderTableActions={tableActions}
renderTableSettings={() => (
<TableSettingsMenu>
<DefaultDatatableSettings
settings={tableState}
hideShowSystemResources={!canAccessSystemResources}
/>
</TableSettingsMenu>
)}
description={
<SystemResourceDescription
showSystemResources={
tableState.showSystemResources || !canAccessSystemResources
}
/>
}
disableSelect={useCheckboxes()} disableSelect={useCheckboxes()}
/> />
); );
@ -62,7 +97,6 @@ export function IngressDatatable() {
<div className="ingressDatatable-actions"> <div className="ingressDatatable-actions">
<Authorized authorizations="AzureContainerGroupDelete"> <Authorized authorizations="AzureContainerGroupDelete">
<Button <Button
className="btn-wrapper"
color="dangerlight" color="dangerlight"
disabled={selectedFlatRows.length === 0} disabled={selectedFlatRows.length === 0}
onClick={() => handleRemoveClick(selectedFlatRows)} onClick={() => handleRemoveClick(selectedFlatRows)}
@ -77,11 +111,7 @@ export function IngressDatatable() {
to="kubernetes.ingresses.create" to="kubernetes.ingresses.create"
className="space-left no-decoration" className="space-left no-decoration"
> >
<Button <Button icon={Plus} color="secondary">
icon={Plus}
className="btn-wrapper vertical-center"
color="secondary"
>
Add with form Add with form
</Button> </Button>
</Link> </Link>
@ -92,9 +122,7 @@ export function IngressDatatable() {
className="space-left no-decoration" className="space-left no-decoration"
params={{ referrer: 'kubernetes.ingresses' }} params={{ referrer: 'kubernetes.ingresses' }}
> >
<Button icon={Plus} className="btn-wrapper"> <Button icon={Plus}>Create from manifest</Button>
Create from manifest
</Button>
</Link> </Link>
</Authorized> </Authorized>
</div> </div>

View File

@ -3,4 +3,5 @@ import { columnHelper } from './helper';
export const className = columnHelper.accessor('ClassName', { export const className = columnHelper.accessor('ClassName', {
header: 'Class Name', header: 'Class Name',
id: 'className', id: 'className',
cell: ({ row }) => row.original.ClassName || '-',
}); });

View File

@ -32,7 +32,7 @@ function Cell({ row }: CellContext<Ingress, string>) {
} }
return ( return (
<div className="flex flex-col gap-y-0.5"> <div className="flex flex-col gap-y-0.5 whitespace-nowrap">
{paths.map((path) => ( {paths.map((path) => (
<div key={`${path.Host}${path.Path}${path.ServiceName}:${path.Port}`}> <div key={`${path.Host}${path.Path}${path.ServiceName}:${path.Port}`}>
<span className="flex flex-nowrap items-center gap-1 px-2"> <span className="flex flex-nowrap items-center gap-1 px-2">

View File

@ -1,8 +1,10 @@
import { CellContext } from '@tanstack/react-table'; import { CellContext } from '@tanstack/react-table';
import { Authorized } from '@/react/hooks/useUser'; import { Authorized } from '@/react/hooks/useUser';
import { isSystemNamespace } from '@/react/kubernetes/namespaces/utils';
import { Link } from '@@/Link'; import { Link } from '@@/Link';
import { Badge } from '@@/Badge';
import { Ingress } from '../../types'; import { Ingress } from '../../types';
@ -16,20 +18,29 @@ export const name = columnHelper.accessor('Name', {
function Cell({ row, getValue }: CellContext<Ingress, string>) { function Cell({ row, getValue }: CellContext<Ingress, string>) {
const name = getValue(); const name = getValue();
const namespace = row.original.Namespace;
const isSystemIngress = isSystemNamespace(namespace);
return ( return (
<Authorized authorizations="K8sIngressesW" childrenUnauthorized={name}> <div className="flex whitespace-nowrap">
<Link <Authorized authorizations="K8sIngressesW" childrenUnauthorized={name}>
to="kubernetes.ingresses.edit" <Link
params={{ to="kubernetes.ingresses.edit"
uid: row.original.UID, params={{
namespace: row.original.Namespace, uid: row.original.UID,
name, namespace,
}} name,
title={name} }}
> title={name}
{name} >
</Link> {name}
</Authorized> </Link>
</Authorized>
{isSystemIngress && (
<Badge type="success" className="ml-2">
system
</Badge>
)}
</div>
); );
} }

View File

@ -3,4 +3,5 @@ import { columnHelper } from './helper';
export const type = columnHelper.accessor('Type', { export const type = columnHelper.accessor('Type', {
header: 'Type', header: 'Type',
id: 'type', id: 'type',
cell: ({ row }) => row.original.Type || '-',
}); });

View File

@ -55,7 +55,8 @@ export function useIngress(
export function useIngresses( export function useIngresses(
environmentId: EnvironmentId, environmentId: EnvironmentId,
namespaces?: string[] namespaces?: string[],
options?: { autoRefreshRate?: number }
) { ) {
return useQuery( return useQuery(
[ [
@ -117,6 +118,9 @@ export function useIngresses(
{ {
enabled: !!namespaces?.length, enabled: !!namespaces?.length,
...withError('Unable to get ingresses'), ...withError('Unable to get ingresses'),
refetchInterval() {
return options?.autoRefreshRate ?? false;
},
} }
); );
} }

View File

@ -4,11 +4,7 @@ import clsx from 'clsx';
import { Row } from '@tanstack/react-table'; import { Row } from '@tanstack/react-table';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
Authorized,
useAuthorizations,
useCurrentUser,
} from '@/react/hooks/useUser';
import { notifyError, notifySuccess } from '@/portainer/services/notifications'; import { notifyError, notifySuccess } from '@/portainer/services/notifications';
import { pluralize } from '@/portainer/helpers/strings'; import { pluralize } from '@/portainer/helpers/strings';
@ -48,11 +44,13 @@ export function ServicesDatatable() {
); );
const readOnly = !useAuthorizations(['K8sServiceW']); const readOnly = !useAuthorizations(['K8sServiceW']);
const { isAdmin } = useCurrentUser(); const canAccessSystemResources = useAuthorizations(
'K8sAccessSystemNamespaces'
);
const filteredServices = services?.filter( const filteredServices = services?.filter(
(service) => (service) =>
(isAdmin && tableState.showSystemResources) || (canAccessSystemResources && tableState.showSystemResources) ||
!isSystemNamespace(service.Namespace) !isSystemNamespace(service.Namespace)
); );
@ -75,13 +73,15 @@ export function ServicesDatatable() {
<TableSettingsMenu> <TableSettingsMenu>
<DefaultDatatableSettings <DefaultDatatableSettings
settings={tableState} settings={tableState}
hideShowSystemResources={!isAdmin} hideShowSystemResources={!canAccessSystemResources}
/> />
</TableSettingsMenu> </TableSettingsMenu>
)} )}
description={ description={
<SystemResourceDescription <SystemResourceDescription
showSystemResources={tableState.showSystemResources || !isAdmin} showSystemResources={
tableState.showSystemResources || !canAccessSystemResources
}
/> />
} }
renderRow={servicesRenderRow} renderRow={servicesRenderRow}