diff --git a/web/src/components/apps/AvailableUpdatesComponent.tsx b/web/src/components/apps/AvailableUpdatesComponent.tsx
index c817d1c57c..cc47821f65 100644
--- a/web/src/components/apps/AvailableUpdatesComponent.tsx
+++ b/web/src/components/apps/AvailableUpdatesComponent.tsx
@@ -5,6 +5,80 @@ import { Utilities } from "@src/utilities/utilities";
import { AvailableUpdate } from "@types";
import ReactTooltip from "react-tooltip";
+export const AvailableUpdateRow = ({
+ update,
+ index,
+ showReleaseNotes,
+ children,
+ upgradeService,
+}: {
+ update: AvailableUpdate;
+ index: number;
+ showReleaseNotes: (releaseNotes: string) => void;
+ children: React.ReactNode;
+ upgradeService?: {
+ versionLabel?: string;
+ isLoading?: boolean;
+ error?: string;
+ } | null;
+}) => {
+ return (
+
+
+
+
+
+ {update.versionLabel}
+
+ {update.isRequired && (
+
+ {" "}
+ Required{" "}
+
+ )}
+
+ {update.upstreamReleasedAt && (
+
+ {" "}
+ Released{" "}
+
+ {Utilities.dateFormat(
+ update.upstreamReleasedAt,
+ "MM/DD/YY @ hh:mm a z"
+ )}
+
+
+ )}
+
+
+ {update?.releaseNotes && (
+ <>
+ showReleaseNotes(update?.releaseNotes)}
+ data-tip="View release notes"
+ className="u-marginRight--5 clickable"
+ />
+
+ >
+ )}
+ {children}
+
+
+ {upgradeService?.error &&
+ upgradeService?.versionLabel === update.versionLabel && (
+
+
+ {upgradeService.error}
+ error
+
+
+ )}
+
+ );
+};
+
const AvailableUpdatesComponent = ({
updates,
showReleaseNotes,
@@ -71,78 +145,33 @@ const AvailableUpdatesComponent = ({
upgradeService?.versionLabel === update.versionLabel &&
upgradeService.isLoading;
return (
-
-
-
-
-
- {update.versionLabel}
-
- {update.isRequired && (
-
- {" "}
- Required{" "}
-
- )}
-
- {update.upstreamReleasedAt && (
-
- {" "}
- Released{" "}
-
- {Utilities.dateFormat(
- update.upstreamReleasedAt,
- "MM/DD/YY @ hh:mm a z"
- )}
-
-
- )}
-
-
- {update?.releaseNotes && (
- <>
-
showReleaseNotes(update?.releaseNotes)}
- data-tip="View release notes"
- className="u-marginRight--5 clickable"
- />
-
- >
- )}
-
-
- {upgradeService?.error &&
- upgradeService?.versionLabel === update.versionLabel && (
-
-
- {upgradeService.error}
-
-
- )}
-
+ {isCurrentVersionLoading ? "Preparing..." : "Deploy"}
+
+
+
+ >
+
);
})}
diff --git a/web/src/features/Dashboard/components/AvailableUpdateCard.tsx b/web/src/features/Dashboard/components/AvailableUpdateCard.tsx
new file mode 100644
index 0000000000..be93b6dce8
--- /dev/null
+++ b/web/src/features/Dashboard/components/AvailableUpdateCard.tsx
@@ -0,0 +1,54 @@
+import { useNavigate } from "react-router-dom";
+
+import { AvailableUpdate } from "@types";
+import { AvailableUpdateRow } from "@components/apps/AvailableUpdatesComponent";
+
+const AvailableUpdateCard = ({
+ updates,
+ showReleaseNotes,
+ appSlug,
+}: {
+ updates: AvailableUpdate[];
+ showReleaseNotes: (releaseNotes: string) => void;
+ appSlug: string;
+}) => {
+ const navigate = useNavigate();
+ const update = updates[0];
+ return (
+
+
+
+ Latest Available Update
+
+
+
+ ({updates.length} available)
+
+
+
+
+
+ navigate(`/app/${appSlug}/version-history`)}
+ >
+
+ Go to Version history
+
+
+
+
+
+ );
+};
+
+export default AvailableUpdateCard;
diff --git a/web/src/features/Dashboard/components/DashboardVersionCard.tsx b/web/src/features/Dashboard/components/DashboardVersionCard.tsx
index 32e9c87294..7f8bc99dad 100644
--- a/web/src/features/Dashboard/components/DashboardVersionCard.tsx
+++ b/web/src/features/Dashboard/components/DashboardVersionCard.tsx
@@ -1,32 +1,31 @@
+import classNames from "classnames";
import { useEffect, useReducer } from "react";
+import Modal from "react-modal";
import { Link, useNavigate, useParams } from "react-router-dom";
import ReactTooltip from "react-tooltip";
-import DashboardGitOpsCard from "./DashboardGitOpsCard";
-import MarkdownRenderer from "@src/components/shared/MarkdownRenderer";
+
+import EditConfigIcon from "@components/shared/EditConfigIcon";
+import { useSelectedApp } from "@features/App";
import VersionDiff from "@features/VersionDiff/VersionDiff";
-import Modal from "react-modal";
import AirgapUploadProgress from "@src/components/AirgapUploadProgress";
-import Loader from "@src/components/shared/Loader";
-import MountAware from "@src/components/shared/MountAware";
+import Icon from "@src/components/Icon";
import ShowDetailsModal from "@src/components/modals/ShowDetailsModal";
import ShowLogsModal from "@src/components/modals/ShowLogsModal";
+import Loader from "@src/components/shared/Loader";
+import MarkdownRenderer from "@src/components/shared/MarkdownRenderer";
import DeployWarningModal from "@src/components/shared/modals/DeployWarningModal";
import SkipPreflightsModal from "@src/components/shared/modals/SkipPreflightsModal";
-import classNames from "classnames";
+import MountAware from "@src/components/shared/MountAware";
+import { AirgapUploader } from "@src/utilities/airgapUploader";
+import { Repeater } from "@src/utilities/repeater";
import {
getPreflightResultState,
getReadableGitOpsProviderName,
secondsAgo,
Utilities,
} from "@src/utilities/utilities";
-import { useNextAppVersionWithIntercept } from "../api/useNextAppVersion";
-import { useSelectedApp } from "@features/App";
-import { Repeater } from "@src/utilities/repeater";
-
-import "@src/scss/components/watches/DashboardCard.scss";
-import Icon from "@src/components/Icon";
-
import {
+ AvailableUpdate,
Downstream,
KotsParams,
Metadata,
@@ -34,8 +33,11 @@ import {
VersionDownloadStatus,
VersionStatus,
} from "@types";
-import { AirgapUploader } from "@src/utilities/airgapUploader";
-import EditConfigIcon from "@components/shared/EditConfigIcon";
+import { useNextAppVersionWithIntercept } from "../api/useNextAppVersion";
+import AvailableUpdateCard from "./AvailableUpdateCard";
+import DashboardGitOpsCard from "./DashboardGitOpsCard";
+
+import "@src/scss/components/watches/DashboardCard.scss";
type Props = {
adminConsoleMetadata: Metadata | null;
@@ -71,6 +73,7 @@ type Props = {
};
type State = {
+ availableUpdates: AvailableUpdate[];
confirmType: string;
deployView: boolean;
displayConfirmDeploymentModal: boolean;
@@ -78,6 +81,7 @@ type State = {
displayShowDetailsModal: boolean;
firstSequence: string;
secondSequence: string;
+ isFetchingAvailableUpdates: boolean;
isRedeploy: boolean;
isSkipPreflights: boolean;
kotsUpdateChecker: Repeater;
@@ -119,12 +123,14 @@ const DashboardVersionCard = (props: Props) => {
...newState,
}),
{
+ availableUpdates: [],
confirmType: "",
deployView: false,
displayConfirmDeploymentModal: false,
displayKotsUpdateModal: false,
displayShowDetailsModal: false,
firstSequence: "",
+ isFetchingAvailableUpdates: false,
isSkipPreflights: false,
isRedeploy: false,
kotsUpdateChecker: new Repeater(),
@@ -168,6 +174,32 @@ const DashboardVersionCard = (props: Props) => {
} = useNextAppVersionWithIntercept();
const { latestDeployableVersion } = newAppVersionWithInterceptData || {};
+ const fetchAvailableUpdates = async () => {
+ const appSlug = params.slug;
+ setState({ isFetchingAvailableUpdates: true });
+ const res = await fetch(
+ `${process.env.API_ENDPOINT}/app/${appSlug}/updates`,
+ {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "include",
+ method: "GET",
+ }
+ );
+ if (!res.ok) {
+ setState({ isFetchingAvailableUpdates: false });
+ return;
+ }
+ const response = await res.json();
+
+ setState({
+ isFetchingAvailableUpdates: false,
+ availableUpdates: response.updates,
+ });
+ return response;
+ };
+
// moving this out of the state because new repeater instances were getting created
// and it doesn't really affect the UI
const versionDownloadStatusJobs: {
@@ -178,6 +210,9 @@ const DashboardVersionCard = (props: Props) => {
if (props.links && props.links.length > 0) {
setState({ selectedAction: props.links[0] });
}
+ if (props.adminConsoleMetadata?.isEmbeddedCluster) {
+ fetchAvailableUpdates();
+ }
}, []);
useEffect(() => {
@@ -1507,6 +1542,35 @@ const DashboardVersionCard = (props: Props) => {
)}
)}
+ {props.adminConsoleMetadata?.isEmbeddedCluster && (
+
+ {!state.isFetchingAvailableUpdates && (
+
fetchAvailableUpdates()}
+ >
+
+ Check for update
+
+ )}
+ {state.isFetchingAvailableUpdates && (
+
+
+
+ Checking for updates
+
+
+ )}
+
+ )}
{currentVersion?.deployedAt ? (
@@ -1520,6 +1584,14 @@ const DashboardVersionCard = (props: Props) => {
)}
+ {props.adminConsoleMetadata?.isEmbeddedCluster &&
+ state.availableUpdates?.length > 0 && (
+
+ )}
{renderBottomSection()}