frontend/app/admin/QuickRestore.jsx (222 lines of code) (raw):

import React from "react"; import axios from "axios"; import BytesFormatter from "../common/BytesFormatter.jsx"; import RefreshButton from "../common/RefreshButton"; import AdminContainer from "./AdminContainer"; import {createStyles, withStyles, Snackbar, TextField, Paper, Button, Grid, Typography} from "@material-ui/core"; import {formatError} from "../common/ErrorViewComponent"; import MuiAlert from "@material-ui/lab/Alert"; import {Check, Search, Warning, WarningRounded, WbIncandescent} from "@material-ui/icons"; import {Helmet} from "react-helmet"; const styles = createStyles((theme)=>({ wide: { width: "100%" }, errorText: { color: theme.palette.error.dark, fontWeight: "bold" }, successText: { color: theme.palette.success.dark, fontWeight: "bold", }, formList: { listStyle: "none" }, formItem: { marginBottom: "1em" }, area: { paddingBottom: "2em", paddingRight: "2em", marginBottom: "3em" }, successIcon: { color: theme.palette.success.dark, width: "200px", height: "200px" }, warningIcon: { color: theme.palette.warning.dark, width: "200px", height: "200px" }, bigIcon: { width: "200px", height: "200px" } })); class QuickRestoreComponent extends React.Component { constructor(props) { super(props); this.state = { isLoading: false, isLightboxing: false, showingAlert: false, quotaExceeded: false, quotaRequired: 0, quotaLevel: 0, lastError: null, requestedPath: "", collectionName: "", fileCount: 0, totalSize: 0, success: false } this.doPathCheck = this.doPathCheck.bind(this); this.doRestore = this.doRestore.bind(this); } makeSearchJson() { return JSON.stringify({ hideDotFiles: false, collection: this.state.collectionName, path: this.state.requestedPath }) } static getDerivedStateFromError(error) { console.error(error); return { isLoading: false, isLightboxing: false, quotaExceeded: false, quotaRequired: 0, quotaLevel: 0, requestedPath: "", collectionName: "", fileCount: 0, totalSize: 0, showingAlert: true, lastError: "A frontend error occurred, probably a bug. See the console for details." } } doPathCheck() { console.log("doPathCheck"); const pathToSearch = this.state.requestedPath.endsWith("/") ? this.state.requestedPath.slice(0, this.state.requestedPath.length - 1) : this.state.requestedPath; const urlsuffix = pathToSearch ? "?prefix=" + encodeURIComponent(pathToSearch) : ""; const url = "/api/browse/" + this.state.collectionName + "/summary" + urlsuffix; this.setState({isLoading:true, lastError: null}, ()=>axios.put(url, this.makeSearchJson(), {headers: {"Content-Type": "application/json"}}).then(response=>{ this.setState({ isLoading:false, lastError:null, fileCount: response.data.totalHits, totalSize: response.data.totalSize, }) }).catch(err=>{ console.error("Could not refresh path summary data: ", err); this.setState({loading: false,lastError: formatError(err, false), showingAlert: true}) })) } doRestore() { console.log("doLightbox"); this.setState({isLightboxing: true, lastError:null}, ()=>axios.put("/api/lightbox/my/addFromSearch", this.makeSearchJson(),{headers:{"Content-Type":"application/json"}}).then(response=>{ console.log(response.data); this.setState({isLightboxing:false, success: true}); }).catch(err=>{ if(err.response && err.response.status===413){ console.log(err.response.data); this.setState({ isLightboxing: false, quotaExceeded: true, quotaRequired: err.response.data.requiredQuota, quotaLevel: err.response.data.actualQuota }) } else { this.setState({loading: false, lastError: formatError(err, false), showingAlert: true}); } }) ) } closeAlert() { this.setState({showingAlert: false}); } render() { return ( <AdminContainer {...this.props}> <Helmet> <title>Failsafe media restore - ArchiveHunter</title> </Helmet> <Snackbar open={this.state.showingAlert} onClose={this.closeAlert} autoHideDuration={8000}> <MuiAlert severity="error" onClose={this.closeAlert}>{this.state.lastError}</MuiAlert> </Snackbar> <Paper elevation={3} className={this.props.classes.area}> <ul className={this.props.classes.formList}> <li className={this.props.classes.formItem}> {/*<label htmlFor="collection-input">Collection name to restore from:</label>*/} <TextField label="Collection name to restore from" className={this.props.classes.wide} id="collection-input" value={this.state.collectionName} onChange={(evt)=>this.setState({collectionName: evt.target.value})} /> </li> <li className={this.props.classes.formItem}> {/*<label htmlFor="path-input">Path to restore:</label>*/} <TextField label="Path to restore" className={this.props.classes.wide} id="path-input" value={this.state.requestedPath} onChange={(evt)=>this.setState({requestedPath: evt.target.value})} /> </li> <li className={this.props.classes.formItem}> <> <Button variant="outlined" startIcon={<Search/>} onClick={this.doPathCheck} disabled={this.state.isLoading} >Check path</Button> {this.state.isLoading ? <RefreshButton isRunning={true} clickedCb={()=>{}}/> : null} </> </li> </ul> </Paper> <Paper elevation={3} className={this.props.classes.area}> <Grid container alignItems="center"> <Grid item> { this.state.fileCount===0 && !this.state.quotaExceeded ? <Search className={this.props.classes.bigIcon}/> : null } { this.state.fileCount>0 && !this.state.quotaExceeded ? <Check className={this.props.classes.successIcon}/> : null } { this.state.quotaExceeded ? <WarningRounded className={this.props.classes.warningIcon}/> : null } </Grid> <Grid item> <ul className={this.props.classes.formList}> <li className={this.props.classes.formItem}> <Typography>There are {this.state.fileCount} files in the path selected totalling <BytesFormatter value={this.state.totalSize}/></Typography> </li> <li> <> <Button startIcon={<WbIncandescent/>} variant="contained" disabled={this.state.isLoading || this.state.fileCount===0 || this.state.success} onClick={this.doRestore} >Add to Lightbox</Button> {this.state.isLightboxing ? <RefreshButton isRunning={true} clickedCb={()=>{}}/> : null} {this.state.quotaExceeded ? <p className={this.props.classes.errorText}>This restore would exceed your quota of <BytesFormatter value={this.state.quotaLevel}/>. You need <BytesFormatter value={this.state.quotaRequired}/></p> : null } { this.state.success ? <Typography className={this.props.classes.successText}>Now go to "My Lightbox" to see the items</Typography> : null } </> </li> </ul> </Grid> </Grid> </Paper> </AdminContainer> ) } } export default withStyles(styles)(QuickRestoreComponent);