mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
refactor(settings): migrate hidden containers panel to react [EE-5507] (#9119)
This commit is contained in:
parent
eefb4c4287
commit
8b11e1678e
8 changed files with 230 additions and 139 deletions
|
@ -2,10 +2,11 @@ import clsx from 'clsx';
|
|||
import { ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children?: ReactNode;
|
||||
children: ReactNode;
|
||||
label: string;
|
||||
colClassName?: string;
|
||||
className?: string;
|
||||
columns?: Array<ReactNode>;
|
||||
}
|
||||
|
||||
export function DetailsRow({
|
||||
|
@ -13,17 +14,21 @@ export function DetailsRow({
|
|||
children,
|
||||
colClassName,
|
||||
className,
|
||||
columns,
|
||||
}: Props) {
|
||||
return (
|
||||
<tr className={className}>
|
||||
<td className={clsx(colClassName, 'min-w-[150px] !break-normal')}>
|
||||
{label}
|
||||
</td>
|
||||
{!!children && (
|
||||
<td className={colClassName} data-cy={`detailsTable-${label}Value`}>
|
||||
{children}
|
||||
<td className={colClassName} data-cy={`detailsTable-${label}Value`}>
|
||||
{children}
|
||||
</td>
|
||||
{columns?.map((column, index) => (
|
||||
<td key={index} className={colClassName}>
|
||||
{column}
|
||||
</td>
|
||||
)}
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Children, PropsWithChildren } from 'react';
|
||||
|
||||
type Props = {
|
||||
headers?: string[];
|
||||
dataCy?: string;
|
||||
className?: string;
|
||||
emptyMessage?: string;
|
||||
};
|
||||
|
||||
export function DetailsTable({
|
||||
headers = [],
|
||||
dataCy,
|
||||
className,
|
||||
emptyMessage,
|
||||
children,
|
||||
}: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<table className="table" data-cy={dataCy}>
|
||||
<table className={clsx('table', className)} data-cy={dataCy}>
|
||||
{headers.length > 0 && (
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -21,7 +26,17 @@ export function DetailsTable({
|
|||
</tr>
|
||||
</thead>
|
||||
)}
|
||||
{children && <tbody>{children}</tbody>}
|
||||
<tbody>
|
||||
{Children.count(children) > 0 ? (
|
||||
children
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={headers.length} className="text-muted text-center">
|
||||
{emptyMessage}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import { Formik, Form, Field } from 'formik';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { SchemaOf, object, string } from 'yup';
|
||||
import { useReducer } from 'react';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
import { Input } from '@@/form-components/Input';
|
||||
|
||||
export function AddLabelForm({
|
||||
onSubmit,
|
||||
isLoading,
|
||||
}: {
|
||||
onSubmit: (name: string, value: string) => void;
|
||||
isLoading: boolean;
|
||||
}) {
|
||||
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
||||
|
||||
const initialValues = {
|
||||
name: '',
|
||||
value: '',
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
validationSchema={validation}
|
||||
key={formKey}
|
||||
>
|
||||
{({ errors, isValid, dirty }) => (
|
||||
<Form className="form-horizontal">
|
||||
<div className="flex w-full items-start gap-4">
|
||||
<FormControl label="Name" errors={errors.name} className="flex-1">
|
||||
<Field
|
||||
as={Input}
|
||||
name="name"
|
||||
placeholder="e.g. com.example.foo"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl label="Value" errors={errors.value} className="flex-1">
|
||||
<Field as={Input} name="value" placeholder="e.g. bar" />
|
||||
</FormControl>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
icon={Plus}
|
||||
disabled={!dirty || !isValid || isLoading}
|
||||
>
|
||||
Add filter
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
|
||||
function handleSubmit(values: typeof initialValues) {
|
||||
clearForm();
|
||||
onSubmit(values.name, values.value);
|
||||
}
|
||||
}
|
||||
|
||||
function validation(): SchemaOf<{ name: string; value: string }> {
|
||||
return object({
|
||||
name: string().required('Name is required'),
|
||||
value: string().default(''),
|
||||
});
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import { Box } from 'lucide-react';
|
||||
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
import { Widget } from '@@/Widget';
|
||||
|
||||
import { useSettings, useUpdateSettingsMutation } from '../../queries';
|
||||
import { Pair } from '../../types';
|
||||
|
||||
import { AddLabelForm } from './AddLabelForm';
|
||||
import { HiddenContainersTable } from './HiddenContainersTable';
|
||||
|
||||
export function HiddenContainersPanel() {
|
||||
const settingsQuery = useSettings((settings) => settings.BlackListedLabels);
|
||||
const mutation = useUpdateSettingsMutation();
|
||||
|
||||
if (!settingsQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labels = settingsQuery.data;
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<Widget>
|
||||
<Widget.Title icon={Box} title="Hidden containers" />
|
||||
<Widget.Body>
|
||||
<div className="mb-3">
|
||||
<TextTip color="blue">
|
||||
You can hide containers with specific labels from Portainer UI.
|
||||
You need to specify the label name and value.
|
||||
</TextTip>
|
||||
</div>
|
||||
|
||||
<AddLabelForm
|
||||
isLoading={mutation.isLoading}
|
||||
onSubmit={(name, value) =>
|
||||
handleSubmit([...labels, { name, value }])
|
||||
}
|
||||
/>
|
||||
|
||||
<HiddenContainersTable
|
||||
labels={labels}
|
||||
isLoading={mutation.isLoading}
|
||||
onDelete={(name) =>
|
||||
handleSubmit(labels.filter((label) => label.name !== name))
|
||||
}
|
||||
/>
|
||||
</Widget.Body>
|
||||
</Widget>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
function handleSubmit(labels: Pair[]) {
|
||||
mutation.mutate(
|
||||
{
|
||||
BlackListedLabels: labels,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifySuccess('Success', 'Hidden container settings updated');
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import { Trash2 } from 'lucide-react';
|
||||
|
||||
import { DetailsTable } from '@@/DetailsTable';
|
||||
import { Button } from '@@/buttons';
|
||||
|
||||
import { Pair } from '../../types';
|
||||
|
||||
export function HiddenContainersTable({
|
||||
labels,
|
||||
isLoading,
|
||||
onDelete,
|
||||
}: {
|
||||
labels: Pair[];
|
||||
isLoading: boolean;
|
||||
onDelete: (name: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<DetailsTable
|
||||
headers={['Name', 'Value', '']}
|
||||
className="table-hover"
|
||||
emptyMessage="No filter available."
|
||||
>
|
||||
{labels.map((label, index) => (
|
||||
<DetailsTable.Row
|
||||
key={index}
|
||||
label={label.name}
|
||||
columns={[
|
||||
<Button
|
||||
color="danger"
|
||||
size="xsmall"
|
||||
icon={Trash2}
|
||||
onClick={() => onDelete(label.name)}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Remove
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
{label.value}
|
||||
</DetailsTable.Row>
|
||||
))}
|
||||
</DetailsTable>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue