mirror of
https://github.com/kopia/kopia.git
synced 2026-03-12 11:16:25 -04:00
* 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
231 lines
7.0 KiB
JavaScript
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" && <>
|
|
<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>
|
|
;
|
|
}
|
|
}
|