frontend/app/multistep/StorageMultistepNew.tsx (289 lines of code) (raw):

import React, { useEffect, useState } from "react"; import { RouteComponentProps } from "react-router-dom"; import CommonMultistepContainer from "./common/CommonMultistepContainer"; import StorageTypeComponent from "./storage/TypeComponent"; import axios from "axios"; import { SystemNotification, SystemNotifcationKind, } from "@guardian/pluto-headers"; import StorageLoginComponent from "./storage/LoginComponent"; import StorageSubfolderComponent from "./storage/SubfolderComponent"; import SummaryComponent from "./storage/SummaryComponent"; import { CreateStorage, MakeStorageCreationDoc, } from "./storage/CreationAction"; import { useHistory } from "react-router"; import { Typography } from "@material-ui/core"; import BackupsComponent from "./storage/BackupsComponent"; interface StorageMultistepParams { itemid?: string; } const StorageMultistepNew: React.FC<RouteComponentProps< StorageMultistepParams >> = (props) => { const [activeStep, setActiveStep] = useState(0); const [creationInProgress, setCreationInProgress] = useState(false); const [creationFailed, setCreationFailed] = useState<string | undefined>( undefined ); const [existingStorageId, setExistingStorageId] = useState< number | undefined >(undefined); const [strgTypes, setStrgTypes] = useState<StorageType[]>([]); const [selectedType, setSelectedType] = useState<number | undefined>(0); const [enableVersions, setEnableVersions] = useState(false); const [loginDetails, setLoginDetails] = useState<StorageLoginDetails>({ hostname: "", port: 0, device: "", username: "", password: "", }); const [rootpath, setRootpath] = useState(""); const [clientpath, setClientpath] = useState(""); const [nickname, setNickname] = useState(""); const [backsUpTo, setBacksUpTo] = useState<number | undefined>(undefined); const history = useHistory(); const steps = [ "Storage type", "Login details", "Subfolder location", "Backups", "Confirm", ]; //load in the known storage types at startup useEffect(() => { axios .get<StorageTypeResponse>("/api/storage/knowntypes") .then((result) => setStrgTypes(result.data.types)) .catch((err) => { console.error("Could not load in storage types: ", err); SystemNotification.open( SystemNotifcationKind.Error, `Could not load in storage types, see console for details` ); }); }, []); const findStorageType = (forName: string) => { for (let i = 0; i < strgTypes.length; i++) { if (strgTypes[i].name === forName) return i; } return 0; }; //if a storage has been specified to edit, load in the data useEffect(() => { if (!props.match.params.itemid || props.match.params.itemid == "new") { console.debug("creating new storage, nothing to load"); return; } if (!strgTypes) { console.debug( "not loading in specified match until storage types available" ); return; } axios .get<PlutoApiResponse<StorageEntry>>( `/api/storage/${props.match.params.itemid}` ) .then((result) => { setSelectedType(findStorageType(result.data.result.storageType)); setEnableVersions(result.data.result.supportsVersion ?? false); setLoginDetails({ hostname: result.data.result.host ?? "", port: result.data.result.port ?? 0, device: result.data.result.device ?? "", username: result.data.result.user ?? "", password: result.data.result.password ?? "", }); setRootpath(result.data.result.rootpath ?? ""); setClientpath(result.data.result.clientpath ?? ""); setNickname(result.data.result.nickname ?? ""); setExistingStorageId(result.data.result.id); setBacksUpTo(result.data.result.backsUpTo); }) .catch((err) => { console.error( `Could not load in existing storage '${props.match.params.itemid}: `, err ); SystemNotification.open( SystemNotifcationKind.Error, "Could not load in storage data, see browser console" ); }); }, [props.match.params, strgTypes]); useEffect(() => { if (strgTypes.length > 0) { setSelectedType(0); } }, [strgTypes]); //when the selected type is changed, toggle the default value for allowing versions useEffect(() => { if ( selectedType != undefined && selectedType < strgTypes.length && selectedType >= 0 ) { const actualSelectedStorage = strgTypes[selectedType]; setEnableVersions(actualSelectedStorage.canVersion); } else { console.log( `can't set versions as storage type index ${selectedType} is out of range` ); } }, [selectedType]); /** * clear any "create failed" flag when the data changes, to re-enable the Create button */ useEffect(() => { if (creationFailed != undefined) setCreationFailed(undefined); }, [rootpath, selectedType, strgTypes, loginDetails]); const canComplete = () => { if ( !( selectedType != undefined && selectedType < strgTypes.length && selectedType >= 0 ) ) { console.log( "can't complete storage because the selected type ", selectedType, " is not valid" ); return false; } if ( strgTypes[selectedType].needsLogin && (loginDetails.username == "" || loginDetails.hostname == "" || loginDetails.password == "") ) { console.log( "can't complete storage because it requires login and the login details are blank" ); return false; } if (strgTypes[selectedType].hasSubFolders && rootpath == "") { console.log( "can't complete storage because it requires subfolders and the root path is blank" ); return false; } console.log("storage information is valid, we can complete"); return true; }; const createClicked = async () => { if ( !( selectedType != undefined && selectedType < strgTypes.length && selectedType >= 0 ) ) return Promise.reject("selectedType must be set"); const doc = MakeStorageCreationDoc( rootpath, clientpath, strgTypes[selectedType].name, loginDetails.hostname, loginDetails.port, loginDetails.username, loginDetails.password, loginDetails.device, enableVersions, nickname, backsUpTo ); setCreationFailed(undefined); setCreationInProgress(true); const result = await CreateStorage(doc, existingStorageId); if (result.createdOk) { setCreationInProgress(false); history.push("/storage/"); SystemNotification.open( SystemNotifcationKind.Success, `${existingStorageId ? "Updated" : "Created"} storage` ); } else { setCreationFailed(result.errorMessage); setCreationInProgress(false); SystemNotification.open(SystemNotifcationKind.Error, result.errorMessage); if (result.shouldRetry) { window.setTimeout(() => createClicked(), 2000); } } }; return ( <CommonMultistepContainer activeStep={activeStep} title="Create a storage - Pluto Admin" id="storage-create-multistep" setActiveStep={setActiveStep} steps={steps} creationInProgress={creationInProgress} creationFailed={creationFailed} canComplete={canComplete} createClicked={createClicked} createButtonLabel={existingStorageId ? "Update" : "Create"} > <> {activeStep == 0 && strgTypes ? ( <StorageTypeComponent strgTypes={strgTypes} selectedType={selectedType} versionsAllowed={enableVersions} valueWasSet={(type: number) => setSelectedType(type)} versionsAllowedChanged={(newValue: boolean) => setEnableVersions(newValue) } /> ) : undefined} {activeStep == 1 && selectedType != undefined ? ( <StorageLoginComponent currentStorage={strgTypes[selectedType]} loginDetails={loginDetails} valueWasSet={(loginDetails) => setLoginDetails(loginDetails)} /> ) : undefined} {activeStep == 2 && selectedType != undefined ? ( <StorageSubfolderComponent rootPath={rootpath} clientPath={clientpath} currentStorage={strgTypes[selectedType]} rootPathWasSet={setRootpath} clientPathWasSet={setClientpath} /> ) : undefined} {activeStep == 3 ? ( <BackupsComponent onChange={(newValue) => setBacksUpTo(newValue)} selectedBackupStorage={backsUpTo} existingStorageId={existingStorageId} /> ) : undefined} {activeStep == 4 && selectedType != undefined ? ( <> <Typography variant="h3"> {existingStorageId ? "Update" : "Set up"} storage </Typography> <Typography> We will{" "} {existingStorageId ? "update the existing" : "set up a new"}{" "} storage definition with the information below. </Typography> <Typography> Press "Confirm" to go ahead, or press Previous if you need to amend any details. </Typography> <SummaryComponent rootPath={rootpath} clientPath={clientpath} loginDetails={loginDetails} storageType={strgTypes[selectedType]} enableVersions={enableVersions} backsUpTo={backsUpTo} nickName={nickname} nickNameChanged={(newValue) => setNickname(newValue)} /> </> ) : undefined} </> </CommonMultistepContainer> ); }; export default StorageMultistepNew;