Files
Backup-Dashboard/public/js/mock.js
T
2026-10-05 04:01:12 +00:00

130 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Sample data matching the API contract. Timestamps are relative to page load so it always looks "live".
const now = Date.now();
const MIN = 60_000;
const HR = 60 * MIN;
const DAY = 24 * HR;
const iso = (offsetMs) => new Date(now + offsetMs).toISOString();
// every_h drives the generated run history; it isn't part of the API.
const JOBS = [
{
id: 1, company_name: 'Acme Corp', job_name: 'File Server – Nightly', status: 'success', every_h: 24,
last_run_at: iso(-6 * HR), next_run_at: iso(18 * HR),
message: 'Job completed successfully. 1,284 files processed.',
source_system: 'Veeam B&R', duration_seconds: 2840, size_bytes: 128_849_018_880,
},
{
id: 2, company_name: 'Acme Corp', job_name: 'SQL01 – Transaction Logs', status: 'warning', every_h: 1,
last_run_at: iso(-25 * MIN), next_run_at: iso(35 * MIN),
message: 'Processing SQL01 Warning:\nFailed to truncate transaction logs for database "ERP_PROD".\nLog backup completed but truncation was skipped.',
source_system: 'Veeam B&R', duration_seconds: 312, size_bytes: 2_147_483_648,
details: { retry_count: 0, repository: 'REPO-ONSITE-01' },
},
{
// Running now; "last run" is still the previous completion
id: 7, company_name: 'Acme Corp', job_name: 'Hyper-V Hosts – Daily', status: 'running', every_h: 24,
running_since: iso(-12 * MIN), last_status: 'success',
last_run_at: iso(-DAY + 40 * MIN), next_run_at: iso(DAY - 12 * MIN),
message: 'Completed successfully. 6 VMs processed.',
source_system: 'Veeam B&R', duration_seconds: 3120, size_bytes: 412_316_860_416,
},
{
id: 3, company_name: 'Bluewater Dental', job_name: 'Workstations – Daily', status: 'failed', every_h: 24,
last_run_at: iso(-3 * HR), next_run_at: iso(21 * HR),
message: 'Error: Unable to connect to backup repository \\\\NAS02\\Backups.\nThe network path was not found. (0x80070035)\nJob terminated after 3 retries.',
source_system: 'Datto', duration_seconds: 95,
details: { retry_count: 3, target: '\\\\NAS02\\Backups' },
},
{
id: 4, company_name: 'Bluewater Dental', job_name: 'Imaging Server – Hourly', status: 'success', every_h: 1,
last_run_at: iso(-50 * MIN), next_run_at: iso(10 * MIN),
message: 'Completed.', source_system: 'Datto', duration_seconds: 420, size_bytes: 8_589_934_592,
},
{
// Green status but next run is in the past → should show as Overdue
id: 5, company_name: 'Northfield Logistics', job_name: 'Exchange – Nightly', status: 'success', every_h: 24,
last_run_at: iso(-30 * HR), next_run_at: iso(-6 * HR),
message: 'Completed successfully.', source_system: 'Acronis', duration_seconds: 5400,
},
{
// Never run / unrecognised status → rendered as Unknown
id: 6, company_name: 'Northfield Logistics', job_name: 'New NAS – Weekly', status: null,
last_run_at: null, next_run_at: iso(2 * DAY),
message: '', source_system: 'Synology Hyper Backup',
},
];
const MESSAGES = {
success: 'Job completed successfully.',
warning: 'Completed with warnings: 1 file skipped (file in use).',
failed: 'Error: Target repository unreachable. Job terminated.',
};
// Deterministic pseudo-random so history doesn't reshuffle on every modal open
function rng(seed) {
let x = seed;
return () => ((x = (x * 1_103_515_245 + 12_345) % 2_147_483_648) / 2_147_483_648);
}
const RUNS = new Map(); // job id → runs, newest first
for (const job of JOBS) {
const runs = [];
if (job.running_since) {
runs.push({ id: `${job.id}-r`, status: 'running', started_at: job.running_since, finished_at: null, message: 'In progress…' });
}
if (job.last_run_at && job.every_h) {
const rand = rng(job.id * 7919);
const dur = (job.duration_seconds ?? 600) * 1000;
let end = Date.parse(job.last_run_at);
for (let i = 0; end - dur > now - 30 * DAY; i++, end -= job.every_h * HR) {
const r = rand();
const status = i === 0 ? (job.last_status ?? job.status) : r < 0.03 ? 'failed' : r < 0.1 ? 'warning' : 'success';
const thisDur = dur * (0.8 + rand() * 0.4);
runs.push({
id: `${job.id}-${i}`,
status,
started_at: new Date(end - thisDur).toISOString(),
finished_at: new Date(end).toISOString(),
duration_seconds: Math.round(thisDur / 1000),
size_bytes: status === 'failed' ? null : job.size_bytes,
message: i === 0 ? job.message : MESSAGES[status],
details: i === 0 ? job.details ?? {} : {},
});
}
}
RUNS.set(String(job.id), runs);
}
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
const summary = ({ details, every_h, last_status, duration_seconds, size_bytes, ...rest }) => rest;
export const mockApi = {
async listJobs() {
await delay(250);
return { generated_at: new Date().toISOString(), jobs: JOBS.map(summary) };
},
async getJob(id) {
await delay(200);
const job = JOBS.find((j) => String(j.id) === String(id));
if (!job) throw new Error('Job not found');
const { every_h, ...rest } = job;
return { last_status: job.status === 'running' ? null : job.status, ...rest, details: job.details ?? {} };
},
async listRuns(id, days) {
await delay(300);
const since = now - days * DAY;
const runs = (RUNS.get(String(id)) ?? [])
.filter((r) => Date.parse(r.started_at) >= since)
.map(({ message, details, ...r }) => r);
return { job_id: id, since: new Date(since).toISOString(), runs };
},
async getRun(id) {
await delay(150);
for (const runs of RUNS.values()) {
const run = runs.find((r) => r.id === id);
if (run) return { details: {}, ...run };
}
throw new Error('Run not found');
},
};