Compare commits

...
7 changed files with 183 additions and 52 deletions
+10 -4
View File
@@ -86,17 +86,23 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
// @Tags Database Redis // @Tags Database Redis
// @Summary Install redis-cli // @Summary Install redis-cli
// @Success 200 // @Accept json
// @Param request body dto.RedisCliInstall true "request"
// @Success 200 {string} string
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /databases/redis/install/cli [post] // @Router /databases/redis/install/cli [post]
func (b *BaseApi) InstallCli(c *gin.Context) { func (b *BaseApi) InstallCli(c *gin.Context) {
if err := redisService.InstallCli(); err != nil { var req dto.RedisCliInstall
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := redisService.InstallCli(req)
if err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
} }
helper.SuccessWithData(c, data)
helper.Success(c)
} }
// @Tags Database Redis // @Tags Database Redis
+4
View File
@@ -22,6 +22,10 @@ type DBBaseInfo struct {
Port int64 `json:"port"` Port int64 `json:"port"`
} }
type RedisCliInstall struct {
TaskID string `json:"taskID" validate:"omitempty,uuid"`
}
// mysql // mysql
type MysqlDBSearch struct { type MysqlDBSearch struct {
PageInfo PageInfo
+63 -19
View File
@@ -8,6 +8,12 @@ import (
"os" "os"
"os/exec" "os/exec"
"strings" "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/app/repo"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
@@ -17,12 +23,15 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/compose" "github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker" "github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt" "github.com/1Panel-dev/1Panel/agent/utils/encrypt"
"github.com/docker/docker/api/types/container" dockerclient "github.com/docker/docker/client"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
) )
type RedisService struct{} type RedisService struct{}
// The CLI container is shared by all remote Redis databases on this node.
var redisCliInstallMutex sync.Mutex
type IRedisService interface { type IRedisService interface {
UpdateConf(req dto.RedisConfUpdate) error UpdateConf(req dto.RedisConfUpdate) error
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
@@ -33,7 +42,7 @@ type IRedisService interface {
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error) LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
CheckHasCli() bool CheckHasCli() bool
InstallCli() error InstallCli(req dto.RedisCliInstall) (string, error)
} }
func NewIRedisService() IRedisService { func NewIRedisService() IRedisService {
@@ -61,30 +70,65 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
} }
func (u *RedisService) CheckHasCli() bool { func (u *RedisService) CheckHasCli() bool {
client, err := docker.NewDockerClient() installed, _ := u.checkCliInstalled()
if err != nil { return installed
return false
}
defer client.Close()
containerLists, err := client.ContainerList(context.Background(), container.ListOptions{})
if err != nil {
return false
}
for _, item := range containerLists {
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
return true
}
}
return false
} }
func (u *RedisService) InstallCli() error { func (u *RedisService) checkCliInstalled() (bool, error) {
client, err := docker.NewDockerClient()
if err != nil {
return false, err
}
defer client.Close()
info, err := client.ContainerInspect(context.Background(), "1Panel-redis-cli-tools")
if dockerclient.IsErrNotFound(err) {
return false, nil
}
if err != nil {
return false, err
}
return info.ContainerJSONBase != nil && info.State != nil && info.State.Running, nil
}
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (string, error) {
if !redisCliInstallMutex.TryLock() {
return "", buserr.New("TaskIsExecuting")
}
defer redisCliInstallMutex.Unlock()
item := dto.ContainerOperate{ item := dto.ContainerOperate{
TaskID: req.TaskID,
Name: "1Panel-redis-cli-tools", Name: "1Panel-redis-cli-tools",
Image: "redis:7.4.4", Image: "redis:7.4.4",
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}}, Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
} }
return NewIContainerService().ContainerCreate(item, false) 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
} }
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error { func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
+26 -1
View File
@@ -15092,9 +15092,26 @@ const docTemplate = `{
}, },
"/databases/redis/install/cli": { "/databases/redis/install/cli": {
"post": { "post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.RedisCliInstall"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK",
"schema": {
"type": "string"
}
} }
}, },
"security": [ "security": [
@@ -40152,6 +40169,14 @@ const docTemplate = `{
], ],
"type": "object" "type": "object"
}, },
"dto.RedisCliInstall": {
"type": "object",
"properties": {
"taskID": {
"type": "string"
}
}
},
"dto.RedisConf": { "dto.RedisConf": {
"properties": { "properties": {
"containerName": { "containerName": {
+26 -1
View File
@@ -15088,9 +15088,26 @@
}, },
"/databases/redis/install/cli": { "/databases/redis/install/cli": {
"post": { "post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.RedisCliInstall"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "OK" "description": "OK",
"schema": {
"type": "string"
}
} }
}, },
"security": [ "security": [
@@ -40148,6 +40165,14 @@
], ],
"type": "object" "type": "object"
}, },
"dto.RedisCliInstall": {
"type": "object",
"properties": {
"taskID": {
"type": "string"
}
}
},
"dto.RedisConf": { "dto.RedisConf": {
"properties": { "properties": {
"containerName": { "containerName": {
+4 -2
View File
@@ -193,8 +193,10 @@ export const redisPersistenceConf = (type: string, database: string) => {
export const checkRedisCli = () => { export const checkRedisCli = () => {
return http.get<boolean>(`/databases/redis/check`); return http.get<boolean>(`/databases/redis/check`);
}; };
export const installRedisCli = () => { export const installRedisCli = (taskID: string, node: string) => {
return http.post(`/databases/redis/install/cli`, {}, TimeoutEnum.T_5M); return http.post<string>(`/databases/redis/install/cli?operateNode=${encodeURIComponent(node)}`, {
taskID,
});
}; };
export const changeRedisPassword = (database: string, password: string) => { export const changeRedisPassword = (database: string, password: string) => {
if (password) { if (password) {
+49 -24
View File
@@ -81,7 +81,13 @@
:style="{ height: `calc(100vh - ${loadHeight()})`, 'background-color': '#000' }" :style="{ height: `calc(100vh - ${loadHeight()})`, 'background-color': '#000' }"
:description="loadErrMsg()" :description="loadErrMsg()"
> >
<el-button v-if="currentDB.from === 'remote'" v-permission type="primary" @click="installCli"> <el-button
v-if="currentDB.from === 'remote'"
v-permission
type="primary"
:loading="cliSubmitting"
@click="installCli"
>
{{ $t('commons.button.enable') }} {{ $t('commons.button.enable') }}
</el-button> </el-button>
</el-empty> </el-empty>
@@ -134,6 +140,7 @@
</DialogPro> </DialogPro>
<QuickCmd ref="dialogQuickCmdRef" @reload="loadQuickCmd" /> <QuickCmd ref="dialogQuickCmdRef" @reload="loadQuickCmd" />
<TaskLog ref="cliTaskLogRef" @close="checkCliValid" />
</div> </div>
</template> </template>
@@ -148,12 +155,14 @@ import { checkAppInstalled } from '@/api/modules/app';
import { useGlobalStore } from '@/composables/useGlobalStore'; import { useGlobalStore } from '@/composables/useGlobalStore';
import { listDatabases, checkRedisCli, installRedisCli } from '@/api/modules/database'; import { listDatabases, checkRedisCli, installRedisCli } from '@/api/modules/database';
import { Database } from '@/api/interface/database'; import { Database } from '@/api/interface/database';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang'; import i18n from '@/lang';
import { getCommandList } from '@/api/modules/command'; import { getCommandList } from '@/api/modules/command';
import { routerToName, routerToNameWithQuery } from '@/utils/router'; import { routerToName, routerToNameWithQuery } from '@/utils/router';
import { useOperateNodeContext } from '@/composables/useOperateNodeContext'; import { useOperateNodeContext } from '@/composables/useOperateNodeContext';
import TaskLog from '@/components/log/task/index.vue';
import { newUUID } from '@/utils/id';
const { currentNode, currentRedisDB, openMenuTabs } = useGlobalStore(); const { currentNode, currentRedisDB, openMenuTabs } = useGlobalStore();
useOperateNodeContext(currentNode); useOperateNodeContext(currentNode);
@@ -170,7 +179,11 @@ const appStatusRef = ref();
const open = ref(false); const open = ref(false);
const redisCliExist = ref(); const redisCliExist = ref(false);
const cliSubmitting = ref(false);
const cliTaskLogRef = ref<InstanceType<typeof TaskLog>>();
const operateNode = currentNode.value;
let disposed = false;
const appKey = ref('redis'); const appKey = ref('redis');
const appName = ref(); const appName = ref();
@@ -250,6 +263,7 @@ const changeDatabase = async () => {
const loadDBOptions = async () => { const loadDBOptions = async () => {
try { try {
const res = await listDatabases('redis,redis-cluster'); const res = await listDatabases('redis,redis-cluster');
if (disposed || operateNode !== currentNode.value) return;
let datas = res.data || []; let datas = res.data || [];
dbOptionsLocal.value = []; dbOptionsLocal.value = [];
dbOptionsRemote.value = []; dbOptionsRemote.value = [];
@@ -286,7 +300,7 @@ const loadDBOptions = async () => {
reOpenTerminal(); reOpenTerminal();
} }
} finally { } finally {
isLoaded.value = true; if (!disposed && operateNode === currentNode.value) isLoaded.value = true;
} }
}; };
@@ -301,6 +315,9 @@ const reOpenTerminal = async () => {
}; };
const initTerminal = 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; loading.value = true;
if (currentDB.value.from === 'remote') { if (currentDB.value.from === 'remote') {
if (!redisCliExist.value) { if (!redisCliExist.value) {
@@ -311,9 +328,10 @@ const initTerminal = async () => {
loading.value = false; loading.value = false;
redisIsExist.value = true; redisIsExist.value = true;
nextTick(() => { nextTick(() => {
if (!isCurrent()) return;
terminalShow.value = true; terminalShow.value = true;
redisStatus.value = 'Running'; redisStatus.value = 'Running';
terminalRef.value.acceptParams({ terminalRef.value?.acceptParams({
endpoint: '/api/v2/hosts/terminal/container', endpoint: '/api/v2/hosts/terminal/container',
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`, args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
error: '', error: '',
@@ -325,13 +343,15 @@ const initTerminal = async () => {
} }
await checkAppInstalled(currentDB.value.type, currentDBName.value) await checkAppInstalled(currentDB.value.type, currentDBName.value)
.then((res) => { .then((res) => {
if (!isCurrent()) return;
redisIsExist.value = res.data.isExist; redisIsExist.value = res.data.isExist;
redisStatus.value = res.data.status; redisStatus.value = res.data.status;
loading.value = false; loading.value = false;
nextTick(() => { nextTick(() => {
if (!isCurrent()) return;
if (res.data.status === 'Running') { if (res.data.status === 'Running') {
terminalShow.value = true; terminalShow.value = true;
terminalRef.value.acceptParams({ terminalRef.value?.acceptParams({
endpoint: '/api/v2/hosts/terminal/container', endpoint: '/api/v2/hosts/terminal/container',
args: `source=${currentDB.value.type}&name=${currentDBName.value}&from=${currentDB.value.from}`, args: `source=${currentDB.value.type}&name=${currentDBName.value}&from=${currentDB.value.from}`,
error: '', error: '',
@@ -342,6 +362,7 @@ const initTerminal = async () => {
isRefresh.value = !isRefresh.value; isRefresh.value = !isRefresh.value;
}) })
.catch(() => { .catch(() => {
if (!isCurrent()) return;
closeTerminal(false); closeTerminal(false);
loading.value = false; loading.value = false;
}); });
@@ -353,27 +374,30 @@ const closeTerminal = async (isKeepShow: boolean) => {
}; };
const checkCliValid = async () => { const checkCliValid = async () => {
await checkRedisCli() if (disposed || operateNode !== currentNode.value) return;
.then((res) => { try {
const res = await checkRedisCli();
if (disposed || operateNode !== currentNode.value) return;
redisCliExist.value = res.data; redisCliExist.value = res.data;
loadDBOptions(); } finally {
}) if (!disposed && operateNode === currentNode.value) await loadDBOptions();
.catch(() => { }
loadDBOptions();
});
}; };
const installCli = async () => { const installCli = async () => {
loading.value = true; if (cliSubmitting.value) return;
await installRedisCli() cliSubmitting.value = true;
.then(() => { try {
loading.value = false; const res = await installRedisCli(newUUID(), operateNode);
redisCliExist.value = true; if (disposed || operateNode !== currentNode.value) return;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); if (res.data) {
reOpenTerminal(); cliTaskLogRef.value?.openWithTaskID(res.data, true, operateNode);
}) } else {
.catch(() => { await checkCliValid();
loading.value = false; }
}); } finally {
cliSubmitting.value = false;
}
}; };
const loadQuickCmd = async () => { const loadQuickCmd = async () => {
@@ -403,6 +427,7 @@ const onAfter = () => {
initTerminal(); initTerminal();
}; };
onBeforeUnmount(() => { onBeforeUnmount(() => {
disposed = true;
closeTerminal(false); closeTerminal(false);
}); });
</script> </script>