fix mongodb root password sync (#12547)

#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
This commit is contained in:
王贺
2026-04-21 12:00:57 +08:00
committed by GitHub
parent 9f5a1d842a
commit cc0ac8cc43
7 changed files with 175 additions and 14 deletions

View File

@@ -171,6 +171,37 @@ func (b *BaseApi) ChangeMongodbPassword(c *gin.Context) {
helper.Success(c)
}
// @Tags Database Mongodb
// @Summary Change mongodb root password
// @Accept json
// @Param request body dto.ChangeDBInfo true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/mongodb/root/password [post]
// @x-panel-log {"bodyKeys":["database"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 mongodb 数据库 [database] root 密码","formatEN":"update mongodb database [database] root password"}
func (b *BaseApi) ChangeMongodbRootPassword(c *gin.Context) {
var req dto.ChangeDBInfo
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Value) != 0 {
value, err := base64.StdEncoding.DecodeString(req.Value)
if err != nil {
helper.BadRequest(c, err)
return
}
req.Value = string(value)
}
if err := mongodbService.ChangeRootPassword(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Database Mongodb
// @Summary Load mongodb privileges
// @Accept json

View File

@@ -11,7 +11,7 @@ type DBConfUpdateByFile struct {
type ChangeDBInfo struct {
ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis mysql-cluster postgresql-cluster redis-cluster"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis mongodb mysql-cluster postgresql-cluster redis-cluster"`
Database string `json:"database" validate:"required"`
Value string `json:"value" validate:"required"`
}

View File

@@ -874,7 +874,7 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
envKey := ""
switch param {
case "password":
if appKey == "mysql" || appKey == "mariadb" || appKey == "postgresql" {
if appKey == "mysql" || appKey == "mariadb" || appKey == "postgresql" || appKey == "mongodb" {
envKey = "PANEL_DB_ROOT_PASSWORD="
} else {
envKey = "PANEL_REDIS_ROOT_PASSWORD="
@@ -915,7 +915,7 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),
}, repo.WithByID(appInstall.ID))
if appKey == "mysql" || appKey == "postgresql" {
if appKey == "mysql" || appKey == "postgresql" || appKey == "mongodb" {
return nil
}
}

View File

@@ -29,6 +29,7 @@ type IMongodbService interface {
UpdateDescription(req dto.UpdateDescription) error
BindUser(req dto.MongodbBind) error
ChangePassword(req dto.MongodbPassword) error
ChangeRootPassword(req dto.ChangeDBInfo) error
LoadPrivileges(req dto.MongodbPrivilegesLoad) (string, error)
ChangePrivileges(req dto.MongodbPrivileges) error
DeleteCheck(req dto.MongodbDBDeleteCheck) ([]dto.DBResource, error)
@@ -188,6 +189,40 @@ func (u *MongodbService) ChangePassword(req dto.MongodbPassword) error {
return mongodbRepo.Update(dbItem.ID, map[string]interface{}{"password": pass})
}
func (u *MongodbService) ChangeRootPassword(req dto.ChangeDBInfo) error {
if cmd.CheckIllegal(req.Value) {
return buserr.New("ErrCmdIllegal")
}
if req.From != constant.AppResourceLocal {
return buserr.New("ErrRecordNotFound")
}
appInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return err
}
if appInfo.UserName == "" {
return buserr.New("ErrRecordNotFound")
}
if err := updateMongodbPassword(req.From, req.Database, "admin", appInfo.UserName, req.Value); err != nil {
return err
}
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
return err
}
remote, err := databaseRepo.Get(repo.WithByName(req.Database))
if err != nil {
return err
}
pass, err := encrypt.StringEncrypt(req.Value)
if err != nil {
return fmt.Errorf("encrypt mongodb root password failed, err: %v", err)
}
_ = databaseRepo.Update(remote.ID, map[string]interface{}{"password": pass})
return nil
}
func (u *MongodbService) DeleteCheck(req dto.MongodbDBDeleteCheck) ([]dto.DBResource, error) {
var res []dto.DBResource
db, err := mongodbRepo.Get(repo.WithByID(req.ID))

View File

@@ -65,6 +65,7 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
cmdRouter.POST("/mongodb/load", baseApi.LoadMongodbFromRemote)
cmdRouter.POST("/mongodb/bind", baseApi.BindMongodbUser)
cmdRouter.POST("/mongodb/password", baseApi.ChangeMongodbPassword)
cmdRouter.POST("/mongodb/root/password", baseApi.ChangeMongodbRootPassword)
cmdRouter.POST("/mongodb/privileges", baseApi.LoadMongodbPrivileges)
cmdRouter.POST("/mongodb/privileges/change", baseApi.ChangeMongodbPrivileges)
cmdRouter.POST("/mongodb/del/check", baseApi.DeleteCheckMongodb)

View File

@@ -112,6 +112,11 @@ export const updateMongodbPassword = (params: Database.MongodbPassword) => {
encodeBase64Fields(request, ['password']);
return http.post(`/databases/mongodb/password`, request, TimeoutEnum.T_40S);
};
export const updateMongodbRootPassword = (params: Database.ChangeInfo) => {
let request = deepCopy(params) as Database.ChangeInfo;
encodeBase64Fields(request, ['value']);
return http.post(`/databases/mongodb/root/password`, request, TimeoutEnum.T_40S);
};
export const updateMongodbDescription = (params: DescriptionUpdate) => {
return http.post(`/databases/mongodb/description`, params, TimeoutEnum.T_40S);
};

View File

@@ -1,6 +1,6 @@
<template>
<DrawerPro v-model="dialogVisible" :header="$t('database.databaseConnInfo')" @close="handleClose" size="small">
<el-form @submit.prevent v-loading="loading" :model="form" label-position="top">
<el-form @submit.prevent v-loading="loading" ref="formRef" :rules="rules" :model="form" label-position="top">
<el-form-item v-if="form.from === 'local'">
<template #label>
<div class="conn-label">
@@ -73,15 +73,43 @@
<el-divider border-style="dashed" />
<el-form-item :label="$t('commons.login.username')">
<el-tag>{{ form.username || '-' }}</el-tag>
<CopyButton v-if="form.username" :content="form.username" />
</el-form-item>
<el-form-item :label="form.from === 'local' ? $t('database.rootPassword') : $t('commons.login.password')">
<el-tag>{{ form.password || '-' }}</el-tag>
<CopyButton v-if="form.password" :content="form.password" />
</el-form-item>
<div v-if="form.from === 'local'">
<el-form-item :label="$t('commons.login.username')">
<el-input type="text" style="width: calc(100% - 60px)" readonly disabled v-model="form.username">
<template #append>
<el-button-group>
<CopyButton :content="form.username" :isIcon="false" />
</el-button-group>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('database.rootPassword')" prop="password">
<el-input
style="width: calc(100% - 205px)"
type="password"
show-password
clearable
v-model="form.password"
/>
<el-button-group>
<CopyButton :isIcon="false" class="copy_button" :content="form.password" />
<el-button @click="random">
{{ $t('commons.button.random') }}
</el-button>
</el-button-group>
<span class="input-help">{{ $t('commons.rule.illegalChar') }}</span>
</el-form-item>
</div>
<div v-else>
<el-form-item :label="$t('commons.login.username')">
<el-tag>{{ form.username || '-' }}</el-tag>
<CopyButton v-if="form.username" :content="form.username" />
</el-form-item>
<el-form-item :label="$t('commons.login.password')">
<el-tag>{{ form.password || '-' }}</el-tag>
<CopyButton v-if="form.password" :content="form.password" />
</el-form-item>
</div>
</el-form>
<template #footer>
@@ -89,6 +117,9 @@
<el-button :disabled="loading" @click="dialogVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading || form.status !== 'Running'" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DrawerPro>
@@ -96,8 +127,12 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { getDatabase } from '@/api/modules/database';
import { Rules } from '@/global/form-rules';
import { ElForm } from 'element-plus';
import { getDatabase, updateMongodbRootPassword } from '@/api/modules/database';
import { getAppConnInfo } from '@/api/modules/app';
import { MsgSuccess } from '@/utils/message';
import { getRandomStr } from '@/utils/id';
import { getAgentSettingInfo } from '@/api/modules/setting';
import { copyText } from '@/utils/clipboard';
import i18n from '@/lang';
@@ -121,6 +156,13 @@ const form = reactive({
username: '',
});
const rules = reactive({
password: [Rules.requiredInput, Rules.noSpace, Rules.illegal],
});
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
interface DialogProps {
from: string;
type: string;
@@ -128,6 +170,7 @@ interface DialogProps {
}
const acceptParams = async (params: DialogProps): Promise<void> => {
form.password = '';
form.from = params.from;
form.type = params.type;
form.database = params.database;
@@ -139,6 +182,10 @@ const handleClose = () => {
dialogVisible.value = false;
};
const random = () => {
form.password = getRandomStr(16);
};
const loadSystemIP = async () => {
const res = await getAgentSettingInfo();
form.systemIP = res.data.systemIP || globalStore.currentNodeAddr || i18n.global.t('database.localIP');
@@ -187,6 +234,38 @@ const copyConnURL = (isContainer: boolean) => {
copyText(`mongodb://${user}:${encodedPassword}@${host}:${port}/admin?authSource=admin`);
};
const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
ElMessageBox.confirm(
i18n.global.t('database.changeConnHelper', [i18n.global.t('database.rootPassword')]),
i18n.global.t('commons.msg.infoTitle'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
},
).then(async () => {
loading.value = true;
await updateMongodbRootPassword({
id: 0,
from: form.from,
type: form.type,
database: form.database,
value: form.password,
})
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
dialogVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
});
};
defineExpose({
acceptParams,
});
@@ -201,6 +280,16 @@ defineExpose({
gap: 12px;
}
.copy_button {
border-radius: 0px;
border-left-width: 0px;
}
:deep(.el-input__wrapper) {
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
:deep(.el-form-item__label) {
width: 100%;
}