refactor: adjust settings components
parent
f2ee86472d
commit
4239db6171
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
|
@ -8,23 +8,84 @@ import { Box, Button, CircularProgress, Stack, Typography } from "@mui/material"
|
|||
import { ButtonProps } from "@mui/material/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { WebauthnDevice } from "@models/Webauthn";
|
||||
import { deleteDevice, updateDevice } from "@services/Webauthn";
|
||||
import WebauthnDeviceDeleteDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceDeleteDialog";
|
||||
import WebauthnDeviceDetailsDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceDetailsDialog";
|
||||
import WebauthnDeviceEditDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceEditDialog";
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
device: WebauthnDevice;
|
||||
deleting: boolean;
|
||||
editing: boolean;
|
||||
webauthnShowDetails: boolean;
|
||||
handleWebAuthnDetailsChange: () => void;
|
||||
handleDetails: () => void;
|
||||
handleDelete: () => void;
|
||||
handleEdit: () => void;
|
||||
handleDeviceEdit(index: number, device: WebauthnDevice): void;
|
||||
handleDeviceDelete(device: WebauthnDevice): void;
|
||||
}
|
||||
|
||||
export default function WebauthnDeviceItem(props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
|
||||
const { createErrorNotification } = useNotifications();
|
||||
|
||||
const [showDialogDetails, setShowDialogDetails] = useState<boolean>(false);
|
||||
const [showDialogEdit, setShowDialogEdit] = useState<boolean>(false);
|
||||
const [showDialogDelete, setShowDialogDelete] = useState<boolean>(false);
|
||||
const [loadingEdit, setLoadingEdit] = useState<boolean>(false);
|
||||
const [loadingDelete, setLoadingDelete] = useState<boolean>(false);
|
||||
|
||||
const handleEdit = async (ok: boolean, name: string) => {
|
||||
setShowDialogEdit(false);
|
||||
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingEdit(true);
|
||||
|
||||
const status = await updateDevice(props.device.id, name);
|
||||
|
||||
setLoadingEdit(false);
|
||||
|
||||
if (status !== 200) {
|
||||
createErrorNotification(translate("There was a problem updating the device"));
|
||||
return;
|
||||
}
|
||||
|
||||
props.handleDeviceEdit(props.index, { ...props.device, description: name });
|
||||
};
|
||||
|
||||
const handleDelete = async (ok: boolean) => {
|
||||
setShowDialogDelete(false);
|
||||
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingDelete(true);
|
||||
|
||||
const status = await deleteDevice(props.device.id);
|
||||
|
||||
setLoadingDelete(false);
|
||||
|
||||
if (status !== 200) {
|
||||
createErrorNotification(translate("There was a problem deleting the device"));
|
||||
return;
|
||||
}
|
||||
|
||||
props.handleDeviceDelete(props.device);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<WebauthnDeviceDetailsDialog
|
||||
device={props.device}
|
||||
open={showDialogDetails}
|
||||
handleClose={() => {
|
||||
setShowDialogDetails(false);
|
||||
}}
|
||||
/>
|
||||
<WebauthnDeviceEditDialog device={props.device} open={showDialogEdit} handleClose={handleEdit} />
|
||||
<WebauthnDeviceDeleteDialog device={props.device} open={showDialogDelete} handleClose={handleDelete} />
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<KeyRoundedIcon fontSize="large" />
|
||||
<Stack spacing={0} sx={{ minWidth: 400 }}>
|
||||
|
@ -44,28 +105,34 @@ export default function WebauthnDeviceItem(props: Props) {
|
|||
: "Last used " + props.device.last_used_at.toString()}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Button variant="outlined" color="primary" startIcon={<InfoOutlinedIcon />} onClick={props.handleDetails}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<InfoOutlinedIcon />}
|
||||
onClick={() => setShowDialogDetails(true)}
|
||||
>
|
||||
{translate("Info")}
|
||||
</Button>
|
||||
<LoadingButton
|
||||
loading={props.editing}
|
||||
loading={loadingEdit}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<EditIcon />}
|
||||
onClick={props.handleEdit}
|
||||
onClick={() => setShowDialogEdit(true)}
|
||||
>
|
||||
{translate("Edit")}
|
||||
</LoadingButton>
|
||||
<LoadingButton
|
||||
loading={props.deleting}
|
||||
loading={loadingDelete}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
startIcon={<DeleteIcon />}
|
||||
onClick={props.handleDelete}
|
||||
onClick={() => setShowDialogDelete(true)}
|
||||
>
|
||||
{translate("Remove")}
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,120 +1,26 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { Fragment, Suspense, useState } from "react";
|
||||
|
||||
import { Box, Button, Paper, Skeleton, Stack, Typography } from "@mui/material";
|
||||
import { Box, Button, Paper, Stack, Typography } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { RegisterWebauthnRoute } from "@constants/Routes";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { WebauthnDevice } from "@models/Webauthn";
|
||||
import { initiateWebauthnRegistrationProcess } from "@services/RegisterDevice";
|
||||
import { AutheliaState, AuthenticationLevel } from "@services/State";
|
||||
import { getWebauthnDevices } from "@services/UserWebauthnDevices";
|
||||
import { deleteDevice, updateDevice } from "@services/Webauthn";
|
||||
import WebauthnDeviceDeleteDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceDeleteDialog";
|
||||
import WebauthnDeviceDetailsDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceDetailsDialog";
|
||||
import WebauthnDeviceEditDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceEditDialog";
|
||||
import WebauthnDeviceItem from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceItem";
|
||||
import LoadingPage from "@views/LoadingPage/LoadingPage";
|
||||
import WebauthnDevicesStack from "@views/Settings/TwoFactorAuthentication/WebauthnDevicesStack";
|
||||
|
||||
interface Props {
|
||||
state: AutheliaState;
|
||||
}
|
||||
|
||||
interface WebauthnDeviceDisplay extends WebauthnDevice {
|
||||
deleting: boolean;
|
||||
editing: boolean;
|
||||
}
|
||||
|
||||
export default function WebauthnDevices(props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { createInfoNotification, createErrorNotification } = useNotifications();
|
||||
const [webauthnShowDetails, setWebauthnShowDetails] = useState<number>(-1);
|
||||
const [detailsIdx, setDetailsIdx] = useState<number>(-1);
|
||||
const [deletingIdx, setDeletingIdx] = useState<number>(-1);
|
||||
const [editingIdx, setEditingIdx] = useState<number>(-1);
|
||||
const [registrationInProgress, setRegistrationInProgress] = useState(false);
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
const [webauthnDevices, setWebauthnDevices] = useState<WebauthnDeviceDisplay[]>([]);
|
||||
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
const devices = await getWebauthnDevices();
|
||||
const devicesDisplay = devices.map((x, idx) => {
|
||||
return {
|
||||
...x,
|
||||
deleting: false,
|
||||
editing: false,
|
||||
} as WebauthnDeviceDisplay;
|
||||
});
|
||||
setWebauthnDevices(devicesDisplay);
|
||||
setReady(true);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const handleWebAuthnDetailsChange = (idx: number) => {
|
||||
if (webauthnShowDetails === idx) {
|
||||
setWebauthnShowDetails(-1);
|
||||
} else {
|
||||
setWebauthnShowDetails(idx);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDetailsItem = async (idx: number) => {
|
||||
setDetailsIdx(idx);
|
||||
setDetailsDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteItem = async (idx: number) => {
|
||||
setDeletingIdx(idx);
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteItemConfirm = async (ok: boolean) => {
|
||||
setDeleteDialogOpen(false);
|
||||
const idx = deletingIdx;
|
||||
if (ok !== true) {
|
||||
return;
|
||||
}
|
||||
webauthnDevices[idx].deleting = true;
|
||||
const status = await deleteDevice(webauthnDevices[idx].id);
|
||||
if (status !== 200) {
|
||||
webauthnDevices[idx].deleting = false;
|
||||
createErrorNotification(translate("There was a problem deleting the device"));
|
||||
return;
|
||||
}
|
||||
let updatedDevices = [...webauthnDevices];
|
||||
updatedDevices.splice(idx, 1);
|
||||
setWebauthnDevices(updatedDevices);
|
||||
};
|
||||
|
||||
const handleEditItem = async (idx: number) => {
|
||||
setEditingIdx(idx);
|
||||
setEditDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleEditItemConfirm = async (ok: boolean, name: string) => {
|
||||
setEditDialogOpen(false);
|
||||
const idx = editingIdx;
|
||||
if (ok !== true) {
|
||||
return;
|
||||
}
|
||||
webauthnDevices[idx].editing = true;
|
||||
const status = await updateDevice(webauthnDevices[idx].id, name);
|
||||
webauthnDevices[idx].editing = false;
|
||||
if (status !== 200) {
|
||||
createErrorNotification(translate("There was a problem updating the device"));
|
||||
return;
|
||||
}
|
||||
let updatedDevices = [...webauthnDevices];
|
||||
updatedDevices[idx].description = name;
|
||||
setWebauthnDevices(updatedDevices);
|
||||
};
|
||||
|
||||
const initiateRegistration = async (initiateRegistrationFunc: () => Promise<void>, redirectRoute: string) => {
|
||||
if (props.state.authentication_level >= AuthenticationLevel.TwoFactor) {
|
||||
|
@ -140,24 +46,7 @@ export default function WebauthnDevices(props: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebauthnDeviceDetailsDialog
|
||||
device={detailsIdx > -1 ? webauthnDevices[detailsIdx] : undefined}
|
||||
open={detailsDialogOpen}
|
||||
handleClose={() => {
|
||||
setDetailsDialogOpen(false);
|
||||
}}
|
||||
/>
|
||||
<WebauthnDeviceEditDialog
|
||||
device={editingIdx > -1 ? webauthnDevices[editingIdx] : undefined}
|
||||
open={editDialogOpen}
|
||||
handleClose={handleEditItemConfirm}
|
||||
/>
|
||||
<WebauthnDeviceDeleteDialog
|
||||
device={deletingIdx > -1 ? webauthnDevices[deletingIdx] : undefined}
|
||||
open={deleteDialogOpen}
|
||||
handleClose={handleDeleteItemConfirm}
|
||||
/>
|
||||
<Fragment>
|
||||
<Paper variant="outlined">
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Stack spacing={2}>
|
||||
|
@ -169,41 +58,12 @@ export default function WebauthnDevices(props: Props) {
|
|||
{"Add new device"}
|
||||
</Button>
|
||||
</Box>
|
||||
{ready ? (
|
||||
<Stack spacing={3}>
|
||||
{webauthnDevices
|
||||
? webauthnDevices.map((x, idx) => (
|
||||
<WebauthnDeviceItem
|
||||
device={x}
|
||||
deleting={x.deleting}
|
||||
editing={x.editing}
|
||||
webauthnShowDetails={webauthnShowDetails === idx}
|
||||
handleWebAuthnDetailsChange={() => {
|
||||
handleWebAuthnDetailsChange(idx);
|
||||
}}
|
||||
handleDetails={() => {
|
||||
handleDetailsItem(idx);
|
||||
}}
|
||||
handleEdit={() => {
|
||||
handleEditItem(idx);
|
||||
}}
|
||||
handleDelete={() => {
|
||||
handleDeleteItem(idx);
|
||||
}}
|
||||
key={`webauthn-device-${idx}`}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
</Stack>
|
||||
) : (
|
||||
<>
|
||||
<Skeleton height={20} />
|
||||
<Skeleton height={40} />
|
||||
</>
|
||||
)}
|
||||
<Suspense fallback={<LoadingPage />}>
|
||||
<WebauthnDevicesStack />
|
||||
</Suspense>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { Stack } from "@mui/material";
|
||||
|
||||
import { WebauthnDevice } from "@models/Webauthn";
|
||||
import { getWebauthnDevices } from "@services/UserWebauthnDevices";
|
||||
import WebauthnDeviceItem from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceItem";
|
||||
|
||||
interface Props {}
|
||||
|
||||
export default function WebauthnDevicesStack(props: Props) {
|
||||
const [devices, setDevices] = useState<WebauthnDevice[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
const devices = await getWebauthnDevices();
|
||||
setDevices(devices);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const handleEdit = (index: number, device: WebauthnDevice) => {
|
||||
const nextDevices = devices.map((d, i) => {
|
||||
if (i === index) {
|
||||
return device;
|
||||
} else {
|
||||
return d;
|
||||
}
|
||||
});
|
||||
|
||||
setDevices(nextDevices);
|
||||
};
|
||||
|
||||
const handleDelete = (device: WebauthnDevice) => {
|
||||
setDevices(devices.filter((d) => d.id !== device.id && d.kid !== device.kid));
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
{devices
|
||||
? devices.map((x, idx) => (
|
||||
<WebauthnDeviceItem
|
||||
index={idx}
|
||||
device={x}
|
||||
handleDeviceEdit={handleEdit}
|
||||
handleDeviceDelete={handleDelete}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
</Stack>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue