mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
feat: Add system date display and setting via API
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
parent
af43388e4b
commit
58a1d243e4
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",
|
||||
"frame_rate": "25.00fps",
|
||||
"system_clock": "10:20:30.005",
|
||||
"system_date": "2025-07-30",
|
||||
"timecode_delta_ms": 5,
|
||||
"timecode_delta_frames": 0,
|
||||
"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
|
||||
|
||||
- **`GET /api/config`**
|
||||
|
|
|
|||
20
src/api.rs
20
src/api.rs
|
|
@ -19,6 +19,7 @@ struct ApiStatus {
|
|||
ltc_timecode: String,
|
||||
frame_rate: String,
|
||||
system_clock: String,
|
||||
system_date: String,
|
||||
timecode_delta_ms: i64,
|
||||
timecode_delta_frames: i64,
|
||||
sync_status: String,
|
||||
|
|
@ -58,6 +59,7 @@ async fn get_status(data: web::Data<AppState>) -> impl Responder {
|
|||
now_local.second(),
|
||||
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 mut delta_frames = 0;
|
||||
|
|
@ -83,6 +85,7 @@ async fn get_status(data: web::Data<AppState>) -> impl Responder {
|
|||
ltc_timecode,
|
||||
frame_rate,
|
||||
system_clock,
|
||||
system_date,
|
||||
timecode_delta_ms: avg_delta,
|
||||
timecode_delta_frames: delta_frames,
|
||||
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")]
|
||||
async fn update_config(
|
||||
data: web::Data<AppState>,
|
||||
|
|
@ -192,6 +211,7 @@ pub async fn start_api_server(
|
|||
.service(update_config)
|
||||
.service(get_logs)
|
||||
.service(nudge_clock)
|
||||
.service(set_date)
|
||||
// Serve frontend static files
|
||||
.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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
<div class="card">
|
||||
<h2>System Clock</h2>
|
||||
<p id="system-clock">--:--:--.---</p>
|
||||
<p>Date: <span id="system-date">---- -- --</span></p>
|
||||
<p>NTP Service: <span id="ntp-active">--</span></p>
|
||||
<p>Sync Status: <span id="sync-status">--</span></p>
|
||||
</div>
|
||||
|
|
@ -90,6 +91,12 @@
|
|||
<button id="nudge-up">+</button>
|
||||
<span id="nudge-message"></span>
|
||||
</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>
|
||||
|
||||
<!-- Logs -->
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
frameRate: document.getElementById('frame-rate'),
|
||||
lockRatio: document.getElementById('lock-ratio'),
|
||||
systemClock: document.getElementById('system-clock'),
|
||||
systemDate: document.getElementById('system-date'),
|
||||
ntpActive: document.getElementById('ntp-active'),
|
||||
syncStatus: document.getElementById('sync-status'),
|
||||
deltaMs: document.getElementById('delta-ms'),
|
||||
|
|
@ -35,12 +36,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
const nudgeValueInput = document.getElementById('nudge-value');
|
||||
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) {
|
||||
statusElements.ltcStatus.textContent = data.ltc_status;
|
||||
statusElements.ltcTimecode.textContent = data.ltc_timecode;
|
||||
statusElements.frameRate.textContent = data.frame_rate;
|
||||
statusElements.lockRatio.textContent = data.lock_ratio.toFixed(2);
|
||||
statusElements.systemClock.textContent = data.system_clock;
|
||||
statusElements.systemDate.textContent = data.system_date;
|
||||
|
||||
statusElements.ntpActive.textContent = data.ntp_active ? 'Active' : 'Inactive';
|
||||
statusElements.ntpActive.className = data.ntp_active ? 'active' : 'inactive';
|
||||
|
|
@ -238,6 +244,35 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
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);
|
||||
manualSyncButton.addEventListener('click', triggerManualSync);
|
||||
nudgeDownButton.addEventListener('click', () => {
|
||||
|
|
@ -248,6 +283,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
const ms = parseInt(nudgeValueInput.value, 10) || 0;
|
||||
nudgeClock(ms);
|
||||
});
|
||||
setDateButton.addEventListener('click', setDate);
|
||||
|
||||
// Initial data load
|
||||
fetchStatus();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue