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

Added directory breadcrumb buttons to see path and navigate back #141

Merged
merged 9 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"license": "Apache-2.0",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-regular-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@fortawesome/react-fontawesome": "^0.2.0",
Expand Down
40 changes: 40 additions & 0 deletions src/DirectoryBreadcrumbs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import Breadcrumb from 'react-bootstrap/Breadcrumb';
import { useHistory, useLocation } from 'react-router-dom';
import { OverlayTrigger, Tooltip } from "react-bootstrap";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";

export function DirectoryBreadcrumbs() {
const location = useLocation();
const history = useHistory();

const breadcrumbs = []
for (let state = location.state; state; state = state.prevState) {
breadcrumbs.unshift(state)
}

return (
<Breadcrumb>
{
breadcrumbs.map((state, i) => {
const index = breadcrumbs.length - i - 1 // revert index
return <Breadcrumb.Item key={index} size="sm" variant="outline-secondary"
onClick={() => {
if (index) history.go(-index);
}}
active={!index}>
{state.label}
{state.oid && !index && <>&nbsp;<OverlayTrigger placement="top"
trigger="click"
overlay={<Tooltip
className={"wide-tooltip"}>OID: {state.oid}</Tooltip>}
>
<FontAwesomeIcon icon={faInfoCircle} />
</OverlayTrigger></>}
</Breadcrumb.Item>;
})
}
</Breadcrumb>
)
}
6 changes: 3 additions & 3 deletions src/DirectoryItems.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ function sizeInfo(item) {
return 0;
}

function directoryLinkOrDownload(x) {
function directoryLinkOrDownload(x, state) {
if (x.obj.startsWith("k")) {
return <Link to={objectLink(x.obj)}>{objectName(x.name, x.type)}</Link>;
return <Link to={objectLink(x.obj, x.name, state)}>{objectName(x.name, x.type)}</Link>;
}

return <a href={"/api/v1/objects/" + x.obj + "?fname=" + encodeURIComponent(x.name)}>{x.name}</a>;
Expand All @@ -39,7 +39,7 @@ export class DirectoryItems extends Component {
id: "name",
Header: 'Name',
width: "",
accessor: x => directoryLinkOrDownload(x),
accessor: x => directoryLinkOrDownload(x, this.props.historyState),
}, {
id: "mtime",
accessor: "mtime",
Expand Down
32 changes: 18 additions & 14 deletions src/DirectoryObject.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Spinner from 'react-bootstrap/Spinner';
import { DirectoryItems } from "./DirectoryItems";
import { CLIEquivalent, GoBackButton } from './uiutil';
import { CLIEquivalent } from './uiutil';
import { DirectoryBreadcrumbs } from "./DirectoryBreadcrumbs";

export class DirectoryObject extends Component {
constructor() {
Expand Down Expand Up @@ -118,33 +119,36 @@ export class DirectoryObject extends Component {
}

return <>
<DirectoryBreadcrumbs />
<Row>
<Col xs={12}>
<GoBackButton onClick={this.props.history.goBack} />
&nbsp;
<Col xs="auto">
{this.state.mountInfo.path ? <>
<Button size="sm" variant="info" onClick={this.unmount} >Unmount</Button>
<Button size="sm" variant="info" onClick={this.unmount}>Unmount</Button>
{window.kopiaUI && <>
&nbsp;
<Button size="sm" variant="info" onClick={this.browseMounted} >Browse</Button>
<Button size="sm" variant="info" onClick={this.browseMounted}>Browse</Button>
</>}
&nbsp;<input id="mountedPath" value={this.state.mountInfo.path} />
<Button size="sm" variant="primary" onClick={this.copyPath} ><FontAwesomeIcon icon={faCopy} /></Button>
<Button size="sm" variant="primary" onClick={this.copyPath}><FontAwesomeIcon
icon={faCopy} /></Button>
</> : <>
<Button size="sm" variant="primary" onClick={this.mount} >Mount as Local Filesystem</Button>
<Button size="sm" variant="primary" onClick={this.mount}>Mount as Local Filesystem</Button>
</>}
&nbsp;
<Button size="sm" variant="info" href={"/snapshots/dir/" + this.props.match.params.oid + "/restore"}>Restore Files & Directories</Button>
<Button size="sm" variant="info"
href={"/snapshots/dir/" + this.props.match.params.oid + "/restore"}>Restore
Files & Directories</Button>
&nbsp;
</Col>
<Col xs={12} md={6}>
You can mount/restore all the files & directories that you see below or restore files
individually.
</Col>
</Row>
<Row><Col>&nbsp;</Col>
</Row>
<Row><Col xs={12}>You can mount/restore all the files & directories that you see below or restore files individually.</Col>
</Row>
<Row><Col>&nbsp;</Col>
</Row>
<Row>
<Col xs={12}><DirectoryItems items={items} /></Col>
<Col xs={12}><DirectoryItems items={items} historyState={this.props.location.state} /></Col>
</Row>
<CLIEquivalent command={`snapshot list ${this.state.oid}`} />
</>
Expand Down
18 changes: 12 additions & 6 deletions src/SnapshotsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ export class SnapshotsTable extends Component {
originalSnapshotDescription: x.description,
})
}}
title={x.description + " - Click to update snapshot description."}
className={x.description ? "text-warning" : "text-muted"}><b><FontAwesomeIcon icon={faFileAlt} /></b></a>;
title={x.description + " - Click to update snapshot description."}
className={x.description ? "text-warning" : "text-muted"}><b><FontAwesomeIcon icon={faFileAlt} /></b></a>;
}

newPinFor(x) {
Expand Down Expand Up @@ -323,6 +323,9 @@ export class SnapshotsTable extends Component {
if (isLoading && !snapshots) {
return <Spinner animation="border" variant="primary" />;
}
const searchParams = new URLSearchParams(window.location.search);
const path = searchParams.get("path");


snapshots.sort((a, b) => -compare(a.startTime, b.startTime));

Expand All @@ -336,7 +339,10 @@ export class SnapshotsTable extends Component {
id: 'startTime',
Header: 'Start time',
width: 200,
accessor: x => <Link to={objectLink(x.rootID)}>{rfc3339TimestampForDisplay(x.startTime)}</Link>,
accessor: x => {
let timestamp = rfc3339TimestampForDisplay(x.startTime);
return <Link to={objectLink(x.rootID, timestamp, { label: path })}>{timestamp}</Link>;
},
}, {
id: 'description',
Header: '',
Expand Down Expand Up @@ -465,9 +471,9 @@ export class SnapshotsTable extends Component {
<Form.Group>
<Form.Label>Enter new description</Form.Label>
<Form.Control as="textarea"
size="sm"
value={this.state.updatedSnapshotDescription}
onChange={e => this.setState({ updatedSnapshotDescription: e.target.value })} />
size="sm"
value={this.state.updatedSnapshotDescription}
onChange={e => this.setState({ updatedSnapshotDescription: e.target.value })} />
</Form.Group>
</Modal.Body>

Expand Down
22 changes: 11 additions & 11 deletions src/uiutil.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ export function rfc3339TimestampForDisplay(n) {
return t.toLocaleString();
}

export function objectLink(n) {
export function objectLink(n, label, prevState) {
if (n.startsWith("k") || n.startsWith("Ik")) {
return "/snapshots/dir/" + n;
return { pathname: "/snapshots/dir/" + n, state: { label, oid: n, prevState } };
}
return "/api/v1/objects/" + n;
}
Expand All @@ -120,7 +120,7 @@ export function redirect(e) {

/**
* Convert a number of milliseconds into a string containing multiple units.
*
*
* e.g. 90000 --> "1m 30s" or "1 minute 30 seconds"
*
* @param {number} ms - A duration (as a number of milliseconds).
Expand All @@ -136,9 +136,9 @@ export function formatMillisecondsUsingMultipleUnits(ms) {
* Separate a duration into integer magnitudes of multiple units which,
* when combined together, equal the original duration (minus any partial
* milliseconds, if the original duration included any partial milliseconds).
*
*
* e.g. 100000123.999 --> 1 day 3 hours 46 minutes 40 seconds 123 milliseconds
*
*
* @param {number} ms - A duration (as a number of milliseconds).
* @returns {object} An object having numeric properties named `days`, `hours`,
* `minutes`, `seconds`, and `milliseconds`; whose values,
Expand All @@ -161,16 +161,16 @@ export function separateMillisecondsIntoMagnitudes(ms) {
* Format a duration in terms of the largest unit having a non-zero magnitude,
* together with the next largest unit (e.g. hours --> hours and minutes),
* disregarding all smaller units (i.e. truncate, as opposed to round).
*
*
* There are some exceptions, which are listed below.
*
*
* Exceptions:
* 1. If the largest unit having a non-zero magnitude is `seconds` and the
* magnitude is at least 10, format it as an integer number of seconds.
* 2. If the largest unit having a non-zero magnitude is `seconds` and the
* magnitude is less than 10, or if the largest unit having a non-zero
* magnitude is `milliseconds`, format it as a fractional number of seconds.
*
*
* @param {object} magnitudes - An object having numeric properties named
* `days`, `hours`, `minutes`, `seconds`, and
* `milliseconds`; whose values, when combined
Expand Down Expand Up @@ -234,7 +234,7 @@ export function formatMagnitudesUsingMultipleUnits(magnitudes, abbreviateUnits =
/**
* Convert a number of milliseconds into a formatted string, either
* using multiple units (e.g. "1m 5s") or using seconds (e.g. "65.0s").
*
*
* @param {number} ms - The number of milliseconds (i.e. some duration).
* @param {boolean} useMultipleUnits - Whether you want to use multiple units.
* @returns {string} The formatted string.
Expand Down Expand Up @@ -410,8 +410,8 @@ export function CLIEquivalent(props) {
return <>
<InputGroup size="sm" >
<Button size="sm" title="Click to show CLI equivalent" variant="submit" onClick={() => setVisible(!visible)}><FontAwesomeIcon size="sm" icon={faTerminal} /></Button>
{visible && <Button class="sm" variant="success" title="Copy to clipboard" onClick={copyToClibopard} ><FontAwesomeIcon size="sm" icon={faCopy} /></Button>}
{visible && <FormControl size="sm" ref={ref} className="cli-equivalent" value={`${cliInfo.executable} ${props.command}`} />}
{visible && <Button size="sm" variant="success" title="Copy to clipboard" onClick={copyToClibopard} ><FontAwesomeIcon size="sm" icon={faCopy} /></Button>}
{visible && <FormControl size="sm" ref={ref} className="cli-equivalent" readOnly={true} value={`${cliInfo.executable} ${props.command}`} />}
</InputGroup>
</>;
}
Expand Down
Loading