Fix worker logs not appearing + align settings form
Docker Build and Push / check-secrets (push) Successful in 5s
Docker Build and Push / build-and-push (cpu, latest) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 12.6) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 11.8) (push) Has been skipped
Docker Build and Push / build-and-push (cuda, 12.8) (push) Has been skipped
Docker Build and Push / build-and-push (directml, latest) (push) Has been skipped
Build Windows CPU / build (push) Has been cancelled
Build Windows CUDA 11.8 / build (push) Has been cancelled
Build Windows CUDA 12.6 / build (push) Has been cancelled
Build Windows CUDA 12.8 / build (push) Has been cancelled
Build Windows DirectML / build (push) Has been cancelled

- Add _WorkerLogHandler that bridges Python logging → WorkerLogBuffer
  (previously only StreamHandler was used, logs went to stdout but not
  the buffer the frontend polls)
- Rewrite settings form: proper form-grid layout, form submit handler,
  App Secret show/hide toggle, hints below number fields, footer save button
- workerStatus shows configured/unconfigured text instead of badge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
liuchen
2026-06-27 14:34:07 +08:00
co-authored by Claude Haiku 4.5
parent e512a842e4
commit abd5d0ce47
2 changed files with 68 additions and 45 deletions
+53 -43
View File
@@ -561,41 +561,56 @@
<!-- Page: Settings -->
<div class="page" id="page-settings">
<div class="panel" style="margin-bottom:20px">
<div class="panel">
<div class="panel-head">
<h2>平台 Worker 配置</h2>
<div id="workerStatus" class="badge neutral"><span class="bdot"></span>未运行</div>
<span id="workerStatus" class="subtle">○ 未配置</span>
</div>
<div class="panel-body">
<div class="form-grid">
<div class="field full">
<label>平台地址 (Base URL)</label>
<input type="url" id="s-base-url" placeholder="https://your-platform.com" />
</div>
<form id="settings-form" onsubmit="saveSettings(event)">
<div class="panel-body" style="display:grid;gap:16px">
<div class="field">
<label>App ID</label>
<input type="text" id="s-app-id" placeholder="your_app_id" autocomplete="off" />
<label for="s-base-url">平台地址(Base URL)</label>
<input type="url" id="s-base-url" name="platform_base_url"
placeholder="https://your-platform.com" autocomplete="off" />
<span class="hint">留空则不启动 Worker 轮询,仅保留手动提交任务的能力。</span>
</div>
<div class="field">
<label>App Secret</label>
<input type="text" id="s-app-secret" placeholder="your_app_secret" autocomplete="new-password" />
<div class="form-grid">
<div class="field">
<label for="s-app-id">App ID</label>
<input type="text" id="s-app-id" name="platform_app_id"
placeholder="your_app_id" autocomplete="off" />
</div>
<div class="field">
<label for="s-app-secret">App Secret</label>
<div style="display:flex;gap:8px">
<input type="password" id="s-app-secret" name="platform_app_secret"
placeholder="your_app_secret" autocomplete="new-password" style="flex:1" />
<button type="button" id="toggle-secret" class="button secondary"
style="min-height:40px;padding:0 12px;font-size:12px;flex-shrink:0"
onclick="toggleSecret()">显示</button>
</div>
</div>
</div>
<div class="field">
<label>并发槽数 (Concurrency)</label>
<input type="number" id="s-concurrency" min="0" max="8" value="1" />
<span class="hint">0 = 关闭平台拉取,仅保留手动提交</span>
</div>
<div class="field">
<label>轮询间隔(秒)</label>
<input type="number" id="s-poll-interval" min="1" max="60" value="5" />
<div class="form-grid">
<div class="field">
<label for="s-concurrency">并发槽数(Concurrency)</label>
<input type="number" id="s-concurrency" name="worker_concurrency"
min="0" max="8" value="1" />
<span class="hint">同时认领的任务数。设为 0 表示不启动 Worker 轮询。</span>
</div>
<div class="field">
<label for="s-poll-interval">轮询间隔(秒)</label>
<input type="number" id="s-poll-interval" name="worker_poll_interval"
min="1" max="60" value="5" />
<span class="hint">每隔多少秒向平台认领一次任务。</span>
</div>
</div>
</div>
<div style="margin-top:16px;display:flex;gap:10px">
<button class="button" onclick="saveSettings()">保存并重启 Worker</button>
<div style="padding:14px 18px;border-top:1px solid var(--line);display:flex;justify-content:flex-end">
<button type="submit" class="button">保存并重启 Worker</button>
</div>
</div>
</form>
</div>
</div>
<!-- 页面:Worker 日志 -->
@@ -1089,20 +1104,15 @@ async function loadSettings() {
document.getElementById('s-app-secret').value = d.platform_app_secret || '';
document.getElementById('s-concurrency').value = d.worker_concurrency ?? 1;
document.getElementById('s-poll-interval').value = d.worker_poll_interval ?? 5;
const status = await api('/api/status');
const configured = !!(d.platform_base_url && d.platform_app_id && d.platform_app_secret);
const el = document.getElementById('workerStatus');
if (status.worker_running) {
el.className = 'badge good';
el.innerHTML = '<span class="bdot"></span>运行中';
} else {
el.className = 'badge neutral';
el.innerHTML = '<span class="bdot"></span>未运行';
}
el.textContent = configured ? '● Worker 已配置' : '○ 未配置';
el.style.color = configured ? 'var(--good)' : 'var(--muted)';
} catch(e) {}
}
async function saveSettings() {
async function saveSettings(event) {
if (event) event.preventDefault();
const body = {
platform_base_url: document.getElementById('s-base-url').value.trim(),
platform_app_id: document.getElementById('s-app-id').value.trim(),
@@ -1112,18 +1122,18 @@ async function saveSettings() {
};
try {
const r = await api('/api/settings', { method: 'PUT', body: JSON.stringify(body) });
const el = document.getElementById('workerStatus');
if (r.worker_running) {
el.className = 'badge good';
el.innerHTML = '<span class="bdot"></span>运行中';
} else {
el.className = 'badge neutral';
el.innerHTML = '<span class="bdot"></span>未运行';
}
showToast(r.worker_running ? 'Worker 已启动' : '设置已保存(Worker 未启动)');
await loadSettings();
} catch(e) { showToast('保存失败: ' + e.message); }
}
function toggleSecret() {
const inp = document.getElementById('s-app-secret');
const show = inp.type === 'password';
inp.type = show ? 'text' : 'password';
document.getElementById('toggle-secret').textContent = show ? '隐藏' : '显示';
}
// ── Worker 日志轮询(同 df-autotest)──────────────────────────
const LEVEL_CLS = { WARNING: 'll-WARNING', ERROR: 'll-ERROR', CRITICAL: 'll-CRITICAL', DEBUG: 'll-DEBUG' };
+15 -2
View File
@@ -609,6 +609,19 @@ class WorkerLogBuffer:
return lines, (lines[-1]["seq"] if lines else seq)
class _WorkerLogHandler(logging.Handler):
"""把 Python logging 桥接到 WorkerLogBuffer,供前端轮询。"""
def __init__(self, buf: WorkerLogBuffer) -> None:
super().__init__()
self._buf = buf
def emit(self, record: logging.LogRecord) -> None:
try:
self._buf.push(record.levelname, self.format(record))
except Exception:
pass
class VSRTaskWorker:
"""轮询外部平台认领 VSR 任务,执行后上报结果。"""
@@ -773,8 +786,8 @@ class WorkerManager:
def __init__(self) -> None:
self._tasks: list[asyncio.Task] = []
self.log_buffer = WorkerLogBuffer()
_handler = logging.StreamHandler()
_handler.setFormatter(logging.Formatter("%(asctime)s [vsr_worker] %(message)s", datefmt="%H:%M:%S"))
_handler = _WorkerLogHandler(self.log_buffer)
_handler.setFormatter(logging.Formatter("%(asctime)s %(message)s", datefmt="%H:%M:%S"))
_wl = logging.getLogger(__name__)
_wl.setLevel(logging.DEBUG)
_wl.addHandler(_handler)