diff --git a/core/http/react-ui/src/pages/Backends.jsx b/core/http/react-ui/src/pages/Backends.jsx index 4e73a9669..3cbb71872 100644 --- a/core/http/react-ui/src/pages/Backends.jsx +++ b/core/http/react-ui/src/pages/Backends.jsx @@ -26,6 +26,7 @@ export default function Backends() { const [expandedRow, setExpandedRow] = useState(null) const [confirmDialog, setConfirmDialog] = useState(null) const [allBackends, setAllBackends] = useState([]) + const [upgrades, setUpgrades] = useState({}) const fetchBackends = useCallback(async () => { try { @@ -52,6 +53,13 @@ export default function Backends() { if (!loading) fetchBackends() }, [operations.length]) + // Fetch available upgrades + useEffect(() => { + backendsApi.checkUpgrades() + .then(data => setUpgrades(data || {})) + .catch(() => {}) + }, [operations.length]) + // Client-side filtering by tag const filteredBackends = filter ? allBackends.filter(b => { @@ -114,6 +122,15 @@ export default function Backends() { }) } + const handleUpgrade = async (id) => { + try { + await backendsApi.upgrade(id) + addToast(`Upgrading ${id}...`, 'info') + } catch (err) { + addToast(`Upgrade failed: ${err.message}`, 'error') + } + } + const handleManualInstall = async (e) => { e.preventDefault() if (!manualUri.trim()) { addToast('Please enter a URI', 'warning'); return } @@ -179,6 +196,14 @@ export default function Backends() {
Installed
+ {Object.keys(upgrades).length > 0 && ( +
+
+ {Object.keys(upgrades).length} +
+
Updates
+
+ )} Docs @@ -300,6 +325,11 @@ export default function Backends() { {/* Name */} {b.name || b.id} + {b.version && ( + + v{b.version} + + )} {/* Description */} @@ -346,9 +376,17 @@ export default function Backends() { ) : b.installed ? ( - - Installed - +
+ + Installed + + {upgrades[b.name] && ( + + + {upgrades[b.name].available_version ? `v${upgrades[b.name].available_version}` : 'Update'} + + )} +
) : ( Not Installed @@ -361,9 +399,15 @@ export default function Backends() {
e.stopPropagation()}> {b.installed ? ( <> - + {upgrades[b.name] ? ( + + ) : ( + + )} diff --git a/core/http/react-ui/src/pages/Manage.jsx b/core/http/react-ui/src/pages/Manage.jsx index 6ac429ff4..3d24ebea8 100644 --- a/core/http/react-ui/src/pages/Manage.jsx +++ b/core/http/react-ui/src/pages/Manage.jsx @@ -22,6 +22,7 @@ export default function Manage() { const [backendsLoading, setBackendsLoading] = useState(true) const [reloading, setReloading] = useState(false) const [reinstallingBackends, setReinstallingBackends] = useState(new Set()) + const [upgrades, setUpgrades] = useState({}) const [confirmDialog, setConfirmDialog] = useState(null) const [distributedMode, setDistributedMode] = useState(false) const [togglingModels, setTogglingModels] = useState(new Set()) @@ -62,6 +63,15 @@ export default function Manage() { nodesApi.list().then(() => setDistributedMode(true)).catch(() => {}) }, [fetchLoadedModels, fetchBackends]) + // Fetch available backend upgrades + useEffect(() => { + if (activeTab === 'backends') { + backendsApi.checkUpgrades() + .then(data => setUpgrades(data || {})) + .catch(() => {}) + } + }, [activeTab]) + const handleStopModel = (modelName) => { setConfirmDialog({ title: 'Stop Model', @@ -169,6 +179,22 @@ export default function Manage() { } } + const handleUpgradeBackend = async (name) => { + try { + setReinstallingBackends(prev => new Set(prev).add(name)) + await backendsApi.upgrade(name) + addToast(`Upgrading ${name}...`, 'info') + } catch (err) { + addToast(`Failed to upgrade: ${err.message}`, 'error') + } finally { + setReinstallingBackends(prev => { + const next = new Set(prev) + next.delete(name) + return next + }) + } + } + const handleDeleteBackend = (name) => { setConfirmDialog({ title: 'Delete Backend', @@ -471,6 +497,17 @@ export default function Manage() { For: {backend.Metadata.meta_backend_for} )} + {backend.Metadata?.version && ( + + + Version: v{backend.Metadata.version} + {upgrades[backend.Name] && ( + + → v{upgrades[backend.Name].available_version} + + )} + + )} {backend.Metadata?.installed_at && ( @@ -485,12 +522,12 @@ export default function Manage() { {!backend.IsSystem ? ( <>
diff --git a/core/http/react-ui/src/utils/api.js b/core/http/react-ui/src/utils/api.js index b9d3983d2..1edac8cbd 100644 --- a/core/http/react-ui/src/utils/api.js +++ b/core/http/react-ui/src/utils/api.js @@ -120,6 +120,9 @@ export const backendsApi = { installExternal: (body) => postJSON(API_CONFIG.endpoints.installExternalBackend, body), getJob: (uid) => fetchJSON(API_CONFIG.endpoints.backendJob(uid)), deleteInstalled: (name) => postJSON(API_CONFIG.endpoints.deleteInstalledBackend(name), {}), + checkUpgrades: () => fetchJSON(API_CONFIG.endpoints.backendsUpgrades), + forceCheckUpgrades: () => postJSON(API_CONFIG.endpoints.backendsUpgradesCheck, {}), + upgrade: (name) => postJSON(API_CONFIG.endpoints.upgradeBackend(name), {}), } // Chat API (non-streaming) diff --git a/core/http/react-ui/src/utils/config.js b/core/http/react-ui/src/utils/config.js index 70fb7f57a..66767eb3f 100644 --- a/core/http/react-ui/src/utils/config.js +++ b/core/http/react-ui/src/utils/config.js @@ -23,6 +23,9 @@ export const API_CONFIG = { installExternalBackend: '/api/backends/install-external', backendJob: (uid) => `/api/backends/job/${uid}`, deleteInstalledBackend: (name) => `/api/backends/system/delete/${name}`, + backendsUpgrades: '/api/backends/upgrades', + backendsUpgradesCheck: '/api/backends/upgrades/check', + upgradeBackend: (name) => `/api/backends/upgrade/${name}`, // Resources resources: '/api/resources',