diff --git a/webui/src/components/ActivityBar.tsx b/webui/src/components/ActivityBar.tsx index eaaa67fc1..ca094caf6 100644 --- a/webui/src/components/ActivityBar.tsx +++ b/webui/src/components/ActivityBar.tsx @@ -15,6 +15,7 @@ import { export const ActivityBar = () => { const [activeOperations, setActiveOperations] = useState([]); + const setRefresh = useState(0)[1]; useEffect(() => { const callback = ({ operation, type }: OperationEvent) => { @@ -32,6 +33,10 @@ export const ActivityBar = () => { subscribeToOperations(callback); + setInterval(() => { + setRefresh((r) => r + 1); + }, 500); + return () => { unsubscribeFromOperations(callback); }; diff --git a/webui/src/components/OperationRow.tsx b/webui/src/components/OperationRow.tsx index b2cd694d0..368ccf0a7 100644 --- a/webui/src/components/OperationRow.tsx +++ b/webui/src/components/OperationRow.tsx @@ -59,6 +59,17 @@ export const OperationRow = ({ const showModal = useShowModal(); const details = detailsForOperation(operation); const displayType = getTypeForDisplay(operation); + const setRefresh = useState(0)[1]; + + useEffect(() => { + if (operation.status === OperationStatus.STATUS_INPROGRESS) { + const interval = setInterval(() => { + setRefresh((x) => x + 1); + }, 1000); + return () => clearInterval(interval); + } + }, [operation.status]); + let avatar: React.ReactNode; switch (displayType) { case DisplayType.BACKUP: diff --git a/webui/src/components/StatsPanel.tsx b/webui/src/components/StatsPanel.tsx index b337cdc83..6c46f03bc 100644 --- a/webui/src/components/StatsPanel.tsx +++ b/webui/src/components/StatsPanel.tsx @@ -13,13 +13,19 @@ import { formatBytes, formatDate } from "../lib/formatting"; import { Col, Empty, Row } from "antd"; import { Operation, + OperationEvent, OperationStats, OperationStatus, } from "../../gen/ts/v1/operations_pb"; import { useAlertApi } from "./Alerts"; -import { BackupInfoCollector, getOperations } from "../state/oplog"; +import { + BackupInfoCollector, + getOperations, + subscribeToOperations, +} from "../state/oplog"; import { MAX_OPERATION_HISTORY } from "../constants"; import { GetOperationsRequest, OpSelector } from "../../gen/ts/v1/service_pb"; +import _ from "lodash"; const StatsPanel = ({ repoId }: { repoId: string }) => { const [operations, setOperations] = useState([]); @@ -30,36 +36,53 @@ const StatsPanel = ({ repoId }: { repoId: string }) => { return; } - const backupCollector = new BackupInfoCollector((op) => { - return ( - op.status === OperationStatus.STATUS_SUCCESS && - op.op.case === "operationStats" && - !!op.op.value.stats - ); - }); + const refreshOperations = _.debounce(() => { + const backupCollector = new BackupInfoCollector((op) => { + return ( + op.status === OperationStatus.STATUS_SUCCESS && + op.op.case === "operationStats" && + !!op.op.value.stats + ); + }); - getOperations( - new GetOperationsRequest({ - selector: new OpSelector({ - repoId: repoId, - }), - lastN: BigInt(MAX_OPERATION_HISTORY), - }) - ) - .then((ops) => { - backupCollector.bulkAddOperations(ops); + getOperations( + new GetOperationsRequest({ + selector: new OpSelector({ + repoId: repoId, + }), + lastN: BigInt(MAX_OPERATION_HISTORY), + }) + ) + .then((ops) => { + backupCollector.bulkAddOperations(ops); - const operations = backupCollector - .getAll() - .flatMap((b) => b.operations); - operations.sort((a, b) => { - return Number(b.unixTimeEndMs - a.unixTimeEndMs); + const operations = backupCollector + .getAll() + .flatMap((b) => b.operations); + operations.sort((a, b) => { + return Number(b.unixTimeEndMs - a.unixTimeEndMs); + }); + setOperations(operations); + }) + .catch((e) => { + alertApi!.error("Failed to fetch operations: " + e.message); }); - setOperations(operations); - }) - .catch((e) => { - alertApi!.error("Failed to fetch operations: " + e.message); - }); + }, 1000); + + refreshOperations(); + + const handler = (event: OperationEvent) => { + if ( + event.operation?.repoId == repoId && + event.operation?.op?.case === "operationStats" + ) { + refreshOperations(); + } + }; + + subscribeToOperations(handler); + + return () => {}; // cleanup }, [repoId]); if (operations.length === 0) {