mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
Merge pull request #21 from cjfranko/add_date
Some checks failed
Build for Raspberry Pi / Build for aarch64 (push) Failing after 16s
Some checks failed
Build for Raspberry Pi / Build for aarch64 (push) Failing after 16s
feat: Add system date display and setting via API
This commit is contained in:
commit
6bc1f5ddbf
5 changed files with 118 additions and 0 deletions
28
docs/api.md
28
docs/api.md
|
|
@ -17,6 +17,7 @@ This document describes the HTTP API for the NTP Timeturner application.
|
||||||
"ltc_timecode": "10:20:30:00",
|
"ltc_timecode": "10:20:30:00",
|
||||||
"frame_rate": "25.00fps",
|
"frame_rate": "25.00fps",
|
||||||
"system_clock": "10:20:30.005",
|
"system_clock": "10:20:30.005",
|
||||||
|
"system_date": "2025-07-30",
|
||||||
"timecode_delta_ms": 5,
|
"timecode_delta_ms": 5,
|
||||||
"timecode_delta_frames": 0,
|
"timecode_delta_frames": 0,
|
||||||
"sync_status": "IN SYNC",
|
"sync_status": "IN SYNC",
|
||||||
|
|
@ -58,6 +59,33 @@ This document describes the HTTP API for the NTP Timeturner application.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **`POST /api/set_date`**
|
||||||
|
|
||||||
|
Sets the system date. This is useful as LTC does not contain date information. Requires `sudo` privileges.
|
||||||
|
|
||||||
|
**Example Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"date": "2025-07-30"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"message": "Date update command issued."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"message": "Date update command failed."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
- **`GET /api/config`**
|
- **`GET /api/config`**
|
||||||
|
|
|
||||||
20
src/api.rs
20
src/api.rs
|
|
@ -19,6 +19,7 @@ struct ApiStatus {
|
||||||
ltc_timecode: String,
|
ltc_timecode: String,
|
||||||
frame_rate: String,
|
frame_rate: String,
|
||||||
system_clock: String,
|
system_clock: String,
|
||||||
|
system_date: String,
|
||||||
timecode_delta_ms: i64,
|
timecode_delta_ms: i64,
|
||||||
timecode_delta_frames: i64,
|
timecode_delta_frames: i64,
|
||||||
sync_status: String,
|
sync_status: String,
|
||||||
|
|
@ -58,6 +59,7 @@ async fn get_status(data: web::Data<AppState>) -> impl Responder {
|
||||||
now_local.second(),
|
now_local.second(),
|
||||||
now_local.timestamp_subsec_millis(),
|
now_local.timestamp_subsec_millis(),
|
||||||
);
|
);
|
||||||
|
let system_date = now_local.format("%Y-%m-%d").to_string();
|
||||||
|
|
||||||
let avg_delta = state.get_ewma_clock_delta();
|
let avg_delta = state.get_ewma_clock_delta();
|
||||||
let mut delta_frames = 0;
|
let mut delta_frames = 0;
|
||||||
|
|
@ -83,6 +85,7 @@ async fn get_status(data: web::Data<AppState>) -> impl Responder {
|
||||||
ltc_timecode,
|
ltc_timecode,
|
||||||
frame_rate,
|
frame_rate,
|
||||||
system_clock,
|
system_clock,
|
||||||
|
system_date,
|
||||||
timecode_delta_ms: avg_delta,
|
timecode_delta_ms: avg_delta,
|
||||||
timecode_delta_frames: delta_frames,
|
timecode_delta_frames: delta_frames,
|
||||||
sync_status: sync_status.to_string(),
|
sync_status: sync_status.to_string(),
|
||||||
|
|
@ -135,6 +138,22 @@ async fn nudge_clock(req: web::Json<NudgeRequest>) -> impl Responder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct SetDateRequest {
|
||||||
|
date: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/api/set_date")]
|
||||||
|
async fn set_date(req: web::Json<SetDateRequest>) -> impl Responder {
|
||||||
|
if system::set_date(&req.date).is_ok() {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.json(serde_json::json!({ "status": "success", "message": "Date update command issued." }))
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError()
|
||||||
|
.json(serde_json::json!({ "status": "error", "message": "Date update command failed." }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/api/config")]
|
#[post("/api/config")]
|
||||||
async fn update_config(
|
async fn update_config(
|
||||||
data: web::Data<AppState>,
|
data: web::Data<AppState>,
|
||||||
|
|
@ -192,6 +211,7 @@ pub async fn start_api_server(
|
||||||
.service(update_config)
|
.service(update_config)
|
||||||
.service(get_logs)
|
.service(get_logs)
|
||||||
.service(nudge_clock)
|
.service(nudge_clock)
|
||||||
|
.service(set_date)
|
||||||
// Serve frontend static files
|
// Serve frontend static files
|
||||||
.service(fs::Files::new("/", "static/").index_file("index.html"))
|
.service(fs::Files::new("/", "static/").index_file("index.html"))
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,33 @@ pub fn nudge_clock(microseconds: i64) -> Result<(), ()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_date(date: &str) -> Result<(), ()> {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let success = Command::new("sudo")
|
||||||
|
.arg("date")
|
||||||
|
.arg("--set")
|
||||||
|
.arg(date)
|
||||||
|
.status()
|
||||||
|
.map(|s| s.success())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if success {
|
||||||
|
log::info!("Set system date to {}", date);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
log::error!("Failed to set system date");
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
{
|
||||||
|
let _ = date;
|
||||||
|
log::warn!("Date setting is only supported on Linux.");
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>System Clock</h2>
|
<h2>System Clock</h2>
|
||||||
<p id="system-clock">--:--:--.---</p>
|
<p id="system-clock">--:--:--.---</p>
|
||||||
|
<p>Date: <span id="system-date">---- -- --</span></p>
|
||||||
<p>NTP Service: <span id="ntp-active">--</span></p>
|
<p>NTP Service: <span id="ntp-active">--</span></p>
|
||||||
<p>Sync Status: <span id="sync-status">--</span></p>
|
<p>Sync Status: <span id="sync-status">--</span></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -90,6 +91,12 @@
|
||||||
<button id="nudge-up">+</button>
|
<button id="nudge-up">+</button>
|
||||||
<span id="nudge-message"></span>
|
<span id="nudge-message"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="date-input">Set System Date:</label>
|
||||||
|
<input type="date" id="date-input">
|
||||||
|
<button id="set-date">Set Date</button>
|
||||||
|
<span id="date-message"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Logs -->
|
<!-- Logs -->
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
frameRate: document.getElementById('frame-rate'),
|
frameRate: document.getElementById('frame-rate'),
|
||||||
lockRatio: document.getElementById('lock-ratio'),
|
lockRatio: document.getElementById('lock-ratio'),
|
||||||
systemClock: document.getElementById('system-clock'),
|
systemClock: document.getElementById('system-clock'),
|
||||||
|
systemDate: document.getElementById('system-date'),
|
||||||
ntpActive: document.getElementById('ntp-active'),
|
ntpActive: document.getElementById('ntp-active'),
|
||||||
syncStatus: document.getElementById('sync-status'),
|
syncStatus: document.getElementById('sync-status'),
|
||||||
deltaMs: document.getElementById('delta-ms'),
|
deltaMs: document.getElementById('delta-ms'),
|
||||||
|
|
@ -35,12 +36,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
const nudgeValueInput = document.getElementById('nudge-value');
|
const nudgeValueInput = document.getElementById('nudge-value');
|
||||||
const nudgeMessage = document.getElementById('nudge-message');
|
const nudgeMessage = document.getElementById('nudge-message');
|
||||||
|
|
||||||
|
const dateInput = document.getElementById('date-input');
|
||||||
|
const setDateButton = document.getElementById('set-date');
|
||||||
|
const dateMessage = document.getElementById('date-message');
|
||||||
|
|
||||||
function updateStatus(data) {
|
function updateStatus(data) {
|
||||||
statusElements.ltcStatus.textContent = data.ltc_status;
|
statusElements.ltcStatus.textContent = data.ltc_status;
|
||||||
statusElements.ltcTimecode.textContent = data.ltc_timecode;
|
statusElements.ltcTimecode.textContent = data.ltc_timecode;
|
||||||
statusElements.frameRate.textContent = data.frame_rate;
|
statusElements.frameRate.textContent = data.frame_rate;
|
||||||
statusElements.lockRatio.textContent = data.lock_ratio.toFixed(2);
|
statusElements.lockRatio.textContent = data.lock_ratio.toFixed(2);
|
||||||
statusElements.systemClock.textContent = data.system_clock;
|
statusElements.systemClock.textContent = data.system_clock;
|
||||||
|
statusElements.systemDate.textContent = data.system_date;
|
||||||
|
|
||||||
statusElements.ntpActive.textContent = data.ntp_active ? 'Active' : 'Inactive';
|
statusElements.ntpActive.textContent = data.ntp_active ? 'Active' : 'Inactive';
|
||||||
statusElements.ntpActive.className = data.ntp_active ? 'active' : 'inactive';
|
statusElements.ntpActive.className = data.ntp_active ? 'active' : 'inactive';
|
||||||
|
|
@ -238,6 +244,35 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
setTimeout(() => { nudgeMessage.textContent = ''; }, 3000);
|
setTimeout(() => { nudgeMessage.textContent = ''; }, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setDate() {
|
||||||
|
const date = dateInput.value;
|
||||||
|
if (!date) {
|
||||||
|
alert('Please select a date.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dateMessage.textContent = 'Setting date...';
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/set_date', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ date: date }),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
dateMessage.textContent = `Success: ${data.message}`;
|
||||||
|
// Fetch status again to update the displayed date immediately
|
||||||
|
fetchStatus();
|
||||||
|
} else {
|
||||||
|
dateMessage.textContent = `Error: ${data.message}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting date:', error);
|
||||||
|
dateMessage.textContent = 'Failed to send date command.';
|
||||||
|
}
|
||||||
|
setTimeout(() => { dateMessage.textContent = ''; }, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
saveConfigButton.addEventListener('click', saveConfig);
|
saveConfigButton.addEventListener('click', saveConfig);
|
||||||
manualSyncButton.addEventListener('click', triggerManualSync);
|
manualSyncButton.addEventListener('click', triggerManualSync);
|
||||||
nudgeDownButton.addEventListener('click', () => {
|
nudgeDownButton.addEventListener('click', () => {
|
||||||
|
|
@ -248,6 +283,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
const ms = parseInt(nudgeValueInput.value, 10) || 0;
|
const ms = parseInt(nudgeValueInput.value, 10) || 0;
|
||||||
nudgeClock(ms);
|
nudgeClock(ms);
|
||||||
});
|
});
|
||||||
|
setDateButton.addEventListener('click', setDate);
|
||||||
|
|
||||||
// Initial data load
|
// Initial data load
|
||||||
fetchStatus();
|
fetchStatus();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue