mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-09-25 01:15:54 +00:00
fix: ui bugs introduced by repo guid migration
This commit is contained in:
@@ -149,7 +149,10 @@ func main() {
|
||||
apiAuthenticationHandler := api.NewAuthenticationHandler(authenticator)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle(v1connect.NewAuthenticationHandler(apiAuthenticationHandler))
|
||||
if cfg.GetMultihost() != nil {
|
||||
// alpha feature, only available if the user manually enables it in the config.
|
||||
mux.Handle(v1connect.NewAuthenticationHandler(apiAuthenticationHandler))
|
||||
}
|
||||
mux.Handle(v1connect.NewBackrestSyncServiceHandler(syncHandler))
|
||||
backrestHandlerPath, backrestHandler := v1connect.NewBackrestHandler(apiBackrestHandler)
|
||||
mux.Handle(backrestHandlerPath, auth.RequireAuthentication(backrestHandler, authenticator))
|
||||
|
||||
@@ -37,7 +37,7 @@ export const LogView = ({ logref }: { logref: string }) => {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
setLines((prev) => [...prev, `Fetch log error: ${e}`]);
|
||||
// setLines((prev) => [...prev, `Fetch log error: ${e}`]);
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { toJsonString } from "@bufbuild/protobuf";
|
||||
|
||||
// OperationList displays a list of operations that are either fetched based on 'req' or passed in via 'useBackups'.
|
||||
// If showPlan is provided the planId will be displayed next to each operation in the operation list.
|
||||
export const OperationList = ({
|
||||
export const OperationListView = ({
|
||||
req,
|
||||
useOperations,
|
||||
showPlan,
|
||||
@@ -34,7 +34,6 @@ import { MessageInstance } from "antd/es/message/interface";
|
||||
import { backrestService } from "../api";
|
||||
import { useShowModal } from "./ModalManager";
|
||||
import { useAlertApi } from "./Alerts";
|
||||
import { OperationList } from "./OperationList";
|
||||
import {
|
||||
displayTypeToString,
|
||||
getTypeForDisplay,
|
||||
@@ -44,6 +43,7 @@ import { OperationIcon } from "./OperationIcon";
|
||||
import { LogView } from "./LogView";
|
||||
import { ConfirmButton } from "./SpinButton";
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import { OperationListView } from "./OperationListView";
|
||||
|
||||
export const OperationRow = ({
|
||||
operation,
|
||||
@@ -133,7 +133,9 @@ export const OperationRow = ({
|
||||
|
||||
const title: React.ReactNode[] = [
|
||||
<div key="title">
|
||||
{showPlan ? operation.planId + " - " : undefined}{" "}
|
||||
{showPlan
|
||||
? operation.instanceId + " - " + operation.planId + " - "
|
||||
: undefined}{" "}
|
||||
{formatTime(Number(operation.unixTimeStartMs))} - {opName}{" "}
|
||||
<span className="backrest operation-details">{details}</span>
|
||||
</div>,
|
||||
@@ -305,7 +307,7 @@ export const OperationRow = ({
|
||||
key: "hookOperations",
|
||||
label: "Hooks Triggered",
|
||||
children: (
|
||||
<OperationList
|
||||
<OperationListView
|
||||
useOperations={hookOperations}
|
||||
displayHooksInline={true}
|
||||
/>
|
||||
|
||||
+94
-65
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Col, Empty, Flex, Modal, Row, Splitter, Tooltip, Tree } from "antd";
|
||||
import _ from "lodash";
|
||||
import _, { flow } from "lodash";
|
||||
import { DataNode } from "antd/es/tree";
|
||||
import { formatDate, formatTime, localISOTime } from "../lib/formatting";
|
||||
import { ExclamationOutlined, QuestionOutlined } from "@ant-design/icons";
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
OperationStatus,
|
||||
} from "../../gen/ts/v1/operations_pb";
|
||||
import { useAlertApi } from "./Alerts";
|
||||
import { OperationList } from "./OperationList";
|
||||
import { OperationListView } from "./OperationListView";
|
||||
import {
|
||||
ClearHistoryRequestSchema,
|
||||
ForgetRequestSchema,
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
type GetOperationsRequest,
|
||||
} from "../../gen/ts/v1/service_pb";
|
||||
import { isMobile } from "../lib/browserutil";
|
||||
import { useShowModal } from "./ModalManager";
|
||||
import { backrestService } from "../api";
|
||||
import { ConfirmButton } from "./SpinButton";
|
||||
import { OplogState, syncStateFromRequest } from "../state/logstate";
|
||||
@@ -35,7 +34,7 @@ type OpTreeNode = DataNode & {
|
||||
backup?: FlowDisplayInfo;
|
||||
};
|
||||
|
||||
export const OperationTree = ({
|
||||
export const OperationTreeView = ({
|
||||
req,
|
||||
isPlanView,
|
||||
}: React.PropsWithoutRef<{
|
||||
@@ -45,10 +44,6 @@ export const OperationTree = ({
|
||||
const alertApi = useAlertApi();
|
||||
const setScreenWidth = useState(window.innerWidth)[1];
|
||||
const [backups, setBackups] = useState<FlowDisplayInfo[]>([]);
|
||||
const [treeData, setTreeData] = useState<{
|
||||
tree: OpTreeNode[];
|
||||
expanded: React.Key[];
|
||||
}>({ tree: [], expanded: [] });
|
||||
const [selectedBackupId, setSelectedBackupId] = useState<bigint | null>(null);
|
||||
|
||||
// track the screen width so we can switch between mobile and desktop layouts.
|
||||
@@ -70,16 +65,6 @@ export const OperationTree = ({
|
||||
|
||||
const backupInfoByFlowID = new Map<bigint, FlowDisplayInfo>();
|
||||
|
||||
const refresh = _.debounce(
|
||||
() => {
|
||||
const flows = Array.from(backupInfoByFlowID.values());
|
||||
setTreeData(buildTree(flows, isPlanView || false));
|
||||
setBackups(flows);
|
||||
},
|
||||
100,
|
||||
{ leading: true, trailing: true }
|
||||
);
|
||||
|
||||
logState.subscribe((ids, flowIDs, event) => {
|
||||
if (
|
||||
event === OperationEventType.EVENT_CREATED ||
|
||||
@@ -88,6 +73,7 @@ export const OperationTree = ({
|
||||
for (const flowID of flowIDs) {
|
||||
const ops = logState.getByFlowID(flowID);
|
||||
if (!ops || ops[0].op.case === "operationRunHook") {
|
||||
// sometimes hook operations become awkwardly orphaned. These are ignored.
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -103,7 +89,8 @@ export const OperationTree = ({
|
||||
backupInfoByFlowID.delete(flowID);
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
|
||||
setBackups([...backupInfoByFlowID.values()]);
|
||||
});
|
||||
|
||||
return syncStateFromRequest(logState, req, (err) => {
|
||||
@@ -111,7 +98,7 @@ export const OperationTree = ({
|
||||
});
|
||||
}, [toJsonString(GetOperationsRequestSchema, req)]);
|
||||
|
||||
if (treeData.tree.length === 0) {
|
||||
if (backups.length === 0) {
|
||||
return (
|
||||
<Empty description={""} image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>
|
||||
);
|
||||
@@ -119,7 +106,88 @@ export const OperationTree = ({
|
||||
|
||||
const useMobileLayout = isMobile();
|
||||
|
||||
const backupTree = (
|
||||
const displayTree = (
|
||||
<DisplayOperationTree
|
||||
operations={backups}
|
||||
isPlanView={isPlanView}
|
||||
onSelect={(flow) => {
|
||||
setSelectedBackupId(flow ? flow.flowID : null);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (useMobileLayout) {
|
||||
const backup = backups.find((b) => b.flowID === selectedBackupId);
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
open={!!backup}
|
||||
footer={null}
|
||||
onCancel={() => {
|
||||
setSelectedBackupId(null);
|
||||
}}
|
||||
width="60vw"
|
||||
>
|
||||
<BackupView backup={backup} />
|
||||
</Modal>
|
||||
{displayTree}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex vertical gap="middle">
|
||||
<Splitter>
|
||||
<Splitter.Panel defaultSize="50%" min="20%" max="70%">
|
||||
{displayTree}
|
||||
</Splitter.Panel>
|
||||
<Splitter.Panel style={{ paddingLeft: "10px" }}>
|
||||
<BackupViewContainer>
|
||||
{selectedBackupId ? (
|
||||
<BackupView
|
||||
backup={backups.find((b) => b.flowID === selectedBackupId)}
|
||||
/>
|
||||
) : null}
|
||||
</BackupViewContainer>{" "}
|
||||
</Splitter.Panel>
|
||||
</Splitter>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const DisplayOperationTree = ({
|
||||
operations,
|
||||
isPlanView,
|
||||
onSelect,
|
||||
}: {
|
||||
operations: FlowDisplayInfo[];
|
||||
isPlanView?: boolean;
|
||||
onSelect?: (flow: FlowDisplayInfo | null) => any;
|
||||
}) => {
|
||||
const [treeData, setTreeData] = useState<{
|
||||
tree: OpTreeNode[];
|
||||
expanded: React.Key[];
|
||||
}>({ tree: [], expanded: [] });
|
||||
|
||||
useEffect(() => {
|
||||
const cancel = setTimeout(
|
||||
() => {
|
||||
const { tree, expanded } = buildTree(operations, isPlanView || false);
|
||||
setTreeData({ tree, expanded });
|
||||
},
|
||||
treeData && treeData.tree.length > 0 ? 100 : 0
|
||||
);
|
||||
|
||||
return () => {
|
||||
clearTimeout(cancel);
|
||||
};
|
||||
}, [operations]);
|
||||
|
||||
if (treeData.tree.length === 0) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tree<OpTreeNode>
|
||||
treeData={treeData.tree}
|
||||
showIcon
|
||||
@@ -127,11 +195,7 @@ export const OperationTree = ({
|
||||
onSelect={(keys, info) => {
|
||||
if (info.selectedNodes.length === 0) return;
|
||||
const backup = info.selectedNodes[0].backup;
|
||||
if (!backup) {
|
||||
setSelectedBackupId(null);
|
||||
return;
|
||||
}
|
||||
setSelectedBackupId(backup!.flowID);
|
||||
onSelect && onSelect(backup || null);
|
||||
}}
|
||||
titleRender={(node: OpTreeNode): React.ReactNode => {
|
||||
if (node.title !== undefined) {
|
||||
@@ -157,44 +221,6 @@ export const OperationTree = ({
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (useMobileLayout) {
|
||||
const backup = backups.find((b) => b.flowID === selectedBackupId);
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
open={!!backup}
|
||||
footer={null}
|
||||
onCancel={() => {
|
||||
setSelectedBackupId(null);
|
||||
}}
|
||||
width="60vw"
|
||||
>
|
||||
<BackupView backup={backup} />
|
||||
</Modal>
|
||||
{backupTree}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex vertical gap="middle">
|
||||
<Splitter>
|
||||
<Splitter.Panel defaultSize="50%" min="20%" max="70%">
|
||||
{backupTree}
|
||||
</Splitter.Panel>
|
||||
<Splitter.Panel style={{ paddingLeft: "10px" }}>
|
||||
<BackupViewContainer>
|
||||
{selectedBackupId ? (
|
||||
<BackupView
|
||||
backup={backups.find((b) => b.flowID === selectedBackupId)}
|
||||
/>
|
||||
) : null}
|
||||
</BackupViewContainer>{" "}
|
||||
</Splitter.Panel>
|
||||
</Splitter>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const treeLeafCache = new WeakMap<FlowDisplayInfo, OpTreeNode>();
|
||||
@@ -528,7 +554,10 @@ const BackupView = ({ backup }: { backup?: FlowDisplayInfo }) => {
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
<OperationList key={backup.flowID} useOperations={backup.operations} />
|
||||
<OperationListView
|
||||
key={backup.flowID}
|
||||
useOperations={backup.operations}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,33 +15,19 @@ import { Operation, OperationStats } from "../../gen/ts/v1/operations_pb";
|
||||
import { useAlertApi } from "./Alerts";
|
||||
import { getOperations } from "../state/oplog";
|
||||
import {
|
||||
GetOperationsRequest,
|
||||
GetOperationsRequestSchema,
|
||||
OpSelector,
|
||||
} from "../../gen/ts/v1/service_pb";
|
||||
import _ from "lodash";
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
|
||||
const StatsPanel = ({
|
||||
instanceId,
|
||||
repoId,
|
||||
}: {
|
||||
instanceId: string;
|
||||
repoId: string;
|
||||
}) => {
|
||||
const StatsPanel = ({ selector }: { selector: OpSelector }) => {
|
||||
const [operations, setOperations] = useState<Operation[]>([]);
|
||||
const alertApi = useAlertApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (!repoId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const req = create(GetOperationsRequestSchema, {
|
||||
selector: {
|
||||
repoId,
|
||||
instanceId,
|
||||
},
|
||||
selector,
|
||||
});
|
||||
|
||||
getOperations(req)
|
||||
@@ -54,7 +40,7 @@ const StatsPanel = ({
|
||||
.catch((e) => {
|
||||
alertApi!.error("Failed to fetch operations: " + e.message);
|
||||
});
|
||||
}, [repoId]);
|
||||
}, [JSON.stringify(selector)]);
|
||||
|
||||
if (operations.length === 0) {
|
||||
return (
|
||||
|
||||
@@ -223,7 +223,7 @@ export const matchSelector = (selector: OpSelector, op: Operation) => {
|
||||
if (selector.planId && selector.planId !== op.planId) {
|
||||
return false;
|
||||
}
|
||||
if (selector.repoId && selector.repoId !== op.repoId) {
|
||||
if (selector.repoGuid && selector.repoGuid !== op.repoGuid) {
|
||||
return false;
|
||||
}
|
||||
if (selector.flowId && selector.flowId !== op.flowId) {
|
||||
|
||||
+25
-29
@@ -29,7 +29,7 @@ import { useConfig } from "../components/ConfigProvider";
|
||||
import { shouldShowSettings } from "../state/configutil";
|
||||
import { OpSelector, OpSelectorSchema } from "../../gen/ts/v1/service_pb";
|
||||
import { colorForStatus } from "../state/flowdisplayaggregator";
|
||||
import { getStatusForSelector } from "../state/logstate";
|
||||
import { getStatusForSelector, matchSelector } from "../state/logstate";
|
||||
import { Route, Routes, useNavigate, useParams } from "react-router-dom";
|
||||
import { MainContentAreaTemplate } from "./MainContentArea";
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
@@ -281,6 +281,7 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
|
||||
return;
|
||||
}
|
||||
|
||||
const reposById = _.keyBy(config.repos, (r) => r.id);
|
||||
const configPlans = config.plans || [];
|
||||
const configRepos = config.repos || [];
|
||||
|
||||
@@ -295,15 +296,15 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
|
||||
},
|
||||
},
|
||||
...configPlans.map((plan) => {
|
||||
const sel = create(OpSelectorSchema, {
|
||||
instanceId: config.instance,
|
||||
planId: plan.id,
|
||||
repoGuid: reposById[plan.repo]?.guid,
|
||||
});
|
||||
|
||||
return {
|
||||
key: "p-" + plan.id,
|
||||
icon: (
|
||||
<IconForResource
|
||||
instanceId={config.instance}
|
||||
planId={plan.id}
|
||||
repoId={plan.repo}
|
||||
/>
|
||||
),
|
||||
icon: <IconForResource selector={sel} />,
|
||||
label: (
|
||||
<div
|
||||
className="backrest visible-on-hover"
|
||||
@@ -344,7 +345,14 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
|
||||
...configRepos.map((repo) => {
|
||||
return {
|
||||
key: "r-" + repo.id,
|
||||
icon: <IconForResource instanceId={config.instance} repoId={repo.id} />,
|
||||
icon: (
|
||||
<IconForResource
|
||||
selector={create(OpSelectorSchema, {
|
||||
instanceId: config.instance,
|
||||
repoGuid: repo.guid,
|
||||
})}
|
||||
/>
|
||||
),
|
||||
label: (
|
||||
<div
|
||||
className="backrest visible-on-hover"
|
||||
@@ -397,23 +405,15 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
|
||||
];
|
||||
};
|
||||
|
||||
const IconForResource = ({
|
||||
instanceId,
|
||||
planId,
|
||||
repoId,
|
||||
}: {
|
||||
instanceId: string;
|
||||
planId?: string;
|
||||
repoId?: string;
|
||||
}) => {
|
||||
const IconForResource = ({ selector }: { selector: OpSelector }) => {
|
||||
const [status, setStatus] = useState(OperationStatus.STATUS_UNKNOWN);
|
||||
useEffect(() => {
|
||||
if (!selector || !selector.instanceId || !selector.repoGuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
setStatus(
|
||||
await getStatusForSelector(
|
||||
create(OpSelectorSchema, { instanceId, planId, repoId })
|
||||
)
|
||||
);
|
||||
setStatus(await getStatusForSelector(selector));
|
||||
};
|
||||
load();
|
||||
const refresh = _.debounce(load, 1000, { maxWait: 10000, trailing: true });
|
||||
@@ -423,11 +423,7 @@ const IconForResource = ({
|
||||
case "createdOperations":
|
||||
case "updatedOperations":
|
||||
const ops = event.event.value.operations;
|
||||
if (
|
||||
ops.find(
|
||||
(op) => (!planId || op.planId === planId) && op.repoId === repoId
|
||||
)
|
||||
) {
|
||||
if (ops.find((op) => matchSelector(selector, op))) {
|
||||
refresh();
|
||||
}
|
||||
break;
|
||||
@@ -441,7 +437,7 @@ const IconForResource = ({
|
||||
return () => {
|
||||
unsubscribeFromOperations(callback);
|
||||
};
|
||||
}, [planId, repoId]);
|
||||
}, [JSON.stringify(selector)]);
|
||||
return iconForStatus(status);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ import React, { useEffect, useState } from "react";
|
||||
import { Plan } from "../../gen/ts/v1/config_pb";
|
||||
import { Button, Flex, Tabs, Tooltip, Typography } from "antd";
|
||||
import { useAlertApi } from "../components/Alerts";
|
||||
import { OperationList } from "../components/OperationList";
|
||||
import { OperationTree } from "../components/OperationTree";
|
||||
import { MAX_OPERATION_HISTORY } from "../constants";
|
||||
import { backrestService } from "../api";
|
||||
import {
|
||||
@@ -16,6 +14,8 @@ import { SpinButton } from "../components/SpinButton";
|
||||
import { useShowModal } from "../components/ModalManager";
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import { useConfig } from "../components/ConfigProvider";
|
||||
import { OperationListView } from "../components/OperationListView";
|
||||
import { OperationTreeView } from "../components/OperationTreeView";
|
||||
|
||||
export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
|
||||
const [config, _] = useConfig();
|
||||
@@ -54,7 +54,7 @@ export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
|
||||
create(ClearHistoryRequestSchema, {
|
||||
selector: {
|
||||
planId: plan.id,
|
||||
repoId: plan.repo,
|
||||
repoGuid: repo!.guid,
|
||||
},
|
||||
onlyFailed: true,
|
||||
})
|
||||
@@ -89,7 +89,7 @@ export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
|
||||
type="default"
|
||||
onClick={async () => {
|
||||
const { RunCommandModal } = await import("./RunCommandModal");
|
||||
showModal(<RunCommandModal repoId={plan.repo!} />);
|
||||
showModal(<RunCommandModal repo={repo} />);
|
||||
}}
|
||||
>
|
||||
Run Command
|
||||
@@ -114,7 +114,7 @@ export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
|
||||
label: "Tree View",
|
||||
children: (
|
||||
<>
|
||||
<OperationTree
|
||||
<OperationTreeView
|
||||
req={create(GetOperationsRequestSchema, {
|
||||
selector: {
|
||||
instanceId: config?.instance,
|
||||
@@ -135,7 +135,7 @@ export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
|
||||
children: (
|
||||
<>
|
||||
<h2>Backup Action History</h2>
|
||||
<OperationList
|
||||
<OperationListView
|
||||
req={create(GetOperationsRequestSchema, {
|
||||
selector: {
|
||||
instanceId: config?.instance,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React, { Suspense, useContext, useEffect, useState } from "react";
|
||||
import { Repo } from "../../gen/ts/v1/config_pb";
|
||||
import { Flex, Tabs, Tooltip, Typography, Button } from "antd";
|
||||
import { OperationList } from "../components/OperationList";
|
||||
import { OperationTree } from "../components/OperationTree";
|
||||
import { OperationListView } from "../components/OperationListView";
|
||||
import { OperationTreeView } from "../components/OperationTreeView";
|
||||
import { MAX_OPERATION_HISTORY, STATS_OPERATION_HISTORY } from "../constants";
|
||||
import {
|
||||
DoRepoTaskRequest_Task,
|
||||
DoRepoTaskRequestSchema,
|
||||
GetOperationsRequestSchema,
|
||||
OpSelectorSchema,
|
||||
} from "../../gen/ts/v1/service_pb";
|
||||
import { backrestService } from "../api";
|
||||
import { SpinButton } from "../components/SpinButton";
|
||||
@@ -109,7 +110,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => {
|
||||
label: "Tree View",
|
||||
children: (
|
||||
<>
|
||||
<OperationTree
|
||||
<OperationTreeView
|
||||
req={create(GetOperationsRequestSchema, {
|
||||
selector: {
|
||||
repoGuid: repo.guid,
|
||||
@@ -127,7 +128,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => {
|
||||
children: (
|
||||
<>
|
||||
<h3>Backup Action History</h3>
|
||||
<OperationList
|
||||
<OperationListView
|
||||
req={create(GetOperationsRequestSchema, {
|
||||
selector: {
|
||||
repoGuid: repo.guid,
|
||||
@@ -146,7 +147,12 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => {
|
||||
label: "Stats",
|
||||
children: (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<StatsPanel repoId={repo.id!} instanceId={config?.instance!} />
|
||||
<StatsPanel
|
||||
selector={create(OpSelectorSchema, {
|
||||
repoGuid: repo.guid,
|
||||
instanceId: config?.instance,
|
||||
})}
|
||||
/>
|
||||
</Suspense>
|
||||
),
|
||||
destroyInactiveTabPane: true,
|
||||
@@ -163,7 +169,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => {
|
||||
type="default"
|
||||
onClick={async () => {
|
||||
const { RunCommandModal } = await import("./RunCommandModal");
|
||||
showModal(<RunCommandModal repoId={repo.id!} />);
|
||||
showModal(<RunCommandModal repo={repo} />);
|
||||
}}
|
||||
>
|
||||
Run Command
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
RunCommandRequest,
|
||||
RunCommandRequestSchema,
|
||||
} from "../../gen/ts/v1/service_pb";
|
||||
import { OperationList } from "../components/OperationList";
|
||||
import { Repo } from "../../gen/ts/v1/config_pb";
|
||||
import { OperationListView } from "../components/OperationListView";
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import { useConfig } from "../components/ConfigProvider";
|
||||
|
||||
@@ -21,7 +22,7 @@ interface Invocation {
|
||||
error: string;
|
||||
}
|
||||
|
||||
export const RunCommandModal = ({ repoId }: { repoId: string }) => {
|
||||
export const RunCommandModal = ({ repo }: { repo: Repo }) => {
|
||||
const [config, _] = useConfig();
|
||||
const showModal = useShowModal();
|
||||
const alertApi = useAlertApi()!;
|
||||
@@ -42,7 +43,7 @@ export const RunCommandModal = ({ repoId }: { repoId: string }) => {
|
||||
try {
|
||||
const opID = await backrestService.runCommand(
|
||||
create(RunCommandRequestSchema, {
|
||||
repoId,
|
||||
repoId: repo.id!,
|
||||
command: toRun,
|
||||
})
|
||||
);
|
||||
@@ -57,7 +58,7 @@ export const RunCommandModal = ({ repoId }: { repoId: string }) => {
|
||||
<Modal
|
||||
open={true}
|
||||
onCancel={handleCancel}
|
||||
title={"Run Command in repo " + repoId}
|
||||
title={"Run Command in repo " + repo.id}
|
||||
width="80vw"
|
||||
footer={[]}
|
||||
>
|
||||
@@ -82,11 +83,11 @@ export const RunCommandModal = ({ repoId }: { repoId: string }) => {
|
||||
before running another operation that requires the repo lock.
|
||||
</em>
|
||||
) : null}
|
||||
<OperationList
|
||||
<OperationListView
|
||||
req={create(GetOperationsRequestSchema, {
|
||||
selector: {
|
||||
instanceId: config?.instance,
|
||||
repoId: repoId,
|
||||
repoGuid: repo.guid,
|
||||
planId: "_system_", // run commands are not associated with a plan
|
||||
},
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user