1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +02:00

feat(ingress): ingresses datatable with add/edit ingresses EE-2615 (#7672)

This commit is contained in:
Prabhat Khera 2022-09-21 16:49:42 +12:00 committed by GitHub
parent 393d1fc91d
commit ef1d648c07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 4938 additions and 61 deletions

View file

@ -1,20 +1,25 @@
import { PropsWithChildren } from 'react';
import { PropsWithChildren, AnchorHTMLAttributes } from 'react';
import { UISref, UISrefProps } from '@uirouter/react';
import clsx from 'clsx';
interface Props {
title?: string;
target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
}
export function Link({
title = '',
className,
children,
...props
}: PropsWithChildren<Props> & UISrefProps) {
return (
// eslint-disable-next-line react/jsx-props-no-spreading
<UISref {...props}>
<UISref className={clsx('no-decoration', className)} {...props}>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a title={title}>{children}</a>
<a title={title} target={props.target}>
{children}
</a>
</UISref>
);
}

View file

@ -141,7 +141,11 @@ export function Datatable<
<TableSettingsProvider settings={settingsStore}>
<Table.Container>
{isTitleVisible(titleOptions) && (
<Table.Title label={titleOptions.title} icon={titleOptions.icon}>
<Table.Title
label={titleOptions.title}
icon={titleOptions.icon}
featherIcon={titleOptions.featherIcon}
>
<SearchBar value={searchBarValue} onChange={setGlobalFilter} />
{renderTableActions && (
<Table.Actions>

View file

@ -3,42 +3,22 @@ import { useMemo } from 'react';
import { Menu, MenuButton, MenuPopover } from '@reach/menu-button';
import { ColumnInstance } from 'react-table';
export function DefaultFilter({
column: { filterValue, setFilter, preFilteredRows, id },
}: {
column: ColumnInstance;
}) {
const options = useMemo(() => {
const options = new Set<string>();
preFilteredRows.forEach((row) => {
options.add(row.values[id]);
});
return Array.from(options);
}, [id, preFilteredRows]);
return (
<MultipleSelectionFilter
options={options}
filterKey={id}
value={filterValue}
onChange={setFilter}
/>
);
}
export const DefaultFilter = filterHOC('Filter by state');
interface MultipleSelectionFilterProps {
options: string[];
value: string[];
filterKey: string;
onChange: (value: string[]) => void;
menuTitle?: string;
}
function MultipleSelectionFilter({
export function MultipleSelectionFilter({
options,
value = [],
filterKey,
onChange,
menuTitle = 'Filter by state',
}: MultipleSelectionFilterProps) {
const enabled = value.length > 0;
return (
@ -59,7 +39,7 @@ function MultipleSelectionFilter({
</MenuButton>
<MenuPopover className="dropdown-menu">
<div className="tableMenu">
<div className="menuHeader">Filter by state</div>
<div className="menuHeader">{menuTitle}</div>
<div className="menuContent">
{options.map((option, index) => (
<div className="md-checkbox" key={index}>
@ -91,3 +71,28 @@ function MultipleSelectionFilter({
onChange([...value, option]);
}
}
export function filterHOC(menuTitle: string) {
return function Filter({
column: { filterValue, setFilter, preFilteredRows, id },
}: {
column: ColumnInstance;
}) {
const options = useMemo(() => {
const options = new Set<string>();
preFilteredRows.forEach((row) => {
options.add(row.values[id]);
});
return Array.from(options);
}, [id, preFilteredRows]);
return (
<MultipleSelectionFilter
options={options}
filterKey={id}
value={filterValue}
onChange={setFilter}
menuTitle={menuTitle}
/>
);
};
}

View file

@ -1,16 +1,17 @@
import { PropsWithChildren } from 'react';
import clsx from 'clsx';
import { Icon } from '@@/Icon';
export function FormError({ children }: PropsWithChildren<unknown>) {
interface Props {
className?: string;
}
export function FormError({ children, className }: PropsWithChildren<Props>) {
return (
<div className="small text-warning vertical-center">
<Icon
icon="alert-triangle"
feather
className="icon icon-sm icon-warning"
/>
{children}
</div>
<p className={clsx(`text-muted small vertical-center`, className)}>
<Icon icon="alert-triangle" className="icon-warning" feather />
<span className="text-warning">{children}</span>
</p>
);
}

View file

@ -57,7 +57,7 @@ export function ContainersDatatable({
<RowProvider context={{ environment }}>
<Datatable
titleOptions={{
icon: 'fa-cubes',
icon: 'svg-cubes',
title: 'Containers',
}}
settingsStore={settings}

View file

@ -0,0 +1,29 @@
import { useQuery } from 'react-query';
import { EnvironmentId } from '@/portainer/environments/types';
import { error as notifyError } from '@/portainer/services/notifications';
import { getConfigMaps } from './service';
export function useConfigurations(
environmentId: EnvironmentId,
namespace?: string
) {
return useQuery(
[
'environments',
environmentId,
'kubernetes',
'namespaces',
namespace,
'configurations',
],
() => (namespace ? getConfigMaps(environmentId, namespace) : []),
{
onError: (err) => {
notifyError('Failure', err as Error, 'Unable to get configurations');
},
enabled: !!namespace,
}
);
}

View file

@ -0,0 +1,18 @@
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/portainer/environments/types';
import { Configuration } from './types';
export async function getConfigMaps(
environmentId: EnvironmentId,
namespace: string
) {
try {
const { data: configmaps } = await axios.get<Configuration[]>(
`kubernetes/${environmentId}/namespaces/${namespace}/configmaps`
);
return configmaps;
} catch (e) {
throw parseAxiosError(e as Error, 'Unable to retrieve configmaps');
}
}

View file

@ -0,0 +1,17 @@
export interface Configuration {
Id: string;
Name: string;
Type: number;
Namespace: string;
CreationDate: Date;
ConfigurationOwner: string;
Used: boolean;
// Applications: any[];
Data: Document;
Yaml: string;
SecretType?: string;
IsRegistrySecret?: boolean;
}

View file

@ -0,0 +1,30 @@
import { useQuery } from 'react-query';
import { EnvironmentId } from '@/portainer/environments/types';
import { error as notifyError } from '@/portainer/services/notifications';
import { getNamespaces, getNamespace } from './service';
export function useNamespaces(environmentId: EnvironmentId) {
return useQuery(
['environments', environmentId, 'kubernetes', 'namespaces'],
() => getNamespaces(environmentId),
{
onError: (err) => {
notifyError('Failure', err as Error, 'Unable to get namespaces.');
},
}
);
}
export function useNamespace(environmentId: EnvironmentId, namespace: string) {
return useQuery(
['environments', environmentId, 'kubernetes', 'namespaces', namespace],
() => getNamespace(environmentId, namespace),
{
onError: (err) => {
notifyError('Failure', err as Error, 'Unable to get namespace.');
},
}
);
}

View file

@ -0,0 +1,39 @@
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/portainer/environments/types';
import { Namespaces } from './types';
export async function getNamespace(
environmentId: EnvironmentId,
namespace: string
) {
try {
const { data: ingress } = await axios.get<Namespaces>(
buildUrl(environmentId, namespace)
);
return ingress;
} catch (e) {
throw parseAxiosError(e as Error, 'Unable to retrieve network details');
}
}
export async function getNamespaces(environmentId: EnvironmentId) {
try {
const { data: ingresses } = await axios.get<Namespaces>(
buildUrl(environmentId)
);
return ingresses;
} catch (e) {
throw parseAxiosError(e as Error, 'Unable to retrieve network details');
}
}
function buildUrl(environmentId: EnvironmentId, namespace?: string) {
let url = `kubernetes/${environmentId}/namespaces`;
if (namespace) {
url += `/${namespace}`;
}
return url;
}

View file

@ -0,0 +1,6 @@
export interface Namespaces {
[key: string]: {
IsDefault: boolean;
IsSystem: boolean;
};
}

View file

@ -0,0 +1,5 @@
## Common Services
This folder contains rest api services that are shared by different features within kubernetes.
This includes api requests to the portainer backend, and also requests to the kubernetes api.

View file

@ -3,6 +3,7 @@ import { Box, Edit, Layers, Lock, Server } from 'react-feather';
import { EnvironmentId } from '@/portainer/environments/types';
import { Authorized } from '@/portainer/hooks/useUser';
import Helm from '@/assets/ico/vendor/helm.svg?c';
import Route from '@/assets/ico/route.svg?c';
import { DashboardLink } from '../items/DashboardLink';
import { SidebarItem } from '../SidebarItem';
@ -69,6 +70,14 @@ export function KubernetesSidebar({ environmentId }: Props) {
data-cy="k8sSidebar-applications"
/>
<SidebarItem
to="kubernetes.ingresses"
params={{ endpointId: environmentId }}
label="Ingresses"
data-cy="k8sSidebar-ingresses"
icon={Route}
/>
<SidebarItem
to="kubernetes.configurations"
params={{ endpointId: environmentId }}
@ -97,7 +106,7 @@ export function KubernetesSidebar({ environmentId }: Props) {
>
<SidebarItem
to="kubernetes.cluster.setup"
params={{ id: environmentId }}
params={{ endpointId: environmentId }}
label="Setup"
data-cy="k8sSidebar-setup"
/>
@ -110,7 +119,7 @@ export function KubernetesSidebar({ environmentId }: Props) {
>
<SidebarItem
to="kubernetes.cluster.securityConstraint"
params={{ id: environmentId }}
params={{ endpointId: environmentId }}
label="Security constraints"
data-cy="k8sSidebar-securityConstraints"
/>