Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ui): Fixed minor bugs in html-ui and added QOL changes #177

Merged
merged 2 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions public/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
div#appVersion {
display: none
}

3 changes: 2 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="index.css">
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Kopia UI" />
Expand All @@ -15,7 +16,7 @@
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div id="appVersion" style="display: none">
<div id="appVersion">
<hr />
<p class="version-info">Version %REACT_APP_FULL_VERSION_INFO%</p>
</div>
Expand Down
28 changes: 14 additions & 14 deletions src/PoliciesTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class PoliciesTable extends Component {
constructor() {
super();
this.state = {
items: [],
policies: [],
isLoading: false,
error: null,
editorTarget: null,
Expand Down Expand Up @@ -64,7 +64,7 @@ export class PoliciesTable extends Component {
fetchPolicies() {
axios.get('/api/v1/policies').then(result => {
this.setState({
items: result.data.policies,
policies: result.data.policies,
isLoading: false,
});
}).catch(error => {
Expand Down Expand Up @@ -166,7 +166,7 @@ export class PoliciesTable extends Component {
}

render() {
let { items, sources, isLoading, error } = this.state;
let { policies, sources, isLoading, error } = this.state;
if (error) {
return <p>{error.message}</p>;
}
Expand All @@ -189,31 +189,31 @@ export class PoliciesTable extends Component {
break;

case globalPolicy:
items = items.filter(x => this.isGlobalPolicy(x));
policies = policies.filter(x => this.isGlobalPolicy(x));
break;

case localPolicies:
items = items.filter(x => this.isLocalUserPolicy(x));
policies = policies.filter(x => this.isLocalUserPolicy(x));
break;

case applicablePolicies:
items = items.filter(x => this.isLocalUserPolicy(x) || this.isLocalHostPolicy(x) || this.isGlobalPolicy(x));
policies = policies.filter(x => this.isLocalUserPolicy(x) || this.isLocalHostPolicy(x) || this.isGlobalPolicy(x));
break;

case perUserPolicies:
items = items.filter(x => !!x.target.userName && !!x.target.host && !x.target.path);
policies = policies.filter(x => !!x.target.userName && !!x.target.host && !x.target.path);
break;

case perHostPolicies:
items = items.filter(x => !x.target.userName && !!x.target.host && !x.target.path);
policies = policies.filter(x => !x.target.userName && !!x.target.host && !x.target.path);
break;

default:
items = items.filter(x => ownerName(x.target) === this.state.selectedOwner);
policies = policies.filter(x => ownerName(x.target) === this.state.selectedOwner);
break;
};

items.sort((l,r) => {
policies.sort((l,r) => {
const hc = compare(l.target.host,r.target.host);
if (hc) {
return hc;
Expand Down Expand Up @@ -242,7 +242,7 @@ export class PoliciesTable extends Component {
accessor: x => this.policySummary(x),
}, {
id: 'edit',
Header: '',
Header: 'Actions',
width: 50,
Cell: x => <Button data-testid="edit-policy" as={Link} to={policyEditorURL(x.row.original.target)} variant="primary" size="sm">Edit</Button>
}]
Expand Down Expand Up @@ -284,9 +284,9 @@ export class PoliciesTable extends Component {
</Form>
</div>}

{items.length > 0 ? <div>
<p>Found {items.length} policies matching criteria.</p>
<MyTable data={items} columns={columns} />
{policies.length > 0 ? <div>
<p>Found {policies.length} policies matching criteria.</p>
<MyTable data={policies} columns={columns} />
</div> : ((this.state.selectedOwner === localPolicies && this.state.policyPath) ? <p>
No policy found for directory <code>{this.state.policyPath}</code>. Click <b>Set Policy</b> to define it.
</p> : <p>No policies found.</p>)}
Expand Down
22 changes: 9 additions & 13 deletions src/PolicyEditor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,8 @@ export class PolicyEditor extends Component {
<Accordion.Body>
<SectionHeaderRow />
<Row>
<LabelColumn name="Ignore Files" help="List of file and directory names to ignore (specified as relative to the directory they are in and not absolute; wildcards are allowed)" />
<WideValueColumn>{StringList(this, "policy.files.ignore")}</WideValueColumn>
<LabelColumn name="Ignore Files" help={<> List of file and directory names to ignore. <br /> (See <a target="_blank" rel="noreferrer" href="https://kopia.io/docs/advanced/kopiaignore/">documentation on ignoring files</a>).</>} />
<WideValueColumn>{StringList(this, "policy.files.ignore", { placeholder: "e.g. /file.txt" })}</WideValueColumn>
{EffectiveTextAreaValue(this, "files.ignore")}
</Row>
<Row>
Expand All @@ -311,7 +311,7 @@ export class PolicyEditor extends Component {
</Row>
<Row>
<LabelColumn name="Ignore Rule Files" help="List of additional files containing ignore rules (each file configures ignore rules for the directory and its subdirectories)" />
<ValueColumn>{StringList(this, "policy.files.ignoreDotFiles")}</ValueColumn>
<ValueColumn>{StringList(this, "policy.files.ignoreDotFiles", { placeholder: "e.g. .kopiaignore" })}</ValueColumn>
{EffectiveTextAreaValue(this, "files.ignoreDotFiles")}
</Row>
<Row>
Expand Down Expand Up @@ -382,14 +382,14 @@ export class PolicyEditor extends Component {
<Row>
<LabelColumn name="Only Compress Extensions" help="Only compress files with the following file extensions (one extension per line)" />
<WideValueColumn>
{StringList(this, "policy.compression.onlyCompress")}
{StringList(this, "policy.compression.onlyCompress", { placeholder: "e.g. *.txt" })}
</WideValueColumn>
{EffectiveTextAreaValue(this, "compression.onlyCompress")}
</Row>
<Row>
<LabelColumn name="Never Compress Extensions" help="Never compress the following file extensions (one extension per line)" />
<WideValueColumn>
{StringList(this, "policy.compression.neverCompress")}
{StringList(this, "policy.compression.neverCompress", { placeholder: "e.g. *.mp4" })}
</WideValueColumn>
{EffectiveTextAreaValue(this, "compression.neverCompress")}
</Row>
Expand Down Expand Up @@ -420,19 +420,17 @@ export class PolicyEditor extends Component {
{EffectiveValue(this, "scheduling.intervalSeconds")}
</Row>
<Row>
<LabelColumn name="Times Of Day" help="Create snapshots at the specified times of day (one per line and listed in 24hr time, ex: 17:00)" />
<LabelColumn name="Times Of Day" help="Create snapshots at the specified times of day (24hr format)" />
<ValueColumn>
{TimesOfDayList(this, "policy.scheduling.timeOfDay")}
{TimesOfDayList(this, "policy.scheduling.timeOfDay", { placeholder: "e.g. 17:00" })}
</ValueColumn>
{EffectiveTimesOfDayValue(this, "scheduling.timeOfDay")}
</Row>
<Row>
<LabelColumn name="Cron Expressions" help={<>Snapshot schedules using UNIX crontab syntax (one per line):
<br/><br/><pre>minute hour day month weekday #comment</pre>

See <a target="_blank" rel="noreferrer" href="https://github.com/hashicorp/cronexpr#implementation">supported format details</a>.</>} />
<br /> See <a target="_blank" rel="noreferrer" href="https://github.com/hashicorp/cronexpr#implementation">supported format details</a>.</>} />
<ValueColumn>
{StringList(this, "policy.scheduling.cron")}
{StringList(this, "policy.scheduling.cron", { placeholder: "minute hour day month weekday #comment" })}
</ValueColumn>
{EffectiveListValue(this, "scheduling.cron")}
</Row>
Expand Down Expand Up @@ -572,8 +570,6 @@ export class PolicyEditor extends Component {
&nbsp;
<Spinner animation="border" variant="primary" size="sm" />
</>}
{/* <pre className="debug-json">{JSON.stringify(this.state, null, 4)}
</pre> */}
</Form>
</>;
}
Expand Down
4 changes: 2 additions & 2 deletions src/PreferencesView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class PreferencesView extends Component {
<form>
<div className='form-group'>
<label className='label-description' id='themeLabel'>Theme</label>
<select className="select_theme, form-select form-select-sm" value={theme} onChange={e => setTheme(e.target.value)}>
<select className="select_theme, form-select form-select-sm" title='Select theme' value={theme} onChange={e => setTheme(e.target.value)}>
<option value="light">light</option>
<option value="dark">dark</option>
<option value="pastel">pastel</option>
Expand All @@ -19,7 +19,7 @@ export class PreferencesView extends Component {
<br />
<div className='form-group'>
<label className='label-description'>Byte representation</label>
<select className="form-select form-select-sm" id='bytesBaseInput' value={bytesStringBase2} onChange={e => setByteStringBase(e.target.value)}>
<select className="form-select form-select-sm" title='Select byte representation' id='bytesBaseInput' value={bytesStringBase2} onChange={e => setByteStringBase(e.target.value)}>
<option value="true">Base-2 (KiB, MiB, GiB, TiB)</option>
<option value="false">Base-10 (KB, MB, GB, TB)</option>
</select>
Expand Down
14 changes: 7 additions & 7 deletions src/RepoStatus.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ export class RepoStatus extends Component {
if (this.state.status.initTaskID) {
return <><h4><Spinner animation="border" variant="primary" size="sm" />&nbsp;Initializing Repository...</h4>
{this.state.showLog ? <>
<Button size="sm" variant="light" onClick={() => this.setState({ showLog: false })}><FontAwesomeIcon icon={faChevronCircleUp} /> Hide Log</Button>
<TaskLogs taskID={this.state.status.initTaskID} />
</> : <Button size="sm" variant="light" onClick={() => this.setState({ showLog: true })}><FontAwesomeIcon icon={faChevronCircleDown} /> Show Log</Button>}
<hr/>
<Button size="sm" variant="light" onClick={() => this.setState({ showLog: false })}><FontAwesomeIcon icon={faChevronCircleUp} /> Hide Log</Button>
<TaskLogs taskID={this.state.status.initTaskID} />
</> : <Button size="sm" variant="light" onClick={() => this.setState({ showLog: true })}><FontAwesomeIcon icon={faChevronCircleDown} /> Show Log</Button>}
<hr />
<Button size="sm" variant="danger" icon={faWindowClose} title="Cancel" onClick={() => cancelTask(this.state.status.initTaskID)}>Cancel Connection</Button>
</>;
}
Expand All @@ -143,7 +143,7 @@ export class RepoStatus extends Component {
<FontAwesomeIcon icon={faCheck} style={{ marginRight: 4 }} />
<span>Connected To Repository</span>
</p>
<Form onSubmit={this.updateDescription}>
<Form>
<Row>
<Form.Group as={Col}>
<InputGroup>
Expand All @@ -155,7 +155,7 @@ export class RepoStatus extends Component {
onChange={this.handleChange}
size="sm" />
&nbsp;
<Button data-testid='update-description' size="sm" type="submit">Update Description</Button>
<Button data-testid='update-description' size="sm" onClick={this.updateDescription} type="button">Update Description</Button>
</InputGroup>
<Form.Control.Feedback type="invalid">Description Is Required</Form.Control.Feedback>
</Form.Group>
Expand Down Expand Up @@ -209,7 +209,7 @@ export class RepoStatus extends Component {
</Form.Group>
<Form.Group as={Col}>
<Form.Label>Error Correction Algorithm</Form.Label>
<Form.Control readOnly defaultValue={this.state.status.ecc||"-"} />
<Form.Control readOnly defaultValue={this.state.status.ecc || "-"} />
</Form.Group>
<Form.Group as={Col}>
<Form.Label>Internal Compression</Form.Label>
Expand Down
4 changes: 2 additions & 2 deletions src/SnapshotsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ export class SnapshotsTable extends Component {

const columns = [{
id: 'selected',
Header: '',
Header: 'Selected',
width: 20,
align: "center",
Cell: x => <div className="form-check multiselect"><input type="checkbox" className="form-check-input" checked={this.isSelected(x.row.original)} onChange={() => this.toggleSelected(x.row.original)} /></div>,
Expand Down Expand Up @@ -397,7 +397,7 @@ export class SnapshotsTable extends Component {
</Col>
<Col xs="auto">
<Button size="sm" variant="primary">
{this.state.isRefreshing ? <Spinner animation="border" variant="light" size="sm" /> : <FontAwesomeIcon icon={faSync} onClick={this.fetchSnapshots} />}
{this.state.isRefreshing ? <Spinner animation="border" variant="light" size="sm" /> : <FontAwesomeIcon icon={faSync} title="Fetch snapshots" onClick={this.fetchSnapshots} />}
</Button>
</Col>
</Row>
Expand Down
28 changes: 26 additions & 2 deletions src/SourcesTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,31 @@ export class SourcesTable extends Component {
});
}

/**
* Sets the header of an cell dynamically based on it's status
* @param x - the cell which status is interpreted
* @returns - the header of the cell
*/
setHeader(x) {
switch (x.cell.value) {
case "IDLE":
case "PAUSED":
return x.cell.column.Header = "Actions"
case "PENDING":
case "UPLOADING":
return x.cell.column.Header = "Status"
default:
return x.cell.column.Header = ""
}
}

/**
* Sets the content an cell dynamically based on it's status
* @param x - the cell which content is changed
* @returns - the content of the cell
*/
statusCell(x, parent, bytesStringBase2) {
this.setHeader(x)
switch (x.cell.value) {
case "IDLE":
case "PAUSED":
Expand Down Expand Up @@ -262,7 +286,7 @@ export class SourcesTable extends Component {
Header: '',
width: 300,
accessor: x => x.status,
Cell: x => this.statusCell(x, this, bytesStringBase2),
Cell: x => this.statusCell(x, this, bytesStringBase2)
}]

return <>
Expand All @@ -288,7 +312,7 @@ export class SourcesTable extends Component {
<Col>
</Col>
<Col xs="auto">
<Button size="sm" variant="primary">
<Button size="sm" title="Synchronize" variant="primary">
{this.state.isRefreshing ? <Spinner animation="border" variant="light" size="sm" /> : <FontAwesomeIcon icon={faSync} onClick={this.sync} />}
</Button>
</Col>
Expand Down
16 changes: 7 additions & 9 deletions src/Table.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,15 @@ export default function MyTable({ columns, data }) {
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map(
(row, i) => {
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
}
<tr {...row.getRowProps()} key={i}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)}
)}
</tbody>
</Table>
Expand Down
5 changes: 3 additions & 2 deletions src/forms/StringList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ function multilineStringToList(target) {
return v.split(/\n/);
}

export function StringList(component, name) {
export function StringList(component, name, props = {}) {
return <Form.Group as={Col}>
<Form.Control
size="sm"
name={name}
value={listToMultilineString(stateProperty(component, name))}
onChange={e => component.handleChange(e, multilineStringToList)}
as="textarea"
rows="5">
rows="5"
{...props}>
</Form.Control>
</Form.Group>;
}
5 changes: 3 additions & 2 deletions src/forms/TimesOfDayList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Form from 'react-bootstrap/Form';
import FormGroup from 'react-bootstrap/FormGroup';
import { stateProperty } from '.';

export function TimesOfDayList(component, name) {
export function TimesOfDayList(component, name, props = {}) {
function parseTimeOfDay(v) {
var re = /(\d+):(\d+)/;

Expand Down Expand Up @@ -65,7 +65,8 @@ export function TimesOfDayList(component, name) {
value={toMultilineString(stateProperty(component, name))}
onChange={e => component.handleChange(e, fromMultilineString)}
as="textarea"
rows="5">
rows="5"
{...props}>
</Form.Control>
<Form.Control.Feedback type="invalid">Invalid Times of Day</Form.Control.Feedback>
</FormGroup>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render, waitFor, logDOM } from '@testing-library/react';
import React from 'react';
import { PolicyEditor } from '.';
import { PolicyEditor } from '../PolicyEditor/.';
import { MemoryRouter } from 'react-router-dom';
import { setupAPIMock } from '../tests/api_mocks';
import moment from 'moment';
Expand Down
Loading
Loading