feat: add repo view

This commit is contained in:
Gareth George
2023-12-22 08:27:48 +00:00
parent 46456a8887
commit 9522ac18de
6 changed files with 103 additions and 62 deletions
+6 -9
View File
@@ -40,7 +40,6 @@ import {
import { SnapshotBrowser } from "./SnapshotBrowser";
import {
formatBytes,
formatDuration,
formatTime,
normalizeSnapshotId,
} from "../lib/formatting";
@@ -48,14 +47,15 @@ import _ from "lodash";
import { GetOperationsRequest, Restora } from "../../gen/ts/v1/service.pb";
import { useAlertApi } from "./Alerts";
import { MessageInstance } from "antd/es/message/interface";
import Operation from "antd/es/transfer/operation";
export const OperationList = ({
req,
useBackups,
showPlan,
}: React.PropsWithoutRef<{
req?: GetOperationsRequest;
useBackups?: BackupInfo[];
showPlan?: boolean,
}>) => {
const alertApi = useAlertApi();
@@ -128,17 +128,13 @@ export const OperationList = ({
dataSource={backups}
renderItem={(backup) => {
const ops = backup.operations;
if (ops.length === 1) {
return <OperationRow alertApi={alertApi!} key={ops[0].id!} operation={toEop(ops[0])} />;
}
return (
<Card size="small" style={{ margin: "5px" }}>
{ops.map((op) => {
if (shouldHideStatus(op.status!)) {
return null;
}
return <OperationRow alertApi={alertApi!} key={op.id!} operation={toEop(op)} />
return <OperationRow alertApi={alertApi!} key={op.id!} operation={toEop(op)} showPlan={showPlan || false} />
})}
</Card>
);
@@ -155,7 +151,8 @@ export const OperationList = ({
export const OperationRow = ({
operation,
alertApi,
}: React.PropsWithoutRef<{ operation: EOperation, alertApi?: MessageInstance }>) => {
showPlan,
}: React.PropsWithoutRef<{ operation: EOperation, alertApi?: MessageInstance, showPlan: boolean }>) => {
const details = detailsForOperation(operation);
const displayType = getTypeForDisplay(operation);
let avatar: React.ReactNode;
@@ -190,7 +187,7 @@ export const OperationRow = ({
const opName = displayTypeToString(getTypeForDisplay(operation));
let title = (
<>
{formatTime(operation.unixTimeStartMs!)} - {opName}{" "}
{showPlan ? operation.planId + " - " : undefined} {formatTime(operation.unixTimeStartMs!)} - {opName}{" "}
<span className="restora operation-details">{details.displayState}</span>
</>
);
+13 -14
View File
@@ -201,65 +201,64 @@ export const OperationTree = ({
const buildTreePlan = (operations: BackupInfo[]): OpTreeNode[] => {
const grouped = _.groupBy(operations, (op) => {
return op.planId;
return op.operations[0].planId!;
});
const entries: OpTreeNode[] = _.map(grouped, (value, key) => {
return {
key: "p" + key,
title: "" + key,
children: buildTreeYear(value),
key: key,
title: key,
children: buildTreeDay(key, value),
};
});
entries.sort(sortByKey);
if (entries.length === 1) {
return entries[0].children!;
}
entries.sort(sortByKey);
return entries;
};
const buildTreeYear = (operations: BackupInfo[]): OpTreeNode[] => {
const buildTreeYear = (keyPrefix: string, operations: BackupInfo[]): OpTreeNode[] => {
const grouped = _.groupBy(operations, (op) => {
return localISOTime(op.displayTime).substring(0, 4);
});
const entries: OpTreeNode[] = _.map(grouped, (value, key) => {
return {
key: "y" + key,
key: keyPrefix + key,
title: "" + key,
children: buildTreeMonth(value),
children: buildTreeDay(keyPrefix, value),
};
});
entries.sort(sortByKey);
return entries;
};
const buildTreeMonth = (operations: BackupInfo[]): OpTreeNode[] => {
const buildTreeMonth = (keyPrefix: string, operations: BackupInfo[]): OpTreeNode[] => {
const grouped = _.groupBy(operations, (op) => {
return localISOTime(op.displayTime).substring(0, 7);
});
const entries: OpTreeNode[] = _.map(grouped, (value, key) => {
return {
key: key,
key: keyPrefix + key,
title: value[0].displayTime.toLocaleString("default", {
month: "long",
}),
children: buildTreeDay(value),
children: buildTreeDay(keyPrefix, value),
};
});
entries.sort(sortByKey);
return entries;
};
const buildTreeDay = (operations: BackupInfo[]): OpTreeNode[] => {
const buildTreeDay = (keyPrefix: string, operations: BackupInfo[]): OpTreeNode[] => {
const grouped = _.groupBy(operations, (op) => {
return localISOTime(op.displayTime).substring(0, 10);
});
const entries = _.map(grouped, (value, key) => {
return {
key: "d" + key,
key: keyPrefix + key,
title: formatDate(value[0].displayTime),
children: buildTreeLeaf(value),
};
+2 -28
View File
@@ -163,37 +163,11 @@ export class BackupInfoCollector {
backupLastStatus,
snapshotInfo,
forgotten,
planId: operations[0].planId,
repoId: operations[0].repoId,
};
}
private operationToBackup(op: Operation): BackupInfo {
const startTimeMs = parseInt(op.unixTimeStartMs!);
const endTimeMs = parseInt(op.unixTimeEndMs!);
const b: BackupInfo = {
id: op.id!,
startTimeMs,
endTimeMs,
status: op.status!,
displayTime: new Date(startTimeMs),
displayType: getTypeForDisplay(op),
repoId: op.repoId,
planId: op.planId,
snapshotId: op.snapshotId,
operations: [op],
};
if (op.operationBackup) {
const ob = op.operationBackup;
b.backupLastStatus = ob.lastStatus;
}
if (op.operationIndexSnapshot) {
const oi = op.operationIndexSnapshot;
b.snapshotInfo = oi.snapshot;
}
return b;
}
private addHelper(op: Operation): BackupInfo {
if (op.snapshotId) {
delete this.backupByOpId[op.id!];
+21 -4
View File
@@ -146,7 +146,6 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
label: "Add Repo",
onClick: async () => {
const { AddRepoModal } = await import("./AddRepoModal");
showModal(<AddRepoModal template={null} />);
},
},
@@ -154,11 +153,29 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
return {
key: "r-" + repo.id,
icon: <CheckCircleOutlined style={{ color: "green" }} />,
label: repo.id,
label: (
<div className="restora visible-on-hover">
{repo.id}{" "}
<Button
className="hidden-child"
type="text"
size="small"
shape="circle"
icon={<SettingOutlined />}
onClick={async () => {
const { AddRepoModal } = await import("./AddRepoModal");
showModal(<AddRepoModal template={repo} />);
}}
/>
</div>
),
onClick: async () => {
const { AddRepoModal } = await import("./AddRepoModal");
const { RepoView } = await import("./RepoView");
showModal(<AddRepoModal template={repo} />);
setContent(<RepoView repo={repo} />, [
{ title: "Repos" },
{ title: repo.id || "" },
]);
},
};
}),
-7
View File
@@ -1,22 +1,15 @@
import React, { useEffect, useState } from "react";
import { Plan } from "../../gen/ts/v1/config.pb";
import { Button, Flex, Tabs, Tooltip, Typography } from "antd";
import { useShowModal } from "../components/ModalManager";
import { useRecoilValue } from "recoil";
import { configState } from "../state/config";
import { useAlertApi } from "../components/Alerts";
import { Restora } from "../../gen/ts/v1/service.pb";
import {
EOperation,
subscribeToOperations,
unsubscribeFromOperations,
} from "../state/oplog";
import { OperationList } from "../components/OperationList";
import { OperationTree } from "../components/OperationTree";
import { MAX_OPERATION_HISTORY } from "../constants";
export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
const showModal = useShowModal();
const alertsApi = useAlertApi()!;
// Gracefully handle deletions by checking if the plan is still in the config.
+61
View File
@@ -0,0 +1,61 @@
import React, { useEffect, useState } from "react";
import { Repo } from "../../gen/ts/v1/config.pb";
import { Button, Flex, Tabs, Tooltip, Typography } from "antd";
import { useRecoilValue } from "recoil";
import { configState } from "../state/config";
import { useAlertApi } from "../components/Alerts";
import { Restora } from "../../gen/ts/v1/service.pb";
import { OperationList } from "../components/OperationList";
import { OperationTree } from "../components/OperationTree";
import { MAX_OPERATION_HISTORY } from "../constants";
export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => {
const alertsApi = useAlertApi()!;
// Gracefully handle deletions by checking if the plan is still in the config.
const config = useRecoilValue(configState);
let repoInConfig = config.repos?.find((p) => p.id === repo.id);
if (!repoInConfig) {
return <p>Repo was deleted.</p>;
}
repo = repoInConfig;
return (
<>
<Flex gap="small" align="center" wrap="wrap">
<Typography.Title>
{repo.id}
</Typography.Title>
</Flex>
<Tabs
defaultActiveKey="1"
items={[
{
key: "1",
label: "Tree View",
children: (
<>
<OperationTree
req={{ repoId: repo.id!, lastN: "" + MAX_OPERATION_HISTORY }}
/>
</>
),
},
{
key: "2",
label: "Operation List",
children: (
<>
<h2>Backup Action History</h2>
<OperationList
req={{ repoId: repo.id!, lastN: "" + MAX_OPERATION_HISTORY }}
showPlan={true}
/>
</>
),
},
]}
/>
</>
);
};