diff --git a/src/components/Dashboard/Management/AppUpdates/AppUpdateProgress.jsx b/src/components/Dashboard/Management/AppUpdates/AppUpdateProgress.jsx
index c098f7d..06571b2 100644
--- a/src/components/Dashboard/Management/AppUpdates/AppUpdateProgress.jsx
+++ b/src/components/Dashboard/Management/AppUpdates/AppUpdateProgress.jsx
@@ -1,6 +1,12 @@
import PropTypes from 'prop-types'
-import { Alert, Flex, Progress, Typography } from 'antd'
-import { LoadingOutlined } from '@ant-design/icons'
+import { useState } from 'react'
+import { Button, Flex, Modal, Progress, Typography, theme } from 'antd'
+
+import CloudIcon from '../../../Icons/CloudIcon'
+import HostIcon from '../../../Icons/HostIcon'
+
+import CheckCircleIcon from '../../../Icons/CheckCircleIcon'
+import XMarkCircleIcon from '../../../Icons/XMarkCircleIcon'
const { Text } = Typography
@@ -21,27 +27,137 @@ const formatBytes = (bytes) => {
}`
}
-const getProgressStatus = (phase) => {
- if (phase === 'error') return 'exception'
- if (phase === 'downloaded' || phase === 'installing') return 'success'
+const STAGE_CONFIG = {
+ download: {
+ icon: CloudIcon,
+ labels: {
+ pending: 'Download',
+ active: 'Downloading',
+ complete: 'Downloaded',
+ error: 'Download failed'
+ }
+ },
+ install: {
+ icon: HostIcon,
+ labels: {
+ pending: 'Install',
+ active: 'Installing',
+ complete: 'Installed',
+ error: 'Install failed'
+ }
+ }
+}
+
+const getStageColor = (status, token) => {
+ if (status === 'complete') return token.colorSuccess
+ if (status === 'active') return token.colorPrimary
+ if (status === 'error') return token.colorError
+ return token.colorTextQuaternary
+}
+
+const getDownloadStageStatus = (phase, isError) => {
+ if (isError && ['preparing', 'downloading'].includes(phase)) return 'error'
+ if (['downloaded', 'installing'].includes(phase)) return 'complete'
+ if (['preparing', 'downloading'].includes(phase)) return 'active'
+ return 'pending'
+}
+
+const getInstallStageStatus = (phase, isError, message) => {
+ if (isError && ['downloaded', 'installing'].includes(phase)) return 'error'
+ if (
+ phase === 'installing' &&
+ String(message || '')
+ .toLowerCase()
+ .includes('complete')
+ ) {
+ return 'complete'
+ }
+ if (phase === 'installing') return 'active'
+ return 'pending'
+}
+
+const getProgressStatus = (stageStatus) => {
+ if (stageStatus === 'error') return 'exception'
+ if (stageStatus === 'complete') return 'success'
return 'active'
}
-const AppUpdateProgress = ({ progress, update }) => {
+const UpdateStage = ({ stage, status, percent, detail }) => {
+ const { token } = theme.useToken()
+ const config = STAGE_CONFIG[stage]
+ const StageIcon = config.icon
+ const color = getStageColor(status, token)
+ const showProgress = status === 'active'
+ const resolvedPercent =
+ typeof percent === 'number' ? Math.min(percent, 100) : undefined
+
+ const StatusIcon =
+ status === 'complete'
+ ? CheckCircleIcon
+ : status === 'error'
+ ? XMarkCircleIcon
+ : StageIcon
+
+ return (
+
+
+
+
+
+ {config.labels[status]}
+
+ {showProgress && (
+
+ )}
+
+
+ {detail && (
+
+ {detail}
+
+ )}
+
+ )
+}
+
+UpdateStage.propTypes = {
+ stage: PropTypes.oneOf(['download', 'install']).isRequired,
+ status: PropTypes.oneOf(['pending', 'active', 'complete', 'error'])
+ .isRequired,
+ percent: PropTypes.number,
+ detail: PropTypes.string
+}
+
+const AppUpdateProgress = ({ progress, update, onClose }) => {
const phase = progress?.phase || 'preparing'
const percent =
- typeof progress?.percent === 'number' ? Math.min(progress.percent, 100) : 0
+ typeof progress?.percent === 'number'
+ ? Math.min(progress.percent, 100)
+ : null
const downloaded = formatBytes(progress?.downloadedBytes)
const total = formatBytes(progress?.totalBytes)
- const artifactName =
- progress?.artifact?.fileName ||
- progress?.artifact?.relativePath ||
- 'Selected installer'
const message = progress?.message || 'Preparing update'
- const isInstalling = phase === 'installing'
const isError = phase === 'error'
- const showProgress =
- !isError && (!isInstalling || typeof progress?.percent === 'number')
+
+ const [errorModalOpen, setErrorModalOpen] = useState(true)
+
+ const downloadStatus = getDownloadStageStatus(phase, isError)
+ const installStatus = getInstallStageStatus(phase, isError, message)
+
+ const downloadPercent =
+ downloadStatus === 'active' ? (phase === 'preparing' ? 0 : percent) : null
+
+ const installPercent = installStatus === 'active' ? percent : null
+
+ const downloadDetail =
+ downloadStatus === 'active' && downloaded && total
+ ? `${downloaded} of ${total}`
+ : null
return (
@@ -52,34 +168,44 @@ const AppUpdateProgress = ({ progress, update }) => {
{update?.branch ? `${update.branch}` : 'unknown'}...
- {isError ? (
-
- ) : (
- }
- message={
- isInstalling
- ? message
- : `Downloading ${artifactName}`
- }
+
+
- )}
-
- {showProgress && (
-
- )}
+
- {downloaded && total && (
-
- {downloaded} of {total} downloaded
-
- )}
+ {
+ setErrorModalOpen(false)
+ onClose()
+ }}
+ footer={[
+
+ ]}
+ >
+ {message}
+
)
}
@@ -93,7 +219,8 @@ AppUpdateProgress.propTypes = {
message: PropTypes.string,
artifact: PropTypes.object
}),
- update: PropTypes.object
+ update: PropTypes.object,
+ onClose: PropTypes.func
}
export default AppUpdateProgress
diff --git a/src/components/Dashboard/context/AppUpdateContext.jsx b/src/components/Dashboard/context/AppUpdateContext.jsx
index bd84155..6de63ef 100644
--- a/src/components/Dashboard/context/AppUpdateContext.jsx
+++ b/src/components/Dashboard/context/AppUpdateContext.jsx
@@ -269,6 +269,7 @@ export const AppUpdateProvider = ({ children }) => {
) : (