fix: deps install
This commit is contained in:
@@ -31,9 +31,6 @@ func (c *DependencyController) List(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
vos := vo.ToDependencyVOListFromModels(deps)
|
vos := vo.ToDependencyVOListFromModels(deps)
|
||||||
for i := range vos {
|
|
||||||
vos[i].Log = "" // 列表不返回日志
|
|
||||||
}
|
|
||||||
utils.Success(ctx, vos)
|
utils.Success(ctx, vos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,14 +112,14 @@ func (c *DependencyController) Install(ctx *gin.Context) {
|
|||||||
Remark: req.Remark,
|
Remark: req.Remark,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.service.Install(dep); err != nil {
|
err := c.service.Install(dep)
|
||||||
|
// 无论成功失败,都同步记录日志
|
||||||
|
c.service.Create(dep)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
utils.ServerError(ctx, err.Error())
|
utils.ServerError(ctx, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 安装成功后保存到数据库
|
|
||||||
c.service.Create(dep)
|
|
||||||
|
|
||||||
utils.SuccessMsg(ctx, "安装成功")
|
utils.SuccessMsg(ctx, "安装成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,9 +191,9 @@ func (c *DependencyController) Uninstall(ctx *gin.Context) {
|
|||||||
// 获取依赖信息
|
// 获取依赖信息
|
||||||
deps, _ := c.service.List("", "")
|
deps, _ := c.service.List("", "")
|
||||||
var dep *models.Dependency
|
var dep *models.Dependency
|
||||||
for _, d := range deps {
|
for i := range deps {
|
||||||
if d.ID == id {
|
if deps[i].ID == id {
|
||||||
dep = &d
|
dep = &deps[i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,9 +225,9 @@ func (c *DependencyController) Reinstall(ctx *gin.Context) {
|
|||||||
// 获取依赖信息
|
// 获取依赖信息
|
||||||
deps, _ := c.service.List("", "")
|
deps, _ := c.service.List("", "")
|
||||||
var dep *models.Dependency
|
var dep *models.Dependency
|
||||||
for _, d := range deps {
|
for i := range deps {
|
||||||
if d.ID == id {
|
if deps[i].ID == id {
|
||||||
dep = &d
|
dep = &deps[i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,11 +237,14 @@ func (c *DependencyController) Reinstall(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.service.Install(dep); err != nil {
|
err := c.service.Install(dep)
|
||||||
|
// 无论成功失败,都同步记录日志
|
||||||
|
c.service.Create(dep)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
utils.ServerError(ctx, err.Error())
|
utils.ServerError(ctx, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.SuccessMsg(ctx, "重新安装成功")
|
utils.SuccessMsg(ctx, "重新安装成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,10 +264,14 @@ func (c *DependencyController) ReinstallAll(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var failed []string
|
var failed []string
|
||||||
for _, d := range deps {
|
for i := range deps {
|
||||||
if err := c.service.Install(&d); err != nil {
|
d := &deps[i]
|
||||||
|
err := c.service.Install(d)
|
||||||
|
if err != nil {
|
||||||
failed = append(failed, d.Name)
|
failed = append(failed, d.Name)
|
||||||
}
|
}
|
||||||
|
// 无论成功失败,都同步记录日志到数据库
|
||||||
|
c.service.Create(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(failed) > 0 {
|
if len(failed) > 0 {
|
||||||
|
|||||||
@@ -31,12 +31,16 @@ func (s *DependencyService) List(language, langVersion string) ([]models.Depende
|
|||||||
|
|
||||||
// Create 创建依赖记录
|
// Create 创建依赖记录
|
||||||
func (s *DependencyService) Create(dep *models.Dependency) error {
|
func (s *DependencyService) Create(dep *models.Dependency) error {
|
||||||
// 检查是否已存在(名称、语言、版本 必须完全匹配)
|
// 检查是否已存在(名称、版本、语言及版本必须完全匹配)
|
||||||
var existing models.Dependency
|
var existing models.Dependency
|
||||||
err := database.DB.Where("name = ? AND language = ? AND lang_version = ?", dep.Name, dep.Language, dep.LangVersion).First(&existing).Error
|
err := database.DB.Where("name = ? AND version = ? AND language = ? AND lang_version = ?", dep.Name, dep.Version, dep.Language, dep.LangVersion).First(&existing).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return errors.New("依赖已存在")
|
// 如果已存在,更新 ID 并执行更新
|
||||||
|
dep.ID = existing.ID
|
||||||
|
return database.DB.Model(&existing).Updates(dep).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 不存在则新建
|
||||||
if dep.ID == "" {
|
if dep.ID == "" {
|
||||||
dep.ID = utils.GenerateID()
|
dep.ID = utils.GenerateID()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ function connectWebSocket() {
|
|||||||
}
|
}
|
||||||
terminal?.write(event.data)
|
terminal?.write(event.data)
|
||||||
|
|
||||||
// 检测安装结果标识
|
// 检测结果标识(保持简单的单次消息检测,不使用持久缓冲区,避免重复触发通知)
|
||||||
if (typeof event.data === 'string') {
|
if (typeof event.data === 'string') {
|
||||||
if (event.data.includes('__INSTALL_SUCCESS__')) {
|
if (event.data.includes('__INSTALL_SUCCESS__')) {
|
||||||
emit('success')
|
emit('success')
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ import { Label } from '@/components/ui/label'
|
|||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog'
|
||||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'
|
||||||
import { Trash2, Package, Search, RefreshCw, Loader2, Download, FileText, RotateCw, ChevronLeft, Terminal as TerminalIcon, X } from 'lucide-vue-next'
|
import { Trash2, Package, Search, RefreshCw, Loader2, Download, FileText, RotateCw, ChevronLeft } from 'lucide-vue-next'
|
||||||
import { api, type Dependency } from '@/api'
|
import { api, type Dependency } from '@/api'
|
||||||
import TextOverflow from '@/components/TextOverflow.vue'
|
import TextOverflow from '@/components/TextOverflow.vue'
|
||||||
import XTerminal from '@/components/XTerminal.vue'
|
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -35,18 +34,10 @@ const newPkgRemark = ref('')
|
|||||||
const showDeleteDialog = ref(false)
|
const showDeleteDialog = ref(false)
|
||||||
const depToDelete = ref<Dependency | null>(null)
|
const depToDelete = ref<Dependency | null>(null)
|
||||||
|
|
||||||
// 日志对话框
|
|
||||||
const showLogDialog = ref(false)
|
const showLogDialog = ref(false)
|
||||||
const logContent = ref('')
|
const logContent = ref('')
|
||||||
const logPkgName = ref('')
|
const logPkgName = ref('')
|
||||||
|
|
||||||
// 终端状态
|
|
||||||
const showTerminalDialog = ref(false)
|
|
||||||
const terminalCommand = ref('')
|
|
||||||
const terminalTitle = ref('依赖安装')
|
|
||||||
const isInstallSuccess = ref(false)
|
|
||||||
const pendingInstall = ref<{ name: string; version?: string; language: string; lang_version?: string; remark?: string } | null>(null)
|
|
||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
|
||||||
@@ -110,41 +101,19 @@ async function installPackage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
installing.value = true
|
installing.value = true
|
||||||
isInstallSuccess.value = false // 重置状态
|
|
||||||
try {
|
try {
|
||||||
const { command } = await api.deps.getInstallCmd(pkgData)
|
await api.deps.install(pkgData)
|
||||||
terminalCommand.value = command
|
toast.success('指令已发送,详情请查看日志')
|
||||||
terminalTitle.value = `安装: ${pkgData.name}`
|
showInstallDialog.value = false
|
||||||
pendingInstall.value = pkgData
|
} catch (e: any) {
|
||||||
|
toast.error('安装过程出错: ' + e.message)
|
||||||
showInstallDialog.value = false
|
showInstallDialog.value = false
|
||||||
showTerminalDialog.value = true
|
|
||||||
} catch (e: unknown) {
|
|
||||||
toast.error((e as Error).message || '获取安装命令失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
installing.value = false
|
installing.value = false
|
||||||
|
await loadDeps()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTerminalClose() {
|
|
||||||
if (pendingInstall.value && isInstallSuccess.value) {
|
|
||||||
try {
|
|
||||||
// 终端关闭后,仅在成功时尝试在数据库中记录
|
|
||||||
await api.deps.create(pendingInstall.value)
|
|
||||||
toast.success('依赖记录已更新')
|
|
||||||
} catch (e: any) {
|
|
||||||
if (e.message !== '依赖已存在') {
|
|
||||||
toast.error('记录依赖失败: ' + e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (pendingInstall.value && !isInstallSuccess.value) {
|
|
||||||
toast.error('安装未成功,记录未保存')
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingInstall.value = null
|
|
||||||
isInstallSuccess.value = false
|
|
||||||
loadDeps()
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmDelete(dep: Dependency) {
|
function confirmDelete(dep: Dependency) {
|
||||||
depToDelete.value = dep
|
depToDelete.value = dep
|
||||||
showDeleteDialog.value = true
|
showDeleteDialog.value = true
|
||||||
@@ -173,20 +142,13 @@ function showLog(dep: Dependency) {
|
|||||||
async function reinstallPackage(dep: Dependency) {
|
async function reinstallPackage(dep: Dependency) {
|
||||||
reinstalling.value = dep.id
|
reinstalling.value = dep.id
|
||||||
try {
|
try {
|
||||||
const { command } = await api.deps.getInstallCmd({
|
await api.deps.reinstall(dep.id)
|
||||||
name: dep.name,
|
toast.success(`重装指令已发送`)
|
||||||
version: dep.version,
|
} catch (e: any) {
|
||||||
language: dep.language,
|
toast.error('重装错误: ' + e.message)
|
||||||
lang_version: dep.lang_version
|
|
||||||
})
|
|
||||||
terminalCommand.value = command
|
|
||||||
terminalTitle.value = `重装: ${dep.name}`
|
|
||||||
pendingInstall.value = null // 重新安装不需要再次记录
|
|
||||||
showTerminalDialog.value = true
|
|
||||||
} catch (e: unknown) {
|
|
||||||
toast.error((e as Error).message || '获取命令失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
reinstalling.value = null
|
reinstalling.value = null
|
||||||
|
await loadDeps()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,16 +157,13 @@ async function reinstallAll() {
|
|||||||
try {
|
try {
|
||||||
const lang = language.value || activeTab.value
|
const lang = language.value || activeTab.value
|
||||||
const ver = langVersion.value
|
const ver = langVersion.value
|
||||||
const { command } = await api.deps.getReinstallAllCmd(lang, ver)
|
await api.deps.reinstallAll(lang, ver)
|
||||||
|
toast.success('全部重装指令执行完毕')
|
||||||
terminalCommand.value = command
|
} catch (e: any) {
|
||||||
terminalTitle.value = `全部重装: ${getTypeLabel(lang)}`
|
toast.error('全部重装错误: ' + e.message)
|
||||||
pendingInstall.value = null // 全部重装不需要记录新条目
|
|
||||||
showTerminalDialog.value = true
|
|
||||||
} catch (e: unknown) {
|
|
||||||
toast.error((e as Error).message || '获取命令失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
reinstallingAll.value = false
|
reinstallingAll.value = false
|
||||||
|
await loadDeps()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +256,7 @@ onMounted(async () => {
|
|||||||
<span class="flex-1">包名</span>
|
<span class="flex-1">包名</span>
|
||||||
<span class="w-32">版本</span>
|
<span class="w-32">版本</span>
|
||||||
<span class="w-48 hidden md:block">备注</span>
|
<span class="w-48 hidden md:block">备注</span>
|
||||||
<span class="w-24 text-center">操作</span>
|
<span class="w-32 text-center">操作</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
@@ -319,15 +278,19 @@ onMounted(async () => {
|
|||||||
<span class="w-48 text-sm text-muted-foreground truncate hidden md:block">
|
<span class="w-48 text-sm text-muted-foreground truncate hidden md:block">
|
||||||
<TextOverflow :text="dep.remark || '-'" title="备注" />
|
<TextOverflow :text="dep.remark || '-'" title="备注" />
|
||||||
</span>
|
</span>
|
||||||
<span class="w-24 flex justify-center gap-1">
|
<span class="w-32 flex justify-center gap-1">
|
||||||
<Button v-if="dep.log" variant="ghost" size="icon" class="h-7 w-7" @click="showLog(dep)">
|
<Button v-if="dep.log || dep.id" variant="ghost" size="icon"
|
||||||
|
class="h-7 w-7 text-blue-500 hover:text-blue-600 hover:bg-blue-50/10" @click="showLog(dep)"
|
||||||
|
title="查看安装日志">
|
||||||
<FileText class="h-4 w-4" />
|
<FileText class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" class="h-7 w-7" @click="reinstallPackage(dep)"
|
<Button variant="ghost" size="icon"
|
||||||
:disabled="reinstalling === dep.id">
|
class="h-7 w-7 text-amber-500 hover:text-amber-600 hover:bg-amber-50/10" @click="reinstallPackage(dep)"
|
||||||
|
:disabled="reinstalling === dep.id" title="重新安装">
|
||||||
<RotateCw class="h-4 w-4" :class="{ 'animate-spin': reinstalling === dep.id }" />
|
<RotateCw class="h-4 w-4" :class="{ 'animate-spin': reinstalling === dep.id }" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" class="h-7 w-7 text-destructive" @click="confirmDelete(dep)">
|
<Button variant="ghost" size="icon" class="h-7 w-7 text-destructive hover:bg-destructive/10"
|
||||||
|
@click="confirmDelete(dep)" title="卸载并删除记录">
|
||||||
<Trash2 class="h-4 w-4" />
|
<Trash2 class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
@@ -401,35 +364,6 @@ onMounted(async () => {
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- 终端对话框 -->
|
|
||||||
<Dialog v-model:open="showTerminalDialog" @update:open="(val) => !val && handleTerminalClose()">
|
|
||||||
<DialogContent
|
|
||||||
class="w-[calc(100%-2rem)] sm:max-w-[90vw] lg:max-w-4xl xl:max-w-5xl h-[60vh] sm:h-[70vh] flex flex-col p-0 overflow-hidden bg-[#1e1e1e] border-none shadow-2xl"
|
|
||||||
:show-close-button="false" @interact-outside="(e) => e.preventDefault()"
|
|
||||||
@escape-key-down="(e) => e.preventDefault()">
|
|
||||||
<DialogHeader class="sr-only">
|
|
||||||
<DialogTitle>{{ terminalTitle }}</DialogTitle>
|
|
||||||
<DialogDescription>正在执行依赖安装指令</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div class="flex flex-col h-full">
|
|
||||||
<div class="flex items-center justify-between px-4 py-2 bg-[#252526] border-b border-[#3c3c3c]">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<TerminalIcon class="h-4 w-4 text-primary" />
|
|
||||||
<span class="text-xs font-medium text-gray-300">正在执行: {{ terminalCommand }}</span>
|
|
||||||
</div>
|
|
||||||
<Button variant="ghost" size="icon" class="h-6 w-6 text-gray-400 hover:text-white"
|
|
||||||
@click="showTerminalDialog = false">
|
|
||||||
<X class="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<XTerminal v-if="showTerminalDialog" :font-size="13" :initial-command="terminalCommand"
|
|
||||||
@success="isInstallSuccess = true" @failed="isInstallSuccess = false" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user