Files
kopia/htmlui/src/TaskDetails.js
Jarek Kowalski 132e2eef50 New snapshot UX - streamlined snapshot creation and policy setting (#878)
* uitask: added support for reporting string progress info

* server: report current directory as task progress

* snapshot: created reusable Estimate() method to be used during upload, cli estimate and via API

* cli: switched to snapshotfs.Estimate()

* server: added API to estimate snapshot size

* kopia-ui: fixed directory selector

* htmlui: streamlined new snapshot flow and cleaned up policy setting

See https://youtu.be/8p6csuoB3kg
2021-03-10 23:04:55 -08:00

231 lines
7.0 KiB
JavaScript

import { faChevronCircleDown, faChevronCircleUp, faStopCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import axios from 'axios';
import React, { Component } from 'react';
import Alert from 'react-bootstrap/Alert';
import Badge from 'react-bootstrap/Badge';
import Button from 'react-bootstrap/Button';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Spinner from 'react-bootstrap/Spinner';
import { TaskLogs } from './TaskLogs';
import { cancelTask, formatDuration, GoBackButton, redirectIfNotConnected, sizeDisplayName } from './uiutil';
export class TaskDetails extends Component {
constructor() {
super();
this.state = {
items: [],
isLoading: true,
error: null,
showLog: false,
};
this.taskID = this.taskID.bind(this);
this.fetchTask = this.fetchTask.bind(this);
// poll frequently, we will stop as soon as the task ends.
this.interval = window.setInterval(() => this.fetchTask(this.props), 500);
}
componentDidMount() {
this.setState({
isLoading: true,
});
this.fetchTask(this.props);
}
componentWillUnmount() {
if (this.interval) {
window.clearInterval(this.interval);
}
}
taskID(props) {
return props.taskID || props.match.params.tid;
}
fetchTask(props) {
axios.get('/api/v1/tasks/' + this.taskID(props)).then(result => {
this.setState({
task: result.data,
isLoading: false,
});
if (result.data.endTime) {
window.clearInterval(this.interval);
this.interval = null;
}
}).catch(error => {
redirectIfNotConnected(error);
this.setState({
error,
isLoading: false
});
});
}
componentWillReceiveProps(props) {
this.fetchTask(props);
}
summaryControl(task) {
const dur = formatDuration(task.startTime, task.endTime)
switch (task.status) {
case "SUCCESS":
return <Alert variant="success">Task succeeded after {dur}.</Alert>;
case "FAILED":
return <Alert variant="danger"><b>Error:</b> {task.errorMessage}.</Alert>;
case "CANCELED":
return <Alert variant="warning">Task canceled.</Alert>;
case "CANCELING":
return <Alert variant="primary">
<Spinner animation="border" variant="warning" size="sm" /> Canceling {dur}: {task.progressInfo}.</Alert>;
default:
return <Alert variant="primary">
<Spinner animation="border" variant="primary" size="sm" /> Running for {dur}: {task.progressInfo}.</Alert>;
}
}
valueThreshold() {
if (this.props.showZeroCounters) {
return -1;
}
return 0
}
counterBadge(label, c) {
if (c.value < this.valueThreshold()) {
return "";
}
let formatted = c.value.toLocaleString();
if (c.units === "bytes") {
formatted = sizeDisplayName(c.value);
}
let variant = "secondary";
switch (c.level) {
case "warning":
variant = "warning";
break;
case "error":
variant = "danger";
break;
case "notice":
variant = "info";
break;
default:
variant = "secondary";
break;
}
return <Badge key={label} className="counter-badge" variant={variant}>{label}: {formatted}</Badge>
}
counterLevelToSortOrder(l) {
switch (l) {
case "error":
return 30
case "notice":
return 10;
case "warning":
return 5;
default:
return 0;
}
}
sortedBadges(counters) {
let keys = Object.keys(counters);
// sort keys by their level and the name alphabetically.
keys.sort((a, b) => {
if (counters[a].level !== counters[b].level) {
return this.counterLevelToSortOrder(counters[b].level) - this.counterLevelToSortOrder(counters[a].level);
}
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
});
return keys.map(c => (counters[c].value > this.valueThreshold()) && this.counterBadge(c, counters[c]));
}
render() {
const { task, isLoading, error } = this.state;
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return <p>Loading ...</p>;
}
return <Form>
{this.props.history &&
<Form.Row>
<Form.Group>
<GoBackButton onClick={this.props.history.goBack} />
{task.status === "RUNNING" && <>
&nbsp;<Button size="sm" variant="danger" onClick={() => cancelTask(task.id)} ><FontAwesomeIcon icon={faStopCircle} /> Stop </Button>
</>}
</Form.Group>
</Form.Row>}
{!this.props.hideDescription && <Form.Row>
<Col xs={3} >
<Form.Group>
<Form.Control type="text" readOnly={true} value={task.kind} />
</Form.Group>
</Col>
<Col xs={9} >
<Form.Group>
<Form.Control type="text" readOnly={true} value={task.description} />
</Form.Group>
</Col>
</Form.Row>}
<Form.Row>
<Col xs={9}>
{this.summaryControl(task)}
</Col>
<Col xs={3}>
<Form.Group>
<Form.Control type="text" readOnly={true} value={"Started: " + new Date(task.startTime).toLocaleString()} />
</Form.Group>
</Col>
</Form.Row>
{task.counters && <Form.Row>
<Col>
{this.sortedBadges(task.counters)}
</Col>
</Form.Row>}
<hr />
<Form.Row>
<Col>
{this.state.showLog ? <>
<Button size="sm" onClick={() => this.setState({ showLog: false })}><FontAwesomeIcon icon={faChevronCircleUp} /> Hide Log</Button>
<TaskLogs taskID={this.taskID(this.props)} />
</> : <Button size="sm" onClick={() => this.setState({ showLog: true })}><FontAwesomeIcon icon={faChevronCircleDown} /> Show Log</Button>}
</Col>
</Form.Row>
</Form>
;
}
}