frontend/app/ScanTargets/ScanTargetEdit.jsx (339 lines of code) (raw):

import React from 'react'; import TimestampFormatter from '../common/TimestampFormatter'; import TimeIntervalComponent from '../common/TimeIntervalComponent.jsx'; import axios from 'axios'; import {Redirect} from 'react-router-dom'; import ErrorViewComponent from "../common/ErrorViewComponent.jsx"; import RegionSelector from "../common/RegionSelector.jsx"; import TranscoderCheckComponent from "./TranscoderCheckComponent.jsx"; import JobEntry from "../common/JobEntry.jsx"; import MuiAlert from '@material-ui/lab/Alert'; import { Grid, Snackbar, TextField, Typography, Button, Switch, Tooltip, Paper, createStyles, IconButton } from "@material-ui/core"; import AdminContainer from "../admin/AdminContainer"; import {makeStyles, withStyles} from "@material-ui/core"; import clsx from "clsx"; import {baseStyles} from "../BaseStyles"; import ScanTargetActionsBox from "./ScanTargetActionsBox"; import {Helmet} from "react-helmet"; import {BugReport, Tune} from "@material-ui/icons"; import MonitoringSetupCheck from "./MonitoringSetupCheck"; const styles = (theme) => Object.assign(createStyles({ formContainer: { padding: "1em", paddingTop: "0.4em", marginBottom: "3em" }, formErrors: { display: "inline" }, actionButtonsContainer: { width: "95%", marginLeft: "auto", marginRight: "auto" } }, baseStyles)); class ScanTargetEdit extends React.Component { constructor(props){ super(props); const defaultValues = { bucketName: "", proxyBucket: "", enabled: false, region: "eu-west-1", lastScanned: null, scanInterval: 7200, scanInProgress: false, lastError: "", lastErrorSeverity: "information", pendingJobIds: [], paranoid: false, proxyEnabled: false }; this.state = { entry: defaultValues, idToLoad: null, loading: false, error: null, formValidationErrors: [], completed: false, showingAlert: false }; this.updateBucketname = this.updateBucketname.bind(this); this.updateProxyBucket = this.updateProxyBucket.bind(this); this.updateRegion = this.updateRegion.bind(this); this.toggleEnabled = this.toggleEnabled.bind(this); this.timeIntervalUpdate = this.timeIntervalUpdate.bind(this); this.toggleParanoid = this.toggleParanoid.bind(this); this.toggleProxyEnabled = this.toggleProxyEnabled.bind(this); this.formSubmit = this.formSubmit.bind(this); this.closeAlert = this.closeAlert.bind(this); this.updatePendingJobs = this.updatePendingJobs.bind(this); this.triggerValidateConfig = this.triggerValidateConfig.bind(this); this.triggerTranscodeSetup = this.triggerTranscodeSetup.bind(this); } loadData(idToLoad){ return new Promise((resolve, reject)=> this.setState({error:null, loading:true, idToLoad: idToLoad}, ()=>axios.get("/api/scanTarget/" + encodeURIComponent(idToLoad)) .then(response=> { this.setState({loading: false, error: null, entry: response.data.entry, pendingJobIds: response.data.entry.pendingJobIds ? response.data.entry.pendingJobIds : []}, ()=>resolve()) }) .catch(err=>{ console.error(err); this.setState({loading: false, error: err, entry: null}, ()=>reject()) }) )) } updatePendingJobs(){ if(this.state.loading) return; this.setState({error:null, loading:true}, ()=>axios.get("/api/scanTarget/" + encodeURIComponent(this.state.idToLoad)) .then(response=>{ this.setState({loading: false, error: null, pendingJobIds: response.data.entry.pendingJobIds ? response.data.entry.pendingJobIds : []}) }) .catch(err=>{ console.error(err); this.setState({loading: false, error: err, entry: null}) }) ); } componentDidMount(){ const idToLoad = this.props.location.pathname.split('/').slice(-1)[0]; console.log("going to load from id", idToLoad); if(idToLoad!=="new"){ this.loadData(idToLoad).then(()=>window.setInterval(this.updatePendingJobs,3000)); } else { } } triggerValidateConfig(){ const targetId = this.state.idToLoad; this.setState({loading: true, showingAlert: true, lastErrorSeverity: "info", lastError: "Checking transcoder setup, refresh the page in a few seconds"}, ()=>axios.post("/api/scanTarget/" + encodeURIComponent(targetId) + "/" + "checkTranscoder") .then(result=>{ console.log("Config validation has been started with job ID " + result.data.entity); this.setState({loading: false}, ); }).catch(err=>{ console.error(err); this.setState({loading: false, lastError: err, lastErrorSeverity: "error", showingAlert: "true", currentActionCaption: null}); })) } triggerTranscodeSetup(){ const targetId = this.state.idToLoad; this.setState({loading:true, lastError:"Starting transcoder setup, this can take a minute or two", lastErrorSeverity: "info", showingAlert: true }, ()=>axios.post("/api/scanTarget/" + encodeURIComponent(targetId) + "/" + "createPipelines?force=true") .then(result=>{ console.log("Transcode setup has been started with job ID " + result.data.entity); this.setState({loading: false}); }).catch(err=>{ console.error(err); this.setState({loading: false, lastError: err, currentActionCaption: null}); })) } updateBucketname(evt){ //this is annoying, but a necessity to avoid modifying this.state.entry directly and selectively over-write the key. const newEntry = Object.assign({}, this.state.entry, {bucketName: evt.target.value}); this.setState({entry: newEntry},()=>console.log("state has been set")); } updateProxyBucket(evt){ const newEntry = Object.assign({}, this.state.entry, {proxyBucket: evt.target.value}); this.setState({entry:newEntry},()=>console.log("state has been set")); } updateRegion(evt){ console.log("updateRegion: ", evt.target); const newEntry = Object.assign({}, this.state.entry, {region: evt.target.value}); this.setState({entry: newEntry},()=>console.log("state has been set")); } toggleEnabled(evt){ const newEntry = Object.assign({}, this.state.entry, {enabled: !this.state.entry.enabled}); this.setState({entry: newEntry}); } toggleParanoid(evt){ const newEntry = Object.assign({}, this.state.entry, {paranoid: !this.state.entry.paranoid}); this.setState({entry: newEntry}); } toggleProxyEnabled(evt){ const newEntry = Object.assign({}, this.state.entry, {proxyEnabled: !this.state.entry.proxyEnabled}); this.setState({entry: newEntry}); } clearErrorLog(evt){ const newEntry = Object.assign({lastError: null}, this.state.entry); this.setState({entry: newEntry}); } timeIntervalUpdate(newValue){ console.log("time interval updated to " + newValue + " seconds"); const newEntry = Object.assign({}, this.state.entry, {scanInterval: newValue}); this.setState({entry: newEntry}); } formSubmit(evt){ evt.preventDefault(); let errorList=[]; const idToSave = this.state.entry.bucketName; if(idToSave===null || idToSave===""){ errorList.concat(["You must specify a valid bucket name"]); } if(errorList.length>0){ this.setState({formValidationErrors: errorList}); return; } let entryToSend = this.state.entry; if(entryToSend.lastError==="") entryToSend.lastError = null; this.setState({loading: true}, ()=>axios.post("/api/scanTarget",entryToSend) .then(result=> { console.log("Saved result successfully"); this.setState({loading: false, completed: true}); }) .catch(err=>{ console.error(err); this.setState({loading: false, error: err}); }) ); } closeAlert() { this.setState({showingAlert: false}); } render(){ if(this.state.completed) return <Redirect to="/admin/scanTargets"/>; return <> <Helmet> <title>Scan Targets - ArchiveHunter</title> </Helmet> <Snackbar open={this.state.showingAlert} autoHideDuration={60000} onClose={this.closeAlert} > <MuiAlert elevation={6} variant="filled" onClose={this.closeAlert} severity={this.state.lastErrorSeverity}> {this.state.lastError} </MuiAlert> </Snackbar> <AdminContainer {...this.props}> <form onSubmit={this.formSubmit}> <Paper elevation={3} className={this.props.classes.formContainer}> <Typography variant="h4" style={{overflow: "hidden"}}>Edit scan target <img src="/assets/images/Spinner-1s-44px.svg" alt="loading" style={{display: this.state.loading ? "inline" : "none", height:"32px"}} className={this.props.classes.inlineThrobber} /></Typography> <div className="centered" style={{display: this.state.formValidationErrors.length>0 ? "block" : "none"}}> <ul className="form-errors"> { this.state.formValidationErrors.map(entry=><li className="form-errors error-text">{entry}</li>) } </ul> </div> <table className="table"> <tbody> <tr> <td>Bucket name</td> <td><TextField value={this.state.entry.bucketName} onChange={this.updateBucketname} style={{width:"95%"}}/></td> </tr> <tr> <td>Proxy bucket</td> <td><TextField value={this.state.entry.proxyBucket} onChange={this.updateProxyBucket} style={{width: "95%"}}/></td> </tr> <tr> <td>Region</td> <td><RegionSelector value={this.state.entry.region} onChange={this.updateRegion}/></td> </tr> <tr> <td>Enabled</td> <td><Switch checked={this.state.entry.enabled} onClick={this.toggleEnabled}/></td> </tr> <tr> <td>Last Scanned</td> <td><TimestampFormatter relative={true} value={this.state.entry.lastScanned}/></td> </tr> <tr> <td>Scan Interval</td> <td><TimeIntervalComponent editable={true} value={this.state.entry.scanInterval} didUpdate={this.timeIntervalUpdate}/></td> </tr> <tr> <td>Last Error</td> <td><TextField multiline={true} contentEditable={false} readOnly={true} value={this.state.entry.lastError ? this.state.entry.lastError : ""} style={{width: "85%", verticalAlign:"middle"}}/> <Button onClick={this.clearErrorLog} style={{marginLeft: "0.5em", verticalAlign: "middle"}}>Clear</Button></td> </tr> <tr> <td>Paranoid Mode</td> <td><Switch checked={this.state.entry.paranoid} onChange={this.toggleParanoid}/></td> </tr> <tr> <td>Enable proxying</td> <td><Tooltip title="Clearing this will prevent any proxies from being generated"> <Switch checked={this.state.entry.proxyEnabled} onChange={this.toggleProxyEnabled} style={{marginRight: "0.2em"}} data-tip="Clearing this will prevent any proxies from being generated" /> </Tooltip> </td> </tr> <tr> <td>Monitoring setup</td> <td><MonitoringSetupCheck scanTarget={this.state.idToLoad}/></td> </tr> <tr> <td style={{verticalAlign: "top"}}>Transcode Setup</td> <td> <Tooltip title="Validate transcode config"> <IconButton onClick={this.triggerValidateConfig}><BugReport/></IconButton> </Tooltip> <Tooltip title="(Redo) Transcode Setup"> <IconButton onClick={this.triggerTranscodeSetup}><Tune/></IconButton> </Tooltip> { this.state.entry.transcoderCheck ? <TranscoderCheckComponent status={this.state.entry.transcoderCheck.status} checkedAt={this.state.entry.transcoderCheck.checkedAt} log={this.state.entry.transcoderCheck.log}/> : <Typography variant="caption">No transcoder check has been run</Typography> } </td> </tr> </tbody> </table> </Paper> <Grid container direction="row" spacing={3}> <Grid item xs={6}> <ScanTargetActionsBox idToLoad={this.state.idToLoad} actionDidStart={(msg)=>this.setState({lastErrorSeverity: "info", lastError:msg, showingAlert: true})} actionDidFail={(msg)=>this.setState({lastErrorSeverity: "error", lastError:msg, showingAlert:true})} actionDidSucceed={} classes={this.props.classes} bucketName={this.state.bucketName}/> </Grid> <Grid item xs={6}> <Paper elevation={3} className={this.props.classes.formContainer}> <Typography variant="h4">Pending jobs</Typography> <ul className="no-bullets"> { this.state.pendingJobIds && this.state.pendingJobIds.length>0 ? this.state.pendingJobIds.map(jobId=><li key={jobId}><JobEntry jobId={jobId} showLink={true}/></li>) : <li><i>no pending job ids</i></li> } </ul> </Paper> </Grid> </Grid> <Grid container direction="row" spacing={3} style={{width:"100%"}}> <Grid item><Button variant="contained" type="submit">Save</Button></Grid> <Grid item><Button variant="contained" type="button" onClick={()=>this.props.location.history.back()}>Back</Button></Grid> </Grid> </form> {this.state.error ? <ErrorViewComponent error={this.state.error}/> : <span/>} </AdminContainer> </> } } export default withStyles(styles)(ScanTargetEdit);