Skip to content

Commit

Permalink
fix: schedule view bug
Browse files Browse the repository at this point in the history
  • Loading branch information
garethgeorge committed May 20, 2024
1 parent 531cd28 commit 0764804
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 194 deletions.
6 changes: 4 additions & 2 deletions internal/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ func validatePlan(plan *v1.Plan, repos map[string]*v1.Repo) error {
err = multierror.Append(err, fmt.Errorf("id %q invalid: %w", plan.Id, e))
}

if e := protoutil.ValidateSchedule(plan.Schedule); e != nil {
err = multierror.Append(err, fmt.Errorf("schedule: %w", e))
if plan.Schedule != nil {
if e := protoutil.ValidateSchedule(plan.Schedule); e != nil {
err = multierror.Append(err, fmt.Errorf("schedule: %w", e))
}
}

for idx, p := range plan.Paths {
Expand Down
8 changes: 6 additions & 2 deletions webui/src/components/OperationTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const OperationTree = ({
treeData={treeData}
showIcon
defaultExpandedKeys={backups
.slice(0, Math.min(5, backups.length))
.slice(0, Math.min(10, backups.length))
.map((b) => b.id!)}
onSelect={(keys, info) => {
if (info.selectedNodes.length === 0) return;
Expand Down Expand Up @@ -250,7 +250,7 @@ const buildTreePlan = (operations: BackupInfo[]): OpTreeNode[] => {
if (entries.length === 1) {
return entries[0].children!;
}
entries.sort(sortByKey);
entries.sort(sortByKeyReverse);
return entries;
};

Expand Down Expand Up @@ -305,6 +305,10 @@ const sortByKey = (a: OpTreeNode, b: OpTreeNode) => {
return 0;
};

const sortByKeyReverse = (a: OpTreeNode, b: OpTreeNode) => {
return -sortByKey(a, b);
};

const BackupView = ({ backup }: { backup?: BackupInfo }) => {
const alertApi = useAlertApi();
if (!backup) {
Expand Down
4 changes: 3 additions & 1 deletion webui/src/components/ScheduleFormItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export const ScheduleFormItem = ({ name }: { name: string[] }) => {
const retention = Form.useWatch(name, { form, preserve: true }) as any;

const determineMode = () => {
if (!retention || retention.disabled) {
if (!retention) {
return "";
} else if (retention.disabled) {
return "disabled";
} else if (retention.maxFrequencyDays) {
return "maxFrequencyDays";
Expand Down
171 changes: 171 additions & 0 deletions webui/src/components/StatsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import React, { useEffect, useState } from "react";
import { LineChart } from "@mui/x-charts/LineChart";
import { formatBytes, formatDate } from "../lib/formatting";
import { Col, Empty, Row } from "antd";
import {
Operation,
OperationStats,
OperationStatus,
} from "../../gen/ts/v1/operations_pb";
import { useAlertApi } from "./Alerts";
import { BackupInfoCollector, getOperations } from "../state/oplog";
import { MAX_OPERATION_HISTORY } from "../constants";
import { GetOperationsRequest, OpSelector } from "../../gen/ts/v1/service_pb";

const StatsPanel = ({ repoId }: { repoId: string }) => {
const [operations, setOperations] = useState<Operation[]>([]);
const alertApi = useAlertApi();

useEffect(() => {
if (!repoId) {
return;
}

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);

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);
});
}, [repoId]);

if (operations.length === 0) {
return (
<Empty description="No stats available. Have you run a prune operation yet?" />
);
}

const dataset: {
time: number;
totalSizeMb: number;
compressionRatio: number;
snapshotCount: number;
totalBlobCount: number;
}[] = operations.map((op) => {
const stats = (op.op.value! as OperationStats).stats!;
return {
time: Number(op.unixTimeEndMs!),
totalSizeMb: Number(stats.totalSize) / 1000000,
compressionRatio: Number(stats.compressionRatio),
snapshotCount: Number(stats.snapshotCount),
totalBlobCount: Number(stats.totalBlobCount),
};
});

const minTime = Math.min(...dataset.map((d) => d.time));
const maxTime = Math.max(...dataset.map((d) => d.time));

return (
<>
<Row>
<Col span={12}>
<LineChart
xAxis={[
{
dataKey: "time",
valueFormatter: (v) => formatDate(v as number),
min: minTime,
max: maxTime,
},
]}
series={[
{
dataKey: "totalSizeMb",
label: "Total Size",
valueFormatter: (v: any) =>
formatBytes((v * 1000000) as number),
},
]}
height={300}
width={600}
dataset={dataset}
/>

<LineChart
xAxis={[
{
dataKey: "time",
valueFormatter: (v) => formatDate(v as number),
min: minTime,
max: maxTime,
},
]}
series={[
{
dataKey: "compressionRatio",
label: "Compression Ratio",
},
]}
height={300}
dataset={dataset}
/>
</Col>
<Col span={12}>
<LineChart
xAxis={[
{
dataKey: "time",
valueFormatter: (v) => formatDate(v as number),
min: minTime,
max: maxTime,
},
]}
series={[
{
dataKey: "snapshotCount",
label: "Snapshot Count",
},
]}
height={300}
dataset={dataset}
/>

<LineChart
xAxis={[
{
dataKey: "time",
valueFormatter: (v) => formatDate(v as number),
min: minTime,
max: maxTime,
},
]}
series={[
{
dataKey: "totalBlobCount",
label: "Blob Count",
},
]}
height={300}
dataset={dataset}
/>
</Col>
</Row>
</>
);
};

export default StatsPanel;
19 changes: 11 additions & 8 deletions webui/src/views/AddPlanModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,17 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
throw new Error("template not found");
}

const configCopy = config.clone();

// Remove the plan from the config
const idx = config.plans.findIndex((r) => r.id === template.id);
const idx = configCopy.plans.findIndex((r) => r.id === template.id);
if (idx === -1) {
throw new Error("failed to update config, plan to delete not found");
}

config.plans.splice(idx, 1);
configCopy.plans.splice(idx, 1);

// Update config and notify success.
setConfig(await backrestService.setConfig(config));
setConfig(await backrestService.setConfig(configCopy));
showModal(null);

alertsApi.success(
Expand All @@ -90,19 +91,21 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
delete plan.retention;
}

const configCopy = config.clone();

// Merge the new plan (or update) into the config
if (template) {
const idx = config.plans.findIndex((r) => r.id === template.id);
const idx = configCopy.plans.findIndex((r) => r.id === template.id);
if (idx === -1) {
throw new Error("failed to update plan, not found");
}
config.plans[idx] = plan;
configCopy.plans[idx] = plan;
} else {
config.plans.push(plan);
configCopy.plans.push(plan);
}

// Update config and notify success.
setConfig(await backrestService.setConfig(config));
setConfig(await backrestService.setConfig(configCopy));
showModal(null);
} catch (e: any) {
alertsApi.error("Operation failed: " + e.message, 15);
Expand Down
Loading

0 comments on commit 0764804

Please sign in to comment.