74 lines
9.6 KiB
JavaScript
74 lines
9.6 KiB
JavaScript
import { loadConfig } from './config.mjs';
|
||
import { incidentImpact, serviceImpact } from '../shared/impact.mjs';
|
||
|
||
// Deterministic illustrative data, isolated from monitoring and live storage.
|
||
export function demoSnapshot(range, now = Date.now()) {
|
||
const config = loadConfig({});
|
||
const dayMs = 86400000;
|
||
const today = Math.floor(now / dayMs) * dayMs;
|
||
const services = config.services.map((service, serviceIndex) => {
|
||
const history = Array.from({ length: range }, (_, index) => {
|
||
const start = today - (range - index - 1) * dayMs;
|
||
const failed = service.id === 'board' && start === today - 8 * dayMs;
|
||
return { date: new Date(start).toISOString().slice(0, 10), status: failed ? 'down' : 'operational', uptime: failed ? 95.83 : 100, checks: Math.max(1, Math.floor((Math.min(now, start + dayMs) - start) / 60000)), latencyMs: 110 + serviceIndex * 40, coverage: 100 };
|
||
});
|
||
const responseHistory = Array.from({ length: 24 }, (_, index) => ({ time: now - (24 - index) * 3600000, checks: 60, uptime: 100, latencyMs: 80 + serviceIndex * 42 + Math.round((1 + Math.sin(index / 2 + serviceIndex)) * 65) }));
|
||
const checks = history.reduce((sum, item) => sum + item.checks, 0);
|
||
const uptime = Number((history.reduce((sum, item) => sum + item.checks * item.uptime, 0) / checks).toFixed(2));
|
||
return {
|
||
id: service.id, icon: service.icon, name: service.name, description: service.description,
|
||
publicUrl: service.publicUrl, kind: service.kind, functional: false, impactPolicy: service.impactPolicy,
|
||
configured: true, status: service.id === 'board' ? 'maintenance' : service.id === 'pwa' ? 'degraded' : 'operational', availabilityStatus: 'operational', activeMaintenance: service.id === 'board' ? ['demo-maintenance-active'] : [],
|
||
scenario: service.scenario ? { type: service.scenario.type, status: 'operational', lastCheckedAt: now, staleAfterSeconds: 900, intervalSeconds: 300, stale: false, reason: null, mode: service.id === 'api' ? 'http' : 'browser', pending: false, steps: (service.id === 'board' ? ['config', 'stops', 'departures', 'render'] : service.id === 'api' ? ['cities', 'stops', 'departures'] : ['cities', 'stops', 'departures', 'render']).map(id => ({ id, status: 'passed', latencyMs: 120, httpCode: 200 })) } : null,
|
||
stale: false, pending: false,
|
||
lastCheckedAt: now, latencyMs: 110 + serviceIndex * 40, avgLatencyMs: 110 + serviceIndex * 40,
|
||
httpCode: 200, reason: null, uptime, checks, history, responseHistory,
|
||
recentChecks: Array.from({ length: 12 }, (_, index) => ({ time: now - index * 60000, ok: true, latencyMs: 110 + serviceIndex * 40 + index * 2, httpCode: 200, reason: null })),
|
||
};
|
||
});
|
||
const pwa = services.find(service => service.id === 'pwa');
|
||
pwa.scenario.status = 'down'; pwa.scenario.reason = 'content';
|
||
pwa.scenario.steps = pwa.scenario.steps.map(step => ({ ...step, status: step.id === 'departures' ? 'failed' : step.id === 'render' ? 'waiting' : 'passed' }));
|
||
services.forEach(service => { service.impact = serviceImpact(service, services); });
|
||
services.forEach((service, index) => {
|
||
const since = new Date(service.history[0].date).getTime();
|
||
const boardRecovery = today - 8 * dayMs + 12 * 3600000;
|
||
const duration = service.id === 'pwa' ? 35 * 60000 : service.id === 'board' && range > 8 ? 3600000 : 0;
|
||
service.reliability = { downtimeMs: duration, longestDowntimeMs: duration, incidentCount: Number(duration > 0), downtimeIncomplete: false, lastRecoveryAt: service.id === 'board' ? boardRecovery : null, lastRecoveryScope: service.id === 'board' ? 'availability' : null, observedMs: now - since, coverage: 100, since, asOf: now };
|
||
service.latencyHistory = service.history.map((day, i) => ({ time: new Date(day.date).getTime(), checks: day.checks, uptime: day.uptime, latencyMs: Math.round(day.latencyMs * (1 + Math.sin(i / 4 + index) * .18)) }));
|
||
const stats = (points, change) => {
|
||
const samples = points.reduce((sum, point) => sum + point.checks, 0);
|
||
const averageMs = Math.round(points.reduce((sum, point) => sum + point.latencyMs * point.checks, 0) / Math.max(1, samples));
|
||
return { averageMs, p95Ms: Math.round(Math.max(...points.map(point => point.latencyMs)) * 1.1), samples, previousAverageMs: Math.round(averageMs / (1 + change / 100)), changePercent: change };
|
||
};
|
||
service.latency = stats(service.latencyHistory, index % 2 ? 12.5 : -8.2);
|
||
service.latency24h = stats(service.responseHistory, index % 2 ? 7.1 : -4.6);
|
||
});
|
||
const active = {
|
||
id: 'example-active', source: 'monitor', scope: 'scenario', serviceId: 'pwa', startedAt: now - 35 * 60000, resolvedAt: null,
|
||
title: { ru: 'Расписание в приложении загружается с перебоями', en: 'Timetable loading disrupted in the app' },
|
||
message: { ru: 'Пример инцидента: проверка расписания не проходит.', en: 'Sample incident: the timetable check fails.' },
|
||
updates: [
|
||
{ id: 'demo-active-1', source: 'monitor', stage: 'detected', createdAt: now - 35 * 60000, message: { ru: 'Пример: повторные проверки подтвердили проблему с загрузкой расписания.', en: 'Sample: repeated checks confirmed a timetable loading problem.' } },
|
||
{ id: 'demo-active-2', source: 'manual', stage: 'investigating', createdAt: now - 25 * 60000, message: { ru: 'Пример: команда проверяет загрузку отправлений для остановок.', en: 'Sample: the team is investigating stop departure loading.' } },
|
||
{ id: 'demo-active-3', source: 'manual', stage: 'identified', createdAt: now - 10 * 60000, message: { ru: 'Пример: найдена причина задержек. Готовим исправление; сохранённое расписание может быть доступно.', en: 'Sample: the cause of delays has been identified. A fix is being prepared; saved timetables may be available.' } },
|
||
],
|
||
};
|
||
active.impact = incidentImpact(active, services);
|
||
return {
|
||
generatedAt: now, lastCheckAt: now, intervalSeconds: config.intervalSeconds, staleAfterSeconds: config.staleAfterSeconds,
|
||
failureThreshold: config.failureThreshold, recoveryThreshold: config.recoveryThreshold, slowMs: config.slowMs,
|
||
scenarioIntervalSeconds: 300, range, demo: true, overall: 'degraded', services,
|
||
incidents: [active, { id: 'example-1', source: 'monitor', scope: 'availability', serviceId: 'board', startedAt: today - 8 * dayMs + 11 * 3600000, resolvedAt: today - 8 * dayMs + 12 * 3600000, title: { ru: 'Задержки в работе электронного табло', en: 'Departure board delays' }, message: { ru: 'Пример инцидента: табло временно не получало данные.', en: 'Sample incident: the board temporarily could not retrieve data.' }, impact: config.services.find(service => service.id === 'board').impactPolicy.availability, updates: [
|
||
{ id: 'demo-closed-1', source: 'monitor', stage: 'detected', createdAt: today - 8 * dayMs + 11 * 3600000, message: { ru: 'Пример: проверки подтвердили задержки обновления табло.', en: 'Sample: checks confirmed delays in board updates.' } },
|
||
{ id: 'demo-closed-2', source: 'manual', stage: 'monitoring', createdAt: today - 8 * dayMs + 11.75 * 3600000, message: { ru: 'Пример: исправление применено, наблюдаем за обновлением расписания.', en: 'Sample: a fix has been applied; watching timetable updates.' } },
|
||
{ id: 'demo-closed-3', source: 'monitor', stage: 'resolved', createdAt: today - 8 * dayMs + 12 * 3600000, message: { ru: 'Пример: проверки подтвердили восстановление обновления данных.', en: 'Sample: checks confirmed that data updates were restored.' } },
|
||
{ id: 'demo-closed-4', source: 'manual', stage: 'postmortem', createdAt: today - 8 * dayMs + 12 * 3600000 + 10 * 60000, message: { ru: 'Пример разбора: причиной были задержки соединения с источником расписания. Добавили проверку времени ожидания, чтобы раньше замечать повторение проблемы.', en: 'Sample review: the cause was a delayed connection to the timetable source. A timeout check was added to detect recurrence sooner.' } },
|
||
] }],
|
||
maintenance: [
|
||
{ id: 'demo-maintenance-active', title: { ru: 'Обновляем электронное табло', en: 'Updating the departure board' }, message: { ru: 'Пример плановых работ: устанавливаем обновление. Возможны короткие перерывы в отображении расписания.', en: 'Sample maintenance: installing an update. Brief interruptions to timetable display are expected.' }, serviceIds: ['board'], startsAt: now - 15 * 60000, endsAt: now + 45 * 60000, createdAt: now - 86400000, cancelledAt: null, completedAt: null, phase: 'active' },
|
||
{ id: 'demo-maintenance-scheduled', title: { ru: 'Плановое обслуживание API', en: 'Scheduled API maintenance' }, message: { ru: 'Пример анонса: обновление транспортных сервисов. Расписание может быть временно недоступно.', en: 'Sample announcement: a transport service update. Timetables may be temporarily unavailable.' }, serviceIds: ['api', 'pwa'], startsAt: now + 86400000, endsAt: now + 86400000 + 30 * 60000, createdAt: now, cancelledAt: null, completedAt: null, phase: 'scheduled' },
|
||
],
|
||
};
|
||
}
|