Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 30f992f

Browse files
authored
feat(ui): add backend reinstall button (#7305)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 2709220 commit 30f992f

File tree

1 file changed

+160
-6
lines changed

1 file changed

+160
-6
lines changed

‎core/http/views/manage.html‎

Lines changed: 160 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,22 @@ <h2 class="text-2xl font-semibold text-[#E5E7EB] mb-1 flex items-center">
279279
<!-- Backends Section -->
280280
<div class="mt-8">
281281
<div class="mb-6">
282-
<h2 class="text-2xl font-semibold text-[#E5E7EB] mb-1 flex items-center">
283-
<i class="fas fa-cogs mr-2 text-[#8B5CF6] text-sm"></i>
284-
Installed Backends
285-
</h2>
282+
<div class="flex items-center justify-between mb-1">
283+
<h2 class="text-2xl font-semibold text-[#E5E7EB] flex items-center">
284+
<i class="fas fa-cogs mr-2 text-[#8B5CF6] text-sm"></i>
285+
Installed Backends
286+
</h2>
287+
{{ if gt (len .InstalledBackends) 0 }}
288+
<button
289+
@click="reinstallAllBackends()"
290+
:disabled="reinstallingAll"
291+
class="inline-flex items-center bg-[#38BDF8] hover:bg-[#38BDF8]/80 disabled:opacity-50 disabled:cursor-not-allowed text-white py-1.5 px-3 rounded text-xs font-medium transition-colors"
292+
title="Reinstall all backends">
293+
<i class="fas fa-arrow-rotate-right mr-1.5 text-[10px]" :class="reinstallingAll ? 'fa-spin' : ''"></i>
294+
<span x-text="reinstallingAll ? 'Reinstalling...' : 'Reinstall All'"></span>
295+
</button>
296+
{{ end }}
297+
</div>
286298
<p class="text-sm text-[#94A3B8] mb-4">
287299
<span class="text-[#8B5CF6] font-medium">{{len .InstalledBackends}}</span> backend{{if gt (len .InstalledBackends) 1}}s{{end}} ready to use
288300
</p>
@@ -324,7 +336,7 @@ <h2 class="text-2xl font-bold text-[#E5E7EB] mb-2">No backends installed yet</h2
324336
</thead>
325337
<tbody>
326338
{{ range .InstalledBackends }}
327-
<tr class="hover:bg-[#1E293B]/50 border-b border-[#1E293B] transition-colors">
339+
<tr class="hover:bg-[#1E293B]/50 border-b border-[#1E293B] transition-colors"data-backend-name="{{.Name}}" data-is-system="{{.IsSystem}}">
328340
<!-- Name Column -->
329341
<td class="p-2">
330342
<div class="flex items-center gap-2">
@@ -378,6 +390,13 @@ <h2 class="text-2xl font-bold text-[#E5E7EB] mb-2">No backends installed yet</h2
378390
<td class="p-2">
379391
<div class="flex items-center justify-end gap-1">
380392
{{ if not .IsSystem }}
393+
<button
394+
@click="reinstallBackend('{{.Name}}')"
395+
:disabled="reinstallingBackends['{{.Name}}']"
396+
class="text-[#38BDF8]/60 hover:text-[#38BDF8] hover:bg-[#38BDF8]/10 disabled:opacity-50 disabled:cursor-not-allowed rounded p-1 transition-colors"
397+
title="Reinstall {{.Name}}">
398+
<i class="fas fa-arrow-rotate-right text-xs" :class="reinstallingBackends['{{.Name}}'] ? 'fa-spin' : ''"></i>
399+
</button>
381400
<button
382401
@click="deleteBackend('{{.Name}}')"
383402
class="text-red-400/60 hover:text-red-400 hover:bg-red-500/10 rounded p-1 transition-colors"
@@ -406,9 +425,13 @@ <h2 class="text-2xl font-bold text-[#E5E7EB] mb-2">No backends installed yet</h2
406425
function indexDashboard() {
407426
return {
408427
notifications: [],
428+
reinstallingBackends: {},
429+
reinstallingAll: false,
430+
backendJobs: {},
409431

410432
init() {
411-
// Initialize component
433+
// Poll for job progress every 600ms
434+
setInterval(() => this.pollJobs(), 600);
412435
},
413436

414437
addNotification(message, type = 'success') {
@@ -422,6 +445,137 @@ <h2 class="text-2xl font-bold text-[#E5E7EB] mb-2">No backends installed yet</h2
422445
this.notifications = this.notifications.filter(n => n.id !== id);
423446
},
424447

448+
async reinstallBackend(backendName) {
449+
if (this.reinstallingBackends[backendName]) {
450+
return; // Already reinstalling
451+
}
452+
453+
try {
454+
this.reinstallingBackends[backendName] = true;
455+
const response = await fetch(`/api/backends/install/${encodeURIComponent(backendName)}`, {
456+
method: 'POST'
457+
});
458+
459+
const data = await response.json();
460+
461+
if (response.ok && data.jobID) {
462+
this.backendJobs[backendName] = data.jobID;
463+
this.addNotification(`Reinstalling backend "${backendName}"...`, 'success');
464+
} else {
465+
this.reinstallingBackends[backendName] = false;
466+
this.addNotification(`Failed to start reinstall: ${data.error || 'Unknown error'}`, 'error');
467+
}
468+
} catch (error) {
469+
console.error('Error reinstalling backend:', error);
470+
this.reinstallingBackends[backendName] = false;
471+
this.addNotification(`Failed to reinstall backend: ${error.message}`, 'error');
472+
}
473+
},
474+
475+
async reinstallAllBackends() {
476+
if (this.reinstallingAll) {
477+
return; // Already reinstalling
478+
}
479+
480+
if (!confirm('Are you sure you want to reinstall all backends? This may take some time.')) {
481+
return;
482+
}
483+
484+
this.reinstallingAll = true;
485+
486+
// Get all non-system backends from the page using data attributes
487+
const backendRows = document.querySelectorAll('tr[data-backend-name]');
488+
const backendsToReinstall = [];
489+
490+
backendRows.forEach(row => {
491+
const backendName = row.getAttribute('data-backend-name');
492+
const isSystem = row.getAttribute('data-is-system') === 'true';
493+
if (backendName && !isSystem && !this.reinstallingBackends[backendName]) {
494+
backendsToReinstall.push(backendName);
495+
}
496+
});
497+
498+
if (backendsToReinstall.length === 0) {
499+
this.reinstallingAll = false;
500+
this.addNotification('No backends available to reinstall', 'error');
501+
return;
502+
}
503+
504+
this.addNotification(`Starting reinstall of ${backendsToReinstall.length} backend(s)...`, 'success');
505+
506+
// Reinstall all backends sequentially to avoid overwhelming the system
507+
for (const backendName of backendsToReinstall) {
508+
await this.reinstallBackend(backendName);
509+
// Small delay between installations
510+
await new Promise(resolve => setTimeout(resolve, 500));
511+
}
512+
513+
// Don't set reinstallingAll to false here - let pollJobs handle it when all jobs complete
514+
// This allows the UI to show the batch operation is in progress
515+
},
516+
517+
async pollJobs() {
518+
for (const [backendName, jobID] of Object.entries(this.backendJobs)) {
519+
try {
520+
const response = await fetch(`/api/backends/job/${jobID}`);
521+
const jobData = await response.json();
522+
523+
if (jobData.completed) {
524+
delete this.backendJobs[backendName];
525+
this.reinstallingBackends[backendName] = false;
526+
this.addNotification(`Backend "${backendName}" reinstalled successfully!`, 'success');
527+
528+
// Only reload if not in batch mode and no other jobs are running
529+
if (!this.reinstallingAll && Object.keys(this.backendJobs).length === 0) {
530+
setTimeout(() => {
531+
window.location.reload();
532+
}, 1500);
533+
}
534+
}
535+
536+
if (jobData.error || (jobData.message && jobData.message.startsWith('error:'))) {
537+
delete this.backendJobs[backendName];
538+
this.reinstallingBackends[backendName] = false;
539+
let errorMessage = 'Unknown error';
540+
if (typeof jobData.error === 'string') {
541+
errorMessage = jobData.error;
542+
} else if (jobData.error && typeof jobData.error === 'object') {
543+
const errorKeys = Object.keys(jobData.error);
544+
if (errorKeys.length > 0) {
545+
errorMessage = jobData.error.message || jobData.error.error || jobData.error.Error || JSON.stringify(jobData.error);
546+
} else {
547+
errorMessage = jobData.message || 'Unknown error';
548+
}
549+
} else if (jobData.message) {
550+
errorMessage = jobData.message;
551+
}
552+
if (errorMessage.startsWith('error: ')) {
553+
errorMessage = errorMessage.substring(7);
554+
}
555+
this.addNotification(`Error reinstalling backend "${backendName}": ${errorMessage}`, 'error');
556+
557+
// If batch mode and all jobs are done (completed or errored), reload
558+
if (this.reinstallingAll && Object.keys(this.backendJobs).length === 0) {
559+
this.reinstallingAll = false;
560+
setTimeout(() => {
561+
window.location.reload();
562+
}, 2000);
563+
}
564+
}
565+
} catch (error) {
566+
console.error('Error polling job:', error);
567+
}
568+
}
569+
570+
// If batch mode completed and no jobs left, reload
571+
if (this.reinstallingAll && Object.keys(this.backendJobs).length === 0) {
572+
this.reinstallingAll = false;
573+
setTimeout(() => {
574+
window.location.reload();
575+
}, 2000);
576+
}
577+
},
578+
425579
async deleteBackend(backendName) {
426580
if (!confirm(`Are you sure you want to delete the backend "${backendName}"?`)) {
427581
return;

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /