diff --git a/core/http/views/manage.html b/core/http/views/manage.html index c9bc9f4684ce..c4c583853aed 100644 --- a/core/http/views/manage.html +++ b/core/http/views/manage.html @@ -279,10 +279,22 @@

-

- - Installed Backends -

+
+

+ + Installed Backends +

+ {{ if gt (len .InstalledBackends) 0 }} + + {{ end }} +

{{len .InstalledBackends}} backend{{if gt (len .InstalledBackends) 1}}s{{end}} ready to use

@@ -324,7 +336,7 @@

No backends installed yet

{{ range .InstalledBackends }} - +
@@ -378,6 +390,13 @@

No backends installed yet

{{ if not .IsSystem }} +

this.pollJobs(), 600); }, addNotification(message, type = 'success') { @@ -422,6 +445,137 @@

No backends installed yet

n.id !== id); }, + async reinstallBackend(backendName) { + if (this.reinstallingBackends[backendName]) { + return; // Already reinstalling + } + + try { + this.reinstallingBackends[backendName] = true; + const response = await fetch(`/api/backends/install/${encodeURIComponent(backendName)}`, { + method: 'POST' + }); + + const data = await response.json(); + + if (response.ok && data.jobID) { + this.backendJobs[backendName] = data.jobID; + this.addNotification(`Reinstalling backend "${backendName}"...`, 'success'); + } else { + this.reinstallingBackends[backendName] = false; + this.addNotification(`Failed to start reinstall: ${data.error || 'Unknown error'}`, 'error'); + } + } catch (error) { + console.error('Error reinstalling backend:', error); + this.reinstallingBackends[backendName] = false; + this.addNotification(`Failed to reinstall backend: ${error.message}`, 'error'); + } + }, + + async reinstallAllBackends() { + if (this.reinstallingAll) { + return; // Already reinstalling + } + + if (!confirm('Are you sure you want to reinstall all backends? This may take some time.')) { + return; + } + + this.reinstallingAll = true; + + // Get all non-system backends from the page using data attributes + const backendRows = document.querySelectorAll('tr[data-backend-name]'); + const backendsToReinstall = []; + + backendRows.forEach(row => { + const backendName = row.getAttribute('data-backend-name'); + const isSystem = row.getAttribute('data-is-system') === 'true'; + if (backendName && !isSystem && !this.reinstallingBackends[backendName]) { + backendsToReinstall.push(backendName); + } + }); + + if (backendsToReinstall.length === 0) { + this.reinstallingAll = false; + this.addNotification('No backends available to reinstall', 'error'); + return; + } + + this.addNotification(`Starting reinstall of ${backendsToReinstall.length} backend(s)...`, 'success'); + + // Reinstall all backends sequentially to avoid overwhelming the system + for (const backendName of backendsToReinstall) { + await this.reinstallBackend(backendName); + // Small delay between installations + await new Promise(resolve => setTimeout(resolve, 500)); + } + + // Don't set reinstallingAll to false here - let pollJobs handle it when all jobs complete + // This allows the UI to show the batch operation is in progress + }, + + async pollJobs() { + for (const [backendName, jobID] of Object.entries(this.backendJobs)) { + try { + const response = await fetch(`/api/backends/job/${jobID}`); + const jobData = await response.json(); + + if (jobData.completed) { + delete this.backendJobs[backendName]; + this.reinstallingBackends[backendName] = false; + this.addNotification(`Backend "${backendName}" reinstalled successfully!`, 'success'); + + // Only reload if not in batch mode and no other jobs are running + if (!this.reinstallingAll && Object.keys(this.backendJobs).length === 0) { + setTimeout(() => { + window.location.reload(); + }, 1500); + } + } + + if (jobData.error || (jobData.message && jobData.message.startsWith('error:'))) { + delete this.backendJobs[backendName]; + this.reinstallingBackends[backendName] = false; + let errorMessage = 'Unknown error'; + if (typeof jobData.error === 'string') { + errorMessage = jobData.error; + } else if (jobData.error && typeof jobData.error === 'object') { + const errorKeys = Object.keys(jobData.error); + if (errorKeys.length > 0) { + errorMessage = jobData.error.message || jobData.error.error || jobData.error.Error || JSON.stringify(jobData.error); + } else { + errorMessage = jobData.message || 'Unknown error'; + } + } else if (jobData.message) { + errorMessage = jobData.message; + } + if (errorMessage.startsWith('error: ')) { + errorMessage = errorMessage.substring(7); + } + this.addNotification(`Error reinstalling backend "${backendName}": ${errorMessage}`, 'error'); + + // If batch mode and all jobs are done (completed or errored), reload + if (this.reinstallingAll && Object.keys(this.backendJobs).length === 0) { + this.reinstallingAll = false; + setTimeout(() => { + window.location.reload(); + }, 2000); + } + } + } catch (error) { + console.error('Error polling job:', error); + } + } + + // If batch mode completed and no jobs left, reload + if (this.reinstallingAll && Object.keys(this.backendJobs).length === 0) { + this.reinstallingAll = false; + setTimeout(() => { + window.location.reload(); + }, 2000); + } + }, + async deleteBackend(backendName) { if (!confirm(`Are you sure you want to delete the backend "${backendName}"?`)) { return;