mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-01 00:27:30 +00:00
feat(channel-monitor-v2): 渠道监控展示首次聚合进度
在用户端监控页显示 bootstrap 进度横幅与百分比,完成后自动消失; bootstrap 期间加快轮询,空态文案与进度联动。
This commit is contained in:
@@ -76,6 +76,14 @@ export interface MonitorHealth {
|
||||
}
|
||||
}
|
||||
|
||||
/** First-upgrade historical fill for 90m/24h/7d/30d; omitted when complete. */
|
||||
export interface MonitorBootstrap {
|
||||
active: boolean
|
||||
progress_percent: number
|
||||
covered_from?: string
|
||||
target_start?: string
|
||||
}
|
||||
|
||||
export interface MonitorCoverage {
|
||||
requested_start: string
|
||||
coverage_start: string
|
||||
@@ -84,6 +92,8 @@ export interface MonitorCoverage {
|
||||
aggregation_lag_seconds: number
|
||||
coverage_complete: boolean
|
||||
bucket_seconds: number
|
||||
/** Present while initial aggregation has not covered the 30d product window. */
|
||||
bootstrap?: MonitorBootstrap | null
|
||||
}
|
||||
|
||||
export interface MonitorConfig {
|
||||
|
||||
@@ -5,6 +5,13 @@ export default {
|
||||
updating: 'Updating data',
|
||||
updatedTo: 'Updated to {time}',
|
||||
partialCoverage: 'Partial historical coverage',
|
||||
bootstrap: {
|
||||
title: 'Building historical monitor data',
|
||||
description:
|
||||
'On first enable, passive aggregation silently fills the 90m, 24h, 7d, and 30d windows in the background. All ranges become complete once this finishes.',
|
||||
progress: '{percent}% complete',
|
||||
working: 'Aggregating in the background…',
|
||||
},
|
||||
timeRange: 'Time range',
|
||||
clearFilters: 'Reset',
|
||||
refreshingFilters: 'Filters changed; refreshing matrix, trend, and details…',
|
||||
|
||||
@@ -5,6 +5,13 @@ export default {
|
||||
updating: '正在更新数据',
|
||||
updatedTo: '更新至 {time}',
|
||||
partialCoverage: '部分历史覆盖',
|
||||
bootstrap: {
|
||||
title: '正在补齐历史监控数据',
|
||||
description:
|
||||
'首次启用被动监控时,系统会在后台静默聚合 90 分钟、24 小时、7 天与 30 天窗口;完成后可切换全部时间范围。',
|
||||
progress: '进度 {percent}%',
|
||||
working: '后台聚合中…',
|
||||
},
|
||||
timeRange: '时间范围',
|
||||
clearFilters: '重置',
|
||||
refreshingFilters: '筛选条件已变化,正在刷新矩阵、趋势和明细…',
|
||||
|
||||
@@ -29,11 +29,18 @@
|
||||
</span>
|
||||
<span v-else class="text-gray-400">{{ t('common.loading') }}</span>
|
||||
<span
|
||||
v-if="snapshot && !snapshot.coverage.coverage_complete"
|
||||
v-if="snapshot && !snapshot.coverage.coverage_complete && !bootstrapActive"
|
||||
class="badge badge-warning"
|
||||
>
|
||||
{{ t('channelMonitorV2.partialCoverage') }}
|
||||
</span>
|
||||
<span
|
||||
v-if="bootstrapActive"
|
||||
class="badge badge-primary inline-flex items-center gap-1"
|
||||
>
|
||||
<LoadingSpinner size="sm" />
|
||||
{{ t('channelMonitorV2.bootstrap.progress', { percent: bootstrapPercent }) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -47,6 +54,41 @@
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- First-upgrade silent backfill: show until 30d product window is covered -->
|
||||
<div
|
||||
v-if="bootstrapActive"
|
||||
class="border-b border-blue-100 bg-blue-50/90 px-5 py-3 dark:border-blue-900/40 dark:bg-blue-950/40 sm:px-6"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-semibold text-blue-900 dark:text-blue-100">
|
||||
{{ t('channelMonitorV2.bootstrap.title') }}
|
||||
</p>
|
||||
<p class="mt-0.5 text-xs text-blue-800/80 dark:text-blue-200/80">
|
||||
{{ t('channelMonitorV2.bootstrap.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="shrink-0 text-xs font-medium tabular-nums text-blue-700 dark:text-blue-300">
|
||||
{{ t('channelMonitorV2.bootstrap.progress', { percent: bootstrapPercent }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mt-2.5 h-1.5 overflow-hidden rounded-full bg-blue-200/80 dark:bg-blue-900/60"
|
||||
role="progressbar"
|
||||
:aria-valuenow="bootstrapPercent"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
:aria-label="t('channelMonitorV2.bootstrap.working')"
|
||||
>
|
||||
<div
|
||||
class="h-full rounded-full bg-blue-500 transition-[width] duration-500 ease-out dark:bg-blue-400"
|
||||
:style="{ width: `${bootstrapPercent}%` }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Single compact toolbar row: range · filters · view controls -->
|
||||
<div class="monitor-toolbar flex flex-nowrap items-center gap-1.5 overflow-x-auto px-4 py-3 sm:gap-2 sm:px-5">
|
||||
<div
|
||||
@@ -394,8 +436,20 @@
|
||||
|
||||
<div v-if="tabLoading" class="empty-state py-10 text-sm text-gray-400">{{ t('common.loading') }}</div>
|
||||
<div v-else-if="activeRowsEmpty" class="empty-state py-10">
|
||||
<p class="empty-state-title text-base">{{ t('channelMonitorV2.empty.title') }}</p>
|
||||
<p class="empty-state-description">{{ t('channelMonitorV2.empty.description') }}</p>
|
||||
<p class="empty-state-title text-base">
|
||||
{{
|
||||
bootstrapActive
|
||||
? t('channelMonitorV2.bootstrap.title')
|
||||
: t('channelMonitorV2.empty.title')
|
||||
}}
|
||||
</p>
|
||||
<p class="empty-state-description">
|
||||
{{
|
||||
bootstrapActive
|
||||
? t('channelMonitorV2.bootstrap.description')
|
||||
: t('channelMonitorV2.empty.description')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -585,6 +639,13 @@ const activeRowsEmpty = computed(() =>
|
||||
? errorRows.value.length === 0
|
||||
: userRows.value.length === 0
|
||||
)
|
||||
/** First-upgrade backfill toward 90m/24h/7d/30d; banner hides when backend omits bootstrap. */
|
||||
const bootstrapActive = computed(() => Boolean(snapshot.value?.coverage?.bootstrap?.active))
|
||||
const bootstrapPercent = computed(() => {
|
||||
const raw = snapshot.value?.coverage?.bootstrap?.progress_percent
|
||||
if (typeof raw !== 'number' || Number.isNaN(raw)) return 0
|
||||
return Math.min(100, Math.max(0, Math.round(raw)))
|
||||
})
|
||||
const matrixRows = computed(() => {
|
||||
const items = matrix.value?.items || []
|
||||
// platform_group views should only show real groups, never bare platform placeholders.
|
||||
@@ -740,12 +801,15 @@ function scheduleAutoRefresh() {
|
||||
window.clearInterval(autoRefreshTimer)
|
||||
autoRefreshTimer = null
|
||||
}
|
||||
const seconds = snapshot.value?.config?.refresh_interval_seconds || 300
|
||||
// Poll faster while first-upgrade bootstrap is filling 90m→30d so the progress bar moves.
|
||||
const seconds = bootstrapActive.value
|
||||
? 10
|
||||
: snapshot.value?.config?.refresh_interval_seconds || 300
|
||||
autoRefreshTimer = window.setInterval(() => {
|
||||
if (!loading.value && !refreshing.value) {
|
||||
void reload(true)
|
||||
}
|
||||
}, Math.max(60, seconds) * 1000)
|
||||
}, Math.max(bootstrapActive.value ? 10 : 60, seconds) * 1000)
|
||||
}
|
||||
function drillModel(row: MonitorModelRow) {
|
||||
filter.value.platforms = [row.platform]
|
||||
|
||||
Reference in New Issue
Block a user