1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-09 15:55:23 +02:00

refactor(ui/modals): replace bootbox with react solution [EE-4541] (#8010)

This commit is contained in:
Chaim Lev-Ari 2023-02-14 13:49:41 +05:30 committed by GitHub
parent 392c7f74b8
commit e66dea44e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
111 changed files with 1330 additions and 1562 deletions

View file

@ -2,8 +2,15 @@
padding-left: 0.5rem;
}
.dialog {
display: flex;
justify-content: center;
align-items: center;
.checkbox-list {
max-height: 200px;
overflow-y: auto;
background-color: var(--white-color);
border: 0;
border-radius: 4px;
}
:root[theme='dark'] .checkbox-list,
:root[theme='highcontrast'] .checkbox-list {
background-color: var(--bg-modal-content-color);
}

View file

@ -1,7 +1,5 @@
import { X } from 'lucide-react';
import clsx from 'clsx';
import { useState } from 'react';
import { DialogContent, DialogOverlay } from '@reach/dialog';
import { downloadKubeconfigFile } from '@/react/kubernetes/services/kubeconfig.service';
import * as notifications from '@/portainer/services/notifications';
@ -17,12 +15,12 @@ import {
} from '@/react/portainer/environments/queries/useEnvironmentList';
import { useListSelection } from '@/react/hooks/useListSelection';
import { Modal } from '@@/modals';
import { PaginationControls } from '@@/PaginationControls';
import { Checkbox } from '@@/form-components/Checkbox';
import { Button } from '@@/buttons';
import styles from './KubeconfigPrompt.module.css';
import '@reach/dialog/styles.css';
export interface KubeconfigPromptProps {
envQueryParams: Query;
@ -63,90 +61,69 @@ export function KubeconfigPrompt({
.every((env) => selection.includes(env.Id));
return (
<DialogOverlay
className={styles.dialog}
aria-label="Kubeconfig View"
role="dialog"
onDismiss={onClose}
>
<DialogContent className="modal-dialog bg-transparent p-0">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" onClick={onClose}>
<X />
</button>
<h5 className="modal-title">Download kubeconfig file</h5>
</div>
<div className="modal-body">
<form className="bootbox-form">
<div className="bootbox-prompt-message">
<span>
Select the kubernetes environments to add to the kubeconfig
file. You may select across multiple pages.
</span>
<span className="space-left">{expiryQuery.data}</span>
</div>
</form>
<br />
<div className="flex h-8 items-center">
<Checkbox
id="settings-container-truncate-name"
label="Select all (in this page)"
checked={isAllPageSelected}
onChange={handleSelectAll}
/>
</div>
<div className="datatable">
<div className="bootbox-checkbox-list">
{environments
.filter((env) => env.Status <= 2)
.map((env) => (
<div
key={env.Id}
className={clsx(
styles.checkbox,
'flex h-8 items-center pt-1'
)}
>
<Checkbox
id={`${env.Id}`}
label={`${env.Name} (${env.URL})`}
checked={selection.includes(env.Id)}
onChange={() =>
toggleSelection(env.Id, !selection.includes(env.Id))
}
/>
</div>
))}
</div>
<div className="flex w-full justify-end pt-3">
<PaginationControls
showAll={totalCount <= 100}
page={page}
onPageChange={setPage}
pageLimit={pageLimit}
onPageLimitChange={setPageLimit}
totalCount={totalCount}
<Modal aria-label="Kubeconfig View" onDismiss={onClose}>
<Modal.Header title="Download kubeconfig file" />
<Modal.Body>
<div>
<span>
Select the kubernetes environments to add to the kubeconfig file.
You may select across multiple pages.
</span>
<span className="space-left">{expiryQuery.data}</span>
</div>
<div className="mt-2 flex h-8 items-center">
<Checkbox
id="settings-container-truncate-name"
label="Select all (in this page)"
checked={isAllPageSelected}
onChange={handleSelectAll}
/>
</div>
<div className="datatable">
<div className={styles.checkboxList}>
{environments
.filter((env) => env.Status <= 2)
.map((env) => (
<div
key={env.Id}
className={clsx(
styles.checkbox,
'flex h-8 items-center pt-1'
)}
>
<Checkbox
id={`${env.Id}`}
label={`${env.Name} (${env.URL})`}
checked={!!selection[env.Id]}
onChange={() => toggleSelection(env.Id, !selection[env.Id])}
/>
</div>
</div>
</div>
<div className="modal-footer">
<Button onClick={onClose} color="default">
Cancel
</Button>
<Button
onClick={handleDownload}
disabled={selection.length === 0}
>
Download File
</Button>
</div>
))}
</div>
<div className="flex w-full justify-end pt-3">
<PaginationControls
showAll={totalCount <= 100}
page={page}
onPageChange={setPage}
pageLimit={pageLimit}
onPageLimitChange={setPageLimit}
totalCount={totalCount}
/>
</div>
</div>
</DialogContent>
</DialogOverlay>
</Modal.Body>
<Modal.Footer>
<Button onClick={onClose} color="default">
Cancel
</Button>
<Button onClick={handleDownload} disabled={selection.length === 0}>
Download File
</Button>
</Modal.Footer>
</Modal>
);
function handleSelectAll() {

View file

@ -5,10 +5,10 @@ import { Environment } from '@/react/portainer/environments/types';
import { snapshotEndpoints } from '@/react/portainer/environments/environment.service';
import { isEdgeEnvironment } from '@/react/portainer/environments/utils';
import * as notifications from '@/portainer/services/notifications';
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
import { buildTitle } from '@/portainer/services/modal.service/utils';
import { confirm } from '@@/modals/confirm';
import { PageHeader } from '@@/PageHeader';
import { ModalType } from '@@/modals';
import { EnvironmentList } from './EnvironmentList';
import { EdgeLoadingSpinner } from './EdgeLoadingSpinner';
@ -72,15 +72,10 @@ export function HomeView() {
}
async function confirmEndpointSnapshot() {
return confirmAsync({
title: buildTitle('Are you sure?'),
return confirm({
title: 'Are you sure?',
modalType: ModalType.Warn,
message:
'Triggering a manual refresh will poll each environment to retrieve its information, this may take a few moments.',
buttons: {
confirm: {
label: 'Continue',
className: 'btn-primary',
},
},
});
}

View file

@ -4,12 +4,14 @@ import { useMutation } from 'react-query';
import { object } from 'yup';
import { useUser } from '@/react/hooks/useUser';
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
import { notifySuccess } from '@/portainer/services/notifications';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { confirm } from '@@/modals/confirm';
import { Button } from '@@/buttons';
import { LoadingButton } from '@@/buttons/LoadingButton';
import { buildConfirmButton } from '@@/modals/utils';
import { ModalType } from '@@/modals';
import { EditDetails } from '../EditDetails';
import { parseAccessControlFormData } from '../utils';
@ -128,15 +130,11 @@ export function AccessControlPanelForm({
}
function confirmAccessControlUpdate() {
return confirmAsync({
return confirm({
modalType: ModalType.Warn,
title: 'Are you sure?',
message:
'Changing the ownership of this resource will potentially restrict its management to some users.',
buttons: {
confirm: {
label: 'Change ownership',
className: 'btn-primary',
},
},
confirmButton: buildConfirmButton('Change ownership'),
});
}

View file

@ -0,0 +1,29 @@
import { confirm } from '@@/modals/confirm';
import { ModalType } from '@@/modals';
import { buildConfirmButton } from '@@/modals/utils';
export function confirmDisassociate() {
const message = (
<>
<p>
Disassociating this Edge environment will mark it as non associated and
will clear the registered Edge ID.
</p>
<p>
Any agent started with the Edge key associated to this environment will
be able to re-associate with this environment.
</p>
<p>
You can re-use the Edge ID and Edge key that you used to deploy the
existing Edge agent to associate a new Edge device to this environment.
</p>
</>
);
return confirm({
title: 'About disassociating',
modalType: ModalType.Warn,
message,
confirmButton: buildConfirmButton('Disassociate'),
});
}

View file

@ -2,9 +2,9 @@ import { Clock, Trash2 } from 'lucide-react';
import { useStore } from 'zustand';
import { notifySuccess } from '@/portainer/services/notifications';
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
import { withLimitToBE } from '@/react/hooks/useLimitToBE';
import { confirmDelete } from '@@/modals/confirm';
import { Datatable } from '@@/datatables';
import { PageHeader } from '@@/PageHeader';
import { Button } from '@@/buttons';
@ -91,7 +91,7 @@ function TableActions({
);
async function handleRemove() {
const confirmed = await confirmDeletionAsync(
const confirmed = await confirmDelete(
'Are you sure you want to remove these?'
);
if (!confirmed) {

View file

@ -1,7 +1,8 @@
import { confirmDestructive } from '@/portainer/services/modal.service/confirm';
import { Settings } from '@/react/portainer/settings/types';
import { confirmDestructive } from '@@/modals/confirm';
import { FormSectionTitle } from '@@/form-components/FormSectionTitle';
import { buildConfirmButton } from '@@/modals/utils';
import { PasswordLengthSlider } from './PasswordLengthSlider/PasswordLengthSlider';
import { SaveAuthSettingsButton } from './SaveAuthSettingsButton';
@ -19,22 +20,18 @@ export function InternalAuth({
value,
onChange,
}: Props) {
function onSubmit() {
async function onSubmit() {
if (value.RequiredPasswordLength < 10) {
confirmDestructive({
const confirmed = await confirmDestructive({
title: 'Allow weak passwords?',
message:
'You have set an insecure minimum password length. This could leave your system vulnerable to attack, are you sure?',
buttons: {
confirm: {
label: 'Yes',
className: 'btn-danger',
},
},
callback: function onConfirm(confirmed) {
if (confirmed) onSaveSettings();
},
confirmButton: buildConfirmButton('Yes', 'danger'),
});
if (confirmed) {
onSaveSettings();
}
} else {
onSaveSettings();
}

View file

@ -1,9 +1,10 @@
import { useField } from 'formik';
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
import { confirm } from '@@/modals/confirm';
import { FormControl } from '@@/form-components/FormControl';
import { Switch } from '@@/form-components/SwitchField/Switch';
import { buildConfirmButton } from '@@/modals/utils';
import { ModalType } from '@@/modals';
export function EnabledWaitingRoomSwitch() {
const [inputProps, meta, helpers] = useField<boolean>('TrustOnFirstConnect');
@ -30,20 +31,12 @@ export function EnabledWaitingRoomSwitch() {
return;
}
const confirmed = await confirmAsync({
const confirmed = await confirm({
modalType: ModalType.Warn,
title: 'Disable Edge Environment Waiting Room',
message:
'By disabling the waiting room feature, all devices requesting association will be automatically associated and could pose a security risk. Are you sure?',
buttons: {
cancel: {
label: 'Cancel',
className: 'btn-default',
},
confirm: {
label: 'Confirm',
className: 'btn-danger',
},
},
confirmButton: buildConfirmButton('Confirm', 'danger'),
});
helpers.setValue(!!confirmed);

View file

@ -1,11 +1,12 @@
import { useRouter } from '@uirouter/react';
import { Plus } from 'lucide-react';
import { promptAsync } from '@/portainer/services/modal.service/prompt';
import { usePublicSettings } from '@/react/portainer/settings/queries';
import { Button } from '@@/buttons';
import { openModal } from '@@/modals';
import { usePublicSettings } from '../../queries';
import { DeployTypePrompt } from './DeployTypePrompt';
enum DeployType {
FDO = 'FDO',
@ -42,30 +43,15 @@ export function AddDeviceButton() {
}
}
function getDeployType(): Promise<DeployType> {
function getDeployType() {
if (!isFDOEnabled) {
return Promise.resolve(DeployType.MANUAL);
}
return promptAsync({
title: 'How would you like to add an Edge Device?',
inputType: 'radio',
inputOptions: [
{
text: 'Provision bare-metal using Intel FDO',
value: DeployType.FDO,
},
{
text: 'Deploy agent manually',
value: DeployType.MANUAL,
},
],
buttons: {
confirm: {
label: 'Confirm',
className: 'btn-primary',
},
},
}) as Promise<DeployType>;
return askForDeployType();
}
}
function askForDeployType() {
return openModal(DeployTypePrompt, {});
}

View file

@ -0,0 +1,69 @@
import { useState } from 'react';
import { DeployType } from '@/react/nomad/jobs/JobsView/JobsDatatable/types';
import { OnSubmit } from '@@/modals';
import { Dialog } from '@@/modals/Dialog';
import { buildCancelButton, buildConfirmButton } from '@@/modals/utils';
export function DeployTypePrompt({
onSubmit,
}: {
onSubmit: OnSubmit<DeployType>;
}) {
const [deployType, setDeployType] = useState<DeployType>(DeployType.FDO);
return (
<Dialog
title="How would you like to add an Edge Device?"
message={
<>
<RadioInput
name="deployType"
value={DeployType.FDO}
label="Provision bare-metal using Intel FDO"
groupValue={deployType}
onChange={setDeployType}
/>
<RadioInput
name="deployType"
value={DeployType.MANUAL}
onChange={setDeployType}
groupValue={deployType}
label="Deploy agent manually"
/>
</>
}
buttons={[buildCancelButton(), buildConfirmButton()]}
onSubmit={(confirm) => onSubmit(confirm ? deployType : undefined)}
/>
);
}
function RadioInput<T extends number | string>({
value,
onChange,
label,
groupValue,
name,
}: {
value: T;
onChange: (value: T) => void;
label: string;
groupValue: T;
name: string;
}) {
return (
<label className="flex items-center gap-2">
<input
className="!m-0"
type="radio"
name={name}
value={value}
checked={groupValue === value}
onChange={() => onChange(value)}
/>
{label}
</label>
);
}

View file

@ -0,0 +1 @@
export { AddDeviceButton } from './AddDeviceButton';

View file

@ -3,18 +3,16 @@ import { useRouter } from '@uirouter/react';
import { PlusCircle, Trash2 } from 'lucide-react';
import { Profile } from '@/portainer/hostmanagement/fdo/model';
import {
confirmAsync,
confirmDestructiveAsync,
} from '@/portainer/services/modal.service/confirm';
import * as notifications from '@/portainer/services/notifications';
import {
deleteProfile,
duplicateProfile,
} from '@/portainer/hostmanagement/fdo/fdo.service';
import { confirm, confirmDestructive } from '@@/modals/confirm';
import { Link } from '@@/Link';
import { Button } from '@@/buttons';
import { buildConfirmButton } from '@@/modals/utils';
interface Props {
isFDOEnabled: boolean;
@ -56,15 +54,9 @@ export function FDOProfilesDatatableActions({
);
async function onDuplicateProfileClick() {
const confirmed = await confirmAsync({
const confirmed = await confirm({
title: 'Are you sure ?',
message: 'This action will duplicate the selected profile. Continue?',
buttons: {
confirm: {
label: 'Confirm',
className: 'btn-primary',
},
},
});
if (!confirmed) {
@ -88,15 +80,10 @@ export function FDOProfilesDatatableActions({
}
async function onDeleteProfileClick() {
const confirmed = await confirmDestructiveAsync({
title: 'Are you sure ?',
const confirmed = await confirmDestructive({
title: 'Are you sure?',
message: 'This action will delete the selected profile(s). Continue?',
buttons: {
confirm: {
label: 'Remove',
className: 'btn-danger',
},
},
confirmButton: buildConfirmButton('Remove', 'danger'),
});
if (!confirmed) {

View file

@ -2,7 +2,6 @@ import { useRouter } from '@uirouter/react';
import { useMutation, useQueryClient } from 'react-query';
import { Trash2, Users } from 'lucide-react';
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
import { usePublicSettings } from '@/react/portainer/settings/queries';
import {
mutationOptions,
@ -10,6 +9,7 @@ import {
withInvalidate,
} from '@/react-tools/react-query';
import { confirmDelete } from '@@/modals/confirm';
import { Button } from '@@/buttons';
import { Widget } from '@@/Widget';
@ -75,7 +75,7 @@ export function Details({ team, memberships, isAdmin }: Props) {
);
async function handleDeleteClick() {
const confirmed = await confirmDeletionAsync(
const confirmed = await confirmDelete(
`Do you want to delete this team? Users in this team will not be deleted.`
);
if (!confirmed) {

View file

@ -7,8 +7,8 @@ import { notifySuccess } from '@/portainer/services/notifications';
import { promiseSequence } from '@/portainer/helpers/promise-utils';
import { Team, TeamId } from '@/react/portainer/users/teams/types';
import { deleteTeam } from '@/react/portainer/users/teams/teams.service';
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
import { confirmDelete } from '@@/modals/confirm';
import { Datatable } from '@@/datatables';
import { Button } from '@@/buttons';
import { buildNameColumn } from '@@/datatables/NameCell';
@ -86,7 +86,7 @@ function useRemoveMutation() {
return { handleRemove };
async function handleRemove(teams: TeamId[]) {
const confirmed = await confirmDeletionAsync(
const confirmed = await confirmDelete(
'Are you sure you want to remove the selected teams?'
);