226 lines
7.0 KiB
JavaScript
226 lines
7.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* skyeye/scripts/weekly-scan.js
|
|
* 天眼周六大巡检主脚本 — 汇总所有 Phase 结果,生成报告
|
|
* Phase 5 of weekly scan: Generate Report
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = path.resolve(__dirname, '../..');
|
|
const SKYEYE_DIR = path.resolve(__dirname, '..');
|
|
const SCAN_REPORT_DIR = path.join(SKYEYE_DIR, 'scan-report');
|
|
const LOGS_DIR = path.join(SKYEYE_DIR, 'logs');
|
|
|
|
function getTimestamp() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
function getDateStr() {
|
|
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
}
|
|
|
|
function getBeijingTime() {
|
|
return new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
|
}
|
|
|
|
function loadJSON(filePath) {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function saveJSON(filePath, data) {
|
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
}
|
|
|
|
function findLatestLog(logDir, prefix) {
|
|
if (!fs.existsSync(logDir)) return null;
|
|
const files = fs.readdirSync(logDir)
|
|
.filter(f => f.startsWith(prefix) && f.endsWith('.json'))
|
|
.sort()
|
|
.reverse();
|
|
if (files.length === 0) return null;
|
|
return loadJSON(path.join(logDir, files[0]));
|
|
}
|
|
|
|
function countGuards() {
|
|
const guardsDir = path.join(SKYEYE_DIR, 'guards');
|
|
if (!fs.existsSync(guardsDir)) return { total: 0, active: 0, suspended: 0 };
|
|
|
|
const files = fs.readdirSync(guardsDir)
|
|
.filter(f => f.endsWith('.json') && f !== 'guard-template.json');
|
|
|
|
let active = 0;
|
|
let suspended = 0;
|
|
|
|
for (const file of files) {
|
|
const guard = loadJSON(path.join(guardsDir, file));
|
|
if (guard) {
|
|
if (guard.status === 'active') active++;
|
|
else if (guard.status === 'suspended' || guard.mode === 'suspended') suspended++;
|
|
}
|
|
}
|
|
|
|
return { total: files.length, active, suspended };
|
|
}
|
|
|
|
function generateReport() {
|
|
console.log(`[SkyEye Weekly Scan] Generating weekly scan report`);
|
|
console.log(`[SkyEye Weekly Scan] Beijing Time: ${getBeijingTime()}`);
|
|
|
|
const dateStr = getDateStr();
|
|
const weeklyLogDir = path.join(LOGS_DIR, 'weekly');
|
|
|
|
// Load Phase results from logs
|
|
const scanResult = findLatestLog(weeklyLogDir, 'scan-');
|
|
const auditResult = findLatestLog(weeklyLogDir, 'quota-audit-');
|
|
const optimizeResult = findLatestLog(weeklyLogDir, 'optimize-');
|
|
const healResult = findLatestLog(weeklyLogDir, 'self-heal-');
|
|
|
|
const guardStatus = countGuards();
|
|
|
|
// Calculate next Saturday 20:00 CST
|
|
const now = new Date();
|
|
const dayOfWeek = now.getUTCDay();
|
|
const daysUntilSaturday = (6 - dayOfWeek + 7) % 7 || 7;
|
|
const nextSaturday = new Date(now);
|
|
nextSaturday.setUTCDate(now.getUTCDate() + daysUntilSaturday);
|
|
nextSaturday.setUTCHours(12, 0, 0, 0); // 20:00 CST = 12:00 UTC
|
|
|
|
const report = {
|
|
report_id: `SKYEYE-SCAN-${dateStr}`,
|
|
scan_time: getTimestamp(),
|
|
scan_time_beijing: getBeijingTime(),
|
|
scan_duration_seconds: 0,
|
|
|
|
infrastructure_status: {
|
|
total_services: 5,
|
|
healthy: 5,
|
|
degraded: 0,
|
|
down: 0
|
|
},
|
|
|
|
quota_status: {
|
|
github_actions: {
|
|
used_percent: auditResult?.services?.github_actions?.usage_percent || 0,
|
|
remaining_minutes: 2000,
|
|
trend: 'stable',
|
|
recommendation: 'maintain_current_frequency'
|
|
},
|
|
google_drive: {
|
|
used_percent: auditResult?.services?.google_drive?.usage_percent || 0,
|
|
recommendation: 'no_action_needed'
|
|
},
|
|
notion_api: {
|
|
status: auditResult?.services?.notion_api?.status || 'healthy',
|
|
recommendation: 'no_action_needed'
|
|
},
|
|
gemini: {
|
|
used_percent: auditResult?.services?.gemini?.usage_percent || 0,
|
|
recommendation: 'no_action_needed'
|
|
}
|
|
},
|
|
|
|
guard_status: {
|
|
total_guards: guardStatus.total,
|
|
active: guardStatus.active,
|
|
suspended: guardStatus.suspended,
|
|
adjustments_made: optimizeResult?.adjustments || []
|
|
},
|
|
|
|
self_heal_actions: {
|
|
files_cleaned: healResult?.summary?.files_cleaned || 0,
|
|
configs_repaired: healResult?.summary?.configs_repaired || 0,
|
|
guards_restarted: healResult?.summary?.guards_restarted || 0
|
|
},
|
|
|
|
phase_details: {
|
|
phase1_scan: scanResult?.summary || null,
|
|
phase2_audit: auditResult?.overall_status || null,
|
|
phase3_optimize: optimizeResult?.summary || null,
|
|
phase4_heal: healResult?.summary || null
|
|
},
|
|
|
|
alerts: [],
|
|
human_action_required: [],
|
|
|
|
hibernation_status: {
|
|
deployed: fs.existsSync(path.join(ROOT, 'skyeye/hibernation')),
|
|
daily_checkpoints: (() => {
|
|
const cpDir = path.join(ROOT, 'skyeye/hibernation/checkpoints');
|
|
try {
|
|
return fs.existsSync(cpDir)
|
|
? fs.readdirSync(cpDir).filter(f => f.startsWith('daily-cp-') && f.endsWith('.json')).length
|
|
: 0;
|
|
} catch (e) { return 0; }
|
|
})(),
|
|
weekly_snapshots: (() => {
|
|
const snapDir = path.join(ROOT, 'skyeye/hibernation/weekly-snapshots');
|
|
try {
|
|
return fs.existsSync(snapDir)
|
|
? fs.readdirSync(snapDir).filter(f => f.startsWith('weekly-snapshot-') && f.endsWith('.json')).length
|
|
: 0;
|
|
} catch (e) { return 0; }
|
|
})(),
|
|
upgrade_packs: (() => {
|
|
const upDir = path.join(ROOT, 'skyeye/hibernation/upgrade-packs');
|
|
try {
|
|
return fs.existsSync(upDir)
|
|
? fs.readdirSync(upDir).filter(f => f.startsWith('upgrade-pack-') && f.endsWith('.json')).length
|
|
: 0;
|
|
} catch (e) { return 0; }
|
|
})()
|
|
},
|
|
|
|
next_scan: nextSaturday.toISOString()
|
|
};
|
|
|
|
// Collect alerts from all phases
|
|
if (scanResult?.issues) {
|
|
for (const issue of scanResult.issues) {
|
|
if (issue.severity === 'error') {
|
|
report.alerts.push(issue.detail);
|
|
}
|
|
}
|
|
}
|
|
if (auditResult?.services) {
|
|
for (const [, svc] of Object.entries(auditResult.services)) {
|
|
report.alerts.push(...(svc.alerts || []));
|
|
}
|
|
}
|
|
|
|
// Determine infrastructure health
|
|
if (report.alerts.length > 0) {
|
|
const criticalAlerts = report.alerts.filter(a =>
|
|
a.includes('90%') || a.includes('critical') || a.includes('严重')
|
|
);
|
|
if (criticalAlerts.length > 0) {
|
|
report.infrastructure_status.degraded = criticalAlerts.length;
|
|
report.infrastructure_status.healthy -= criticalAlerts.length;
|
|
}
|
|
}
|
|
|
|
// Write report
|
|
if (!fs.existsSync(SCAN_REPORT_DIR)) fs.mkdirSync(SCAN_REPORT_DIR, { recursive: true });
|
|
const reportPath = path.join(SCAN_REPORT_DIR, `${dateStr}-weekly-scan.json`);
|
|
saveJSON(reportPath, report);
|
|
|
|
console.log(`[SkyEye Weekly Scan] Report generated: ${reportPath}`);
|
|
console.log(`[SkyEye Weekly Scan] Report ID: ${report.report_id}`);
|
|
console.log(`[SkyEye Weekly Scan] Guards: ${guardStatus.active}/${guardStatus.total} active`);
|
|
console.log(`[SkyEye Weekly Scan] Alerts: ${report.alerts.length}`);
|
|
console.log(`[SkyEye Weekly Scan] Human action required: ${report.human_action_required.length}`);
|
|
console.log(`[SkyEye Weekly Scan] Next scan: ${report.next_scan}`);
|
|
|
|
console.log('---REPORT_JSON---');
|
|
console.log(JSON.stringify(report, null, 2));
|
|
|
|
return report;
|
|
}
|
|
|
|
generateReport();
|