feat: add default env settings
This commit is contained in:
@@ -76,3 +76,23 @@ func (c *MiseController) VerifyCommand(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
utils.Success(ctx, gin.H{"command": cmd})
|
utils.Success(ctx, gin.H{"command": cmd})
|
||||||
}
|
}
|
||||||
|
// UseGlobal 设置全局默认版本
|
||||||
|
func (c *MiseController) UseGlobal(ctx *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
Plugin string `json:"plugin"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
utils.BadRequest(ctx, "参数错误: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Plugin == "" || req.Version == "" {
|
||||||
|
utils.BadRequest(ctx, "参数 plugin 和 version 不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := c.service.UseGlobal(req.Plugin, req.Version); err != nil {
|
||||||
|
utils.ServerError(ctx, "设置全局版本失败: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.Success(ctx, nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ func registerMiseRoutes(g *gin.RouterGroup, c *Controllers) {
|
|||||||
mise.GET("/plugins", c.Mise.Plugins)
|
mise.GET("/plugins", c.Mise.Plugins)
|
||||||
mise.GET("/versions", c.Mise.Versions)
|
mise.GET("/versions", c.Mise.Versions)
|
||||||
mise.GET("/verify-cmd", c.Mise.VerifyCommand)
|
mise.GET("/verify-cmd", c.Mise.VerifyCommand)
|
||||||
|
mise.POST("/use-global", c.Mise.UseGlobal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type MiseLanguage struct {
|
|||||||
Plugin string `json:"plugin"`
|
Plugin string `json:"plugin"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Source MiseSource `json:"source"`
|
Source MiseSource `json:"source"`
|
||||||
Active bool `json:"active"`
|
IsGlobal bool `json:"is_global"`
|
||||||
InstallPath string `json:"install_path,omitempty"`
|
InstallPath string `json:"install_path,omitempty"`
|
||||||
InstalledAt string `json:"installed_at,omitempty"` // 安装日期
|
InstalledAt string `json:"installed_at,omitempty"` // 安装日期
|
||||||
}
|
}
|
||||||
@@ -38,42 +38,17 @@ type MiseSource struct {
|
|||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// List 从数据库获取已保存的语言列表
|
// List 实时从系统检测 mise 环境并同步到数据库
|
||||||
func (s *MiseService) List() ([]MiseLanguage, error) {
|
func (s *MiseService) List() ([]MiseLanguage, error) {
|
||||||
db := database.GetDB()
|
langs, err := s.fetchLiveLanguages()
|
||||||
var models []models.Language
|
if err != nil {
|
||||||
if err := db.Order("installed_at DESC, plugin ASC").Find(&models).Error; err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果数据库为空,尝试进行一次自动同步(延迟加载)
|
// 异步同步到数据库,确保列表响应速度
|
||||||
if len(models) == 0 {
|
go s.syncToDB(langs)
|
||||||
logger.Info("[Mise] 数据库中没有语言记录,正在进行首次同步...")
|
|
||||||
if err := s.Sync(); err == nil {
|
|
||||||
// 同步成功后重新查询
|
|
||||||
if err := db.Order("installed_at DESC, plugin ASC").Find(&models).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Warnf("[Mise] 首次自动同步失败: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]MiseLanguage, 0, len(models))
|
return langs, nil
|
||||||
for _, m := range models {
|
|
||||||
lang := MiseLanguage{
|
|
||||||
Plugin: m.Plugin,
|
|
||||||
Version: m.Version,
|
|
||||||
InstallPath: m.InstallPath,
|
|
||||||
Active: true,
|
|
||||||
}
|
|
||||||
lang.Source.Path = m.Source
|
|
||||||
if m.InstalledAt != nil {
|
|
||||||
lang.InstalledAt = time.Time(*m.InstalledAt).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
result = append(result, lang)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync 实时检测本地 mise 环境并同步到数据库
|
// Sync 实时检测本地 mise 环境并同步到数据库
|
||||||
@@ -166,7 +141,6 @@ func (s *MiseService) listFallback() ([]MiseLanguage, error) {
|
|||||||
lang := MiseLanguage{
|
lang := MiseLanguage{
|
||||||
Plugin: parts[0],
|
Plugin: parts[0],
|
||||||
Version: parts[1],
|
Version: parts[1],
|
||||||
Active: true,
|
|
||||||
}
|
}
|
||||||
if len(parts) >= 3 {
|
if len(parts) >= 3 {
|
||||||
lang.Source = MiseSource{Path: parts[2]}
|
lang.Source = MiseSource{Path: parts[2]}
|
||||||
@@ -220,6 +194,13 @@ func (s *MiseService) Versions(plugin string) ([]string, error) {
|
|||||||
// enrichInstallDates 为语言列表添加安装日期信息
|
// enrichInstallDates 为语言列表添加安装日期信息
|
||||||
func (s *MiseService) enrichInstallDates(languages []MiseLanguage) {
|
func (s *MiseService) enrichInstallDates(languages []MiseLanguage) {
|
||||||
for i := range languages {
|
for i := range languages {
|
||||||
|
// 判断是否是 global
|
||||||
|
if languages[i].Source.Type == "global" {
|
||||||
|
languages[i].IsGlobal = true
|
||||||
|
} else if strings.Contains(languages[i].Source.Path, ".config/mise/config.toml") {
|
||||||
|
languages[i].IsGlobal = true
|
||||||
|
}
|
||||||
|
|
||||||
if languages[i].InstallPath != "" {
|
if languages[i].InstallPath != "" {
|
||||||
if installDate := s.getInstallDate(languages[i].InstallPath); installDate != "" {
|
if installDate := s.getInstallDate(languages[i].InstallPath); installDate != "" {
|
||||||
languages[i].InstalledAt = installDate
|
languages[i].InstalledAt = installDate
|
||||||
@@ -349,3 +330,13 @@ func (s *MiseService) GetVerifyCommand(plugin, version string) (string, error) {
|
|||||||
}
|
}
|
||||||
return m.GetVerifyCommand(version)
|
return m.GetVerifyCommand(version)
|
||||||
}
|
}
|
||||||
|
// UseGlobal 设置全局默认版本
|
||||||
|
func (s *MiseService) UseGlobal(plugin, version string) error {
|
||||||
|
cmd := exec.Command("mise", "use", "-g", fmt.Sprintf("%s@%s", plugin, version))
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("mise use -g failed: %v, output: %s", err, string(output))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -267,7 +267,8 @@ export const api = {
|
|||||||
sync: () => request<void>('/mise/sync', { method: 'POST' }),
|
sync: () => request<void>('/mise/sync', { method: 'POST' }),
|
||||||
plugins: () => request<string[]>('/mise/plugins'),
|
plugins: () => request<string[]>('/mise/plugins'),
|
||||||
versions: (plugin: string) => request<string[]>(`/mise/versions?plugin=${plugin}`),
|
versions: (plugin: string) => request<string[]>(`/mise/versions?plugin=${plugin}`),
|
||||||
verifyCommand: (plugin: string, version: string) => request<{ command: string }>(`/mise/verify-cmd?plugin=${plugin}&version=${version}`)
|
verifyCommand: (plugin: string, version: string) => request<{ command: string }>(`/mise/verify-cmd?plugin=${plugin}&version=${version}`),
|
||||||
|
useGlobal: (plugin: string, version: string) => request<void>('/mise/use-global', { method: 'POST', body: JSON.stringify({ plugin, version }) })
|
||||||
},
|
},
|
||||||
terminal: {
|
terminal: {
|
||||||
cmds: () => request<{ name: string, description: string }[]>('/terminal/cmds')
|
cmds: () => request<{ name: string, description: string }[]>('/terminal/cmds')
|
||||||
@@ -543,7 +544,7 @@ export interface MiseLanguage {
|
|||||||
plugin: string
|
plugin: string
|
||||||
version: string
|
version: string
|
||||||
source: { type?: string; path?: string } | string
|
source: { type?: string; path?: string } | string
|
||||||
active: boolean
|
is_global: boolean
|
||||||
install_path?: string
|
install_path?: string
|
||||||
installed_at?: string // 安装日期
|
installed_at?: string // 安装日期
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const SUPPORTED_DEPS_LANGS = [
|
|||||||
|
|
||||||
interface DisplayLanguage extends Omit<MiseLanguage, 'source'> {
|
interface DisplayLanguage extends Omit<MiseLanguage, 'source'> {
|
||||||
source: string
|
source: string
|
||||||
|
isGlobal: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const languages = ref<DisplayLanguage[]>([])
|
const languages = ref<DisplayLanguage[]>([])
|
||||||
@@ -83,7 +84,8 @@ async function loadLanguages() {
|
|||||||
}
|
}
|
||||||
languages.value = data.map(item => ({
|
languages.value = data.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
source: typeof item.source === 'object' ? (item.source.path || item.source.type || '-') : (item.source || '-')
|
source: typeof item.source === 'object' ? (item.source.path || item.source.type || '-') : (item.source || '-'),
|
||||||
|
isGlobal: !!item.is_global
|
||||||
}))
|
}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error('获取语言列表失败')
|
toast.error('获取语言列表失败')
|
||||||
@@ -243,15 +245,31 @@ async function handleVerify(lang: MiseLanguage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSetDefault(lang: MiseLanguage) {
|
||||||
|
try {
|
||||||
|
await api.mise.useGlobal(lang.plugin, lang.version)
|
||||||
|
toast.success(`已将 ${lang.plugin} ${lang.version} 设为全局默认版本`)
|
||||||
|
await loadLanguages()
|
||||||
|
} catch (e) {
|
||||||
|
toast.error('设置默认版本失败: ' + e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(loadLanguages)
|
onMounted(loadLanguages)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
<div>
|
<div class="flex-1">
|
||||||
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">语言依赖</h2>
|
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">语言依赖</h2>
|
||||||
|
<div class="mt-1 space-y-1">
|
||||||
<p class="text-muted-foreground text-sm">管理系统环境中的编程语言运行时及相关包依赖 (Mise)</p>
|
<p class="text-muted-foreground text-sm">管理系统环境中的编程语言运行时及相关包依赖 (Mise)</p>
|
||||||
|
<div class="flex items-center gap-1.5 text-xs text-amber-600 dark:text-amber-500 bg-amber-500/5 w-fit px-2 py-0.5 rounded-full border border-amber-500/20">
|
||||||
|
<AlertCircle class="h-3 w-3" />
|
||||||
|
<span><b>设为默认</b>:将选定版本设为系统全局默认 (mise use -g),生效后所有未通过高级配置指定特定环境的任务将默认调用此环境。</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button @click="openInstallDialog">
|
<Button @click="openInstallDialog">
|
||||||
<Plus class="h-4 w-4 mr-2" /> 新增语言
|
<Plus class="h-4 w-4 mr-2" /> 新增语言
|
||||||
@@ -316,6 +334,10 @@ onMounted(loadLanguages)
|
|||||||
<span class="font-bold capitalize truncate">{{ lang.plugin }}</span>
|
<span class="font-bold capitalize truncate">{{ lang.plugin }}</span>
|
||||||
<Badge variant="outline" class="font-mono whitespace-nowrap">{{ lang.version }}
|
<Badge variant="outline" class="font-mono whitespace-nowrap">{{ lang.version }}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<Badge v-if="lang.isGlobal" variant="secondary"
|
||||||
|
class="bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20 text-[10px] h-5 px-1.5 font-normal">
|
||||||
|
默认
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-muted-foreground mt-1 space-y-0.5">
|
<div class="text-xs text-muted-foreground mt-1 space-y-0.5">
|
||||||
<div class="font-mono opacity-60 truncate" :title="lang.source">来源: {{ lang.source
|
<div class="font-mono opacity-60 truncate" :title="lang.source">来源: {{ lang.source
|
||||||
@@ -342,6 +364,10 @@ onMounted(loadLanguages)
|
|||||||
@click="handleVerify(lang)">
|
@click="handleVerify(lang)">
|
||||||
环境验证
|
环境验证
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant="outline" size="sm" class="whitespace-nowrap flex-1 sm:flex-none"
|
||||||
|
:disabled="lang.isGlobal" @click="handleSetDefault(lang)">
|
||||||
|
设为默认
|
||||||
|
</Button>
|
||||||
<Button variant="ghost" size="icon"
|
<Button variant="ghost" size="icon"
|
||||||
class="text-destructive h-8 w-8 shrink-0 ml-auto sm:ml-0" @click="confirmDelete(lang)"
|
class="text-destructive h-8 w-8 shrink-0 ml-auto sm:ml-0" @click="confirmDelete(lang)"
|
||||||
title="卸载">
|
title="卸载">
|
||||||
|
|||||||
Reference in New Issue
Block a user