Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions tests/unit/web-ui-behavior-parity.test.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -411,6 +411,7 @@ test('captured bundled app skeleton only exposes expected data key drift versus
];
allowedExtraCurrentKeys.push(
'lang',
'configTemplateContext',
'configTemplateDiffVisible',
'configTemplateDiffLoading',
'configTemplateDiffError',
Expand All@@ -424,6 +425,8 @@ test('captured bundled app skeleton only exposes expected data key drift versus
'healthCheckBatchDone',
'healthCheckBatchFailed',
'showHealthCheckModal',
'showCodexBridgePoolModal',
'showClaudeBridgePoolModal',
'pluginsActiveId',
'pluginsLoading',
'pluginsError',
Expand DownExpand Up@@ -571,6 +574,8 @@ test('captured bundled app skeleton only exposes expected data key drift versus
'normalizeConfigTemplateDiffConfirmEnabled',
'setConfigTemplateDiffConfirmEnabled',
'extractClaudeResumeKeyFromFilePath',
'openClaudeConfigTemplateEditor',
'applyClaudeLocalBridge',
'loadPluginsOverview',
'selectPlugin',
'createPromptTemplate',
Expand Down
3 changes: 3 additions & 0 deletions web-ui/app.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,6 +71,8 @@ document.addEventListener('DOMContentLoaded', () => {
showAgentsModal: false,
showSkillsModal: false,
showHealthCheckModal: false,
showCodexBridgePoolModal: false,
showClaudeBridgePoolModal: false,
// Plugins
pluginsActiveId: 'prompt-templates',
pluginsLoading: false,
Expand DownExpand Up@@ -98,6 +100,7 @@ document.addEventListener('DOMContentLoaded', () => {
confirmDialogResolver: null,
configTemplateContent: '',
configTemplateApplying: false,
configTemplateContext: 'codex',
configTemplateDiffVisible: false,
configTemplateDiffLoading: false,
configTemplateDiffError: '',
Expand Down
4 changes: 4 additions & 0 deletions web-ui/logic.claude.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,6 +111,10 @@ export function matchClaudeConfigFromSettings(claudeConfigs = {}, env = {}) {
if (!normalizedSettings.baseUrl || !normalizedSettings.model || !hasClaudeCredential(normalizedSettings)) {
return '';
}
// 检测本地桥接 URL
if (typeof normalizedSettings.baseUrl === 'string' && normalizedSettings.baseUrl.includes('/bridge/claude-local/')) {
return 'claude-local';
}
Comment on lines +114 to +117

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Local-bridge URL detection is too strict and misses valid forms.

The current check only matches /bridge/claude-local/. URLs without trailing slash (e.g., /bridge/claude-local) won’t match.

Proposed fix
- if (typeof normalizedSettings.baseUrl === 'string' && normalizedSettings.baseUrl.includes('/bridge/claude-local/')) {+ const normalizedBaseUrl = normalizeClaudeComparableUrl(normalizedSettings.baseUrl).toLowerCase();+ if (normalizedBaseUrl.includes('/bridge/claude-local')) {
return 'claude-local';
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web-ui/logic.claude.mjs` around lines 114 - 117, The local-bridge URL check
inside web-ui/logic.claude.mjs is too strict — the current conditional that
inspects normalizedSettings.baseUrl only matches '/bridge/claude-local/' and
misses URLs without a trailing slash; update the detection logic (the
conditional referencing normalizedSettings.baseUrl in this module) to treat
'/bridge/claude-local' and '/bridge/claude-local/' (and any URL where
'/bridge/claude-local' is a path segment) as matches — e.g., replace the exact
include check with a test that accepts either a trailing slash or end-of-string
(or use startsWith or a regex like '/bridge/claude-local(\/|$)') so the function
returns 'claude-local' for both forms.

const comparableSettingsUrl = normalizeClaudeComparableUrl(normalizedSettings.baseUrl);
const entries = Object.entries(claudeConfigs || {});
for (const [name, config] of entries) {
Expand Down
37 changes: 37 additions & 0 deletions web-ui/modules/app.methods.claude-config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -264,6 +264,43 @@ export function createClaudeConfigMethods(options = {}) {

claudeLocalBridgeConfigured() {
return this.claudeLocalBridgeCandidateProviders().some(p => p.hasKey);
},

async applyClaudeLocalBridge() {
this.currentClaudeConfig = 'claude-local';
try { localStorage.setItem('currentClaudeConfig', 'claude-local'); } catch (_) {}
this.refreshClaudeModelContext();

const candidates = this.claudeLocalBridgeCandidateProviders();
if (candidates.length === 0) {
return this.showMessage('请先添加并配置至少一个 Claude 提供商', 'error');
}

try {
const res = await api('claude-local-bridge-toggle', { enable: true });
if (res.error) {
this.showMessage(res.error || '启用本地负载均衡失败', 'error');
return;
}
this.showMessage('Claude 本地负载均衡已启用', 'success');
} catch (e) {
this.showMessage('启用本地负载均衡失败', 'error');
}
Comment on lines +269 to +288

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Switching to claude-local before enable succeeds leaves stale UI state.

If validation or claude-local-bridge-toggle fails, the app still persists currentClaudeConfig='claude-local'. Keep current selection unchanged until enable succeeds.

Proposed fix
 async applyClaudeLocalBridge() {
- this.currentClaudeConfig = 'claude-local';- try { localStorage.setItem('currentClaudeConfig', 'claude-local'); } catch (_) {}- this.refreshClaudeModelContext();-
const candidates = this.claudeLocalBridgeCandidateProviders();
if (candidates.length === 0) {
return this.showMessage('请先添加并配置至少一个 Claude 提供商', 'error');
}
try {
const res = await api('claude-local-bridge-toggle', { enable: true });
if (res.error) {
this.showMessage(res.error || '启用本地负载均衡失败', 'error');
return;
}
+ this.currentClaudeConfig = 'claude-local';+ try { localStorage.setItem('currentClaudeConfig', 'claude-local'); } catch (_) {}+ this.refreshClaudeModelContext();
this.showMessage('Claude 本地负载均衡已启用', 'success');
} catch (e) {
this.showMessage('启用本地负载均衡失败', 'error');
}
},
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncapplyClaudeLocalBridge(){
this.currentClaudeConfig='claude-local';
try{localStorage.setItem('currentClaudeConfig','claude-local');}catch(_){}
this.refreshClaudeModelContext();
constcandidates=this.claudeLocalBridgeCandidateProviders();
if(candidates.length===0){
returnthis.showMessage('请先添加并配置至少一个 Claude 提供商','error');
}
try{
constres=awaitapi('claude-local-bridge-toggle',{enable: true});
if(res.error){
this.showMessage(res.error||'启用本地负载均衡失败','error');
return;
}
this.showMessage('Claude 本地负载均衡已启用','success');
}catch(e){
this.showMessage('启用本地负载均衡失败','error');
}
asyncapplyClaudeLocalBridge(){
constcandidates=this.claudeLocalBridgeCandidateProviders();
if(candidates.length===0){
returnthis.showMessage('请先添加并配置至少一个 Claude 提供商','error');
}
try{
constres=awaitapi('claude-local-bridge-toggle',{enable: true});
if(res.error){
this.showMessage(res.error||'启用本地负载均衡失败','error');
return;
}
this.currentClaudeConfig='claude-local';
try{localStorage.setItem('currentClaudeConfig','claude-local');}catch(_){}
this.refreshClaudeModelContext();
this.showMessage('Claude 本地负载均衡已启用','success');
}catch(e){
this.showMessage('启用本地负载均衡失败','error');
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web-ui/modules/app.methods.claude-config.mjs` around lines 269 - 288, The
method applyClaudeLocalBridge currently sets this.currentClaudeConfig =
'claude-local' and writes localStorage before the API call, causing stale UI
state if validation or api('claude-local-bridge-toggle', { enable: true })
fails; change the flow in applyClaudeLocalBridge so you first validate
candidates via claudeLocalBridgeCandidateProviders(), then call the API, and
only on successful response set this.currentClaudeConfig = 'claude-local',
persist to localStorage, and call this.refreshClaudeModelContext(); if the API
returns an error or throws, do not modify currentClaudeConfig or localStorage
(or revert them if already changed), and continue to show the existing error
messages via this.showMessage.

},

async openClaudeConfigTemplateEditor() {
try {
const res = await api('get-claude-settings-raw');
if (res.error) {
this.showMessage(res.error, 'error');
return;
}
this.configTemplateContent = res.content || '{}';
this.configTemplateContext = 'claude';
this.showConfigTemplateModal = true;
} catch (e) {
this.showMessage('加载 Claude settings 失败', 'error');
}
}
};
}
14 changes: 11 additions & 3 deletions web-ui/modules/app.methods.codex-config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -558,6 +558,7 @@ export function createCodexConfigMethods(options = {}) {
template = `${template.trimEnd()}\n\n${appendBlock}\n`;
}
this.configTemplateContent = template;
this.configTemplateContext = 'codex';
this.showConfigTemplateModal = true;
} catch (e) {
this.showMessage('加载模板失败', 'error');
Expand DownExpand Up@@ -807,9 +808,16 @@ export function createCodexConfigMethods(options = {}) {
const performApply = async () => {
this.configTemplateApplying = true;
try {
const res = await api('apply-config-template', {
template: this.configTemplateContent
});
let res;
if (this.configTemplateContext === 'claude') {
res = await api('apply-claude-settings-raw', {
content: this.configTemplateContent
});
} else {
res = await api('apply-config-template', {
template: this.configTemplateContent
});
}
Comment on lines +811 to +820

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Claude apply path is context-aware, but confirmation preview gating is not.

At Line 812 you route apply correctly, but applyConfigTemplate() still enforces the shared diff-preview gate (preview-config-template-diff) with no Claude context. In Claude mode this can block or mislead apply decisions based on the wrong target.

Suggested frontend-safe fix (bypass codex diff gate for Claude context)
 async applyConfigTemplate() {
if (this.configTemplateApplying) {
return;
}
if (!this.configTemplateContent || !this.configTemplateContent.trim()) {
this.showMessage('模板不能为空', 'error');
return;
}
+ const isClaudeTemplate = this.configTemplateContext === 'claude';
// Default to two-step confirmation when the setting is unset.
// (The normalize helper lives in session-actions; keep a safe fallback here.)
- const shouldUseTwoStepConfirm = normalizeConfigTemplateDiffConfirmEnabled(this.configTemplateDiffConfirmEnabled);+ const shouldUseTwoStepConfirm = !isClaudeTemplate+ && normalizeConfigTemplateDiffConfirmEnabled(this.configTemplateDiffConfirmEnabled);
const performApply = async () => {
this.configTemplateApplying = true;
try {
let res;
if (this.configTemplateContext === 'claude') {
res = await api('apply-claude-settings-raw', {
content: this.configTemplateContent
});
} else {
res = await api('apply-config-template', {
template: this.configTemplateContent
});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web-ui/modules/app.methods.codex-config.mjs` around lines 811 - 820, The
preview gating logic for config applies is using the shared
"preview-config-template-diff" path even when configTemplateContext ===
'claude', causing Claude-mode applies to be blocked/misrepresented; update
applyConfigTemplate() (or the code path that checks
preview-config-template-diff) to skip or bypass the shared diff-preview gate
when this.configTemplateContext === 'claude' (or alternatively propagate the
'claude' context so the preview check uses a Claude-aware gate), ensuring the
Claude branch that calls api('apply-claude-settings-raw', ...) is not forced
through the generic preview gate.

if (res.error) {
this.showMessage(res.error, 'error');
return;
Expand Down
26 changes: 26 additions & 0 deletions web-ui/partials/index/modals-basic.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -171,3 +171,29 @@
</div>
</div>
</div>

<!-- Codex 轮询池控制模态框 -->
<div v-if="showCodexBridgePoolModal" class="modal-overlay" @click.self="showCodexBridgePoolModal = false">
<div class="modal modal-bridge-pool" role="dialog" aria-modal="true" aria-labelledby="codex-bridge-pool-modal-title">
<div class="modal-title" id="codex-bridge-pool-modal-title">
<svg class="modal-title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="12" cy="18" r="2"/><path d="M6 8v4h6v4"/><path d="M18 8v4h-6v4"/></svg>
轮询池设置
</div>
<div class="bridge-pool-modal-hint">勾选参与负载均衡的提供商</div>
<div v-if="localBridgeCandidateProviders().length === 0" class="bridge-pool-empty">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>
<span>暂无可用上游 provider,请先添加直连 provider</span>
</div>
<div v-else class="bridge-pool-list">
<label v-for="cp in localBridgeCandidateProviders()" :key="cp.name" class="bridge-pool-item">
<span class="bridge-pool-item-name">{{ cp.name }}</span>
<span class="bridge-pool-item-status" :class="{ active: !isLocalBridgeExcluded(cp.name) }">{{ isLocalBridgeExcluded(cp.name) ? '未启用' : '已启用' }}</span>
<input type="checkbox" :checked="!isLocalBridgeExcluded(cp.name)" @change="toggleLocalBridgeExcluded(cp.name)" />
</label>
</div>
<div class="btn-group">
<button class="btn btn-confirm" @click="showCodexBridgePoolModal = false">{{ t('common.close') }}</button>
</div>
</div>
</div>

36 changes: 1 addition & 35 deletions web-ui/partials/index/panel-config-claude.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,6 +82,7 @@
:placeholder="t('claude.model.placeholder')"
>
<div class="config-template-hint">{{ t('claude.model.hint') }}</div>
<button class="btn-tool btn-template-editor" @click="openClaudeConfigTemplateEditor" :disabled="loading || !!initError">{{ t('config.template.openEditor') }}</button>
</div>

<div class="selector-section">
Expand All@@ -97,20 +98,6 @@
</div>

<div class="card-list">
<div :class="['card', { active: currentClaudeConfig === 'claude-local' }]" @click="currentClaudeConfig = 'claude-local'" @keydown.enter.self.prevent="currentClaudeConfig = 'claude-local'" @keydown.space.self.prevent="currentClaudeConfig = 'claude-local'" tabindex="0" role="button" :aria-current="currentClaudeConfig === 'claude-local' ? 'true' : null">
<div class="card-leading">
<div class="card-icon">L</div>
<div class="card-content">
<div class="card-title">
<span>local</span>
<span class="provider-readonly-badge">{{ t('config.badge.system') }}</span>
</div>
</div>
</div>
<div class="card-trailing">
<span :class="['pill', claudeLocalBridgeConfigured() ? 'configured' : 'empty']">{{ claudeLocalBridgeConfigured() ? t('claude.configured') : t('claude.notConfigured') }}</span>
</div>
</div>
<div v-for="(config, name) in claudeConfigs" :key="name" :class="['card', { active: currentClaudeConfig === name }]" @click="applyClaudeConfig(name)" @keydown.enter.self.prevent="applyClaudeConfig(name)" @keydown.space.self.prevent="applyClaudeConfig(name)" tabindex="0" role="button" :aria-current="currentClaudeConfig === name ? 'true' : null">
<div class="card-leading">
<div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
Expand DownExpand Up@@ -141,26 +128,5 @@
</div>
</div>

<div v-if="currentClaudeConfig === 'claude-local'" class="bridge-pool-panel">
<div class="bridge-pool-header">
<span class="bridge-pool-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="12" cy="18" r="2"/><path d="M6 8v4h6v4"/><path d="M18 8v4h-6v4"/></svg>
</span>
<span class="bridge-pool-title">{{ t('claude.localBridge.poolTitle') }}</span>
<span class="bridge-pool-hint">{{ t('claude.localBridge.poolHint') }}</span>
</div>
<div v-if="Object.keys(claudeConfigs || {}).length === 0" class="bridge-pool-empty">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>
<span>{{ t('claude.localBridge.noProviders') }}</span>
</div>
<div v-else class="bridge-pool-list">
<label v-for="(config, name) in claudeConfigs" :key="name" class="bridge-pool-item">
<span class="bridge-pool-item-name">{{ name }}</span>
<span class="bridge-pool-item-status" :class="{ active: !isClaudeLocalBridgeExcluded(name) }">{{ isClaudeLocalBridgeExcluded(name) ? t('claude.localBridge.disabled') : t('claude.localBridge.enabled') }}</span>
<input type="checkbox" :checked="!isClaudeLocalBridgeExcluded(name)" @change="toggleClaudeLocalBridgeExcluded(name)" />
</label>
</div>
</div>

</template>
</div>
28 changes: 7 additions & 21 deletions web-ui/partials/index/panel-config-codex.html
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,10 @@
<div class="card-leading">
<div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}<span v-if="isTransformProvider(provider)" class="card-icon-dot" title="通过内建转换适配"></span></div>
<div class="card-content">
<div v-if="provider.name === 'local'" class="bridge-pool-summary">
<svg class="bridge-pool-summary-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"><circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="12" cy="18" r="2"/><path d="M6 8v4h6v4"/><path d="M18 8v4h-6v4"/></svg>
<span class="bridge-pool-summary-text">已启用 {{ localBridgeCandidateProviders().filter(cp => !isLocalBridgeExcluded(cp.name)).length }} / {{ localBridgeCandidateProviders().length }}</span>
</div>
<div class="card-title">
<span>{{ provider.name }}</span>
<span v-if="provider.readOnly" class="provider-readonly-badge">{{ t('config.badge.system') }}</span>
Expand All@@ -136,6 +140,9 @@
<span v-if="speedResults[provider.name]" :class="['latency', speedResults[provider.name].ok ? 'ok' : 'error']">{{ formatLatency(speedResults[provider.name]) }}</span>
<span :class="['pill', providerPillConfigured(provider) ? 'configured' : 'empty']">{{ providerPillText(provider) }}</span>
<div class="card-actions" @click.stop>
<button v-if="provider.name === 'local'" class="card-action-btn bridge-pool-trigger" @click="showCodexBridgePoolModal = true" :aria-label="'轮询池设置'" :title="'轮询池设置'">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="12" cy="18" r="2"/><path d="M6 8v4h6v4"/><path d="M18 8v4h-6v4"/></svg>
</button>
<button class="card-action-btn" :class="{ loading: speedLoading[provider.name] }" :disabled="!!speedLoading[provider.name]" @click="runSpeedTest(provider.name, { silent: true })" :aria-label="t('config.availabilityTestAria', { name: provider.name })" :title="t('config.availabilityTest')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
</button>
Expand DownExpand Up@@ -166,26 +173,5 @@
</div>
</div>
</div>

<div v-if="displayCurrentProvider === 'local'" class="bridge-pool-panel">
<div class="bridge-pool-header">
<span class="bridge-pool-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="12" cy="18" r="2"/><path d="M6 8v4h6v4"/><path d="M18 8v4h-6v4"/></svg>
</span>
<span class="bridge-pool-title">轮询池</span>
<span class="bridge-pool-hint">勾选参与负载均衡的提供商</span>
</div>
<div v-if="localBridgeCandidateProviders().length === 0" class="bridge-pool-empty">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="16" height="16"><path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>
<span>暂无可用上游 provider,请先添加直连 provider</span>
</div>
<div v-else class="bridge-pool-list">
<label v-for="cp in localBridgeCandidateProviders()" :key="cp.name" class="bridge-pool-item">
<span class="bridge-pool-item-name">{{ cp.name }}</span>
<span class="bridge-pool-item-status" :class="{ active: !isLocalBridgeExcluded(cp.name) }">{{ isLocalBridgeExcluded(cp.name) ? '未启用' : '已启用' }}</span>
<input type="checkbox" :checked="!isLocalBridgeExcluded(cp.name)" @change="toggleLocalBridgeExcluded(cp.name)" />
</label>
</div>
</div>
</template>
</div>
69 changes: 69 additions & 0 deletions web-ui/styles/bridge-pool.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,43 @@
============================================ */


/* ---- 摘要状态(在卡片标题上方) ---- */
.bridge-pool-summary {
display: flex;
align-items: center;
gap: 5px;
margin-bottom: 4px;
}

.bridge-pool-summary-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 5px;
background: var(--color-brand-light);
color: var(--color-brand-dark);
flex-shrink: 0;
}

.bridge-pool-summary-text {
font-size: 11px;
font-weight: 600;
color: var(--color-text-muted);
letter-spacing: -0.01em;
}

/* ---- 轮询池触发按钮 ---- */
.bridge-pool-trigger {
color: var(--color-brand);
background: rgba(200, 121, 99, 0.08);
}

.bridge-pool-trigger:hover {
background: rgba(200, 121, 99, 0.16);
}

.bridge-pool-panel {
margin-top: 18px;
padding: 18px 20px 16px;
Expand DownExpand Up@@ -195,3 +232,35 @@
padding: 10px 12px 10px 14px;
}
}

/* ---- 模态框中的轮询池 ---- */
.modal-bridge-pool {
max-width: 380px;
}

.modal-title-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 6px;
background: var(--color-brand-light);
color: var(--color-brand-dark);
margin-right: 8px;
vertical-align: middle;
}

.bridge-pool-modal-hint {
font-size: 12px;
color: var(--color-text-muted);
margin-bottom: 14px;
padding-bottom: 12px;
border-bottom: 1px solid var(--color-border-soft);
}

.modal-bridge-pool .bridge-pool-list {
max-height: 320px;
overflow-y: auto;
margin-bottom: 16px;
}
Loading