diff --git a/e2e/playwright/tests/@smoke-test/test.spec.ts b/e2e/playwright/tests/@smoke-test/test.spec.ts
index cc7ea645e0..5b0973de6b 100644
--- a/e2e/playwright/tests/@smoke-test/test.spec.ts
+++ b/e2e/playwright/tests/@smoke-test/test.spec.ts
@@ -283,6 +283,7 @@ test("smoke test", async ({ page }) => {
.click();
await page.locator(".SnapshotRow--wrapper").click();
await expect(page.locator("#app")).toContainText("Snapshot timeline");
+ await page.getByText("Infrastructure", { exact: true }).click();
await page.getByText("View logs").click();
await expect(page.locator(".view-lines")).toContainText("level=info");
await page.getByRole("button", { name: "Ok, got it!" }).click();
@@ -344,4 +345,4 @@ const validateDeployLogs = async (page, expect) => {
await page.getByText("applyStdout").click();
await expect(page.locator(".view-lines")).toContainText("created");
await page.getByRole("button", { name: "Ok, got it!" }).click();
-};
+};
\ No newline at end of file
diff --git a/web/src/components/snapshots/SnapshotDetails.jsx b/web/src/components/snapshots/SnapshotDetails.jsx
index cc30f34029..cc41612a98 100644
--- a/web/src/components/snapshots/SnapshotDetails.jsx
+++ b/web/src/components/snapshots/SnapshotDetails.jsx
@@ -1,18 +1,20 @@
-import { Component } from "react";
-import { withRouter } from "@src/utilities/react-router-utilities";
import MonacoEditor from "@monaco-editor/react";
-import Modal from "react-modal";
+import dayjs from "dayjs";
+import duration from "dayjs/plugin/duration";
+import minMax from "dayjs/plugin/minMax";
import filter from "lodash/filter";
import isEmpty from "lodash/isEmpty";
+import { Component, useState } from "react";
import ReactApexChart from "react-apexcharts";
-import dayjs from "dayjs";
-import minMax from "dayjs/plugin/minMax";
-import duration from "dayjs/plugin/duration";
-import Loader from "../shared/Loader";
+import Modal from "react-modal";
+
+import Icon from "@components/Icon";
+import { withRouter } from "@src/utilities/react-router-utilities";
+import { Utilities } from "../../utilities/utilities";
+import ErrorModal from "../modals/ErrorModal";
import ShowAllModal from "../modals/ShowAllModal";
import ViewSnapshotLogsModal from "../modals/ViewSnapshotLogsModal";
-import ErrorModal from "../modals/ErrorModal";
-import { Utilities } from "../../utilities/utilities";
+import Loader from "../shared/Loader";
dayjs.extend(minMax);
dayjs.extend(duration);
@@ -22,6 +24,7 @@ let mapColors = {};
class SnapshotDetails extends Component {
state = {
+ activeIds: [],
showScriptsOutput: false,
scriptOutput: "",
selectedTab: "stdout",
@@ -40,6 +43,7 @@ class SnapshotDetails extends Component {
snapshotLogsErrMsg: "",
loading: true,
+ currentSnapshotIndex: 0,
snapshotDetails: {},
errorMessage: "",
errorTitle: "",
@@ -127,6 +131,32 @@ class SnapshotDetails extends Component {
}
}
+ getSeriesDataForSnapshot = (snapshot) => {
+ let series = [];
+ if (!isEmpty(snapshot?.volumes)) {
+ if (snapshot?.hooks && !isEmpty(snapshot?.hooks)) {
+ series = this.getSeriesData(
+ [...snapshot?.volumes, ...snapshot?.hooks].sort(
+ (a, b) => new Date(a.started) - new Date(b.started)
+ )
+ );
+ } else {
+ series = this.getSeriesData(
+ (snapshot?.volumes).sort(
+ (a, b) => new Date(a.started) - new Date(b.started)
+ )
+ );
+ }
+ } else if (snapshot?.hooks && !isEmpty(snapshot?.hooks)) {
+ series = this.getSeriesData(
+ (snapshot?.hooks).sort(
+ (a, b) => new Date(a.started) - new Date(b.started)
+ )
+ );
+ }
+ return series;
+ };
+
fetchSnapshotDetails = async () => {
const { params } = this.props;
const snapshotName = params.id;
@@ -158,35 +188,10 @@ class SnapshotDetails extends Component {
}
const response = await res.json();
- const snapshotDetails = response.backupDetails?.[0];
-
- let series = [];
- if (!isEmpty(snapshotDetails?.volumes)) {
- if (snapshotDetails?.hooks && !isEmpty(snapshotDetails?.hooks)) {
- series = this.getSeriesData(
- [...snapshotDetails?.volumes, ...snapshotDetails?.hooks].sort(
- (a, b) => new Date(a.started) - new Date(b.started)
- )
- );
- } else {
- series = this.getSeriesData(
- (snapshotDetails?.volumes).sort(
- (a, b) => new Date(a.started) - new Date(b.started)
- )
- );
- }
- } else if (snapshotDetails?.hooks && !isEmpty(snapshotDetails?.hooks)) {
- series = this.getSeriesData(
- (snapshotDetails?.hooks).sort(
- (a, b) => new Date(a.started) - new Date(b.started)
- )
- );
- }
-
this.setState({
loading: false,
- snapshotDetails: snapshotDetails,
- series: series,
+ snapshotDetails: response,
+ currentSnapshotIndex: 0,
errorMessage: "",
errorTitle: "",
});
@@ -202,14 +207,14 @@ class SnapshotDetails extends Component {
}
};
- preSnapshotScripts = () => {
- return filter(this.state.snapshotDetails?.hooks, (hook) => {
+ preSnapshotScripts = (snapshotDetail) => {
+ return filter(snapshotDetail?.hooks, (hook) => {
return hook.phase === "pre";
});
};
- postSnapshotScripts = () => {
- return filter(this.state.snapshotDetails?.hooks, (hook) => {
+ postSnapshotScripts = (snapshotDetail) => {
+ return filter(snapshotDetail?.hooks, (hook) => {
return hook.phase === "post";
});
};
@@ -246,14 +251,13 @@ class SnapshotDetails extends Component {
this.setState({ showAllErrors: !this.state.showAllErrors });
};
- viewLogs = () => {
+ viewLogs = (name) => {
this.setState(
{
toggleViewLogsModal: !this.state.toggleViewLogsModal,
},
() => {
this.setState({ loadingSnapshotLogs: true });
- const name = this.state.snapshotDetails?.name;
const url = `${process.env.API_ENDPOINT}/snapshot/${name}/logs`;
fetch(url, {
credentials: "include",
@@ -376,8 +380,8 @@ class SnapshotDetails extends Component {
);
};
- renderErrorsWarningsTabs = () => {
- const { snapshotDetails, selectedErrorsWarningTab } = this.state;
+ renderErrorsWarningsTabs = (snapshotDetail) => {
+ const { selectedErrorsWarningTab } = this.state;
const tabs = ["Errors", "Warnings"];
return (
@@ -393,14 +397,14 @@ class SnapshotDetails extends Component {
{tab === "Errors" ? (
{" "}
- {snapshotDetails?.errors?.length}{" "}
+ {snapshotDetail?.errors?.length}{" "}
) : (
{" "}
- {!snapshotDetails?.warnings
+ {!snapshotDetail?.warnings
? "0"
- : snapshotDetails?.warnings?.length}{" "}
+ : snapshotDetail?.warnings?.length}{" "}
)}
@@ -577,19 +581,16 @@ class SnapshotDetails extends Component {
return series;
};
- renderTimeInterval = () => {
+ renderTimeInterval = (snapshotDetail) => {
let data;
- if (!isEmpty(this.state.snapshotDetails?.volumes)) {
- if (!isEmpty(this.state.snapshotDetails?.hooks)) {
- data = [
- ...this.state.snapshotDetails?.volumes,
- ...this.state.snapshotDetails?.hooks,
- ];
+ if (!isEmpty(snapshotDetail?.volumes)) {
+ if (!isEmpty(snapshotDetail?.hooks)) {
+ data = [...snapshotDetail?.volumes, ...snapshotDetail?.hooks];
} else {
- data = this.state.snapshotDetails?.volumes;
+ data = snapshotDetail?.volumes;
}
- } else if (!isEmpty(this.state.snapshotDetails?.hooks)) {
- data = this.state.snapshotDetails?.hooks;
+ } else if (!isEmpty(snapshotDetail?.hooks)) {
+ data = snapshotDetail?.hooks;
}
return (
@@ -649,284 +650,512 @@ class SnapshotDetails extends Component {
);
};
- render() {
- const { isEmbeddedCluster } = this.props;
+ componentDidUpdate = (lastProps, lastState) => {
+ const { snapshotDetails } = this.state;
+ const { backupDetails } = snapshotDetails;
+ if (
+ lastState.snapshotDetails !== snapshotDetails &&
+ backupDetails.length > 0
+ ) {
+ // Filter snapshots with status not equal to "Completed"
+ const activeIds = backupDetails
+ .filter((snapshotDetail) => snapshotDetail.status !== "Completed")
+ .map((snapshotDetail) => snapshotDetail.name);
+
+ // Set the filtered snapshot names in state
+ this.setState({ activeIds });
+ }
+ };
+ renderSnapshot = () => {
+ const { backupDetails, backup } = this.state.snapshotDetails;
const {
- loading,
- showScriptsOutput,
- selectedTab,
+ series,
selectedScriptTab,
- scriptOutput,
+ selectedErrorsWarningTab,
+ currentSnapshotIndex,
+ activeIds,
showAllVolumes,
showAllPreSnapshotScripts,
showAllPostSnapshotScripts,
- selectedErrorsWarningTab,
- showAllErrors,
showAllWarnings,
- snapshotDetails,
- series,
- errorMessage,
- errorTitle,
+ showAllErrors,
+ showAllScriptsOutput,
} = this.state;
-
+ const { isEmbeddedCluster, navigate } = this.props;
let featureName = "snapshot";
if (isEmbeddedCluster) {
featureName = "backup";
}
+ const { params } = this.props;
+ const snapshotName = params.id;
- if (loading) {
- return (
-
-
-
- );
- }
+ const toggleAccordion = (name) => {
+ this.setState({
+ activeIds: this.state.activeIds.includes(name)
+ ? this.state.activeIds.filter((id) => id !== name)
+ : [...this.state.activeIds, name],
+ });
+ };
return (
-
-
- this.props.navigate(-1)}>
- {Utilities.toTitleCase(featureName)}s
-
- >
- {snapshotDetails?.name}
-
-
-
-
-
- {snapshotDetails?.name}
-
+
+
+
+
+ {snapshotName}
+
+
Total size:{" "}
- {snapshotDetails?.volumeSizeHuman}
+ {backup?.volumeSizeHuman}
-
-
-
+
Status:{" "}
- {Utilities.snapshotStatusToDisplayName(
- snapshotDetails?.status
- )}
-
+ className={`tw-mb-4 status-indicator ${backup?.status?.toLowerCase()} u-marginLeft--5`}
+ >
+ {Utilities.snapshotStatusToDisplayName(backup?.status)}
-
- {snapshotDetails?.status !== "InProgress" && (
- this.viewLogs()}>
- View logs
-
- )}
-
-
- {snapshotDetails?.status === "InProgress" ? (
-
-
-
- {" "}
- This {featureName} has not completed yet, check back soon{" "}
-
-
- ) : (
-
- {!isEmpty(snapshotDetails?.volumes) ||
- !isEmpty(this.preSnapshotScripts()) ||
- !isEmpty(this.postSnapshotScripts()) ? (
-
-
- {Utilities.toTitleCase(featureName)} timeline
-
-
-
{
+ const isActive = activeIds.includes(snapshotDetail.name);
+ return (
+
+ {showAllVolumes && (
+
- {this.renderTimeInterval()}
-
-
- ) : null}
-
-
-
-
-
-
- Volumes
-
- {snapshotDetails?.volumes?.length > 3 ? (
-
-
this.toggleShowAllVolumes()}
- >
- Show all {snapshotDetails?.volumes?.length} volumes
+ )}
+ {showAllPreSnapshotScripts && (
+
+ )}
+ {showAllPostSnapshotScripts && (
+
+ )}
+ {showAllWarnings && (
+
+ )}
+ {showAllErrors && (
+
+ )}
+
+
+
- {!isEmpty(snapshotDetails?.volumes) ? (
- this.renderShowAllVolumes(
- snapshotDetails?.volumes?.slice(0, 3)
- )
- ) : (
-
-
- {" "}
- No volumes to display{" "}
-
-
- )}
-
-
-
-
-
- Scripts
-
- {this.preSnapshotScripts()?.length > 3 &&
- selectedScriptTab === "Pre-snapshot scripts" ? (
-
- this.toggleShowAllPreScripts()}
- >
- Show all {this.preSnapshotScripts()?.length}{" "}
- pre-scripts
-
-
- ) : null}
- {this.postSnapshotScripts()?.length > 3 &&
- selectedScriptTab === "Post-snapshot scripts" ? (
-
+
+ {snapshotDetail.status !== "Completed" ? (
this.toggleShowAllPostScripts()}
- >
- Show all {this.postSnapshotScripts()?.length}{" "}
- post-scripts
-
-
- ) : null}
-
-
- {this.renderScriptsTabs()}
-
-
-
- {selectedScriptTab === "Pre-snapshot scripts" ? (
- !isEmpty(this.preSnapshotScripts()) ? (
- this.renderShowAllScripts(
- this.preSnapshotScripts().slice(0, 3)
- )
- ) : (
-
-
- {" "}
- No pre-{featureName} scripts to display{" "}
-
-
- )
- ) : selectedScriptTab === "Post-snapshot scripts" &&
- !isEmpty(this.postSnapshotScripts()) ? (
- this.renderShowAllScripts(
- this.postSnapshotScripts().slice(0, 3)
- )
- ) : (
-
-
- {" "}
- No post-{featureName} scripts to display{" "}
-
+ className={`status-indicator ${snapshotDetail?.status?.toLowerCase()} tw-mx-2 tw-mb-4`}
+ >
+ ) : null}
- )}
-
-
-
-
+
+
- {(!isEmpty(snapshotDetails?.errors) ||
- !isEmpty(snapshotDetails?.warnings)) && (
-
-
-
-
-
-
- Errors and warnings
-
- {snapshotDetails?.errors?.length > 3 &&
- selectedErrorsWarningTab === "Errors" ? (
-
-
this.toggleShowAllErrors()}
- >
- Show all {snapshotDetails?.errors?.length}{" "}
- errors{" "}
+
+
+
+
+ Size:{" "}
+
+ {snapshotDetail?.volumeSizeHuman}
-
- ) : null}
- {snapshotDetails?.warnings?.length > 3 &&
- selectedErrorsWarningTab === "Warnings" ? (
-
+
+
+ Status:{" "}
this.toggleShowAllWarnings()}
+ className={`tw-mb-4 status-indicator ${snapshotDetail?.status?.toLowerCase()} u-marginLeft--5`}
+ >
+ {Utilities.snapshotStatusToDisplayName(
+ snapshotDetail?.status
+ )}
+
+
+
+ {snapshotDetail?.status !== "InProgress" && (
+
+ this.viewLogs(snapshotDetail?.name)
+ }
>
- Show all {snapshotDetails?.warnings?.length}{" "}
- warnings{" "}
+ View logs
-
- ) : null}
-
-
- {this.renderErrorsWarningsTabs()}
+ )}
+
-
-
- {selectedErrorsWarningTab === "Errors" ? (
- !isEmpty(snapshotDetails?.errors) ? (
- this.renderShowAllErrors(
- snapshotDetails?.errors.slice(0, 3)
- )
- ) : (
-
-
- {" "}
- No errors to display{" "}
-
-
- )
- ) : selectedErrorsWarningTab === "Warnings" &&
- !isEmpty(snapshotDetails?.warnings) ? (
- this.renderShowAllWarnings(
- snapshotDetails?.warnings?.slice(0, 3)
- )
- ) : (
-
-
+
+ {snapshotDetail?.status === "InProgress" ? (
+
+
+
{" "}
- No warnings to display{" "}
+ This {featureName} has not completed yet, check
+ back soon{" "}
+ ) : (
+
+ {!isEmpty(snapshotDetail?.volumes) ||
+ !isEmpty(this.preSnapshotScripts(snapshotDetail)) ||
+ !isEmpty(
+ this.postSnapshotScripts(snapshotDetail)
+ ) ? (
+
+
+ {Utilities.toTitleCase(featureName)} timeline
+
+
+
+ {/* {this.renderTimeInterval(snapshotDetail)} */}
+
+
+ ) : null}
+
+
+
+
+
+
+ Volumes
+
+ {snapshotDetail?.volumes?.length > 3 ? (
+
+
+ this.toggleShowAllVolumes()
+ }
+ >
+ Show all{" "}
+ {snapshotDetail?.volumes?.length}{" "}
+ volumes
+
+
+ ) : null}
+
+ {!isEmpty(snapshotDetail?.volumes) ? (
+ this.renderShowAllVolumes(
+ snapshotDetail?.volumes?.slice(0, 3)
+ )
+ ) : (
+
+
+ {" "}
+ No volumes to display{" "}
+
+
+ )}
+
+
+
+
+
+ Scripts
+
+ {this.preSnapshotScripts()?.length > 3 &&
+ selectedScriptTab ===
+ "Pre-snapshot scripts" ? (
+
+
+ this.toggleShowAllPreScripts()
+ }
+ >
+ Show all{" "}
+ {this.preSnapshotScripts()?.length}{" "}
+ pre-scripts
+
+
+ ) : null}
+ {this.postSnapshotScripts(snapshotDetail)
+ ?.length > 3 &&
+ selectedScriptTab ===
+ "Post-snapshot scripts" ? (
+
+
+ this.toggleShowAllPostScripts()
+ }
+ >
+ Show all{" "}
+ {
+ this.postSnapshotScripts(
+ snapshotDetail
+ )?.length
+ }{" "}
+ post-scripts
+
+
+ ) : null}
+
+
+ {this.renderScriptsTabs()}
+
+
+
+ {selectedScriptTab ===
+ "Pre-snapshot scripts" ? (
+ !isEmpty(
+ this.preSnapshotScripts(snapshotDetail)
+ ) ? (
+ this.renderShowAllScripts(
+ this.preSnapshotScripts(
+ snapshotDetail
+ ).slice(0, 3)
+ )
+ ) : (
+
+
+ {" "}
+ No pre-{featureName} scripts to
+ display{" "}
+
+
+ )
+ ) : selectedScriptTab ===
+ "Post-snapshot scripts" &&
+ !isEmpty(
+ this.postSnapshotScripts(snapshotDetail)
+ ) ? (
+ this.renderShowAllScripts(
+ this.postSnapshotScripts(
+ snapshotDetail
+ ).slice(0, 3)
+ )
+ ) : (
+
+
+ {" "}
+ No post-{featureName} scripts to
+ display{" "}
+
+
+ )}
+
+
+
+
+
+ {(!isEmpty(snapshotDetail?.errors) ||
+ !isEmpty(snapshotDetail?.warnings)) && (
+
+
+
+
+
+
+ Errors and warnings
+
+ {snapshotDetail?.errors?.length > 3 &&
+ selectedErrorsWarningTab ===
+ "Errors" ? (
+
+
+ this.toggleShowAllErrors()
+ }
+ >
+ Show all{" "}
+ {snapshotDetail?.errors?.length}{" "}
+ errors{" "}
+
+
+ ) : null}
+ {snapshotDetail?.warnings?.length > 3 &&
+ selectedErrorsWarningTab ===
+ "Warnings" ? (
+
+
+ this.toggleShowAllWarnings()
+ }
+ >
+ Show all{" "}
+ {snapshotDetail?.warnings?.length}{" "}
+ warnings{" "}
+
+
+ ) : null}
+
+
+ {this.renderErrorsWarningsTabs(
+ snapshotDetail
+ )}
+
+
+
+ {selectedErrorsWarningTab === "Errors" ? (
+ !isEmpty(snapshotDetail?.errors) ? (
+ this.renderShowAllErrors(
+ snapshotDetail?.errors.slice(0, 3)
+ )
+ ) : (
+
+
+ {" "}
+ No errors to display{" "}
+
+
+ )
+ ) : selectedErrorsWarningTab ===
+ "Warnings" &&
+ !isEmpty(snapshotDetail?.warnings) ? (
+ this.renderShowAllWarnings(
+ snapshotDetail?.warnings?.slice(0, 3)
+ )
+ ) : (
+
+
+ {" "}
+ No warnings to display{" "}
+
+
+ )}
+
+
+
+
+ )}
+
)}
- )}
-
- )}
+ );
+ })}
+
+
+ );
+ };
+
+ render() {
+ const { isEmbeddedCluster, navigate } = this.props;
+
+ const {
+ loading,
+ showScriptsOutput,
+ selectedTab,
+ selectedScriptTab,
+ scriptOutput,
+ showAllVolumes,
+ showAllPreSnapshotScripts,
+ showAllPostSnapshotScripts,
+ selectedErrorsWarningTab,
+ showAllErrors,
+ showAllWarnings,
+ snapshotDetails,
+ currentSnapshotIndex,
+ series,
+ errorMessage,
+ errorTitle,
+ } = this.state;
+
+ let featureName = "snapshot";
+ if (isEmbeddedCluster) {
+ featureName = "backup";
+ }
+
+ const { params } = this.props;
+
+ const snapshotName = params.id;
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ this.props.navigate(-1)}>
+ {Utilities.toTitleCase(featureName)}
+
+ >
+ {snapshotName}
+
+
+ {this.renderSnapshot()}
{showScriptsOutput && scriptOutput && (
)}
- {showAllVolumes && (
-
- )}
- {showAllPreSnapshotScripts && (
-
- )}
- {showAllPostSnapshotScripts && (
-
- )}
- {showAllWarnings && (
-
- )}
- {showAllErrors && (
-
- )}
+
{this.state.toggleViewLogsModal && (
)}
-
- {" "}
- {snapshot?.volumeSuccessCount}/{snapshot?.volumeCount}
-
{snapshot?.status === "Completed" ? (
diff --git a/web/src/scss/utilities/base.scss b/web/src/scss/utilities/base.scss
index 480333a6a2..863e1a4432 100644
--- a/web/src/scss/utilities/base.scss
+++ b/web/src/scss/utilities/base.scss
@@ -247,7 +247,7 @@ body a {
background-color: #4999ad;
}
&.partiallyfailed::before {
- background-color: #f1843c;
+ background-color: #bc4752;
}
&.deleting::before {
background-color: #f7b500;