/* 监控中心视图:默认到期倒计时,可按类别筛选 */ const MonitorView = { template: `
⏳ 所有设置了到期日的资产按剩余天数倒计时排序,越靠前越紧急 红=7天内 橙=30天内 绿=90天内
`, setup() { const tabs = [ { key: '', label: '全部', tip: '所有有到期日的资产' }, { key: 'vps', label: 'VPS', tip: '仅看云服务器' }, { key: 'domain', label: '域名', tip: '仅看域名' }, { key: 'cloudflare', label: 'Cloudflare', tip: '仅看 Cloudflare 资源' }, { key: 'subscription', label: '付费资产', tip: '仅看有费用、需要续费的项目' }, { key: 'ai_agent', label: 'AI账号', tip: '仅看 AI 服务账号' }, { key: 'ssl', label: 'SSL证书', tip: '站点 HTTPS 证书到期倒计时(独立于资产)' }, ]; const hasExpiry = a => a.expiry_date && a.status !== 'cancelled'; // 当前 TAB 下、有到期日的资产(未叠加到期区间子筛选),作为统计卡片与列表的共同基础数据集 const tabScopedAssets = Vue.computed(() => { if (store.monitorTab === 'ssl') return []; let list = store.assets.filter(hasExpiry); const t = store.monitorTab; if (t === 'subscription') list = list.filter(a => a.cost > 0); else if (t) list = list.filter(a => a.asset_type === t); return list; }); const items = Vue.computed(() => { let list = tabScopedAssets.value; const ef = store.monitorExpiryFilter; if (ef === 'soon30') list = list.filter(a => a.days_to_expiry >= 0 && a.days_to_expiry <= 30); else if (ef === 'soon7') list = list.filter(a => a.days_to_expiry >= 0 && a.days_to_expiry <= 7); else if (ef === 'expired') list = list.filter(a => a.days_to_expiry < 0); return [...list].sort((x, y) => (x.days_to_expiry ?? 9999) - (y.days_to_expiry ?? 9999)); }); // SSL 证书:按剩余天数倒计时排序,可叠加状态子筛选 const sslList = Vue.computed(() => { let list = store.siteCerts; const f = store.monitorSslStatusFilter; if (f === 'valid') list = list.filter(c => c.status === 'valid'); else if (f === 'expiring') list = list.filter(c => c.status === 'expiring'); else if (f === 'expired_error') list = list.filter(c => c.status === 'expired' || c.status === 'error'); return [...list].sort((x, y) => (x.days_to_expiry ?? 9999) - (y.days_to_expiry ?? 9999)); }); const sslStats = Vue.computed(() => { const s = { total: store.siteCerts.length, valid: 0, expiring: 0, expired: 0, error: 0 }; for (const c of store.siteCerts) if (s[c.status] !== undefined) s[c.status]++; return s; }); async function recheck(c) { try { await Api.post('/site-certs/' + c.id + '/check', {}); await loadSiteCerts(); } catch (e) { alert('重测失败:' + e.message); } } async function delCert(c) { if (!confirm('删除证书监控「' + c.hostname + '」?')) return; try { await Api.del('/site-certs/' + c.id); await loadSiteCerts(); } catch (e) { alert('删除失败:' + e.message); } } const total = Vue.computed(() => tabScopedAssets.value.length); const stats = Vue.computed(() => { const s = { soon30: 0, soon7: 0, expired: 0 }; for (const a of tabScopedAssets.value) { const d = a.days_to_expiry; if (d === null || d === undefined) continue; if (d < 0) s.expired++; else if (d <= 7) { s.soon7++; s.soon30++; } else if (d <= 30) s.soon30++; } return s; }); const emptyText = Vue.computed(() => { if (store.monitorExpiryFilter) return '当前到期区间筛选下暂无资产,点上方已选中的卡片可取消筛选'; return store.monitorTab ? '该类别暂无到期资产' : '暂无到期资产(资产设置到期日即可出现在这里)'; }); const sslEmptyText = Vue.computed(() => { if (store.monitorSslStatusFilter) return '当前状态筛选下暂无证书,点上方已选中的卡片可取消筛选'; return '暂无证书监控(在「资产 → 域名」的子域名面板点「探测SSL」,或稍后支持直接添加)'; }); function switchTab(k) { store.monitorTab = k; } function toggleExpiryFilter(k) { store.monitorExpiryFilter = store.monitorExpiryFilter === k ? '' : k; } function clearExpiryFilter() { store.monitorExpiryFilter = ''; } function toggleSslStatusFilter(k) { store.monitorSslStatusFilter = store.monitorSslStatusFilter === k ? '' : k; } function clearSslStatusFilter() { store.monitorSslStatusFilter = ''; } function countdownText(d) { if (d === null || d === undefined) return '—'; if (d < 0) return '已过期 ' + Math.abs(d) + ' 天'; if (d === 0) return '今天到期'; return d + ' 天'; } function countdownBadge(d) { if (d === null || d === undefined) return 'bg-slate-500/10 text-slate-500'; if (d < 0 || d === 0 || d <= 7) return 'bg-red-500/10 text-red-600 dark:text-red-400'; if (d <= 30) return 'bg-amber-500/10 text-amber-600 dark:text-amber-400'; if (d <= 90) return 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'; return 'bg-slate-500/10 text-slate-500'; } return { store, Fmt, tabs, items, total, stats, sslList, sslStats, emptyText, sslEmptyText, switchTab, toggleExpiryFilter, clearExpiryFilter, toggleSslStatusFilter, clearSslStatusFilter, countdownText, countdownBadge, recheck, delCert, edit: openAssetEdit, del: deleteAsset }; }, };