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

feat(k8sconfigure): migrate configure to react [EE-5524] (#10218)

This commit is contained in:
Ali 2023-09-05 18:06:36 +02:00 committed by GitHub
parent 0f1e77a6d5
commit 515b02813b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 1819 additions and 833 deletions

View file

@ -10,7 +10,7 @@ import { createPersistedStore } from '@@/datatables/types';
import { buildConfirmButton } from '@@/modals/utils';
import { useTableState } from '@@/datatables/useTableState';
import { IngressControllerClassMap } from '../types';
import { IngressControllerClassMapRowData } from '../types';
import { columns } from './columns';
@ -19,10 +19,11 @@ const settingsStore = createPersistedStore(storageKey, 'name');
interface Props {
onChangeControllers: (
controllerClassMap: IngressControllerClassMap[]
controllerClassMap: IngressControllerClassMapRowData[]
) => void; // angular function to save the ingress class list
description: string;
ingressControllers: IngressControllerClassMap[] | undefined;
ingressControllers: IngressControllerClassMapRowData[] | undefined;
initialIngressControllers: IngressControllerClassMapRowData[] | undefined;
allowNoneIngressClass: boolean;
isLoading: boolean;
noIngressControllerLabel: string;
@ -32,6 +33,7 @@ interface Props {
export function IngressClassDatatable({
onChangeControllers,
description,
initialIngressControllers,
ingressControllers,
allowNoneIngressClass,
isLoading,
@ -44,12 +46,23 @@ export function IngressClassDatatable({
ingressControllers || []
);
// set the ingress controller form values when the ingress controller list changes
// and the ingress controller form values are not set
useEffect(() => {
if (allowNoneIngressClass === undefined) {
if (
ingressControllers &&
ingControllerFormValues.length !== ingressControllers.length
) {
setIngControllerFormValues(ingressControllers);
}
}, [ingressControllers, ingControllerFormValues]);
useEffect(() => {
if (allowNoneIngressClass === undefined || isLoading) {
return;
}
let newIngFormValues: IngressControllerClassMap[];
let newIngFormValues: IngressControllerClassMapRowData[];
const isCustomTypeExist = ingControllerFormValues.some(
(ic) => ic.Type === 'custom'
);
@ -93,7 +106,9 @@ export function IngressClassDatatable({
</div>
);
function renderTableActions(selectedRows: IngressControllerClassMap[]) {
function renderTableActions(
selectedRows: IngressControllerClassMapRowData[]
) {
return (
<div className="flex items-start">
<ButtonGroup>
@ -140,9 +155,12 @@ export function IngressClassDatatable({
return (
<div className="text-muted flex w-full flex-col !text-xs">
<div className="mt-1">{description}</div>
{ingressControllers &&
{initialIngressControllers &&
ingControllerFormValues &&
isUnsavedChanges(ingressControllers, ingControllerFormValues) && (
isUnsavedChanges(
initialIngressControllers,
ingControllerFormValues
) && (
<span className="text-warning mt-1 flex items-center">
<Icon icon={AlertTriangle} className="!mr-1" />
<span className="text-warning">Unsaved changes.</span>
@ -153,8 +171,8 @@ export function IngressClassDatatable({
}
async function updateIngressControllers(
selectedRows: IngressControllerClassMap[],
ingControllerFormValues: IngressControllerClassMap[],
selectedRows: IngressControllerClassMapRowData[],
ingControllerFormValues: IngressControllerClassMapRowData[],
availability: boolean
) {
const updatedIngressControllers = getUpdatedIngressControllers(
@ -222,8 +240,8 @@ export function IngressClassDatatable({
}
function isUnsavedChanges(
oldIngressControllers: IngressControllerClassMap[],
newIngressControllers: IngressControllerClassMap[]
oldIngressControllers: IngressControllerClassMapRowData[],
newIngressControllers: IngressControllerClassMapRowData[]
) {
if (oldIngressControllers.length !== newIngressControllers.length) {
return true;
@ -240,8 +258,8 @@ function isUnsavedChanges(
}
function getUpdatedIngressControllers(
selectedRows: IngressControllerClassMap[],
allRows: IngressControllerClassMap[],
selectedRows: IngressControllerClassMapRowData[],
allRows: IngressControllerClassMapRowData[],
allow: boolean
) {
const selectedRowClassNames = selectedRows.map((row) => row.ClassName);

View file

@ -4,7 +4,7 @@ import { Check, X } from 'lucide-react';
import { Badge } from '@@/Badge';
import { Icon } from '@@/Icon';
import type { IngressControllerClassMap } from '../../types';
import type { IngressControllerClassMapRowData } from '../../types';
import { columnHelper } from './helper';
@ -16,7 +16,9 @@ export const availability = columnHelper.accessor('Availability', {
sortingFn: 'basic',
});
function Cell({ getValue }: CellContext<IngressControllerClassMap, boolean>) {
function Cell({
getValue,
}: CellContext<IngressControllerClassMapRowData, boolean>) {
const availability = getValue();
return (

View file

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

View file

@ -2,7 +2,7 @@ import { CellContext } from '@tanstack/react-table';
import { Badge } from '@@/Badge';
import type { IngressControllerClassMap } from '../../types';
import type { IngressControllerClassMapRowData } from '../../types';
import { columnHelper } from './helper';
@ -15,7 +15,7 @@ export const name = columnHelper.accessor('ClassName', {
function NameCell({
row,
getValue,
}: CellContext<IngressControllerClassMap, string>) {
}: CellContext<IngressControllerClassMapRowData, string>) {
const className = getValue();
return (

View file

@ -6,3 +6,24 @@ import {
export interface TableSettings
extends SortableTableSettings,
PaginationTableSettings {}
export type SupportedIngControllerTypes =
| 'nginx'
| 'traefik'
| 'other'
| 'custom';
// Not having 'extends Record<string, unknown>' fixes validation type errors from yup
export interface IngressControllerClassMap {
Name: string;
ClassName: string;
Type: string;
Availability: boolean;
New: boolean;
Used: boolean; // if the controller is used by any ingress in the cluster
}
// Record<string, unknown> fixes type errors when using the type with a react datatable
export interface IngressControllerClassMapRowData
extends Record<string, unknown>,
IngressControllerClassMap {}

View file

@ -1,8 +1,45 @@
import { useQuery } from 'react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import PortainerError from '@/portainer/error';
import axios from '@/portainer/services/axios';
import { withError } from '@/react-tools/react-query';
import { IngressControllerClassMap } from '../types';
import { IngressControllerClassMapRowData } from './types';
export function useIngressControllerClassMapQuery({
environmentId,
namespace,
allowedOnly,
}: {
environmentId?: EnvironmentId;
namespace?: string;
allowedOnly?: boolean;
}) {
return useQuery(
[
'environments',
environmentId,
'ingresscontrollers',
namespace,
allowedOnly,
],
() => {
if (!environmentId) {
return [];
}
return getIngressControllerClassMap({
environmentId,
namespace,
allowedOnly,
});
},
{
...withError('Failure', 'Unable to get ingress controllers.'),
enabled: !!environmentId,
}
);
}
// get all supported ingress classes and controllers for the cluster
// allowedOnly set to true will hide globally disallowed ingresscontrollers
@ -17,7 +54,7 @@ export async function getIngressControllerClassMap({
}) {
try {
const { data: controllerMaps } = await axios.get<
IngressControllerClassMap[]
IngressControllerClassMapRowData[]
>(
buildUrl(environmentId, namespace),
allowedOnly ? { params: { allowedOnly: true } } : undefined
@ -31,12 +68,12 @@ export async function getIngressControllerClassMap({
// get all supported ingress classes and controllers for the cluster
export async function updateIngressControllerClassMap(
environmentId: EnvironmentId,
ingressControllerClassMap: IngressControllerClassMap[],
ingressControllerClassMap: IngressControllerClassMapRowData[],
namespace?: string
) {
try {
const { data: controllerMaps } = await axios.put<
IngressControllerClassMap[]
IngressControllerClassMapRowData[]
>(buildUrl(environmentId, namespace), ingressControllerClassMap);
return controllerMaps;
} catch (e) {

View file

@ -4,11 +4,17 @@ export type SupportedIngControllerTypes =
| 'other'
| 'custom';
export interface IngressControllerClassMap extends Record<string, unknown> {
// Not having 'extends Record<string, unknown>' fixes validation type errors from yup
export interface IngressControllerClassMap {
Name: string;
ClassName: string;
Type: SupportedIngControllerTypes;
Type: string;
Availability: boolean;
New: boolean;
Used: boolean; // if the controller is used by any ingress in the cluster
}
// Record<string, unknown> fixes type errors when using the type with a react datatable
export interface IngressControllerClassMapRowData
extends Record<string, unknown>,
IngressControllerClassMap {}

View file

@ -0,0 +1,94 @@
import { useQuery } from 'react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import PortainerError from '@/portainer/error';
import axios from '@/portainer/services/axios';
import { withError } from '@/react-tools/react-query';
import { IngressControllerClassMapRowData } from './types';
export function useIngressControllerClassMapQuery({
environmentId,
namespace,
allowedOnly,
}: {
environmentId?: EnvironmentId;
namespace?: string;
allowedOnly?: boolean;
}) {
return useQuery(
[
'environments',
environmentId,
'ingresscontrollers',
namespace,
allowedOnly,
],
() => {
if (!environmentId) {
return [];
}
return getIngressControllerClassMap({
environmentId,
namespace,
allowedOnly,
});
},
{
...withError('Failure', 'Unable to get ingress controllers.'),
enabled: !!environmentId,
}
);
}
// get all supported ingress classes and controllers for the cluster
// allowedOnly set to true will hide globally disallowed ingresscontrollers
export async function getIngressControllerClassMap({
environmentId,
namespace,
allowedOnly,
}: {
environmentId: EnvironmentId;
namespace?: string;
allowedOnly?: boolean;
}) {
try {
const { data: controllerMaps } = await axios.get<
IngressControllerClassMapRowData[]
>(
buildUrl(environmentId, namespace),
allowedOnly ? { params: { allowedOnly: true } } : undefined
);
return controllerMaps;
} catch (e) {
throw new PortainerError('Unable to get ingress controllers.', e as Error);
}
}
// get all supported ingress classes and controllers for the cluster
export async function updateIngressControllerClassMap(
environmentId: EnvironmentId,
ingressControllerClassMap: IngressControllerClassMapRowData[],
namespace?: string
) {
try {
const { data: controllerMaps } = await axios.put<
IngressControllerClassMapRowData[]
>(buildUrl(environmentId, namespace), ingressControllerClassMap);
return controllerMaps;
} catch (e) {
throw new PortainerError(
'Unable to update ingress controllers.',
e as Error
);
}
}
function buildUrl(environmentId: EnvironmentId, namespace?: string) {
let url = `kubernetes/${environmentId}/`;
if (namespace) {
url += `namespaces/${namespace}/`;
}
url += 'ingresscontrollers';
return url;
}