feat: add package reisntall

This commit is contained in:
engigu
2025-12-24 09:57:12 +08:00
parent 19a2a1adf7
commit e3131e15dc
5 changed files with 104 additions and 5 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
version: '3.8'
services:
baihu:
image: ghcr.io/engigu/baihu:main
image: ghcr.io/engigu/baihu:latest
container_name: baihu
ports:
- "8052:8052"
@@ -2,6 +2,7 @@ package controllers
import (
"strconv"
"strings"
"baihu/internal/models"
"baihu/internal/services"
@@ -147,6 +148,66 @@ func (c *DependencyController) Uninstall(ctx *gin.Context) {
utils.SuccessMsg(ctx, "卸载成功")
}
// Reinstall 重新安装依赖
func (c *DependencyController) Reinstall(ctx *gin.Context) {
id, err := strconv.Atoi(ctx.Param("id"))
if err != nil {
utils.BadRequest(ctx, "无效的 ID")
return
}
// 获取依赖信息
deps, _ := c.service.List("")
var dep *models.Dependency
for _, d := range deps {
if d.ID == id {
dep = &d
break
}
}
if dep == nil {
utils.NotFound(ctx, "依赖不存在")
return
}
if err := c.service.Install(dep); err != nil {
utils.ServerError(ctx, err.Error())
return
}
utils.SuccessMsg(ctx, "重新安装成功")
}
// ReinstallAll 重新安装所有依赖
func (c *DependencyController) ReinstallAll(ctx *gin.Context) {
depType := ctx.Query("type")
if depType == "" {
utils.BadRequest(ctx, "缺少 type 参数")
return
}
deps, err := c.service.List(depType)
if err != nil {
utils.ServerError(ctx, "获取依赖列表失败")
return
}
var failed []string
for _, d := range deps {
if err := c.service.Install(&d); err != nil {
failed = append(failed, d.Name)
}
}
if len(failed) > 0 {
utils.ServerError(ctx, "部分包安装失败: "+strings.Join(failed, ", "))
return
}
utils.SuccessMsg(ctx, "全部重新安装成功")
}
// GetInstalled 获取已安装的包
func (c *DependencyController) GetInstalled(ctx *gin.Context) {
depType := ctx.Query("type")
+2
View File
@@ -193,6 +193,8 @@ func Setup(c *Controllers) *gin.Engine {
deps.DELETE("/:id", c.Dependency.Delete)
deps.POST("/install", c.Dependency.Install)
deps.POST("/uninstall/:id", c.Dependency.Uninstall)
deps.POST("/reinstall/:id", c.Dependency.Reinstall)
deps.POST("/reinstall-all", c.Dependency.ReinstallAll)
deps.GET("/installed", c.Dependency.GetInstalled)
}
}
+2
View File
@@ -203,6 +203,8 @@ export const api = {
install: (data: { name: string; version?: string; type: string; remark?: string }) =>
request('/deps/install', { method: 'POST', body: JSON.stringify(data) }),
uninstall: (id: number) => request(`/deps/uninstall/${id}`, { method: 'POST' }),
reinstall: (id: number) => request(`/deps/reinstall/${id}`, { method: 'POST' }),
reinstallAll: (type: string) => request(`/deps/reinstall-all?type=${type}`, { method: 'POST' }),
getInstalled: (type: string) => request<Dependency[]>(`/deps/installed?type=${type}`)
}
}
+37 -3
View File
@@ -7,7 +7,7 @@ import { Badge } from '@/components/ui/badge'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Trash2, Package, Search, RefreshCw, Loader2, Download, FileText } from 'lucide-vue-next'
import { Trash2, Package, Search, RefreshCw, Loader2, Download, FileText, RotateCw } from 'lucide-vue-next'
import { api, type Dependency } from '@/api'
import { toast } from 'vue-sonner'
@@ -15,6 +15,8 @@ const activeTab = ref('py')
const deps = ref<Dependency[]>([])
const loading = ref(false)
const installing = ref(false)
const reinstalling = ref<number | null>(null)
const reinstallingAll = ref(false)
// 安装对话框
const showInstallDialog = ref(false)
@@ -107,6 +109,32 @@ function showLog(dep: Dependency) {
showLogDialog.value = true
}
async function reinstallPackage(dep: Dependency) {
reinstalling.value = dep.id
try {
await api.deps.reinstall(dep.id)
toast.success(`${dep.name} 重新安装成功`)
await loadDeps()
} catch (e: unknown) {
toast.error((e as Error).message || '重新安装失败')
} finally {
reinstalling.value = null
}
}
async function reinstallAll() {
reinstallingAll.value = true
try {
await api.deps.reinstallAll(activeTab.value)
toast.success('全部重新安装成功')
await loadDeps()
} catch (e: unknown) {
toast.error((e as Error).message || '重新安装失败')
} finally {
reinstallingAll.value = false
}
}
function getTypeLabel(type: string) {
return type === 'py' ? 'Python' : 'Node.js'
}
@@ -144,6 +172,9 @@ onMounted(loadDeps)
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="loadDeps" :disabled="loading">
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
</Button>
<Button variant="outline" size="sm" class="h-9 shrink-0" @click="reinstallAll" :disabled="reinstallingAll || filteredDeps.length === 0">
<RotateCw class="h-4 w-4 sm:mr-1.5" :class="{ 'animate-spin': reinstallingAll }" /> <span class="hidden sm:inline">全部重装</span>
</Button>
<Button size="sm" class="h-9 shrink-0" @click="openInstallDialog">
<Download class="h-4 w-4 sm:mr-1.5" /> <span class="hidden sm:inline">安装</span>
</Button>
@@ -155,7 +186,7 @@ onMounted(loadDeps)
<span class="flex-1">包名</span>
<span class="w-32">版本</span>
<span class="w-48 hidden md:block">备注</span>
<span class="w-20 text-center">操作</span>
<span class="w-24 text-center">操作</span>
</div>
<!-- 列表 -->
@@ -177,10 +208,13 @@ onMounted(loadDeps)
<span class="flex-1 font-mono text-sm">{{ dep.name }}</span>
<span class="w-32 text-sm text-muted-foreground">{{ dep.version || '-' }}</span>
<span class="w-48 text-sm text-muted-foreground truncate hidden md:block" :title="dep.remark">{{ dep.remark || '-' }}</span>
<span class="w-20 flex justify-center gap-1">
<span class="w-24 flex justify-center gap-1">
<Button v-if="dep.log" variant="ghost" size="icon" class="h-7 w-7" @click="showLog(dep)">
<FileText class="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" class="h-7 w-7" @click="reinstallPackage(dep)" :disabled="reinstalling === dep.id">
<RotateCw class="h-4 w-4" :class="{ 'animate-spin': reinstalling === dep.id }" />
</Button>
<Button variant="ghost" size="icon" class="h-7 w-7 text-destructive" @click="confirmDelete(dep)">
<Trash2 class="h-4 w-4" />
</Button>