refactor(settings): migrate hidden containers panel to react [EE-5507] (#9119)

pull/9163/head
Chaim Lev-Ari 2023-07-10 03:39:11 +07:00 committed by GitHub
parent eefb4c4287
commit 8b11e1678e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 230 additions and 139 deletions

View File

@ -9,6 +9,7 @@ import { withUIRouter } from '@/react-tools/withUIRouter';
import { ApplicationSettingsPanel } from '@/react/portainer/settings/SettingsView/ApplicationSettingsPanel';
import { KubeSettingsPanel } from '@/react/portainer/settings/SettingsView/KubeSettingsPanel';
import { HelmCertPanel } from '@/react/portainer/settings/SettingsView/HelmCertPanel';
import { HiddenContainersPanel } from '@/react/portainer/settings/SettingsView/HiddenContainersPanel/HiddenContainersPanel';
export const settingsModule = angular
.module('portainer.app.react.components.settings', [])
@ -26,6 +27,10 @@ export const settingsModule = angular
r2a(withReactQuery(ApplicationSettingsPanel), ['onSuccess'])
)
.component('helmCertPanel', r2a(withReactQuery(HelmCertPanel), []))
.component(
'hiddenContainersPanel',
r2a(withUIRouter(withReactQuery(HiddenContainersPanel)), [])
)
.component(
'kubeSettingsPanel',
r2a(withUIRouter(withReactQuery(KubeSettingsPanel)), [])

View File

@ -8,65 +8,7 @@
<ssl-certificate-settings ng-show="state.showHTTPS"></ssl-certificate-settings>
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="box" title-text="Hidden containers"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal" ng-submit="addFilteredContainerLabel()" name="addTagForm">
<div class="form-group">
<span class="col-sm-12 text-muted small"> You can hide containers with specific labels from Portainer UI. You need to specify the label name and value. </span>
</div>
<div class="form-group">
<label for="header_name" class="col-sm-1 control-label text-left">Name</label>
<div class="col-sm-11 col-md-4">
<input type="text" required class="form-control" id="header_name" name="label_name" ng-model="formValues.labelName" placeholder="e.g. com.example.foo" />
</div>
<label for="header_value" class="col-sm-1 control-label text-left">Value</label>
<div class="col-sm-11 col-md-4">
<input type="text" class="form-control" id="header_value" ng-model="formValues.labelValue" placeholder="e.g. bar" />
</div>
<div class="col-sm-12 col-md-2">
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="!formValues.labelName"><pr-icon icon="'plus'" class-name="'mr-1'"></pr-icon>Add filter</button>
</div>
</div>
<div class="form-group">
<div class="col-sm-12 table-responsive">
<table class="table-hover table">
<thead>
<tr>
<th>Name</th>
<th>Value</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="label in formValues.BlackListedLabels">
<td>{{ label.name }}</td>
<td>{{ label.value }}</td>
<td>
<button type="button" class="btn btn-danger btn-xs" ng-click="removeFilteredContainerLabel($index)">
<pr-icon icon="'trash-2'" class-name="'mr-1'"></pr-icon>
Remove</button
>
</td>
</tr>
<tr ng-if="formValues.BlackListedLabels.length === 0">
<td colspan="3" class="text-muted text-center">No filter available.</td>
</tr>
<tr ng-if="!formValues.BlackListedLabels">
<td colspan="3" class="text-muted text-center">Loading...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- !filtered-labels -->
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>
<hidden-containers-panel></hidden-containers-panel>
<!-- backup -->
<backup-settings-panel></backup-settings-panel>

View File

@ -1,78 +1,20 @@
import angular from 'angular';
angular.module('portainer.app').controller('SettingsController', [
'$scope',
'Notifications',
'SettingsService',
'StateManager',
function ($scope, Notifications, SettingsService, StateManager) {
$scope.updateSettings = updateSettings;
$scope.handleSuccess = handleSuccess;
angular.module('portainer.app').controller('SettingsController', SettingsController);
$scope.state = {
actionInProgress: false,
showHTTPS: !window.ddExtension,
};
/* @ngInject */
function SettingsController($scope, StateManager) {
$scope.handleSuccess = handleSuccess;
$scope.formValues = {
BlackListedLabels: [],
labelName: '',
labelValue: '',
};
$scope.state = {
showHTTPS: !window.ddExtension,
};
$scope.removeFilteredContainerLabel = function (index) {
const filteredSettings = $scope.formValues.BlackListedLabels.filter((_, i) => i !== index);
const filteredSettingsPayload = { BlackListedLabels: filteredSettings };
updateSettings(filteredSettingsPayload, 'Hidden container settings updated');
};
$scope.addFilteredContainerLabel = function () {
var label = {
name: $scope.formValues.labelName,
value: $scope.formValues.labelValue,
};
const filteredSettings = [...$scope.formValues.BlackListedLabels, label];
const filteredSettingsPayload = { BlackListedLabels: filteredSettings };
updateSettings(filteredSettingsPayload, 'Hidden container settings updated');
};
function updateSettings(settings, successMessage = 'Settings updated') {
return SettingsService.update(settings)
.then(function success(settings) {
Notifications.success('Success', successMessage);
handleSuccess(settings);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update settings');
})
.finally(function final() {
$scope.state.actionInProgress = false;
});
function handleSuccess(settings) {
if (settings) {
StateManager.updateLogo(settings.LogoURL);
StateManager.updateSnapshotInterval(settings.SnapshotInterval);
StateManager.updateEnableTelemetry(settings.EnableTelemetry);
}
function handleSuccess(settings) {
if (settings) {
StateManager.updateLogo(settings.LogoURL);
StateManager.updateSnapshotInterval(settings.SnapshotInterval);
StateManager.updateEnableTelemetry(settings.EnableTelemetry);
$scope.formValues.BlackListedLabels = settings.BlackListedLabels;
}
}
function initView() {
SettingsService.settings()
.then(function success(data) {
var settings = data;
$scope.settings = settings;
$scope.formValues.BlackListedLabels = settings.BlackListedLabels;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve application settings');
});
}
initView();
},
]);
}
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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(''),
});
}

View File

@ -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');
},
}
);
}
}

View File

@ -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>
);
}