diff --git a/src/components/Dashboard/Management/AppUpdate.jsx b/src/components/Dashboard/Management/AppUpdate.jsx
new file mode 100644
index 0000000..b9fb4ca
--- /dev/null
+++ b/src/components/Dashboard/Management/AppUpdate.jsx
@@ -0,0 +1,186 @@
+import { useContext, useEffect, useMemo, useState } from 'react'
+import {
+ Button,
+ Collapse,
+ Descriptions,
+ Empty,
+ Flex,
+ Select,
+ Space,
+ Typography
+} from 'antd'
+import { CaretLeftOutlined } from '@ant-design/icons'
+import { ApiServerContext } from '../context/ApiServerContext'
+import useCollapseState from '../hooks/useCollapseState'
+
+const { Title, Text, Link } = Typography
+const { Option } = Select
+
+const AppUpdate = () => {
+ const { fetchAppUpdateBranches, fetchAppUpdateCurrent } =
+ useContext(ApiServerContext)
+ const [collapseState, updateCollapseState] = useCollapseState('AppUpdate', {
+ updater: true
+ })
+ const [branches, setBranches] = useState([])
+ const [selectedBranch, setSelectedBranch] = useState(undefined)
+ const [branchLoading, setBranchLoading] = useState(false)
+ const [checking, setChecking] = useState(false)
+ const [currentUpdate, setCurrentUpdate] = useState(null)
+
+ useEffect(() => {
+ const loadBranches = async () => {
+ setBranchLoading(true)
+ const availableBranches = await fetchAppUpdateBranches()
+ setBranches(availableBranches)
+
+ if (availableBranches.length > 0) {
+ setSelectedBranch((previous) =>
+ previous && availableBranches.includes(previous)
+ ? previous
+ : availableBranches[0]
+ )
+ }
+ setBranchLoading(false)
+ }
+
+ loadBranches()
+ }, [fetchAppUpdateBranches])
+
+ const branchOptions = useMemo(
+ () =>
+ branches.map((branch) => (
+
+ )),
+ [branches]
+ )
+
+ const handleCheckForUpdates = async () => {
+ if (!selectedBranch) return
+ setChecking(true)
+ const updateData = await fetchAppUpdateCurrent(selectedBranch)
+ setCurrentUpdate(updateData)
+ setChecking(false)
+ }
+
+ const buildTimestamp = currentUpdate?.buildTimestamp
+ ? new Date(currentUpdate.buildTimestamp).toLocaleString()
+ : 'Unknown'
+
+ return (
+
+
+ updateCollapseState('updater', keys.length > 0)}
+ expandIcon={({ isActive }) => (
+
+ )}
+ className='no-h-padding-collapse'
+ >
+
+
+ Application Updater
+
+
+ }
+ key='1'
+ >
+
+
+
+
+
+
+
+
+
+
+ {currentUpdate ? (
+
+
+ {currentUpdate.branch || selectedBranch}
+
+
+ {currentUpdate.buildNumber || 'Unknown'}
+
+
+ {currentUpdate.buildSource || 'Unknown'}
+
+
+ {currentUpdate.buildResult || 'Unknown'}
+
+
+ {buildTimestamp}
+
+
+ {currentUpdate.buildUrl ? (
+
+ Open Jenkins Build
+
+ ) : (
+ No build URL available
+ )}
+
+
+ {Array.isArray(currentUpdate.artifacts) &&
+ currentUpdate.artifacts.length > 0 ? (
+
+ {currentUpdate.artifacts.map((artifact) => (
+
+ {artifact.fileName || artifact.relativePath}
+
+ ))}
+
+ ) : (
+ No artifacts published
+ )}
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+ )
+}
+
+export default AppUpdate
diff --git a/src/components/Dashboard/context/ApiServerContext.jsx b/src/components/Dashboard/context/ApiServerContext.jsx
index f4d5ab9..8132d9f 100644
--- a/src/components/Dashboard/context/ApiServerContext.jsx
+++ b/src/components/Dashboard/context/ApiServerContext.jsx
@@ -1678,6 +1678,46 @@ const ApiServerProvider = ({ children }) => {
return response.data
}, [])
+ const fetchAppUpdateBranches = useCallback(async () => {
+ try {
+ const response = await axios.get(`${config.backendUrl}/appupdate/branches`, {
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ })
+ return Array.isArray(response.data?.branches) ? response.data.branches : []
+ } catch (err) {
+ console.error(err)
+ showError(err, () => {
+ fetchAppUpdateBranches()
+ })
+ return []
+ }
+ }, [token])
+
+ const fetchAppUpdateCurrent = useCallback(
+ async (branch) => {
+ try {
+ const response = await axios.get(`${config.backendUrl}/appupdate/current`, {
+ params: { branch },
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`
+ }
+ })
+ return response.data
+ } catch (err) {
+ console.error(err)
+ showError(err, () => {
+ fetchAppUpdateCurrent(branch)
+ })
+ return null
+ }
+ },
+ [token]
+ )
+
const flushFile = async (id) => {
logger.debug('Flushing file...')
try {
@@ -1774,7 +1814,9 @@ const ApiServerProvider = ({ children }) => {
getMarketplaceAuthUrl,
refreshMarketplaceAuth,
completeAppLaunchSession,
- getAppLaunchSession
+ getAppLaunchSession,
+ fetchAppUpdateBranches,
+ fetchAppUpdateCurrent
}}
>
{contextHolder}
diff --git a/src/database/sidebars/management.js b/src/database/sidebars/management.js
index c2c0b77..d0d2e5d 100644
--- a/src/database/sidebars/management.js
+++ b/src/database/sidebars/management.js
@@ -143,6 +143,12 @@ const managementSidebarItems = [
label: 'Settings',
path: '/dashboard/management/settings'
},
+ {
+ key: 'appUpdate',
+ iconKey: 'settings',
+ label: 'App Update',
+ path: '/dashboard/management/appupdate'
+ },
{
key: 'files',
iconKey: 'file',
diff --git a/src/routes/ManagementRoutes.jsx b/src/routes/ManagementRoutes.jsx
index 5836544..dcb7bab 100644
--- a/src/routes/ManagementRoutes.jsx
+++ b/src/routes/ManagementRoutes.jsx
@@ -24,6 +24,7 @@ const CourierInfo = lazy(() => import('../components/Dashboard/Management/Courie
const CourierServices = lazy(() => import('../components/Dashboard/Management/CourierServices'))
const CourierServiceInfo = lazy(() => import('../components/Dashboard/Management/CourierServices/CourierServiceInfo.jsx'))
const Settings = lazy(() => import('../components/Dashboard/Management/Settings'))
+const AppUpdate = lazy(() => import('../components/Dashboard/Management/AppUpdate'))
const AuditLogs = lazy(() => import('../components/Dashboard/Management/AuditLogs.jsx'))
const NoteTypes = lazy(() => import('../components/Dashboard/Management/NoteTypes.jsx'))
const NoteTypeInfo = lazy(() => import('../components/Dashboard/Management/NoteTypes/NoteTypeInfo.jsx'))
@@ -206,6 +207,7 @@ const ManagementRoutes = [
element={}
/>,
} />,
+ } />,
} />,
} />,