feat: add package reisntall
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user