mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-10 00:00:30 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac1a474fae |
@@ -86,23 +86,17 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
|
||||
|
||||
// @Tags Database Redis
|
||||
// @Summary Install redis-cli
|
||||
// @Accept json
|
||||
// @Param request body dto.RedisCliInstall true "request"
|
||||
// @Success 200 {string} string
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/redis/install/cli [post]
|
||||
func (b *BaseApi) InstallCli(c *gin.Context) {
|
||||
var req dto.RedisCliInstall
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := redisService.InstallCli(req)
|
||||
if err != nil {
|
||||
if err := redisService.InstallCli(); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Database Redis
|
||||
|
||||
@@ -22,10 +22,6 @@ type DBBaseInfo struct {
|
||||
Port int64 `json:"port"`
|
||||
}
|
||||
|
||||
type RedisCliInstall struct {
|
||||
TaskID string `json:"taskID" validate:"omitempty,uuid"`
|
||||
}
|
||||
|
||||
// mysql
|
||||
type MysqlDBSearch struct {
|
||||
PageInfo
|
||||
|
||||
@@ -2,6 +2,7 @@ package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@@ -110,7 +111,9 @@ func (t TaskRepo) Update(ctx context.Context, task *model.Task) error {
|
||||
}
|
||||
|
||||
func (t TaskRepo) UpdateRunningTaskToFailed() error {
|
||||
return getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{"status": constant.StatusFailed, "error_msg": "1Panel restart causes failure"}).Error
|
||||
return getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{
|
||||
"status": constant.StatusFailed, "error_msg": constant.InterruptedMsg, "end_at": time.Now(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (t TaskRepo) CountExecutingTask() (int64, error) {
|
||||
|
||||
@@ -550,6 +550,11 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
|
||||
installTask, err := task.NewTaskWithOps(appInstall.Name, task.TaskInstall, task.TaskScopeApp, req.TaskID, appInstall.ID)
|
||||
if err != nil {
|
||||
appInstall.Status = constant.StatusInstallErr
|
||||
appInstall.Message = err.Error()
|
||||
if saveErr := appInstallRepo.Save(context.Background(), appInstall); saveErr != nil {
|
||||
err = fmt.Errorf("%w; save failed install status: %v", err, saveErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -557,7 +562,7 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
return
|
||||
}
|
||||
|
||||
installApp := func(t *task.Task) error {
|
||||
installApp := func(t *task.Task) (err error) {
|
||||
if err = copyData(t, app, appDetail, appInstall, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -339,6 +339,11 @@ func operateAppWithLifecycleScripts(install model.AppInstall, req request.AppIns
|
||||
|
||||
operationTask, err := task.NewTaskWithOps(install.Name, taskType, task.TaskScopeApp, req.TaskID, install.ID)
|
||||
if err != nil {
|
||||
install.Status = constant.StatusUpErr
|
||||
install.Message = err.Error()
|
||||
if saveErr := appInstallRepo.Save(context.Background(), &install); saveErr != nil {
|
||||
return fmt.Errorf("%w; save failed operation status: %v", err, saveErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
operation := string(req.Operate)
|
||||
@@ -567,11 +572,15 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
|
||||
return err
|
||||
}
|
||||
for _, i := range allList {
|
||||
if i.Status == constant.StatusInstalling || i.Status == constant.StatusUpgrading || i.Status == constant.StatusRebuilding || i.Status == constant.StatusUninstalling {
|
||||
if appInstallOperationInterruptedOnRestart(i.Status) {
|
||||
if systemInit {
|
||||
i.Status = constant.StatusError
|
||||
i.Message = "1Panel restart causes the task to terminate"
|
||||
_ = appInstallRepo.Save(context.Background(), &i)
|
||||
i.Status = appInstallOperationFailureStatus(i.Status)
|
||||
i.Message = constant.InterruptedMsg
|
||||
if err := appInstallRepo.Save(context.Background(), &i); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := syncAppInstallStatus(&i, false); err != nil {
|
||||
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -931,10 +940,34 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
}
|
||||
|
||||
func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error {
|
||||
operation := ""
|
||||
switch appInstall.Status {
|
||||
case constant.StatusInstalling:
|
||||
operation = task.TaskInstall
|
||||
case constant.StatusUpgrading:
|
||||
operation = task.TaskUpgrade
|
||||
case constant.StatusRebuilding:
|
||||
operation = task.TaskBuild
|
||||
case constant.StatusUninstalling:
|
||||
operation = task.TaskUninstall
|
||||
case constant.StatusStarting, constant.StatusWaiting:
|
||||
operation = task.TaskUpdate
|
||||
case constant.StatusRestarting:
|
||||
operation = task.TaskRestart
|
||||
}
|
||||
if operation != "" {
|
||||
if err := reconcileAppInstallTaskFailure(appInstall, operation, global.DB, global.TaskDB); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch appInstall.Status {
|
||||
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
||||
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
||||
return nil
|
||||
case constant.StatusInstallErr, constant.StatusUpgradeErr, constant.StatusUpErr:
|
||||
if !force {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
cli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func appInstallOperationFailureStatus(status string) string {
|
||||
switch status {
|
||||
case constant.StatusInstalling:
|
||||
return constant.StatusInstallErr
|
||||
case constant.StatusUpgrading:
|
||||
return constant.StatusUpgradeErr
|
||||
case constant.StatusUninstalling:
|
||||
return constant.StatusError
|
||||
default:
|
||||
return constant.StatusUpErr
|
||||
}
|
||||
}
|
||||
|
||||
func appInstallOperationPending(status string) bool {
|
||||
switch status {
|
||||
case constant.StatusInstalling, constant.StatusRebuilding, constant.StatusUpgrading, constant.StatusUninstalling,
|
||||
constant.StatusStarting, constant.StatusRestarting, constant.StatusWaiting:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func appInstallOperationInterruptedOnRestart(status string) bool {
|
||||
// ReStarting can also describe a container's Docker restart policy.
|
||||
return appInstallOperationPending(status) && status != constant.StatusRestarting
|
||||
}
|
||||
|
||||
func reconcileAppInstallTaskFailure(install *model.AppInstall, operation string, appDB, taskDB *gorm.DB) error {
|
||||
if !appInstallOperationPending(install.Status) {
|
||||
return nil
|
||||
}
|
||||
var operationTask model.Task
|
||||
err := taskDB.Where("type = ? AND resource_id = ? AND operate = ?", "App", install.ID, operation).
|
||||
Order("created_at DESC").First(&operationTask).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if operationTask.Status != constant.StatusFailed && operationTask.Status != constant.StatusCanceled {
|
||||
return nil
|
||||
}
|
||||
// A previous operation must not turn a newly queued retry into a failure.
|
||||
if operationTask.CreatedAt.Before(install.UpdatedAt) &&
|
||||
(operationTask.EndAt.IsZero() || operationTask.EndAt.Before(install.UpdatedAt)) {
|
||||
return nil
|
||||
}
|
||||
message := operationTask.ErrorMsg
|
||||
if message == "" {
|
||||
message = "the application operation task failed"
|
||||
}
|
||||
status := appInstallOperationFailureStatus(install.Status)
|
||||
result := appDB.Model(&model.AppInstall{}).
|
||||
Where("id = ? AND status = ? AND updated_at = ?", install.ID, install.Status, install.UpdatedAt).
|
||||
Updates(map[string]interface{}{"status": status, "message": message})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected > 0 {
|
||||
install.Status = status
|
||||
install.Message = message
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
func operationStatusTestDB(t *testing.T) (*gorm.DB, *gorm.DB, model.AppInstall) {
|
||||
t.Helper()
|
||||
open := func(name string, schema interface{}) *gorm.DB {
|
||||
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), name)), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(schema); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
sqlDB, _ := db.DB()
|
||||
if sqlDB != nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
})
|
||||
return db
|
||||
}
|
||||
appDB := open("apps.db", &model.AppInstall{})
|
||||
taskDB := open("tasks.db", &model.Task{})
|
||||
install := model.AppInstall{
|
||||
Name: "gb10-ds-vision", AppId: 1, AppDetailId: 1,
|
||||
Version: "nvidia-gb10-dspark-0.1.1", ContainerName: "vllm-dspark-1", ServiceName: "vllm-dspark",
|
||||
Status: constant.StatusStarting, Env: `{"MODEL_DIR":"/models/vision"}`,
|
||||
}
|
||||
if err := appDB.Omit(clause.Associations).Create(&install).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return appDB, taskDB, install
|
||||
}
|
||||
|
||||
func TestAppInstallFailedTaskRecoversPendingState(t *testing.T) {
|
||||
for _, state := range []string{constant.StatusStarting, constant.StatusWaiting, constant.StatusRestarting, constant.StatusInstalling} {
|
||||
t.Run(state, func(t *testing.T) {
|
||||
appDB, taskDB, install := operationStatusTestDB(t)
|
||||
install.Status = state
|
||||
if err := appDB.Omit(clause.Associations).Save(&install).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task := model.Task{ID: "failed", Type: "App", Operate: "TaskUpdate", ResourceID: install.ID,
|
||||
Status: constant.StatusFailed, ErrorMsg: "model startup script failed", CreatedAt: install.UpdatedAt.Add(time.Second), EndAt: install.UpdatedAt.Add(2 * time.Second)}
|
||||
if err := taskDB.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := reconcileAppInstallTaskFailure(&install, task.Operate, appDB, taskDB); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := constant.StatusUpErr
|
||||
if state == constant.StatusInstalling {
|
||||
want = constant.StatusInstallErr
|
||||
}
|
||||
var stored model.AppInstall
|
||||
if err := appDB.First(&stored, install.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if install.Status != want || stored.Status != want || stored.Message != task.ErrorMsg || stored.Env != install.Env {
|
||||
t.Fatalf("recovered state = %s/%s, message %q, env %q", install.Status, stored.Status, stored.Message, stored.Env)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppInstallPendingStateKeepsActiveAndUnrelatedTasks(t *testing.T) {
|
||||
for _, taskStatus := range []string{constant.StatusExecuting, constant.StatusSuccess, "missing", "previous-failure", "other-resource"} {
|
||||
t.Run(taskStatus, func(t *testing.T) {
|
||||
appDB, taskDB, install := operationStatusTestDB(t)
|
||||
task := model.Task{ID: "task", Type: "App", Operate: "TaskUpdate", ResourceID: install.ID,
|
||||
Status: taskStatus, CreatedAt: install.UpdatedAt.Add(time.Second), EndAt: install.UpdatedAt.Add(2 * time.Second)}
|
||||
if taskStatus == "previous-failure" {
|
||||
task.Status = constant.StatusFailed
|
||||
task.CreatedAt = install.UpdatedAt.Add(-2 * time.Hour)
|
||||
task.EndAt = install.UpdatedAt.Add(-time.Hour)
|
||||
}
|
||||
if taskStatus == "other-resource" {
|
||||
task.Status = constant.StatusFailed
|
||||
task.ResourceID++
|
||||
}
|
||||
if taskStatus != "missing" {
|
||||
if err := taskDB.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := reconcileAppInstallTaskFailure(&install, task.Operate, appDB, taskDB); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if install.Status != constant.StatusStarting {
|
||||
t.Fatalf("unrelated or active task changed pending state to %s", install.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppInstallRecoveryDoesNotOverwriteConcurrentRetry(t *testing.T) {
|
||||
appDB, taskDB, install := operationStatusTestDB(t)
|
||||
task := model.Task{ID: "failed", Type: "App", Operate: "TaskUpdate", ResourceID: install.ID,
|
||||
Status: constant.StatusCanceled, ErrorMsg: "interrupted", CreatedAt: install.UpdatedAt.Add(time.Second), EndAt: install.UpdatedAt.Add(2 * time.Second)}
|
||||
if err := taskDB.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := appDB.Callback().Update().Before("gorm:update").Register("new-operation", func(db *gorm.DB) {
|
||||
if err := db.Exec("UPDATE app_installs SET updated_at = ?, message = ? WHERE id = ?", install.UpdatedAt.Add(time.Hour), "new attempt", install.ID).Error; err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := reconcileAppInstallTaskFailure(&install, task.Operate, appDB, taskDB); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var stored model.AppInstall
|
||||
if err := appDB.First(&stored, install.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stored.Status != constant.StatusStarting || stored.Message != "new attempt" {
|
||||
t.Fatalf("recovery overwrote a newer operation: %s, %s", stored.Status, stored.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppInstallRestartRecoveryCoversLifecycleStates(t *testing.T) {
|
||||
for _, state := range []string{constant.StatusStarting, constant.StatusWaiting, constant.StatusInstalling} {
|
||||
if !appInstallOperationInterruptedOnRestart(state) {
|
||||
t.Errorf("restart recovery omitted %s", state)
|
||||
}
|
||||
}
|
||||
for _, state := range []string{constant.StatusRunning, constant.StatusStopped, constant.StatusRestarting} {
|
||||
if appInstallOperationInterruptedOnRestart(state) {
|
||||
t.Errorf("restart recovery changes a Docker runtime state: %s", state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1406,9 +1406,8 @@ func handleErr(install model.AppInstall, err error, out string) error {
|
||||
}
|
||||
|
||||
func doNotNeedSync(installed model.AppInstall) bool {
|
||||
return installed.Status == constant.StatusInstalling || installed.Status == constant.StatusRebuilding || installed.Status == constant.StatusUpgrading ||
|
||||
installed.Status == constant.StatusSyncing || installed.Status == constant.StatusUninstalling || installed.Status == constant.StatusInstallErr ||
|
||||
installed.Status == constant.StatusStarting || installed.Status == constant.StatusRestarting || installed.Status == constant.StatusWaiting
|
||||
return appInstallOperationPending(installed.Status) || installed.Status == constant.StatusSyncing ||
|
||||
installed.Status == constant.StatusInstallErr || installed.Status == constant.StatusUpgradeErr || installed.Status == constant.StatusUpErr
|
||||
}
|
||||
|
||||
func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) {
|
||||
@@ -1502,6 +1501,11 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
|
||||
}
|
||||
|
||||
for _, installed := range appInstallList {
|
||||
if sync && appInstallOperationPending(installed.Status) {
|
||||
if err := syncAppInstallStatus(&installed, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if updated && ignoreUpdate(installed) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -8,12 +8,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@@ -23,15 +17,12 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
type RedisService struct{}
|
||||
|
||||
// The CLI container is shared by all remote Redis databases on this node.
|
||||
var redisCliInstallMutex sync.Mutex
|
||||
|
||||
type IRedisService interface {
|
||||
UpdateConf(req dto.RedisConfUpdate) error
|
||||
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
||||
@@ -42,7 +33,7 @@ type IRedisService interface {
|
||||
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
||||
|
||||
CheckHasCli() bool
|
||||
InstallCli(req dto.RedisCliInstall) (string, error)
|
||||
InstallCli() error
|
||||
}
|
||||
|
||||
func NewIRedisService() IRedisService {
|
||||
@@ -70,65 +61,30 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
|
||||
}
|
||||
|
||||
func (u *RedisService) CheckHasCli() bool {
|
||||
installed, _ := u.checkCliInstalled()
|
||||
return installed
|
||||
}
|
||||
|
||||
func (u *RedisService) checkCliInstalled() (bool, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false
|
||||
}
|
||||
defer client.Close()
|
||||
info, err := client.ContainerInspect(context.Background(), "1Panel-redis-cli-tools")
|
||||
if dockerclient.IsErrNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
containerLists, err := client.ContainerList(context.Background(), container.ListOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false
|
||||
}
|
||||
return info.ContainerJSONBase != nil && info.State != nil && info.State.Running, nil
|
||||
for _, item := range containerLists {
|
||||
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (string, error) {
|
||||
if !redisCliInstallMutex.TryLock() {
|
||||
return "", buserr.New("TaskIsExecuting")
|
||||
}
|
||||
defer redisCliInstallMutex.Unlock()
|
||||
func (u *RedisService) InstallCli() error {
|
||||
item := dto.ContainerOperate{
|
||||
TaskID: req.TaskID,
|
||||
Name: "1Panel-redis-cli-tools",
|
||||
Image: "redis:7.4.4",
|
||||
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
||||
}
|
||||
running, err := taskRepo.GetFirst(
|
||||
repo.WithByName(task.GetTaskName(item.Name, task.TaskCreate, task.TaskScopeContainer)),
|
||||
repo.WithByType(task.TaskScopeContainer),
|
||||
taskRepo.WithByStatus(constant.StatusExecuting),
|
||||
)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", err
|
||||
}
|
||||
if running.ID != "" {
|
||||
return running.ID, nil
|
||||
}
|
||||
installed, err := u.checkCliInstalled()
|
||||
if err != nil || installed {
|
||||
return "", err
|
||||
}
|
||||
if item.TaskID == "" {
|
||||
item.TaskID = uuid.NewString()
|
||||
}
|
||||
if _, err := taskRepo.GetFirst(taskRepo.WithByID(item.TaskID)); !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", buserr.New("TaskIsExecuting")
|
||||
}
|
||||
if err := NewIContainerService().ContainerCreate(item, true); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return item.TaskID, nil
|
||||
return NewIContainerService().ContainerCreate(item, false)
|
||||
}
|
||||
|
||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||
|
||||
@@ -57,7 +57,10 @@ func (u *TaskLogService) ReadByLine(req request.TaskLogReadReq) (*response.FileL
|
||||
if req.TaskID != "" {
|
||||
opts = append(opts, taskRepo.WithByID(req.TaskID))
|
||||
} else {
|
||||
opts = append(opts, repo.WithOrderRuleBy("created_at", "desc"), repo.WithByType(req.TaskType), taskRepo.WithOperate(req.TaskOperate), taskRepo.WithResourceID(req.ResourceID))
|
||||
opts = append(opts, repo.WithOrderRuleBy("created_at", "desc"), repo.WithByType(req.TaskType), taskRepo.WithResourceID(req.ResourceID))
|
||||
if req.TaskOperate != "" {
|
||||
opts = append(opts, taskRepo.WithOperate(req.TaskOperate))
|
||||
}
|
||||
}
|
||||
taskModel, err := taskRepo.GetFirst(opts...)
|
||||
if err != nil {
|
||||
|
||||
@@ -15092,26 +15092,9 @@ const docTemplate = `{
|
||||
},
|
||||
"/databases/redis/install/cli": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliInstall"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@@ -40166,14 +40149,6 @@ const docTemplate = `{
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"dto.RedisCliInstall": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisConf": {
|
||||
"properties": {
|
||||
"containerName": {
|
||||
|
||||
@@ -15088,26 +15088,9 @@
|
||||
},
|
||||
"/databases/redis/install/cli": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"in": "body",
|
||||
"name": "request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliInstall"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@@ -40162,14 +40145,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"dto.RedisCliInstall": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisConf": {
|
||||
"properties": {
|
||||
"containerName": {
|
||||
|
||||
@@ -193,10 +193,8 @@ export const redisPersistenceConf = (type: string, database: string) => {
|
||||
export const checkRedisCli = () => {
|
||||
return http.get<boolean>(`/databases/redis/check`);
|
||||
};
|
||||
export const installRedisCli = (taskID: string, node: string) => {
|
||||
return http.post<string>(`/databases/redis/install/cli?operateNode=${encodeURIComponent(node)}`, {
|
||||
taskID,
|
||||
});
|
||||
export const installRedisCli = () => {
|
||||
return http.post(`/databases/redis/install/cli`, {}, TimeoutEnum.T_5M);
|
||||
};
|
||||
export const changeRedisPassword = (database: string, password: string) => {
|
||||
if (password) {
|
||||
|
||||
@@ -1065,6 +1065,13 @@ const message = {
|
||||
whiteListHelper: 'Restrict access to only IPs in the whitelist',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI Gateway supports standalone deployment',
|
||||
deploymentNoticeDescription:
|
||||
'AI Gateway now supports standalone deployment. The built-in AI Gateway in 1Panel will no longer receive updates. We recommend installing the standalone version from the App Store. You can also continue using the current version.',
|
||||
deploymentNoticeContinue: 'Understood, continue using',
|
||||
deploymentNoticeAppStore: 'Go to App Store',
|
||||
deploymentNoticeNoAppPermission:
|
||||
'You do not have access to the App Store. Contact your administrator to install the standalone version.',
|
||||
title: 'AI Gateway',
|
||||
apiReference: 'API Reference',
|
||||
notInstalled: 'Not installed',
|
||||
|
||||
@@ -1069,6 +1069,13 @@ const message = {
|
||||
whiteListHelper: 'Restringir el acceso solo a las IP incluidas en la lista blanca',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI Gateway admite la implementación independiente',
|
||||
deploymentNoticeDescription:
|
||||
'AI Gateway ahora admite la implementación independiente. AI Gateway integrado en 1Panel dejará de recibir actualizaciones. Recomendamos instalar la versión independiente desde la tienda de aplicaciones. También puede seguir usando la versión actual.',
|
||||
deploymentNoticeContinue: 'Entendido, seguir usando',
|
||||
deploymentNoticeAppStore: 'Ir a la tienda de aplicaciones',
|
||||
deploymentNoticeNoAppPermission:
|
||||
'No tiene acceso a la tienda de aplicaciones. Contacte con su administrador para instalar la versión independiente.',
|
||||
title: 'Gateway de IA',
|
||||
apiReference: 'Referencia de API',
|
||||
notInstalled: 'No instalado',
|
||||
|
||||
@@ -1051,6 +1051,13 @@ const message = {
|
||||
whiteListHelper: 'دسترسی را فقط به IPهای موجود در لیست سفید محدود کنید',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'درگاه هوش مصنوعی از استقرار مستقل پشتیبانی میکند',
|
||||
deploymentNoticeDescription:
|
||||
'درگاه هوش مصنوعی اکنون از استقرار مستقل پشتیبانی میکند. درگاه هوش مصنوعی داخلی 1Panel دیگر بهروزرسانی دریافت نخواهد کرد. توصیه میکنیم نسخه مستقل را از فروشگاه برنامه نصب کنید. همچنین میتوانید به استفاده از نسخه فعلی ادامه دهید.',
|
||||
deploymentNoticeContinue: 'متوجه شدم، ادامه استفاده',
|
||||
deploymentNoticeAppStore: 'رفتن به فروشگاه برنامه',
|
||||
deploymentNoticeNoAppPermission:
|
||||
'شما به فروشگاه برنامه دسترسی ندارید. برای نصب نسخه مستقل با مدیر تماس بگیرید.',
|
||||
title: 'دروازه AI',
|
||||
apiReference: 'راهنمای API',
|
||||
notInstalled: 'نصب نشده',
|
||||
|
||||
@@ -1054,6 +1054,13 @@ const message = {
|
||||
whiteListHelper: 'ホワイトリスト内のIPのみアクセスを許可する',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI ゲートウェイの独立したデプロイに対応',
|
||||
deploymentNoticeDescription:
|
||||
'AI ゲートウェイは独立したデプロイに対応しました。1Panel 内蔵の AI ゲートウェイは今後更新されません。アプリストアから独立版をインストールすることをお勧めします。現在のバージョンを引き続き使用することもできます。',
|
||||
deploymentNoticeContinue: '了解して使用を継続',
|
||||
deploymentNoticeAppStore: 'アプリストアへ',
|
||||
deploymentNoticeNoAppPermission:
|
||||
'アプリストアへのアクセス権限がありません。管理者に独立版のインストールを依頼してください。',
|
||||
title: 'AI ゲートウェイ',
|
||||
apiReference: 'API リファレンス',
|
||||
notInstalled: '未インストール',
|
||||
|
||||
@@ -1043,6 +1043,12 @@ const message = {
|
||||
whiteListHelper: '화이트리스트에 있는 IP만 접근 허용',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI 게이트웨이 독립 배포 지원',
|
||||
deploymentNoticeDescription:
|
||||
'AI 게이트웨이가 독립 배포를 지원합니다. 1Panel 내장 AI 게이트웨이는 더 이상 업데이트되지 않습니다. 앱 스토어에서 독립 버전을 설치하는 것을 권장합니다. 현재 버전을 계속 사용할 수도 있습니다.',
|
||||
deploymentNoticeContinue: '확인, 계속 사용',
|
||||
deploymentNoticeAppStore: '앱 스토어로 이동',
|
||||
deploymentNoticeNoAppPermission: '앱 스토어 접근 권한이 없습니다. 관리자에게 독립 버전 설치를 요청하세요.',
|
||||
title: 'AI 게이트웨이',
|
||||
apiReference: 'API 안내',
|
||||
notInstalled: '설치되지 않음',
|
||||
|
||||
@@ -1040,6 +1040,13 @@ const message = {
|
||||
whiteListHelper: 'ຈຳກັດການເຂົ້າເຖິງສະເພາະ IP ໃນລາຍຊື່ທີ່ອະນຸຍາດ',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI Gateway ຮອງຮັບການຕິດຕັ້ງແບບແຍກຕ່າງຫາກ',
|
||||
deploymentNoticeDescription:
|
||||
'AI Gateway ຮອງຮັບການຕິດຕັ້ງແບບແຍກຕ່າງຫາກແລ້ວ. AI Gateway ທີ່ມາພ້ອມກັບ 1Panel ຈະບໍ່ໄດ້ຮັບການອັບເດດອີກ. ແນະນຳໃຫ້ຕິດຕັ້ງລຸ້ນແຍກຕ່າງຫາກຈາກຮ້ານແອັບ. ທ່ານຍັງສາມາດໃຊ້ລຸ້ນປັດຈຸບັນຕໍ່ໄດ້.',
|
||||
deploymentNoticeContinue: 'ຮັບຊາບ, ໃຊ້ຕໍ່',
|
||||
deploymentNoticeAppStore: 'ໄປທີ່ຮ້ານແອັບ',
|
||||
deploymentNoticeNoAppPermission:
|
||||
'ທ່ານບໍ່ມີສິດເຂົ້າເຖິງຮ້ານແອັບ. ກະລຸນາຕິດຕໍ່ຜູ້ດູແລເພື່ອຕິດຕັ້ງລຸ້ນແຍກຕ່າງຫາກ.',
|
||||
title: 'AI Gateway',
|
||||
apiReference: 'ຄູ່ມື API',
|
||||
notInstalled: 'ຍັງບໍ່ໄດ້ຕິດຕັ້ງ',
|
||||
|
||||
@@ -1077,6 +1077,13 @@ const message = {
|
||||
whiteListHelper: 'Hadkan akses kepada hanya IP dalam senarai putih',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI Gateway menyokong penggunaan kendiri',
|
||||
deploymentNoticeDescription:
|
||||
'AI Gateway kini menyokong penggunaan kendiri. AI Gateway terbina dalam 1Panel tidak lagi akan menerima kemas kini. Kami mengesyorkan pemasangan versi kendiri daripada gedung aplikasi. Anda juga boleh terus menggunakan versi semasa.',
|
||||
deploymentNoticeContinue: 'Faham, terus gunakan',
|
||||
deploymentNoticeAppStore: 'Pergi ke gedung aplikasi',
|
||||
deploymentNoticeNoAppPermission:
|
||||
'Anda tidak mempunyai akses ke gedung aplikasi. Hubungi pentadbir untuk memasang versi kendiri.',
|
||||
title: 'Gateway AI',
|
||||
apiReference: 'Rujukan API',
|
||||
notInstalled: 'Belum dipasang',
|
||||
|
||||
@@ -1072,6 +1072,13 @@ const message = {
|
||||
'Após ativar, você pode desativar o acesso externo à porta em Loja de Apps - Instalado - Ollama - Parâmetros para melhorar a segurança.',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI Gateway permite implantação independente',
|
||||
deploymentNoticeDescription:
|
||||
'O AI Gateway agora permite implantação independente. O AI Gateway integrado ao 1Panel não receberá mais atualizações. Recomendamos instalar a versão independente pela loja de aplicativos. Você também pode continuar usando a versão atual.',
|
||||
deploymentNoticeContinue: 'Entendido, continuar usando',
|
||||
deploymentNoticeAppStore: 'Ir à loja de aplicativos',
|
||||
deploymentNoticeNoAppPermission:
|
||||
'Você não tem acesso à loja de aplicativos. Contate o administrador para instalar a versão independente.',
|
||||
title: 'Gateway de IA',
|
||||
apiReference: 'Referência da API',
|
||||
notInstalled: 'Não instalado',
|
||||
|
||||
@@ -1065,6 +1065,13 @@ const message = {
|
||||
whiteListHelper: 'Ограничить доступ только для IP-адресов из белого списка',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI Gateway поддерживает отдельное развёртывание',
|
||||
deploymentNoticeDescription:
|
||||
'AI Gateway теперь поддерживает отдельное развёртывание. Встроенный AI Gateway в 1Panel больше не будет получать обновления. Рекомендуем установить отдельную версию из магазина приложений. Вы также можете продолжить использовать текущую версию.',
|
||||
deploymentNoticeContinue: 'Понятно, продолжить использование',
|
||||
deploymentNoticeAppStore: 'Перейти в магазин приложений',
|
||||
deploymentNoticeNoAppPermission:
|
||||
'У вас нет доступа к магазину приложений. Обратитесь к администратору для установки отдельной версии.',
|
||||
title: 'AI-шлюз',
|
||||
apiReference: 'Справочник API',
|
||||
notInstalled: 'Не установлен',
|
||||
|
||||
@@ -1076,6 +1076,13 @@ const message = {
|
||||
whiteListHelper: 'Erişimi yalnızca beyaz listedeki IPlerle sınırlayın',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI Gateway bağımsız dağıtımı destekliyor',
|
||||
deploymentNoticeDescription:
|
||||
'AI Gateway artık bağımsız dağıtımı destekliyor. 1Panel içindeki AI Gateway artık güncelleme almayacak. Uygulama mağazasından bağımsız sürümü yüklemenizi öneririz. Mevcut sürümü kullanmaya da devam edebilirsiniz.',
|
||||
deploymentNoticeContinue: 'Anladım, kullanmaya devam et',
|
||||
deploymentNoticeAppStore: 'Uygulama mağazasına git',
|
||||
deploymentNoticeNoAppPermission:
|
||||
'Uygulama mağazasına erişim izniniz yok. Bağımsız sürümü yüklemek için yöneticinizle iletişime geçin.',
|
||||
title: 'AI Ağ Geçidi',
|
||||
apiReference: 'API Referansı',
|
||||
notInstalled: 'Kurulu değil',
|
||||
|
||||
@@ -1006,6 +1006,12 @@ const message = {
|
||||
whiteListHelper: '限制僅白名單中的 IP 可瀏覽',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI 網關已支援獨立部署',
|
||||
deploymentNoticeDescription:
|
||||
'AI 網關現已支援獨立部署,1Panel 內建 AI 網關後續將不再更新。建議前往應用程式商店安裝獨立版本,您也可以繼續使用目前版本。',
|
||||
deploymentNoticeContinue: '已知悉,繼續使用',
|
||||
deploymentNoticeAppStore: '前往應用程式商店',
|
||||
deploymentNoticeNoAppPermission: '您沒有應用程式商店存取權限,請聯絡管理員安裝獨立版本。',
|
||||
title: 'AI 閘道',
|
||||
apiReference: '介面說明',
|
||||
notInstalled: '未安裝',
|
||||
|
||||
@@ -1019,6 +1019,12 @@ const message = {
|
||||
whiteListHelper: '限制仅白名单中的 IP 可访问',
|
||||
},
|
||||
aiProxy: {
|
||||
deploymentNoticeTitle: 'AI 网关已支持独立部署',
|
||||
deploymentNoticeDescription:
|
||||
'AI 网关现已支持独立部署,1Panel 内置 AI 网关后续将不再更新。建议前往应用商店安装独立版本,您也可以继续使用当前版本。',
|
||||
deploymentNoticeContinue: '已知晓,继续使用',
|
||||
deploymentNoticeAppStore: '前往应用商店',
|
||||
deploymentNoticeNoAppPermission: '您没有应用商店访问权限,请联系管理员安装独立版本。',
|
||||
title: 'AI 网关',
|
||||
apiReference: '接口说明',
|
||||
notInstalled: '未安装',
|
||||
|
||||
@@ -81,13 +81,7 @@
|
||||
:style="{ height: `calc(100vh - ${loadHeight()})`, 'background-color': '#000' }"
|
||||
:description="loadErrMsg()"
|
||||
>
|
||||
<el-button
|
||||
v-if="currentDB.from === 'remote'"
|
||||
v-permission
|
||||
type="primary"
|
||||
:loading="cliSubmitting"
|
||||
@click="installCli"
|
||||
>
|
||||
<el-button v-if="currentDB.from === 'remote'" v-permission type="primary" @click="installCli">
|
||||
{{ $t('commons.button.enable') }}
|
||||
</el-button>
|
||||
</el-empty>
|
||||
@@ -140,7 +134,6 @@
|
||||
</DialogPro>
|
||||
|
||||
<QuickCmd ref="dialogQuickCmdRef" @reload="loadQuickCmd" />
|
||||
<TaskLog ref="cliTaskLogRef" @close="checkCliValid" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -155,14 +148,12 @@ import { checkAppInstalled } from '@/api/modules/app';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { listDatabases, checkRedisCli, installRedisCli } from '@/api/modules/database';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
import { getCommandList } from '@/api/modules/command';
|
||||
import { routerToName, routerToNameWithQuery } from '@/utils/router';
|
||||
import { useOperateNodeContext } from '@/composables/useOperateNodeContext';
|
||||
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { newUUID } from '@/utils/id';
|
||||
|
||||
const { currentNode, currentRedisDB, openMenuTabs } = useGlobalStore();
|
||||
useOperateNodeContext(currentNode);
|
||||
|
||||
@@ -179,11 +170,7 @@ const appStatusRef = ref();
|
||||
|
||||
const open = ref(false);
|
||||
|
||||
const redisCliExist = ref(false);
|
||||
const cliSubmitting = ref(false);
|
||||
const cliTaskLogRef = ref<InstanceType<typeof TaskLog>>();
|
||||
const operateNode = currentNode.value;
|
||||
let disposed = false;
|
||||
const redisCliExist = ref();
|
||||
|
||||
const appKey = ref('redis');
|
||||
const appName = ref();
|
||||
@@ -263,7 +250,6 @@ const changeDatabase = async () => {
|
||||
const loadDBOptions = async () => {
|
||||
try {
|
||||
const res = await listDatabases('redis,redis-cluster');
|
||||
if (disposed || operateNode !== currentNode.value) return;
|
||||
let datas = res.data || [];
|
||||
dbOptionsLocal.value = [];
|
||||
dbOptionsRemote.value = [];
|
||||
@@ -300,7 +286,7 @@ const loadDBOptions = async () => {
|
||||
reOpenTerminal();
|
||||
}
|
||||
} finally {
|
||||
if (!disposed && operateNode === currentNode.value) isLoaded.value = true;
|
||||
isLoaded.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -315,9 +301,6 @@ const reOpenTerminal = async () => {
|
||||
};
|
||||
|
||||
const initTerminal = async () => {
|
||||
if (disposed || !currentDB.value) return;
|
||||
const database = currentDBName.value;
|
||||
const isCurrent = () => !disposed && operateNode === currentNode.value && database === currentDBName.value;
|
||||
loading.value = true;
|
||||
if (currentDB.value.from === 'remote') {
|
||||
if (!redisCliExist.value) {
|
||||
@@ -328,10 +311,9 @@ const initTerminal = async () => {
|
||||
loading.value = false;
|
||||
redisIsExist.value = true;
|
||||
nextTick(() => {
|
||||
if (!isCurrent()) return;
|
||||
terminalShow.value = true;
|
||||
redisStatus.value = 'Running';
|
||||
terminalRef.value?.acceptParams({
|
||||
terminalRef.value.acceptParams({
|
||||
endpoint: '/api/v2/hosts/terminal/container',
|
||||
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||
error: '',
|
||||
@@ -343,15 +325,13 @@ const initTerminal = async () => {
|
||||
}
|
||||
await checkAppInstalled(currentDB.value.type, currentDBName.value)
|
||||
.then((res) => {
|
||||
if (!isCurrent()) return;
|
||||
redisIsExist.value = res.data.isExist;
|
||||
redisStatus.value = res.data.status;
|
||||
loading.value = false;
|
||||
nextTick(() => {
|
||||
if (!isCurrent()) return;
|
||||
if (res.data.status === 'Running') {
|
||||
terminalShow.value = true;
|
||||
terminalRef.value?.acceptParams({
|
||||
terminalRef.value.acceptParams({
|
||||
endpoint: '/api/v2/hosts/terminal/container',
|
||||
args: `source=${currentDB.value.type}&name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||
error: '',
|
||||
@@ -362,7 +342,6 @@ const initTerminal = async () => {
|
||||
isRefresh.value = !isRefresh.value;
|
||||
})
|
||||
.catch(() => {
|
||||
if (!isCurrent()) return;
|
||||
closeTerminal(false);
|
||||
loading.value = false;
|
||||
});
|
||||
@@ -374,30 +353,27 @@ const closeTerminal = async (isKeepShow: boolean) => {
|
||||
};
|
||||
|
||||
const checkCliValid = async () => {
|
||||
if (disposed || operateNode !== currentNode.value) return;
|
||||
try {
|
||||
const res = await checkRedisCli();
|
||||
if (disposed || operateNode !== currentNode.value) return;
|
||||
redisCliExist.value = res.data;
|
||||
} finally {
|
||||
if (!disposed && operateNode === currentNode.value) await loadDBOptions();
|
||||
}
|
||||
await checkRedisCli()
|
||||
.then((res) => {
|
||||
redisCliExist.value = res.data;
|
||||
loadDBOptions();
|
||||
})
|
||||
.catch(() => {
|
||||
loadDBOptions();
|
||||
});
|
||||
};
|
||||
|
||||
const installCli = async () => {
|
||||
if (cliSubmitting.value) return;
|
||||
cliSubmitting.value = true;
|
||||
try {
|
||||
const res = await installRedisCli(newUUID(), operateNode);
|
||||
if (disposed || operateNode !== currentNode.value) return;
|
||||
if (res.data) {
|
||||
cliTaskLogRef.value?.openWithTaskID(res.data, true, operateNode);
|
||||
} else {
|
||||
await checkCliValid();
|
||||
}
|
||||
} finally {
|
||||
cliSubmitting.value = false;
|
||||
}
|
||||
loading.value = true;
|
||||
await installRedisCli()
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
redisCliExist.value = true;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
reOpenTerminal();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const loadQuickCmd = async () => {
|
||||
@@ -427,7 +403,6 @@ const onAfter = () => {
|
||||
initTerminal();
|
||||
};
|
||||
onBeforeUnmount(() => {
|
||||
disposed = true;
|
||||
closeTerminal(false);
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user