mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 08:00:34 +00:00
feat(redis): enable remote terminal through tracked background tasks
This commit is contained in:
@@ -88,7 +88,7 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
|
||||
// @Summary Install redis-cli
|
||||
// @Accept json
|
||||
// @Param request body dto.RedisCliInstall true "request"
|
||||
// @Success 200 {object} dto.RedisCliStatus
|
||||
// @Success 200 {string} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/redis/install/cli [post]
|
||||
@@ -105,21 +105,6 @@ func (b *BaseApi) InstallCli(c *gin.Context) {
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database Redis
|
||||
// @Summary Load redis-cli installation status
|
||||
// @Success 200 {object} dto.RedisCliStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /databases/redis/cli/status [get]
|
||||
func (b *BaseApi) LoadRedisCliStatus(c *gin.Context) {
|
||||
data, err := redisService.LoadCliStatus()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Database Redis
|
||||
// @Summary Update redis conf
|
||||
// @Accept json
|
||||
|
||||
@@ -26,13 +26,6 @@ type RedisCliInstall struct {
|
||||
TaskID string `json:"taskID" validate:"omitempty,uuid"`
|
||||
}
|
||||
|
||||
type RedisCliStatus struct {
|
||||
Installed bool `json:"installed"`
|
||||
TaskID string `json:"taskID"`
|
||||
Status string `json:"status"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
|
||||
// mysql
|
||||
type MysqlDBSearch struct {
|
||||
PageInfo
|
||||
|
||||
@@ -29,8 +29,6 @@ import (
|
||||
|
||||
type RedisService struct{}
|
||||
|
||||
const redisCliTaskName = "RedisCliEnable"
|
||||
|
||||
// The CLI container is shared by all remote Redis databases on this node.
|
||||
var redisCliInstallMutex sync.Mutex
|
||||
|
||||
@@ -44,8 +42,7 @@ type IRedisService interface {
|
||||
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
||||
|
||||
CheckHasCli() bool
|
||||
InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error)
|
||||
LoadCliStatus() (*dto.RedisCliStatus, error)
|
||||
InstallCli(req dto.RedisCliInstall) (string, error)
|
||||
}
|
||||
|
||||
func NewIRedisService() IRedisService {
|
||||
@@ -93,55 +90,45 @@ func (u *RedisService) checkCliInstalled() (bool, error) {
|
||||
return info.ContainerJSONBase != nil && info.State != nil && info.State.Running, nil
|
||||
}
|
||||
|
||||
func (u *RedisService) LoadCliStatus() (*dto.RedisCliStatus, error) {
|
||||
latest, err := taskRepo.GetFirst(repo.WithByName(redisCliTaskName), repo.WithByType(task.TaskScopeContainer), repo.WithOrderDesc("created_at"))
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
installed, err := u.checkCliInstalled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.RedisCliStatus{
|
||||
Installed: installed,
|
||||
TaskID: latest.ID,
|
||||
Status: latest.Status,
|
||||
ErrorMsg: latest.ErrorMsg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error) {
|
||||
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (string, error) {
|
||||
if !redisCliInstallMutex.TryLock() {
|
||||
return nil, buserr.New("TaskIsExecuting")
|
||||
return "", buserr.New("TaskIsExecuting")
|
||||
}
|
||||
defer redisCliInstallMutex.Unlock()
|
||||
status, err := u.LoadCliStatus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status.Status == constant.StatusExecuting || status.Installed {
|
||||
return status, nil
|
||||
}
|
||||
if req.TaskID == "" {
|
||||
req.TaskID = uuid.NewString()
|
||||
}
|
||||
// Never reuse an existing task ID: doing so would truncate its log.
|
||||
if _, err := taskRepo.GetFirst(taskRepo.WithByID(req.TaskID)); !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, buserr.New("TaskIsExecuting")
|
||||
}
|
||||
item := dto.ContainerOperate{
|
||||
TaskID: req.TaskID,
|
||||
Name: "1Panel-redis-cli-tools",
|
||||
Image: "redis:7.4.4",
|
||||
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
||||
}
|
||||
if err := (&ContainerService{}).containerCreate(item, true, redisCliTaskName); err != nil {
|
||||
return nil, err
|
||||
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
|
||||
}
|
||||
return &dto.RedisCliStatus{TaskID: req.TaskID, Status: constant.StatusExecuting}, nil
|
||||
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
|
||||
}
|
||||
|
||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||
|
||||
@@ -15134,7 +15134,7 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -40201,23 +40201,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisCliStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"errorMsg": {
|
||||
"type": "string"
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisConf": {
|
||||
"properties": {
|
||||
"containerName": {
|
||||
|
||||
@@ -15130,7 +15130,7 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -40197,23 +40197,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisCliStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"errorMsg": {
|
||||
"type": "string"
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"taskID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.RedisConf": {
|
||||
"properties": {
|
||||
"containerName": {
|
||||
|
||||
@@ -198,7 +198,7 @@ export const loadRedisCliStatus = (node: string) => {
|
||||
);
|
||||
};
|
||||
export const installRedisCli = (taskID: string, node: string) => {
|
||||
return http.post<Database.RedisCliStatus>(`/databases/redis/install/cli?operateNode=${encodeURIComponent(node)}`, {
|
||||
return http.post<string>(`/databases/redis/install/cli?operateNode=${encodeURIComponent(node)}`, {
|
||||
taskID,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -86,28 +86,9 @@
|
||||
v-permission
|
||||
type="primary"
|
||||
:loading="cliSubmitting"
|
||||
:disabled="(!cliStatusLoaded || cliInstalling) && !cliPollFailed"
|
||||
@click="installCli"
|
||||
>
|
||||
{{
|
||||
cliPollFailed
|
||||
? $t('commons.button.retry')
|
||||
: cliInstalling
|
||||
? $t('database.redisCliEnabling')
|
||||
: $t('commons.button.enable')
|
||||
}}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="
|
||||
currentDB.from === 'remote' &&
|
||||
cliTaskID &&
|
||||
(cliInstalling || redisCliExist || cliTaskStatus === 'Failed')
|
||||
"
|
||||
link
|
||||
type="primary"
|
||||
@click="openCliTaskLog"
|
||||
>
|
||||
{{ $t('commons.button.log') }}
|
||||
{{ $t('commons.button.enable') }}
|
||||
</el-button>
|
||||
</el-empty>
|
||||
<div>
|
||||
@@ -159,7 +140,7 @@
|
||||
</DialogPro>
|
||||
|
||||
<QuickCmd ref="dialogQuickCmdRef" @reload="loadQuickCmd" />
|
||||
<TaskLog ref="cliTaskLogRef" />
|
||||
<TaskLog ref="cliTaskLogRef" @close="checkCliValid" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -174,14 +155,12 @@ import { checkAppInstalled } from '@/api/modules/app';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { listDatabases, loadRedisCliStatus, installRedisCli } from '@/api/modules/database';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import { MsgError, 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 bus from '@/global/bus';
|
||||
import { newUUID } from '@/utils/id';
|
||||
|
||||
const { currentNode, currentRedisDB, openMenuTabs } = useGlobalStore();
|
||||
@@ -202,21 +181,8 @@ const open = ref(false);
|
||||
|
||||
const redisCliExist = ref(false);
|
||||
const cliSubmitting = ref(false);
|
||||
const cliStatusLoaded = ref(false);
|
||||
const cliPollFailed = ref(false);
|
||||
const cliTaskID = ref('');
|
||||
const cliTaskStatus = ref('');
|
||||
const cliTaskLogRef = ref<InstanceType<typeof TaskLog>>();
|
||||
const cliInstalling = computed(() => cliTaskStatus.value === 'Executing');
|
||||
const cliStatusText = computed(() => {
|
||||
if (cliPollFailed.value) return i18n.global.t('commons.res.commonError');
|
||||
if (cliInstalling.value) return i18n.global.t('database.redisCliEnabling');
|
||||
if (cliTaskStatus.value === 'Failed') return i18n.global.t('database.redisCliEnableFailed');
|
||||
return redisCliExist.value ? i18n.global.t('database.redisCliEnabled') : i18n.global.t('database.redisCliHelper');
|
||||
});
|
||||
let cliPollTimer: ReturnType<typeof setTimeout>;
|
||||
let cliGeneration = 0;
|
||||
let cliPollFailures = 0;
|
||||
const operateNode = currentNode.value;
|
||||
let disposed = false;
|
||||
|
||||
const appKey = ref('redis');
|
||||
@@ -298,7 +264,7 @@ const loadDBOptions = async () => {
|
||||
const generation = cliGeneration;
|
||||
try {
|
||||
const res = await listDatabases('redis,redis-cluster');
|
||||
if (disposed || generation !== cliGeneration) return;
|
||||
if (disposed || operateNode !== currentNode.value) return;
|
||||
let datas = res.data || [];
|
||||
dbOptionsLocal.value = [];
|
||||
dbOptionsRemote.value = [];
|
||||
@@ -335,7 +301,7 @@ const loadDBOptions = async () => {
|
||||
reOpenTerminal();
|
||||
}
|
||||
} finally {
|
||||
if (!disposed && generation === cliGeneration) isLoaded.value = true;
|
||||
if (!disposed && operateNode === currentNode.value) isLoaded.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -351,9 +317,8 @@ const reOpenTerminal = async () => {
|
||||
|
||||
const initTerminal = async () => {
|
||||
if (disposed || !currentDB.value) return;
|
||||
const generation = cliGeneration;
|
||||
const database = currentDBName.value;
|
||||
const isCurrent = () => !disposed && generation === cliGeneration && database === currentDBName.value;
|
||||
const isCurrent = () => !disposed && operateNode === currentNode.value && database === currentDBName.value;
|
||||
loading.value = true;
|
||||
if (currentDB.value.from === 'remote') {
|
||||
if (!redisCliExist.value) {
|
||||
@@ -461,44 +426,29 @@ const refreshCliStatus = async (generation: number) => {
|
||||
};
|
||||
|
||||
const checkCliValid = async () => {
|
||||
const generation = cliGeneration;
|
||||
await refreshCliStatus(generation);
|
||||
if (!disposed && generation === cliGeneration) await loadDBOptions();
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
const installCli = async () => {
|
||||
if (cliSubmitting.value) return;
|
||||
if (cliPollFailed.value) {
|
||||
cliPollFailures = 0;
|
||||
cliPollFailed.value = false;
|
||||
cliSubmitting.value = true;
|
||||
const generation = cliGeneration;
|
||||
try {
|
||||
await refreshCliStatus(generation);
|
||||
} finally {
|
||||
if (!disposed && generation === cliGeneration) cliSubmitting.value = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (cliInstalling.value || !cliStatusLoaded.value) return;
|
||||
const generation = cliGeneration;
|
||||
const node = currentNode.value;
|
||||
cliSubmitting.value = true;
|
||||
clearTimeout(cliPollTimer);
|
||||
try {
|
||||
const res = await installRedisCli(newUUID(), node);
|
||||
if (disposed || generation !== cliGeneration) return;
|
||||
applyCliStatus(res.data);
|
||||
if (cliInstalling.value) {
|
||||
MsgSuccess(i18n.global.t('database.redisCliTaskSubmitted'));
|
||||
openCliTaskLog();
|
||||
scheduleCliPoll(generation);
|
||||
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();
|
||||
}
|
||||
} catch {
|
||||
// The server may have accepted the task even if the response was lost.
|
||||
if (!disposed && generation === cliGeneration) await refreshCliStatus(generation);
|
||||
} finally {
|
||||
if (!disposed && generation === cliGeneration) cliSubmitting.value = false;
|
||||
cliSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -551,8 +501,6 @@ const onAfter = () => {
|
||||
};
|
||||
onBeforeUnmount(() => {
|
||||
disposed = true;
|
||||
cliGeneration++;
|
||||
clearTimeout(cliPollTimer);
|
||||
closeTerminal(false);
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user