mirror of
https://github.com/kopia/kopia.git
synced 2026-03-17 21:56:14 -04:00
292 lines
13 KiB
JavaScript
292 lines
13 KiB
JavaScript
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import axios from 'axios';
|
|
import React, { Component } from 'react';
|
|
import Button from 'react-bootstrap/Button';
|
|
import Col from 'react-bootstrap/Col';
|
|
import Form from 'react-bootstrap/Form';
|
|
import Row from 'react-bootstrap/Row';
|
|
import Spinner from 'react-bootstrap/Spinner';
|
|
import Tab from 'react-bootstrap/Tab';
|
|
import Tabs from 'react-bootstrap/Tabs';
|
|
import { handleChange, OptionalBoolean, OptionalNumberField, RequiredBoolean, stateProperty, StringList } from './forms';
|
|
import { errorAlert, sourceQueryStringParams } from './uiutil';
|
|
|
|
function policyTypeName(s) {
|
|
if (!s.host && !s.userName) {
|
|
return "Global Policy"
|
|
}
|
|
|
|
if (!s.userName) {
|
|
return "Per-Host Policy";
|
|
}
|
|
|
|
if (!s.path) {
|
|
return "Per-User Policy";
|
|
}
|
|
|
|
return "Directory Policy";
|
|
}
|
|
|
|
export class PolicyEditor extends Component {
|
|
constructor() {
|
|
super();
|
|
this.state = {
|
|
items: [],
|
|
isLoading: false,
|
|
error: null,
|
|
};
|
|
|
|
this.fetchPolicy = this.fetchPolicy.bind(this);
|
|
this.handleChange = handleChange.bind(this);
|
|
this.saveChanges = this.saveChanges.bind(this);
|
|
this.isGlobal = this.isGlobal.bind(this);
|
|
this.deletePolicy = this.deletePolicy.bind(this);
|
|
this.snapshotURL = this.snapshotURL.bind(this);
|
|
}
|
|
|
|
componentDidMount() {
|
|
axios.get('/api/v1/repo/algorithms').then(result => {
|
|
this.setState({
|
|
algorithms: result.data,
|
|
});
|
|
|
|
this.fetchPolicy(this.props);
|
|
});
|
|
}
|
|
|
|
componentWillReceiveProps(props) {
|
|
this.fetchPolicy(props);
|
|
}
|
|
|
|
fetchPolicy(props) {
|
|
axios.get(this.snapshotURL(props)).then(result => {
|
|
this.setState({
|
|
isLoading: false,
|
|
policy: result.data,
|
|
});
|
|
}).catch(error => {
|
|
if (error.response && error.response.data.code !== "NOT_FOUND") {
|
|
this.setState({
|
|
error: error,
|
|
isLoading: false
|
|
})
|
|
} else {
|
|
this.setState({
|
|
policy: {},
|
|
isNew: true,
|
|
isLoading: false
|
|
})
|
|
}
|
|
});
|
|
}
|
|
|
|
saveChanges(e) {
|
|
e.preventDefault()
|
|
|
|
function removeEmpty(l) {
|
|
if (!l) {
|
|
return l;
|
|
}
|
|
|
|
let result = [];
|
|
for (let i = 0; i < l.length; i++) {
|
|
const s = l[i];
|
|
if (s === "") {
|
|
continue;
|
|
}
|
|
|
|
result.push(s);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// clean up policy before saving
|
|
let policy = JSON.parse(JSON.stringify(this.state.policy));
|
|
if (policy.files) {
|
|
if (policy.files.ignore) {
|
|
policy.files.ignore = removeEmpty(policy.files.ignore)
|
|
}
|
|
if (policy.files.ignoreDotFiles) {
|
|
policy.files.ignoreDotFiles = removeEmpty(policy.files.ignoreDotFiles)
|
|
}
|
|
}
|
|
|
|
if (policy.compression) {
|
|
if (policy.compression.onlyCompress) {
|
|
policy.compression.onlyCompress = removeEmpty(policy.compression.onlyCompress)
|
|
}
|
|
if (policy.compression.neverCompress) {
|
|
policy.compression.neverCompress = removeEmpty(policy.compression.neverCompress)
|
|
}
|
|
}
|
|
|
|
this.setState({saving: true});
|
|
axios.put(this.snapshotURL(this.props), policy).then(result => {
|
|
this.props.close();
|
|
}).catch(error => {
|
|
this.setState({saving: false});
|
|
errorAlert(error, 'Error saving policy');
|
|
});
|
|
}
|
|
|
|
deletePolicy() {
|
|
if (window.confirm('Are you sure you want to delete this policy?')) {
|
|
this.setState({saving: true});
|
|
|
|
axios.delete(this.snapshotURL(this.props)).then(result => {
|
|
this.props.close();
|
|
}).catch(error => {
|
|
this.setState({saving: false});
|
|
errorAlert(error, 'Error deleting policy');
|
|
});
|
|
}
|
|
}
|
|
|
|
snapshotURL(props) {
|
|
return '/api/v1/policy?' + sourceQueryStringParams(props);
|
|
}
|
|
|
|
isGlobal() {
|
|
return !this.props.host && !this.props.userName && !this.props.path;
|
|
}
|
|
|
|
render() {
|
|
const { isLoading, error } = this.state;
|
|
if (error) {
|
|
return <p>{error.message}</p>;
|
|
}
|
|
|
|
if (isLoading) {
|
|
return <p>Loading ...</p>;
|
|
}
|
|
|
|
return <div className="padded">
|
|
{!this.props.embedded && <h4><Button size="sm" variant="outline-secondary" onClick={this.props.close} ><FontAwesomeIcon icon={faChevronLeft} /> Return </Button>
|
|
{policyTypeName(this.props)}</h4>}
|
|
<Form onSubmit={this.saveChanges}>
|
|
{!this.props.embedded && <Row>
|
|
<Col xs="2">
|
|
<Form.Group>
|
|
<Form.Label className="required">Target User</Form.Label>
|
|
<Form.Control size="sm" type="text" readOnly value={this.props.userName || "<any user>"} />
|
|
</Form.Group>
|
|
</Col>
|
|
<Col xs="2">
|
|
<Form.Group>
|
|
<Form.Label className="required">Target Host</Form.Label>
|
|
<Form.Control size="sm" type="text" readOnly value={this.props.host || "<any host>"} />
|
|
</Form.Group>
|
|
</Col>
|
|
<Col xs="8">
|
|
<Form.Group>
|
|
<Form.Label className="required">Target Directory Path</Form.Label>
|
|
<Form.Control size="sm" type="text" readOnly value={this.props.path || "<any path>"} />
|
|
</Form.Group>
|
|
</Col>
|
|
</Row>}
|
|
<Tabs defaultActiveKey="retention">
|
|
<Tab eventKey="retention" title="Retention">
|
|
<div className="tab-body">
|
|
<p className="policy-help">Controls how many latest snapshots to keep per source directory</p>
|
|
<Form.Row>
|
|
{OptionalNumberField(this, "Latest", "policy.retention.keepLatest", { placeholder: "# of latest snapshots" })}
|
|
{OptionalNumberField(this, "Hourly", "policy.retention.keepHourly", { placeholder: "# of hourly snapshots" })}
|
|
{OptionalNumberField(this, "Daily", "policy.retention.keepDaily", { placeholder: "# of daily snapshots" })}
|
|
</Form.Row>
|
|
<Form.Row>
|
|
{OptionalNumberField(this, "Weekly", "policy.retention.keepWeekly", { placeholder: "# of weekly snapshots" })}
|
|
{OptionalNumberField(this, "Monthly", "policy.retention.keepMonthly", { placeholder: "# of monthly snapshots" })}
|
|
{OptionalNumberField(this, "Annual", "policy.retention.keepAnnual", { placeholder: "# of annual snapshots" })}
|
|
</Form.Row>
|
|
</div>
|
|
</Tab>
|
|
<Tab eventKey="files" title="Files">
|
|
<div className="tab-body">
|
|
<p className="policy-help">Controls which files should be included and excluded when snapshotting. Use <a target="_blank" rel="noreferrer" href="https://git-scm.com/docs/gitignore">.gitignore</a> syntax.</p>
|
|
<Form.Row>
|
|
{StringList(this, "Ignore Rules", "policy.files.ignore", "List of file name patterns to ignore.")}
|
|
{StringList(this, "Ignore Rule Files", "policy.files.ignoreDotFiles", "List of additional files containing ignore rules. Each file configures ignore rules for the directory and its subdirectories.")}
|
|
</Form.Row>
|
|
<Form.Row>
|
|
{RequiredBoolean(this, "Ignore Parent Rules", "policy.files.noParentIgnore")}
|
|
{RequiredBoolean(this, "Ignore Parent Rule Files", "policy.files.noParentDotFiles")}
|
|
</Form.Row>
|
|
<Form.Row>
|
|
{OptionalBoolean(this, "Ignore Well-Known Cache Directories", "policy.files.ignoreCacheDirs", "inherit from parent")}
|
|
</Form.Row>
|
|
<Form.Row>
|
|
{OptionalBoolean(this, "Scan only one filesystem", "policy.files.oneFileSystem", "inherit from parent")}
|
|
</Form.Row>
|
|
</div>
|
|
</Tab>
|
|
<Tab eventKey="errors" title="Errors">
|
|
<div className="tab-body">
|
|
<p className="policy-help">Controls how errors detected while snapshotting are handled.</p>
|
|
<Form.Row>
|
|
{OptionalBoolean(this, "Ignore Directory Errors", "policy.errorHandling.ignoreDirectoryErrors", "inherit from parent")}
|
|
{OptionalBoolean(this, "Ignore File Errors", "policy.errorHandling.ignoreFileErrors", "inherit from parent")}
|
|
{OptionalBoolean(this, "Ignore Unknown Types", "policy.errorHandling.ignoreUnknownTypes", "inherit from parent")}
|
|
</Form.Row>
|
|
</div>
|
|
</Tab>
|
|
<Tab eventKey="compression" title="Compression">
|
|
<div className="tab-body">
|
|
<p className="policy-help">Controls which files are compressed.</p>
|
|
<Form.Row>
|
|
<Form.Group as={Col}>
|
|
<Form.Label className="required">Compression</Form.Label>
|
|
<Form.Control as="select"
|
|
name="policy.compression.compressorName"
|
|
onChange={this.handleChange}
|
|
value={stateProperty(this, "policy.compression.compressorName")}>
|
|
<option value="">(none)</option>
|
|
{this.state.algorithms && this.state.algorithms.compression.map(x => <option key={x} value={x}>{x}</option>)}
|
|
</Form.Control>
|
|
</Form.Group>
|
|
{OptionalNumberField(this, "Min File Size", "policy.compression.minSize", { placeholder: "minimum file size to compress" })}
|
|
{OptionalNumberField(this, "Max File Size", "policy.compression.maxSize", { placeholder: "maximum file size to compress" })}
|
|
</Form.Row>
|
|
<Form.Row>
|
|
{StringList(this, "Only Compress Extensions", "policy.compression.onlyCompress", "Only compress files with the above file extensions (one extension per line)")}
|
|
{StringList(this, "Never Compress Extensions", "policy.compression.neverCompress", "Never compress the above file extensions (one extension per line)")}
|
|
</Form.Row>
|
|
</div>
|
|
</Tab>
|
|
<Tab eventKey="scheduling" title="Scheduling">
|
|
<div className="tab-body">
|
|
<p className="policy-help">Controls when snapshots are automatically created.</p>
|
|
<Form.Row>
|
|
{OptionalNumberField(this, "Snapshot Interval", "policy.scheduling.intervalSeconds", { placeholder: "seconds" })}
|
|
</Form.Row>
|
|
<Form.Row>
|
|
{OptionalBoolean(this, "Only create snapshots manually (disables scheduled snapshots)", "policy.scheduling.manual")}
|
|
</Form.Row>
|
|
</div>
|
|
</Tab>
|
|
</Tabs>
|
|
<Form.Row>
|
|
{RequiredBoolean(this, "Disable Parent Policy Evaluation (prevents any parent policies from affecting this directory and subdirectories)", "policy.noParent")}
|
|
</Form.Row>
|
|
|
|
<Button size="sm" variant="success" type="submit" onClick={this.saveChanges} disabled={this.state.saving}>Save Policy</Button>
|
|
{!this.state.isNew && <>
|
|
<Button size="sm" variant="danger" disabled={this.isGlobal() || this.state.saving} onClick={this.deletePolicy}>Delete Policy</Button>
|
|
</>}
|
|
{this.state.saving && <>
|
|
|
|
<Spinner animation="border" variant="primary" size="sm" />
|
|
</>}
|
|
|
|
{!this.props.embedded && <>
|
|
<hr />
|
|
<h5>JSON representation</h5>
|
|
<pre className="debug-json">{JSON.stringify(this.state.policy, null, 4)}
|
|
</pre>
|
|
</>}
|
|
</Form>
|
|
</div>;
|
|
}
|
|
}
|