From 84cb2a2193f8228461801a35abf5d8e252ecfd37 Mon Sep 17 00:00:00 2001 From: Gareth Date: Fri, 14 Nov 2025 22:13:11 -0800 Subject: [PATCH] chore: put a feature flag around multihost sync UI --- webui/src/state/buildcfg.ts | 6 +- webui/src/views/AddPlanModal.tsx | 199 ++++++++++++++++++++++-------- webui/src/views/SettingsModal.tsx | 9 +- 3 files changed, 155 insertions(+), 59 deletions(-) diff --git a/webui/src/state/buildcfg.ts b/webui/src/state/buildcfg.ts index 51a21a70..00cd9457 100644 --- a/webui/src/state/buildcfg.ts +++ b/webui/src/state/buildcfg.ts @@ -6,4 +6,8 @@ export const uiBuildVersion = ( export const isDevBuild = uiBuildVersion === "dev-snapshot-build"; export const pathSeparator = isWindows ? "\\" : "/"; export const backendUrl = process.env.UI_BACKEND_URL || "./"; -console.log(`UI OS: ${uios}, Build Version: ${uiBuildVersion}, Backend URL: ${backendUrl}`); \ No newline at end of file +console.log(`UI OS: ${uios}, Build Version: ${uiBuildVersion}, Backend URL: ${backendUrl}`); +export const features = new Set((process.env.UI_FEATURES || "").split(",")); + +// Feature flags +export const isMultihostSyncEnabled = features.has("multihost-sync"); \ No newline at end of file diff --git a/webui/src/views/AddPlanModal.tsx b/webui/src/views/AddPlanModal.tsx index 9f7637bf..083c55f9 100644 --- a/webui/src/views/AddPlanModal.tsx +++ b/webui/src/views/AddPlanModal.tsx @@ -12,6 +12,7 @@ import { Col, Collapse, Checkbox, + AutoComplete, } from "antd"; import React, { useEffect, useMemo, useState } from "react"; import { useShowModal } from "../components/ModalManager"; @@ -44,6 +45,101 @@ import { import { clone, create, equals, fromJson, toJson } from "@bufbuild/protobuf"; import { formatDuration } from "../lib/formatting"; import { getMinimumCronDuration } from "../lib/cronutil"; +import _ from "lodash"; +import { StringList } from "../../gen/ts/types/value_pb"; +import { isWindows } from "../state/buildcfg"; + +const { TextArea } = Input; +const sep = isWindows ? "\\" : "/"; + +const PathsTextArea = ({ value, onChange, ...props }: any) => { + const [options, setOptions] = useState<{ value: string }[]>([]); + const [currentLine, setCurrentLine] = useState(""); + const [cursorPosition, setCursorPosition] = useState(0); + + const handleSearch = _.debounce((searchValue: string) => { + if (!searchValue) { + setOptions([]); + return; + } + + const lastSlash = searchValue.lastIndexOf(sep); + let searchPath = searchValue; + if (lastSlash !== -1) { + searchPath = searchValue.substring(0, lastSlash); + } + + backrestService + .pathAutocomplete({ value: searchPath + sep }) + .then((res: StringList) => { + if (!res.values) { + return; + } + const vals = res.values.map((v) => { + return { + value: searchPath + sep + v, + }; + }); + setOptions(vals.filter((o) => o.value.indexOf(searchValue) !== -1)); + }) + .catch((e) => { + console.log("Path autocomplete error: ", e); + }); + }, 200); + + const handleTextAreaChange = (e: React.ChangeEvent) => { + const newValue = e.target.value; + const cursorPos = e.target.selectionStart || 0; + + // Find the current line based on cursor position + const lines = newValue.substring(0, cursorPos).split("\n"); + const currentLineValue = lines[lines.length - 1]; + + setCurrentLine(currentLineValue); + setCursorPosition(cursorPos); + + // Trigger autocomplete for the current line + handleSearch(currentLineValue); + + // Update the form value + if (onChange) { + onChange(newValue); + } + }; + + const onSelect = (selectedValue: string) => { + const lines = (value || "").split("\n"); + const beforeCursor = (value || "").substring(0, cursorPosition); + const afterCursor = (value || "").substring(cursorPosition); + const linesBeforeCursor = beforeCursor.split("\n"); + + // Replace the current line with the selected value + linesBeforeCursor[linesBeforeCursor.length - 1] = selectedValue; + const newValue = linesBeforeCursor.join("\n") + afterCursor; + + if (onChange) { + onChange(newValue); + } + setOptions([]); + }; + + return ( + {}} // We handle search in textarea change + {...props} + > +