mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-09-19 06:25:40 +00:00
fix: improve handling of restore operations
- restore operations are split into a new flow - added support displaying restore operation percentage and other details in tree view
This commit is contained in:
@@ -259,7 +259,7 @@ export const OperationRow = ({
|
||||
} else if (operation.op.case === "operationRestore") {
|
||||
const restore = operation.op.value;
|
||||
const progress = Math.round((details.percentage || 0) * 10) / 10;
|
||||
const st = restore.status! || {};
|
||||
const st = restore.lastStatus! || {};
|
||||
|
||||
body = (
|
||||
<>
|
||||
@@ -288,6 +288,8 @@ export const OperationRow = ({
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
<br />
|
||||
Snapshot ID: {normalizeSnapshotId(operation.snapshotId!)}
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Typography.Text strong>Bytes Done/Total</Typography.Text>
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
BackupInfo,
|
||||
BackupInfoCollector,
|
||||
colorForStatus,
|
||||
detailsForOperation,
|
||||
displayTypeToString,
|
||||
getOperations,
|
||||
getTypeForDisplay,
|
||||
@@ -159,15 +160,26 @@ export const OperationTree = ({
|
||||
);
|
||||
} else if (b.backupLastStatus.entry.case === "status") {
|
||||
const s = b.backupLastStatus.entry.value;
|
||||
const percent = Math.floor(
|
||||
(Number(s.bytesDone) / Number(s.totalBytes)) * 100
|
||||
);
|
||||
const percent = Number(s.bytesDone / s.totalBytes) * 100;
|
||||
details.push(
|
||||
`${percent}% processed ${formatBytes(
|
||||
`${percent.toFixed(1)}% processed ${formatBytes(
|
||||
Number(s.bytesDone)
|
||||
)} / ${formatBytes(Number(s.totalBytes))}`
|
||||
);
|
||||
}
|
||||
} else if (b.operations.length === 1) {
|
||||
const op = b.operations[0];
|
||||
const opDetails = detailsForOperation(op);
|
||||
if (
|
||||
opDetails.percentage &&
|
||||
opDetails.percentage > 0.1 &&
|
||||
opDetails.percentage < 99.9
|
||||
) {
|
||||
details.push(opDetails.displayState);
|
||||
}
|
||||
if (op.snapshotId) {
|
||||
details.push(`ID: ${normalizeSnapshotId(op.snapshotId)}`);
|
||||
}
|
||||
}
|
||||
if (b.snapshotInfo) {
|
||||
details.push(`ID: ${normalizeSnapshotId(b.snapshotInfo.id)}`);
|
||||
|
||||
@@ -242,6 +242,17 @@ const RestoreModal = ({
|
||||
const [form] = Form.useForm<RestoreSnapshotRequest>();
|
||||
const showModal = useShowModal();
|
||||
|
||||
const defaultPath = useMemo(() => {
|
||||
if (path === pathSeparator) {
|
||||
return "";
|
||||
}
|
||||
return path + "-backrest-restore-" + normalizeSnapshotId(snapshotId);
|
||||
}, [path]);
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ target: defaultPath });
|
||||
}, [defaultPath]);
|
||||
|
||||
const handleCancel = () => {
|
||||
showModal(null);
|
||||
};
|
||||
@@ -265,13 +276,6 @@ const RestoreModal = ({
|
||||
}
|
||||
};
|
||||
|
||||
const defaultPath = useMemo(() => {
|
||||
if (path === pathSeparator) {
|
||||
return "";
|
||||
}
|
||||
return path + "-backrest-restore-" + normalizeSnapshotId(snapshotId);
|
||||
}, [path]);
|
||||
|
||||
let targetPath = Form.useWatch("target", form);
|
||||
useEffect(() => {
|
||||
if (!targetPath) {
|
||||
@@ -279,17 +283,18 @@ const RestoreModal = ({
|
||||
}
|
||||
(async () => {
|
||||
try {
|
||||
if (targetPath.endsWith(pathSeparator)) {
|
||||
targetPath = targetPath.slice(0, -1);
|
||||
let p = targetPath;
|
||||
if (p.endsWith(pathSeparator)) {
|
||||
p = p.slice(0, -1);
|
||||
}
|
||||
|
||||
const dirname = basename(targetPath);
|
||||
const dirname = basename(p);
|
||||
const files = await backrestService.pathAutocomplete(
|
||||
new StringValue({ value: dirname })
|
||||
);
|
||||
|
||||
for (const file of files.values) {
|
||||
if (dirname + file === targetPath) {
|
||||
if (dirname + file === p) {
|
||||
form.setFields([
|
||||
{
|
||||
name: "target",
|
||||
|
||||
+25
-18
@@ -5,7 +5,7 @@ import {
|
||||
OperationStatus,
|
||||
} from "../../gen/ts/v1/operations_pb";
|
||||
import { GetOperationsRequest, OpSelector } from "../../gen/ts/v1/service_pb";
|
||||
import { BackupProgressEntry, ResticSnapshot } from "../../gen/ts/v1/restic_pb";
|
||||
import { BackupProgressEntry, ResticSnapshot, RestoreProgressEntry } from "../../gen/ts/v1/restic_pb";
|
||||
import _ from "lodash";
|
||||
import { formatDuration, formatTime } from "../lib/formatting";
|
||||
import { backrestService } from "../api";
|
||||
@@ -71,13 +71,16 @@ export const getStatusForSelector = async (sel: OpSelector) => {
|
||||
// getStatus returns the status of the last N operations that belong to a single snapshot.
|
||||
const getStatus = async (req: GetOperationsRequest) => {
|
||||
let ops = await getOperations(req);
|
||||
ops = ops
|
||||
.reverse()
|
||||
.filter((op) => op.status !== OperationStatus.STATUS_PENDING);
|
||||
ops.sort((a, b) => {
|
||||
return Number(b.unixTimeStartMs - a.unixTimeStartMs);
|
||||
});
|
||||
if (ops.length === 0) {
|
||||
return OperationStatus.STATUS_SUCCESS;
|
||||
}
|
||||
const flowId = ops[0].flowId;
|
||||
const flowId = ops.find((op) => op.status !== OperationStatus.STATUS_PENDING)?.flowId;
|
||||
if (!flowId) {
|
||||
return OperationStatus.STATUS_SUCCESS;
|
||||
}
|
||||
for (const op of ops) {
|
||||
if (op.status === OperationStatus.STATUS_PENDING) {
|
||||
continue;
|
||||
@@ -120,6 +123,7 @@ export interface BackupInfo {
|
||||
planId?: string;
|
||||
snapshotId?: string;
|
||||
backupLastStatus?: BackupProgressEntry;
|
||||
restoreLastStatus?: RestoreProgressEntry;
|
||||
snapshotInfo?: ResticSnapshot;
|
||||
forgotten: boolean;
|
||||
}
|
||||
@@ -224,17 +228,22 @@ export class BackupInfoCollector {
|
||||
statusIdx--;
|
||||
}
|
||||
|
||||
let backupLastStatus = undefined;
|
||||
let snapshotInfo = undefined;
|
||||
let forgotten = false;
|
||||
let snapshotId = "";
|
||||
let backupLastStatus: BackupProgressEntry | undefined = undefined;
|
||||
let snapshotInfo: ResticSnapshot | undefined = undefined;
|
||||
let forgotten: boolean = false;
|
||||
let snapshotId: string = "";
|
||||
for (const op of operations) {
|
||||
if (op.op.case === "operationBackup") {
|
||||
backupLastStatus = op.op.value.lastStatus;
|
||||
} else if (op.op.case === "operationIndexSnapshot") {
|
||||
snapshotInfo = op.op.value.snapshot;
|
||||
forgotten = op.op.value.forgot || false;
|
||||
snapshotId = op.op.value.snapshot?.id || "";
|
||||
switch (op.op.case) {
|
||||
case "operationBackup":
|
||||
backupLastStatus = op.op.value.lastStatus;
|
||||
break;
|
||||
case "operationIndexSnapshot":
|
||||
snapshotInfo = op.op.value.snapshot;
|
||||
forgotten = op.op.value.forgot || false;
|
||||
snapshotId = op.op.value.snapshot?.id || "";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,9 +507,7 @@ export const detailsForOperation = (
|
||||
}
|
||||
} else if (op.op.case === "operationRestore") {
|
||||
const restore = op.op.value;
|
||||
if (restore.status) {
|
||||
percentage = (restore.status.percentDone || 1) * 100;
|
||||
}
|
||||
percentage = (restore.lastStatus?.percentDone || 0) * 100;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user