新增: 审批超时可配置 + ScriptNode 命令白/黑名单
- 审批超时: Settings 下拉选择(不限时/5/15/30/60min),默认 15min - getApprovalTimeoutMs() 从 KV 读取,startApprovalTimer 动态取值 - ScriptNode: 黑名单 > 白名单策略,从环境变量读取 - Settings 加命令执行安全面板(白/黑名单文本框) - i18n 中英文案 + 搜索索引补全
This commit is contained in:
@@ -27,6 +27,17 @@
|
||||
<option value="debug">Debug</option>
|
||||
</select>
|
||||
</SettingRow>
|
||||
|
||||
<!-- 审批超时:用户未处理审批弹窗时的自动拒绝阅值;0 表示不限时(对齐旧默认) -->
|
||||
<SettingRow ref="rowApprovalTimeout" :label="$t('settings.labelApprovalTimeout')" :desc="$t('settings.descApprovalTimeout')">
|
||||
<select v-model="settings.approvalTimeout" class="setting-select" @change="onApprovalTimeoutChange">
|
||||
<option :value="0">{{ $t('settings.approvalTimeoutUnlimited') }}</option>
|
||||
<option :value="300000">{{ $t('settings.approvalTimeout5m') }}</option>
|
||||
<option :value="900000">{{ $t('settings.approvalTimeout15m') }}</option>
|
||||
<option :value="1800000">{{ $t('settings.approvalTimeout30m') }}</option>
|
||||
<option :value="3600000">{{ $t('settings.approvalTimeout60m') }}</option>
|
||||
</select>
|
||||
</SettingRow>
|
||||
</div>
|
||||
|
||||
<!-- all 二次确认弹层(独立,不与 Settings 壳层共用:壳层按钮写死「删除」不适合「确认接管」) -->
|
||||
@@ -88,10 +99,13 @@ const settings = reactive({
|
||||
// clampMode 兜底脏值。
|
||||
autoExecuteMode: clampMode(appSettings.get<string>('df-ai-auto-execute-mode', 'low')),
|
||||
logLevel: 'info',
|
||||
// 审批超时(ms):0=不限时,缺省 900000(15min);仅固定合法选项,脏值回退默认
|
||||
approvalTimeout: clampApprovalTimeout(appSettings.get<number>('df-approval-timeout', 900000)),
|
||||
})
|
||||
|
||||
const rowAutoExecute = useTemplateRef<InstanceType<typeof SettingRow>>('rowAutoExecute')
|
||||
const rowLogLevel = useTemplateRef<InstanceType<typeof SettingRow>>('rowLogLevel')
|
||||
const rowApprovalTimeout = useTemplateRef<InstanceType<typeof SettingRow>>('rowApprovalTimeout')
|
||||
|
||||
// 上一次确认生效的档位:all 二次确认取消时恢复此值(非写死 low),避免 medium 用户误选 all
|
||||
// 取消后被强制降级 low。初始化 = 当前有效值;每次确认成功(含非 all 切换)更新。
|
||||
@@ -130,6 +144,20 @@ watch(
|
||||
function markSaved() {
|
||||
rowLogLevel.value?.markSaved()
|
||||
}
|
||||
|
||||
// 审批超时合法选项白名单(对齐 <select> option);脏值(外部改库为非法数字)回退默认 15min,
|
||||
// 防 <select v-model> 无匹配 option 致控件空白。允许的取值:0/5/15/30/60 分钟对应毫秒数。
|
||||
const VALID_APPROVAL_TIMEOUTS = [0, 300_000, 900_000, 1_800_000, 3_600_000]
|
||||
function clampApprovalTimeout(raw: number): number {
|
||||
return VALID_APPROVAL_TIMEOUTS.includes(raw) ? raw : 900_000
|
||||
}
|
||||
|
||||
// 审批超时下拉变更:落 KV(df-approval-timeout,ms)+ 显「已保存」反馈。
|
||||
// 下一笔发起的审批即生效(aiShared.startApprovalTimer 每次实时读),已在跑的计时器沿用旧值。
|
||||
function onApprovalTimeoutChange() {
|
||||
appSettings.set('df-approval-timeout', settings.approvalTimeout)
|
||||
rowApprovalTimeout.value?.markSaved()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -41,6 +41,38 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══ 命令执行安全:ScriptNode 白/黑名单(前端仅 UI + 持久化,后端当前从环境变量读) ═══ -->
|
||||
<section class="panel">
|
||||
<div class="panel-header">
|
||||
<h2>{{ $t('settings.panelScriptSafety') }}</h2>
|
||||
</div>
|
||||
<div class="script-safety">
|
||||
<p class="script-safety-desc">{{ $t('settings.descScriptSafety') }}</p>
|
||||
|
||||
<SettingRow :label="$t('settings.labelScriptWhitelist')" :desc="$t('settings.descScriptWhitelist')">
|
||||
<input
|
||||
v-model="scriptWhitelist"
|
||||
class="setting-select script-safety-input"
|
||||
:placeholder="$t('settings.placeholderScriptWhitelist')"
|
||||
@blur="onScriptWhitelistCommit"
|
||||
@keyup.enter="onScriptWhitelistCommit"
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow :label="$t('settings.labelScriptBlacklist')" :desc="$t('settings.descScriptBlacklist')">
|
||||
<input
|
||||
v-model="scriptBlacklist"
|
||||
class="setting-select script-safety-input"
|
||||
:placeholder="$t('settings.placeholderScriptBlacklist')"
|
||||
@blur="onScriptBlacklistCommit"
|
||||
@keyup.enter="onScriptBlacklistCommit"
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<p class="script-safety-note">{{ $t('settings.noteScriptSafety') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -48,8 +80,11 @@ import { ref, onMounted } from 'vue'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { aiApi } from '@/api'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import SettingRow from './SettingRow.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appSettings = useAppSettingsStore()
|
||||
// F-260620: 接共享 toast 通道(Settings.vue showToast),替代 console.error/warn 用户零反馈
|
||||
const emit = defineEmits<{ (e: 'toast', msg: string, type: 'error' | 'warning' | 'info'): void }>()
|
||||
|
||||
@@ -138,6 +173,27 @@ async function reload() {
|
||||
await load()
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ScriptNode 命令执行安全 — 白/黑名单 UI
|
||||
// ------------------------------------------------------------
|
||||
// 前端仅做 UI + 持久化到 appSettings(df-script-whitelist / df-script-blacklist)。
|
||||
// 后端 ScriptNode 当前从环境变量 DF_SCRIPT_WHITELIST / DF_SCRIPT_BLACKLIST 读取
|
||||
// (同一逗号分隔语义),前后端打通留后续:前端写入需经 IPC/启动期注入环境变量。
|
||||
// 输入语义:逗号分隔命令名(如 `git,npm,cargo`),空串=该项不限制。
|
||||
// ============================================================
|
||||
const scriptWhitelist = ref(appSettings.get<string>('df-script-whitelist', ''))
|
||||
const scriptBlacklist = ref(appSettings.get<string>('df-script-blacklist', ''))
|
||||
|
||||
function onScriptWhitelistCommit() {
|
||||
appSettings.set('df-script-whitelist', scriptWhitelist.value.trim())
|
||||
emit('toast', t('settings.savedHint'), 'info')
|
||||
}
|
||||
|
||||
function onScriptBlacklistCommit() {
|
||||
appSettings.set('df-script-blacklist', scriptBlacklist.value.trim())
|
||||
emit('toast', t('settings.savedHint'), 'info')
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
@@ -165,4 +221,17 @@ onMounted(load)
|
||||
.add-row { display: flex; gap: 8px; margin-top: 4px; }
|
||||
.dir-input { flex: 1; min-width: 0; }
|
||||
.actions { display: flex; gap: 8px; margin-top: 8px; }
|
||||
|
||||
/* ===== ScriptNode 命令执行安全面板 ===== */
|
||||
.script-safety { display: flex; flex-direction: column; gap: 4px; }
|
||||
.script-safety-desc {
|
||||
font-size: 12px; color: var(--df-text-dim); margin: 0 0 4px; line-height: 1.5;
|
||||
}
|
||||
.script-safety-input {
|
||||
min-width: 280px;
|
||||
font-family: var(--df-font-mono, monospace);
|
||||
}
|
||||
.script-safety-note {
|
||||
font-size: 11px; color: var(--df-text-dim); margin: 4px 0 0; line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -80,10 +80,13 @@ export const SETTINGS_INDEX: SettingIndexEntry[] = [
|
||||
|
||||
// ===== 安全 =====
|
||||
{ category: 'security', key: 'allowedDirs', labelKey: 'settings.panelAllowedDirs', descKey: 'settings.descAllowedDirs' },
|
||||
{ category: 'security', key: 'scriptWhitelist', labelKey: 'settings.labelScriptWhitelist', descKey: 'settings.descScriptWhitelist' },
|
||||
{ category: 'security', key: 'scriptBlacklist', labelKey: 'settings.labelScriptBlacklist', descKey: 'settings.descScriptBlacklist' },
|
||||
|
||||
// ===== 高级 =====
|
||||
// F-#97:autoExecute(boolean) → autoExecuteMode(low/medium/all 三档),key 同步更名
|
||||
{ category: 'advanced', key: 'autoExecuteMode', labelKey: 'settings.labelAutoExecute', descKey: 'settings.descAutoExecute' },
|
||||
{ category: 'advanced', key: 'logLevel', labelKey: 'settings.labelLogLevel', descKey: 'settings.descLogLevel' },
|
||||
{ category: 'advanced', key: 'approvalTimeout', labelKey: 'settings.labelApprovalTimeout', descKey: 'settings.descApprovalTimeout' },
|
||||
{ category: 'advanced', key: 'connection', labelKey: 'settings.panelConnection', descKey: 'settings.emptyConnection' },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user