feat: Add installation support for Redis Cluster (#9561)

This commit is contained in:
CityFun 2025-07-18 11:41:23 +08:00 committed by GitHub
parent 5d4ab23d95
commit a74c6dd522
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 60 additions and 19 deletions

View file

@ -11,13 +11,13 @@ import (
// @Tags Database Redis
// @Summary Load redis status info
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Param request body dto.LoadRedisStatus true "request"
// @Success 200 {object} dto.RedisStatus
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/redis/status [post]
func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
var req dto.OperationWithName
var req dto.LoadRedisStatus
if err := helper.CheckBind(&req, c); err != nil {
return
}

View file

@ -89,8 +89,8 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
var containerID string
var initCmd []string
switch source {
case "redis":
containerID, initCmd, err = loadRedisInitCmd(c)
case "redis", "redis-cluster":
containerID, initCmd, err = loadRedisInitCmd(c, source)
case "ollama":
containerID, initCmd, err = loadOllamaInitCmd(c)
case "container":
@ -127,7 +127,7 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
}
func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
func loadRedisInitCmd(c *gin.Context, redisType string) (string, []string, error) {
name := c.Query("name")
from := c.Query("from")
commands := []string{"exec", "-it"}
@ -136,7 +136,7 @@ func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
return "", nil, fmt.Errorf("no such database in db, err: %v", err)
}
if from == "local" {
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: redisType})
if err != nil {
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
}

View file

@ -319,3 +319,8 @@ type DatabaseDelete struct {
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
}
type LoadRedisStatus struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
}

View file

@ -482,6 +482,8 @@ func (a *AppInstallService) GetServices(key string) ([]response.AppService, erro
types = []string{constant.AppMysql, constant.AppMysqlCluster}
case constant.AppPostgresql:
types = []string{constant.AppPostgresql, constant.AppPostgresqlCluster}
case constant.AppRedis:
types = []string{constant.AppRedis, constant.AppRedisCluster}
}
dbs, _ := databaseRepo.GetList(repo.WithTypes(types))

View file

@ -125,6 +125,7 @@ var DatabaseKeys = map[string]uint{
constant.AppMemcached: 11211,
constant.AppMysqlCluster: 3306,
constant.AppPostgresqlCluster: 5432,
constant.AppRedisCluster: 6379,
}
var ToolKeys = map[string]uint{

View file

@ -54,6 +54,8 @@ func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (st
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name))
case "redis-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
case "redis-cluster-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/redis-cluster/%s/conf/redis.conf", req.Name))
}
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")

View file

@ -28,7 +28,7 @@ type IRedisService interface {
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
ChangePassword(info dto.ChangeRedisPass) error
LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error)
LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error)
LoadConf(req dto.OperationWithName) (*dto.RedisConf, error)
LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error)
@ -129,8 +129,8 @@ func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate)
return nil
}
func (u *RedisService) LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
func (u *RedisService) LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}

View file

@ -108,8 +108,8 @@ export const loadRemoteAccess = (type: string, database: string) => {
};
// redis
export const loadRedisStatus = (database: string) => {
return http.post<Database.RedisStatus>(`/databases/redis/status`, { name: database });
export const loadRedisStatus = (type: string, database: string) => {
return http.post<Database.RedisStatus>(`/databases/redis/status`, { type: type, name: database });
};
export const loadRedisConf = (database: string) => {
return http.post<Database.RedisConf>(`/databases/redis/conf`, { name: database });

View file

@ -3657,6 +3657,8 @@ const message = {
master: 'Master Node',
slave: 'Slave Node',
replicaStatus: 'Master-Slave Status',
unhealthyDeleteError: 'The installation node status is abnormal, please check the node list and try again!',
replicaStatusError: 'Status acquisition is abnormal, please check the master node.',
},
},
};

View file

@ -3520,6 +3520,9 @@ const message = {
master: 'マスターノード',
slave: 'スレーブノード',
replicaStatus: 'マスタースレーブステータス',
unhealthyDeleteError:
'インストールノードのステータスが異常ですノードリストを確認してから再試行してください',
replicaStatusError: 'ステータスの取得が異常ですマスターノードを確認してください',
},
},
};

View file

@ -3458,6 +3458,8 @@ const message = {
master: '마스터 노드',
slave: '슬레이브 노드',
replicaStatus: '마스터-슬레이브 상태',
unhealthyDeleteError: '설치 노드 상태가 비정상입니다. 노드 목록을 확인한 다시 시도하세요!',
replicaStatusError: '상태 획득이 비정상입니다. 마스터 노드를 확인하세요.',
},
},
};

View file

@ -3601,6 +3601,8 @@ const message = {
master: 'Node Utama',
slave: 'Node Hamba',
replicaStatus: 'Utama-Hamba Status',
unhealthyDeleteError: 'Status nod pemasangan tidak normal, sila periksa senarai nod dan cuba lagi!',
replicaStatusError: 'Pengambilan status tidak normal, sila periksa nod utama.',
},
},
};

View file

@ -3608,6 +3608,9 @@ const message = {
master: ' Mestre',
slave: ' Escravo',
replicaStatus: 'Status Mestre-Escravo',
unhealthyDeleteError:
'O status do de instalação está anormal, verifique a lista de nós e tente novamente!',
replicaStatusError: 'A aquisição do status está anormal, verifique o mestre.',
},
},
};

View file

@ -3599,6 +3599,9 @@ const message = {
master: 'Главный узел',
slave: 'Подчиненный узел',
replicaStatus: 'Состояние мастер-слейв',
unhealthyDeleteError:
'Состояние узла установки аномально, пожалуйста, проверьте список узлов и повторите попытку!',
replicaStatusError: 'Получение статуса аномально, пожалуйста, проверьте главный узел.',
},
},
};

View file

@ -3697,6 +3697,9 @@ const message = {
master: 'Главный узел',
slave: 'Подчиненный узел',
replicaStatus: 'Ana-Çalışan Durumu',
unhealthyDeleteError:
'Yükleme düğümü durumu anormal, lütfen düğüm listesini kontrol edin ve tekrar deneyin!',
replicaStatusError: 'Durum alımı anormal, lütfen ana düğümü kontrol edin.',
},
},
};

View file

@ -3402,6 +3402,8 @@ const message = {
master: '主節點',
slave: '從節點',
replicaStatus: '主從狀態',
unhealthyDeleteError: '安裝節點狀態異常請在節點列表檢查後重試',
replicaStatusError: '狀態獲取異常請檢查主節點',
},
},
};

View file

@ -3382,6 +3382,8 @@ const message = {
master: '主节点',
slave: '从节点',
replicaStatus: '主从状态',
unhealthyDeleteError: '安装节点状态异常请在节点列表检查后重试',
replicaStatusError: '状态获取异常 请检查主节点',
},
},
};

View file

@ -188,7 +188,7 @@ const onSetting = async () => {
isOnSetting.value = true;
terminalRef.value?.onClose(false);
terminalShow.value = false;
settingRef.value!.acceptParams({ status: redisStatus.value, database: currentDBName.value });
settingRef.value!.acceptParams({ status: redisStatus.value, database: currentDBName.value, type: appKey.value });
};
const loadHeight = () => {
@ -244,7 +244,7 @@ const changeDatabase = async () => {
const loadDBOptions = async () => {
try {
const res = await listDatabases('redis');
const res = await listDatabases('redis,redis-cluster');
let datas = res.data || [];
dbOptionsLocal.value = [];
dbOptionsRemote.value = [];
@ -318,8 +318,10 @@ const initTerminal = async () => {
isRefresh.value = !isRefresh.value;
return;
}
await checkAppInstalled('redis', currentDBName.value)
console.log(currentDBName.value);
await checkAppInstalled(currentDB.value.type, currentDBName.value)
.then((res) => {
console.log(res.data);
redisIsExist.value = res.data.isExist;
redisStatus.value = res.data.status;
loading.value = false;
@ -328,7 +330,7 @@ const initTerminal = async () => {
terminalShow.value = true;
terminalRef.value.acceptParams({
endpoint: '/api/v2/containers/exec',
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
args: `source=${currentDB.value.type}&name=${currentDBName.value}&from=${currentDB.value.from}`,
error: '',
initCmd: '',
});

View file

@ -153,6 +153,7 @@ const useOld = ref(false);
const redisStatus = ref();
const database = ref();
const dbType = ref('redis');
const formRef = ref<FormInstance>();
const redisConf = ref();
@ -163,6 +164,7 @@ const settingShow = ref<boolean>(false);
interface DialogProps {
database: string;
status: string;
type: string;
}
const changeTab = (val: string) => {
@ -179,7 +181,7 @@ const changeTab = (val: string) => {
loadForm();
break;
case 'status':
statusRef.value!.acceptParams({ status: redisStatus.value, database: database.value });
statusRef.value!.acceptParams({ status: redisStatus.value, database: database.value, type: dbType.value });
break;
}
};
@ -191,6 +193,7 @@ const changeLoading = (status: boolean) => {
const acceptParams = (prop: DialogProps): void => {
redisStatus.value = prop.status;
database.value = prop.database;
dbType.value = prop.type;
settingShow.value = true;
changeTab('status');
};
@ -274,7 +277,7 @@ const submitForm = async () => {
const getDefaultConfig = async () => {
loading.value = true;
await getAppDefaultConfig('redis', '')
await getAppDefaultConfig(dbType.value, '')
.then((res) => {
redisConf.value = res.data;
useOld.value = true;
@ -323,7 +326,7 @@ const loadForm = async () => {
const loadConfFile = async () => {
useOld.value = false;
loading.value = true;
await loadDBFile('redis-conf', database.value)
await loadDBFile(dbType.value + '-conf', database.value)
.then((res) => {
loading.value = false;
redisConf.value = res.data;

View file

@ -163,21 +163,25 @@ const redisStatus = reactive({
const database = ref();
const statusShow = ref(false);
const dbType = ref('redis');
interface DialogProps {
database: string;
status: string;
type: string;
}
const acceptParams = (prop: DialogProps): void => {
statusShow.value = true;
database.value = prop.database;
dbType.value = prop.type;
if (prop.status === 'Running') {
loadStatus();
}
};
const loadStatus = async () => {
const res = await loadRedisStatus(database.value);
console.log('loadStatus', database.value);
const res = await loadRedisStatus(dbType.value, database.value);
let hit = (
(Number(res.data.keyspace_hits) / (Number(res.data.keyspace_hits) + Number(res.data.keyspace_misses))) *
100