feat: reveal encrypted config secrets on demand
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from "vue";
|
import { Hide, View } from "@element-plus/icons-vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { computed, reactive, ref } from "vue";
|
||||||
|
|
||||||
|
import { api } from "../services/api";
|
||||||
import {
|
import {
|
||||||
systemSettingSections,
|
systemSettingSections,
|
||||||
type SystemSettingDefinition,
|
type SystemSettingDefinition,
|
||||||
@@ -127,6 +130,8 @@ const wideSettingKeys = new Set([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const activeSectionTitle = ref(systemSettingSections[0]?.title || "");
|
const activeSectionTitle = ref(systemSettingSections[0]?.title || "");
|
||||||
|
const revealedSecrets = reactive<Record<string, string>>({});
|
||||||
|
const revealingSecrets = reactive<Record<string, boolean>>({});
|
||||||
|
|
||||||
const activeSection = computed(
|
const activeSection = computed(
|
||||||
() =>
|
() =>
|
||||||
@@ -200,6 +205,33 @@ function updateSetting(key: string, value: unknown) {
|
|||||||
if (value === undefined || value === null) return;
|
if (value === undefined || value === null) return;
|
||||||
emit("update", key, value as SystemSettingValue);
|
emit("update", key, value as SystemSettingValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleSecret(setting: SystemSettingDefinition) {
|
||||||
|
if (revealedSecrets[setting.key] !== undefined) {
|
||||||
|
delete revealedSecrets[setting.key];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentValue = String(props.values[setting.key] ?? "");
|
||||||
|
if (currentValue && currentValue !== "******") {
|
||||||
|
revealedSecrets[setting.key] = currentValue;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (revealingSecrets[setting.key]) return;
|
||||||
|
revealingSecrets[setting.key] = true;
|
||||||
|
try {
|
||||||
|
const result = await api.revealConfigSecret(setting.key);
|
||||||
|
revealedSecrets[setting.key] = result.configValue;
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error instanceof Error ? error.message : "敏感配置读取失败");
|
||||||
|
} finally {
|
||||||
|
revealingSecrets[setting.key] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePasswordSetting(key: string, value: string) {
|
||||||
|
if (revealedSecrets[key] !== undefined) revealedSecrets[key] = value;
|
||||||
|
updateSetting(key, value);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -326,12 +358,26 @@ function updateSetting(key: string, value: unknown) {
|
|||||||
<el-input
|
<el-input
|
||||||
v-else-if="setting.type === 'password'"
|
v-else-if="setting.type === 'password'"
|
||||||
:id="`setting-${setting.key}`"
|
:id="`setting-${setting.key}`"
|
||||||
:model-value="String(values[setting.key] ?? '')"
|
:model-value="revealedSecrets[setting.key] ?? String(values[setting.key] ?? '')"
|
||||||
:placeholder="setting.placeholder"
|
:placeholder="setting.placeholder"
|
||||||
type="password"
|
:type="revealedSecrets[setting.key] !== undefined ? 'text' : 'password'"
|
||||||
show-password
|
@update:model-value="updatePasswordSetting(setting.key, $event)"
|
||||||
@update:model-value="updateSetting(setting.key, $event)"
|
>
|
||||||
/>
|
<template #suffix>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="secret-visibility-toggle"
|
||||||
|
:class="{ loading: revealingSecrets[setting.key] }"
|
||||||
|
:aria-label="revealedSecrets[setting.key] !== undefined ? '隐藏真实内容' : '查看真实内容'"
|
||||||
|
@click="toggleSecret(setting)"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<Hide v-if="revealedSecrets[setting.key] !== undefined" />
|
||||||
|
<View v-else />
|
||||||
|
</el-icon>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
<el-select
|
<el-select
|
||||||
v-else-if="setting.type === 'select'"
|
v-else-if="setting.type === 'select'"
|
||||||
:id="`setting-${setting.key}`"
|
:id="`setting-${setting.key}`"
|
||||||
@@ -721,6 +767,20 @@ function updateSetting(key: string, value: unknown) {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.secret-visibility-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 2px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #66736f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secret-visibility-toggle.loading {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
.config-field :deep(.el-input__wrapper),
|
.config-field :deep(.el-input__wrapper),
|
||||||
.config-field :deep(.el-select__wrapper),
|
.config-field :deep(.el-select__wrapper),
|
||||||
.config-field :deep(.el-input-number) {
|
.config-field :deep(.el-input-number) {
|
||||||
|
|||||||
@@ -276,6 +276,10 @@ export const api = {
|
|||||||
body: "{}",
|
body: "{}",
|
||||||
}),
|
}),
|
||||||
configs: () => request<SystemConfigItem[]>("/admin/config"),
|
configs: () => request<SystemConfigItem[]>("/admin/config"),
|
||||||
|
revealConfigSecret: (configKey: string) =>
|
||||||
|
request<{ configKey: string; configValue: string }>(
|
||||||
|
`/admin/config/${encodeURIComponent(configKey)}/secret`,
|
||||||
|
),
|
||||||
saveConfig: (payload: Record<string, unknown>) =>
|
saveConfig: (payload: Record<string, unknown>) =>
|
||||||
request<SystemConfigItem>("/admin/config", { method: "PUT", body: JSON.stringify(payload) }),
|
request<SystemConfigItem>("/admin/config", { method: "PUT", body: JSON.stringify(payload) }),
|
||||||
ssoConfig: () => request<{ userClientUrl: string }>("/admin/sso/config"),
|
ssoConfig: () => request<{ userClientUrl: string }>("/admin/sso/config"),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from app.services.agent_debug_service import AgentDebugService
|
|||||||
from app.services.feishu_service import FeishuKnowledgeService
|
from app.services.feishu_service import FeishuKnowledgeService
|
||||||
from app.services.knowledge_service import KnowledgeScope
|
from app.services.knowledge_service import KnowledgeScope
|
||||||
from app.services.model_service import ModelClientService
|
from app.services.model_service import ModelClientService
|
||||||
|
from app.services.admin_permission_service import require_permission
|
||||||
from app.services.reasoning_policy_service import ReasoningPolicyService
|
from app.services.reasoning_policy_service import ReasoningPolicyService
|
||||||
from app.services.response_style_service import ResponseStyleService
|
from app.services.response_style_service import ResponseStyleService
|
||||||
from app.services.secret_service import MASKED_SECRET, SENSITIVE_CONFIG_KEYS, SecretService
|
from app.services.secret_service import MASKED_SECRET, SENSITIVE_CONFIG_KEYS, SecretService
|
||||||
@@ -446,6 +447,30 @@ def list_config(db: Session = Depends(get_db), current_admin: Admin = Depends(ge
|
|||||||
return api_success([_config_dict(config) for config in configs])
|
return api_success([_config_dict(config) for config in configs])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/config/{config_key}/secret")
|
||||||
|
def reveal_config_secret(
|
||||||
|
config_key: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_admin: Admin = Depends(get_current_admin),
|
||||||
|
) -> dict:
|
||||||
|
require_permission(current_admin, "configs.view")
|
||||||
|
if config_key not in SENSITIVE_CONFIG_KEYS:
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="该配置不是可查看的敏感配置")
|
||||||
|
config = db.scalar(select(SystemConfig).where(SystemConfig.config_key == config_key))
|
||||||
|
if config is None or not config.config_value:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="该配置尚未填写")
|
||||||
|
value = SecretService.decrypt(config.config_value)
|
||||||
|
OperationLogService.write(
|
||||||
|
db,
|
||||||
|
admin_id=current_admin.id,
|
||||||
|
module="config",
|
||||||
|
action="reveal_secret",
|
||||||
|
target_id=config.id,
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return api_success({"configKey": config_key, "configValue": value})
|
||||||
|
|
||||||
|
|
||||||
@router.put("/config")
|
@router.put("/config")
|
||||||
def save_config(
|
def save_config(
|
||||||
payload: SystemConfigSaveRequest,
|
payload: SystemConfigSaveRequest,
|
||||||
|
|||||||
Reference in New Issue
Block a user