feat(scheduler): per-source cadence + source-health log with failure diagnosis + UI copy
This commit is contained in:
18
frontend/dist/assets/index-CUQk4rwt.js
vendored
18
frontend/dist/assets/index-CUQk4rwt.js
vendored
File diff suppressed because one or more lines are too long
18
frontend/dist/assets/index-DOVbTpbx.js
vendored
Normal file
18
frontend/dist/assets/index-DOVbTpbx.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
frontend/dist/index.html
vendored
2
frontend/dist/index.html
vendored
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0b1018" />
|
||||
<title>SET50 Signal Lab</title>
|
||||
<script type="module" crossorigin src="/assets/index-CUQk4rwt.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DOVbTpbx.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-z73iDem3.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -53,6 +53,9 @@ const factorRows = computed(() => factorData.value?.factors ?? [])
|
||||
// real multi-theme dashboard (3 themes + macro + sources)
|
||||
const dashboardThemes = computed(() => dashData.value?.themes ?? [])
|
||||
const dashboardSources = computed(() => dashData.value?.sources ?? [])
|
||||
// data scheduler source-health log (refresh cadence + failure diagnosis)
|
||||
const sourceHealth = ref([])
|
||||
const sourceHealthLoaded = ref(false)
|
||||
const dashboardMacro = computed(() => dashData.value?.macro ?? {})
|
||||
const sourceCount = computed(() => dashboardSources.value.length)
|
||||
const factorCount = computed(() => dashData.value?.source_summary?.factor_keys ?? sourceCount.value)
|
||||
@@ -403,6 +406,34 @@ async function loadBacktestReadiness() {
|
||||
} catch { btReadiness.value = null }
|
||||
}
|
||||
|
||||
async function loadSourceHealth() {
|
||||
try {
|
||||
const body = await fetchJson('/api/v1/scheduler/sources')
|
||||
sourceHealth.value = body.sources || []
|
||||
} catch { sourceHealth.value = [] }
|
||||
sourceHealthLoaded.value = true
|
||||
}
|
||||
|
||||
const healthCategoryLabel = (cat) => ({
|
||||
ok: 'ปกติ', network: 'เครือข่ายขัดข้อง', timeout: 'หมดเวลา',
|
||||
http: 'HTTP error', parse: 'รูปแบบข้อมูลผิด', structure: 'หน้าเว็บเปลี่ยนโครงสร้าง',
|
||||
auth: 'สิทธิ์/ยืนยันตัวตน', other: 'อื่น ๆ',
|
||||
})[cat] || cat
|
||||
|
||||
async function copyHealthLine(entry) {
|
||||
const line = `[${entry.at}] ${entry.label} (${entry.key}) — ${entry.ok ? 'OK' : 'FAIL: ' + healthCategoryLabel(entry.category)} ${entry.detail ? '| ' + entry.detail : ''}`
|
||||
try {
|
||||
await navigator.clipboard.writeText(line)
|
||||
notice.value = `คัดลอกสาเหตุของ ${entry.key} แล้ว`
|
||||
} catch {
|
||||
notice.value = line // fallback: show raw text as a notice
|
||||
}
|
||||
}
|
||||
|
||||
function appendHealthRationale(entry) {
|
||||
return entry.ok ? '' : ` (สาเหตุน่าจะ: ${healthCategoryLabel(entry.category)})`
|
||||
}
|
||||
|
||||
async function runBacktest() {
|
||||
btLoading.value = true
|
||||
btResult.value = null
|
||||
@@ -489,7 +520,7 @@ async function recordPaperEntry() {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => { await loadDashboard(); await Promise.all([loadBacktestRuns(), loadBacktestReadiness()]) })
|
||||
onMounted(async () => { await loadDashboard(); await Promise.all([loadBacktestRuns(), loadBacktestReadiness(), loadSourceHealth()]) })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -510,6 +541,7 @@ onMounted(async () => { await loadDashboard(); await Promise.all([loadBacktestRu
|
||||
<a class="nav-item" href="#signals"><span class="nav-glyph">↗</span>สัญญาณ</a>
|
||||
<a class="nav-item" href="#factors"><span class="nav-glyph">∿</span>ปัจจัย</a>
|
||||
<a class="nav-item" href="#lineage"><span class="nav-glyph">⌁</span>ที่มาข้อมูล</a>
|
||||
<a class="nav-item" href="#health"><span class="nav-glyph">⛨</span>สถานะข้อมูล</a>
|
||||
<a class="nav-item" href="#research-run"><span class="nav-glyph">✓</span>งานวิจัย</a>
|
||||
<a class="nav-item" href="#simulation"><span class="nav-glyph">➛</span>จำลอง</a>
|
||||
</nav>
|
||||
@@ -684,6 +716,42 @@ onMounted(async () => { await loadDashboard(); await Promise.all([loadBacktestRu
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel health-panel" id="health">
|
||||
<div class="panel-header signal-header">
|
||||
<div>
|
||||
<div class="section-kicker">สถานะการดึงข้อมูล</div>
|
||||
<h2>Log — สถานะแหล่งข้อมูล</h2>
|
||||
<p class="panel-subtitle">ผลการดึงข้อมูลครั้งล่าสุดของแต่ละแหล่ง โดยระบบวิเคราะห์สาเหตุให้อัตโนมัติ (เครือข่าย / หมดเวลา / หน้าเว็บเปลี่ยนโครงสร้าง / รูปแบบข้อมูล เป็นต้น) — กดปุ่ม "คัดลอก" เพื่อ copy สาเหตุไปแจ้ง/ตรวจสอบได้ทันที.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sourceHealth.length === 0" class="empty-research muted-cell">ยังไม่มี log — รอรอบ refresh ถัดไป (ปกติ ~ทุกวันสำหรับราคา, ~รายเดือน/ไตรมาสสำหรับปัจจัย).</div>
|
||||
<div v-else>
|
||||
<table class="source-table">
|
||||
<thead><tr><th>แหล่ง</th><th>ผลลัพธ์</th><th>สาเหตุ</th><th>เวลา</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(e, i) in sourceHealth.slice(0, 20)" :key="i">
|
||||
<td class="source-name">{{ e.label }}<div class="muted-cell" style="font-size:11px">{{ e.key }}</div></td>
|
||||
<td>
|
||||
<span v-if="e.ok" class="status-tag" style="background:#1a7f37;color:#fff">OK</span>
|
||||
<span v-else class="status-tag warning-tag">FAIL</span>
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="e.ok">—</template>
|
||||
<template v-else>
|
||||
<div>{{ healthCategoryLabel(e.category) }}{{ appendHealthRationale(e) }}</div>
|
||||
<div v-if="e.detail" class="muted-cell" style="font-size:11px;word-break:break-word">{{ e.detail.slice(0, 160) }}</div>
|
||||
</template>
|
||||
</td>
|
||||
<td class="muted-cell">{{ e.at ? formatDate(e.at) : '—' }}</td>
|
||||
<td>
|
||||
<button v-if="!e.ok" class="primary-btn" style="padding:2px 8px" @click="copyHealthLine(e)">คัดลอกสาเหตุ</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Sections 01-04 (tourism-only surprise, provenance, signal table, frozen research) removed per user: superseded by the 3-theme panel + sources table above. -->
|
||||
<section class="panel sim-panel" id="simulation">
|
||||
<div class="panel-header signal-header">
|
||||
|
||||
Reference in New Issue
Block a user