mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-09-14 03:55:37 +00:00
fix: improve Windows path handling
This commit is contained in:
@@ -103,12 +103,20 @@ func backupHelper(ctx context.Context, orchestrator *Orchestrator, plan *v1.Plan
|
||||
}
|
||||
|
||||
lastSent := time.Now() // debounce progress updates, these can endup being very frequent.
|
||||
var lastFiles []string
|
||||
summary, err := repo.Backup(ctx, plan, func(entry *restic.BackupProgressEntry) {
|
||||
if time.Since(lastSent) < 250*time.Millisecond {
|
||||
return
|
||||
}
|
||||
lastSent = time.Now()
|
||||
|
||||
// prevents flickering output when a status entry omits the CurrentFiles property. Largely cosmetic.
|
||||
if len(entry.CurrentFiles) == 0 {
|
||||
entry.CurrentFiles = lastFiles
|
||||
} else {
|
||||
lastFiles = entry.CurrentFiles
|
||||
}
|
||||
|
||||
backupOp.OperationBackup.LastStatus = protoutil.BackupProgressEntryToProto(entry)
|
||||
if err := orchestrator.OpLog.Update(op); err != nil {
|
||||
zap.S().Errorf("failed to update oplog with progress for backup: %v", err)
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"start": "parcel serve src/index.html",
|
||||
"build": "RESTICUI_BUILD_VERSION=$(git describe --tags --abbrev=0) parcel build src/index.html",
|
||||
"build": "RESTICUI_BUILD_VERSION=$(git describe --tags --abbrev=0) UI_OS=unix parcel build src/index.html",
|
||||
"build-windows": "set UI_OS=windows & set RESTICUI_BUILD_VERSION=$(git describe --tags --abbrev=0) & parcel build src/index.html",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
|
||||
@@ -2,8 +2,10 @@ import { AutoComplete } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ResticUI } from "../../gen/ts/v1/service.pb";
|
||||
import { StringList } from "../../gen/ts/types/value.pb";
|
||||
import { isWindows } from "../state/buildcfg";
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined;
|
||||
const sep = isWindows ? "\\" : "/";
|
||||
|
||||
export const URIAutocomplete = (props: React.PropsWithChildren<any>) => {
|
||||
const [value, setValue] = useState("");
|
||||
@@ -17,7 +19,7 @@ export const URIAutocomplete = (props: React.PropsWithChildren<any>) => {
|
||||
const onChange = (value: string) => {
|
||||
setValue(value);
|
||||
|
||||
const lastSlash = value.lastIndexOf("/");
|
||||
const lastSlash = value.lastIndexOf(sep);
|
||||
if (lastSlash !== -1) {
|
||||
value = value.substring(0, lastSlash);
|
||||
}
|
||||
@@ -27,14 +29,14 @@ export const URIAutocomplete = (props: React.PropsWithChildren<any>) => {
|
||||
}
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
ResticUI.PathAutocomplete({ value: value + "/" }, { pathPrefix: "/api" })
|
||||
ResticUI.PathAutocomplete({ value: value + sep }, { pathPrefix: "/api" })
|
||||
.then((res: StringList) => {
|
||||
if (!res.values) {
|
||||
return;
|
||||
}
|
||||
const vals = res.values.map((v) => {
|
||||
return {
|
||||
value: value + "/" + v,
|
||||
value: value + sep + v,
|
||||
};
|
||||
});
|
||||
setOptions(vals);
|
||||
@@ -45,5 +47,34 @@ export const URIAutocomplete = (props: React.PropsWithChildren<any>) => {
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return <AutoComplete options={showOptions} onSearch={onChange} {...props} />;
|
||||
return (
|
||||
<AutoComplete
|
||||
options={showOptions}
|
||||
onSearch={onChange}
|
||||
rules={[
|
||||
{
|
||||
validator: async (_: any, value: string) => {
|
||||
if (props.globAllowed) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (isWindows) {
|
||||
if (value.match(/^[a-zA-Z]:\\$/)) {
|
||||
return Promise.reject(
|
||||
new Error("Path must start with a drive letter e.g. C:\\")
|
||||
);
|
||||
} else if (value.includes("/")) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
"Path must use backslashes e.g. C:\\Users\\MyUsers\\Documents"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export const uios = (process.env.UI_OS || "").trim().toLowerCase();
|
||||
export const isWindows = uios === "windows";
|
||||
export const uiBuildVersion = (
|
||||
process.env.RESTICUI_BUILD_VERSION || "dev"
|
||||
).trim();
|
||||
@@ -291,6 +291,7 @@ export const AddPlanModal = ({
|
||||
<URIAutocomplete
|
||||
style={{ width: "90%" }}
|
||||
onBlur={() => form.validateFields()}
|
||||
globAllowed={true}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
|
||||
@@ -9,14 +9,15 @@ import {
|
||||
import type { MenuProps } from "antd";
|
||||
import { Button, Layout, Menu, Spin, theme } from "antd";
|
||||
import { configState, fetchConfig } from "../state/config";
|
||||
import { useRecoilState, useRecoilValue } from "recoil";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { Config } from "../../gen/ts/v1/config.pb";
|
||||
import { useAlertApi } from "../components/Alerts";
|
||||
import { useShowModal } from "../components/ModalManager";
|
||||
import { MainContentArea, useSetContent } from "./MainContentArea";
|
||||
import { AddPlanModal } from "./AddPlanModal";
|
||||
import { uiBuildVersion } from "../state/buildcfg";
|
||||
|
||||
const { Header, Content, Sider } = Layout;
|
||||
const { Header, Sider } = Layout;
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const {
|
||||
@@ -58,9 +59,7 @@ export const App: React.FC = () => {
|
||||
BackRest<span style={{ color: "grey" }}>ic</span>{" "}
|
||||
</a>
|
||||
<small style={{ color: "rgba(255,255,255,0.3)", fontSize: "0.6em" }}>
|
||||
{process.env.RESTICUI_BUILD_VERSION
|
||||
? process.env.RESTICUI_BUILD_VERSION
|
||||
: ""}
|
||||
{uiBuildVersion}
|
||||
</small>
|
||||
</h1>
|
||||
</Header>
|
||||
|
||||
Reference in New Issue
Block a user