diff --git a/static/index.html b/static/index.html index e67216a..1b2880a 100644 --- a/static/index.html +++ b/static/index.html @@ -9,6 +9,16 @@

NTP TimeTurner

+ + + +
@@ -107,6 +117,7 @@
+ diff --git a/static/mock-data.js b/static/mock-data.js new file mode 100644 index 0000000..2765680 --- /dev/null +++ b/static/mock-data.js @@ -0,0 +1,168 @@ +// This file contains mock data sets for UI development and testing without a live backend. +const mockApiDataSets = { + allGood: { + status: { + ltc_status: 'LOCK', + ltc_timecode: '10:20:30:00', + frame_rate: '25.00', + lock_ratio: 99.5, + system_clock: '10:20:30.500', + system_date: '2025-08-07', + ntp_active: true, + sync_status: 'IN SYNC', + timecode_delta_ms: 5, + timecode_delta_frames: 0.125, + jitter_status: 'GOOD', + interfaces: ['192.168.1.100 (eth0)', '10.0.0.5 (wlan0)'], + }, + config: { + hardwareOffsetMs: 10, + autoSyncEnabled: true, + defaultNudgeMs: 2, + timeturnerOffset: { hours: 1, minutes: 2, seconds: 3, frames: 4, milliseconds: 50 }, + }, + logs: [ + '2025-08-07 10:20:30 [INFO] Starting up...', + '2025-08-07 10:20:32 [INFO] LTC LOCK detected. Frame rate: 25.00fps.', + '2025-08-07 10:20:35 [INFO] Initial sync complete. Clock adjusted by -15ms.', + ] + }, + ltcFree: { + status: { + ltc_status: 'FREE', + ltc_timecode: '11:22:33:11', + frame_rate: '25.00', + lock_ratio: 40.2, + system_clock: '11:22:33.800', + system_date: '2025-08-07', + ntp_active: true, + sync_status: 'IN SYNC', + timecode_delta_ms: 3, + timecode_delta_frames: 0.075, + jitter_status: 'GOOD', + interfaces: ['192.168.1.100 (eth0)'], + }, + config: { + hardwareOffsetMs: 10, + autoSyncEnabled: true, + defaultNudgeMs: 2, + timeturnerOffset: { hours: 0, minutes: 0, seconds: 0, frames: 0, milliseconds: 0 }, + }, + logs: [ '2025-08-07 11:22:30 [WARN] LTC signal lost, entering freewheel.' ] + }, + clockAhead: { + status: { + ltc_status: 'LOCK', + ltc_timecode: '12:00:05:00', + frame_rate: '25.00', + lock_ratio: 98.1, + system_clock: '12:00:04.500', + system_date: '2025-08-07', + ntp_active: true, + sync_status: 'CLOCK AHEAD', + timecode_delta_ms: -500, + timecode_delta_frames: -12.5, + jitter_status: 'AVERAGE', + interfaces: ['192.168.1.100 (eth0)'], + }, + config: { + hardwareOffsetMs: 10, + autoSyncEnabled: true, + defaultNudgeMs: 2, + timeturnerOffset: { hours: 0, minutes: 0, seconds: 0, frames: 0, milliseconds: 0 }, + }, + logs: [ '2025-08-07 12:00:00 [WARN] System clock is ahead of LTC source by 500ms.' ] + }, + clockBehind: { + status: { + ltc_status: 'LOCK', + ltc_timecode: '13:30:10:00', + frame_rate: '25.00', + lock_ratio: 99.9, + system_clock: '13:30:10.800', + system_date: '2025-08-07', + ntp_active: true, + sync_status: 'CLOCK BEHIND', + timecode_delta_ms: 800, + timecode_delta_frames: 20, + jitter_status: 'AVERAGE', + interfaces: ['192.168.1.100 (eth0)'], + }, + config: { + hardwareOffsetMs: 10, + autoSyncEnabled: true, + defaultNudgeMs: 2, + timeturnerOffset: { hours: 0, minutes: 0, seconds: 0, frames: 0, milliseconds: 0 }, + }, + logs: [ '2025-08-07 13:30:00 [WARN] System clock is behind LTC source by 800ms.' ] + }, + timeturning: { + status: { + ltc_status: 'LOCK', + ltc_timecode: '14:00:00:00', + frame_rate: '25.00', + lock_ratio: 100, + system_clock: '15:02:03.050', + system_date: '2025-08-07', + ntp_active: true, + sync_status: 'TIMETURNING', + timecode_delta_ms: 3723050, // a big number + timecode_delta_frames: 93076, + jitter_status: 'GOOD', + interfaces: ['192.168.1.100 (eth0)'], + }, + config: { + hardwareOffsetMs: 10, + autoSyncEnabled: false, + defaultNudgeMs: 2, + timeturnerOffset: { hours: 1, minutes: 2, seconds: 3, frames: 4, milliseconds: 50 }, + }, + logs: [ '2025-08-07 14:00:00 [INFO] Timeturner offset is active.' ] + }, + badJitter: { + status: { + ltc_status: 'LOCK', + ltc_timecode: '15:15:15:15', + frame_rate: '25.00', + lock_ratio: 95.0, + system_clock: '15:15:15.515', + system_date: '2025-08-07', + ntp_active: true, + sync_status: 'IN SYNC', + timecode_delta_ms: 10, + timecode_delta_frames: 0.25, + jitter_status: 'BAD', + interfaces: ['192.168.1.100 (eth0)'], + }, + config: { + hardwareOffsetMs: 10, + autoSyncEnabled: true, + defaultNudgeMs: 2, + timeturnerOffset: { hours: 0, minutes: 0, seconds: 0, frames: 0, milliseconds: 0 }, + }, + logs: [ '2025-08-07 15:15:00 [ERROR] High jitter detected on LTC source.' ] + }, + ntpInactive: { + status: { + ltc_status: 'UNKNOWN', + ltc_timecode: '--:--:--:--', + frame_rate: '--', + lock_ratio: 0, + system_clock: '16:00:00.000', + system_date: '2025-08-07', + ntp_active: false, + sync_status: 'UNKNOWN', + timecode_delta_ms: 0, + timecode_delta_frames: 0, + jitter_status: 'UNKNOWN', + interfaces: [], + }, + config: { + hardwareOffsetMs: 0, + autoSyncEnabled: false, + defaultNudgeMs: 2, + timeturnerOffset: { hours: 0, minutes: 0, seconds: 0, frames: 0, milliseconds: 0 }, + }, + logs: [ '2025-08-07 16:00:00 [INFO] NTP service is inactive.' ] + } +}; diff --git a/static/script.js b/static/script.js index d11cf20..a9c5d5d 100644 --- a/static/script.js +++ b/static/script.js @@ -1,4 +1,8 @@ document.addEventListener('DOMContentLoaded', () => { + // --- Mock Data Configuration --- + // Set to true to use mock data, false for live API. + const useMockData = true; + let currentMockSetKey = 'allGood'; // Default mock data set let lastApiData = null; let lastApiFetchTime = null; @@ -41,6 +45,35 @@ document.addEventListener('DOMContentLoaded', () => { const setDateButton = document.getElementById('set-date'); const dateMessage = document.getElementById('date-message'); + // --- Mock Controls Setup --- + const mockControls = document.getElementById('mock-controls'); + const mockDataSelector = document.getElementById('mock-data-selector'); + + function setupMockControls() { + if (useMockData) { + mockControls.style.display = 'block'; + + // Populate dropdown + Object.keys(mockApiDataSets).forEach(key => { + const option = document.createElement('option'); + option.value = key; + option.textContent = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase()); + mockDataSelector.appendChild(option); + }); + + mockDataSelector.value = currentMockSetKey; + + // Handle selection change + mockDataSelector.addEventListener('change', (event) => { + currentMockSetKey = event.target.value; + // Re-fetch all data from the new mock set + fetchStatus(); + fetchConfig(); + fetchLogs(); + }); + } + } + function updateStatus(data) { const ltcStatus = data.ltc_status || 'UNKNOWN'; const ltcIconSrc = iconMap.ltcStatus[ltcStatus] || iconMap.ltcStatus.default; @@ -148,6 +181,13 @@ document.addEventListener('DOMContentLoaded', () => { } async function fetchStatus() { + if (useMockData) { + const data = mockApiDataSets[currentMockSetKey].status; + updateStatus(data); + lastApiData = data; + lastApiFetchTime = new Date(); + return; + } try { const response = await fetch('/api/status'); if (!response.ok) throw new Error('Failed to fetch status'); @@ -163,6 +203,18 @@ document.addEventListener('DOMContentLoaded', () => { } async function fetchConfig() { + if (useMockData) { + const data = mockApiDataSets[currentMockSetKey].config; + hwOffsetInput.value = data.hardwareOffsetMs; + autoSyncCheckbox.checked = data.autoSyncEnabled; + offsetInputs.h.value = data.timeturnerOffset.hours; + offsetInputs.m.value = data.timeturnerOffset.minutes; + offsetInputs.s.value = data.timeturnerOffset.seconds; + offsetInputs.f.value = data.timeturnerOffset.frames; + offsetInputs.ms.value = data.timeturnerOffset.milliseconds || 0; + nudgeValueInput.value = data.defaultNudgeMs; + return; + } try { const response = await fetch('/api/config'); if (!response.ok) throw new Error('Failed to fetch config'); @@ -194,6 +246,14 @@ document.addEventListener('DOMContentLoaded', () => { } }; + if (useMockData) { + console.log('Mock save:', config); + alert('Configuration saved (mock).'); + // We can also update the mock data in memory to see changes reflected + mockApiDataSets[currentMockSetKey].config = config; + return; + } + try { const response = await fetch('/api/config', { method: 'POST', @@ -209,6 +269,12 @@ document.addEventListener('DOMContentLoaded', () => { } async function fetchLogs() { + if (useMockData) { + const logs = mockApiDataSets[currentMockSetKey].logs; + statusElements.logs.textContent = logs.join('\n'); + statusElements.logs.scrollTop = statusElements.logs.scrollHeight; + return; + } try { const response = await fetch('/api/logs'); if (!response.ok) throw new Error('Failed to fetch logs'); @@ -224,6 +290,11 @@ document.addEventListener('DOMContentLoaded', () => { async function triggerManualSync() { syncMessage.textContent = 'Issuing sync command...'; + if (useMockData) { + syncMessage.textContent = 'Success: Manual sync triggered (mock).'; + setTimeout(() => { syncMessage.textContent = ''; }, 5000); + return; + } try { const response = await fetch('/api/sync', { method: 'POST' }); const data = await response.json(); @@ -241,6 +312,11 @@ document.addEventListener('DOMContentLoaded', () => { async function nudgeClock(ms) { nudgeMessage.textContent = 'Nudging clock...'; + if (useMockData) { + nudgeMessage.textContent = `Success: Clock nudged by ${ms}ms (mock).`; + setTimeout(() => { nudgeMessage.textContent = ''; }, 3000); + return; + } try { const response = await fetch('/api/nudge_clock', { method: 'POST', @@ -268,6 +344,13 @@ document.addEventListener('DOMContentLoaded', () => { } dateMessage.textContent = 'Setting date...'; + if (useMockData) { + mockApiDataSets[currentMockSetKey].status.system_date = date; + dateMessage.textContent = `Success: Date set to ${date} (mock).`; + fetchStatus(); // re-render + setTimeout(() => { dateMessage.textContent = ''; }, 5000); + return; + } try { const response = await fetch('/api/set_date', { method: 'POST', @@ -302,12 +385,15 @@ document.addEventListener('DOMContentLoaded', () => { setDateButton.addEventListener('click', setDate); // Initial data load + setupMockControls(); fetchStatus(); fetchConfig(); fetchLogs(); - // Refresh data every 2 seconds - setInterval(fetchStatus, 2000); - setInterval(fetchLogs, 2000); + // Refresh data every 2 seconds if not using mock data + if (!useMockData) { + setInterval(fetchStatus, 2000); + setInterval(fetchLogs, 2000); + } setInterval(animateClocks, 50); // High-frequency clock animation });