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:
parent
0f1e77a6d5
commit
515b02813b
59 changed files with 1819 additions and 833 deletions
|
@ -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);
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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) {
|
|
@ -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 {}
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue