internal data sources (clock, static text), connection indicator, rendering fixes
DataSourceConfig refactored to enum: External/Clock/StaticText. Clock generates formatted time via chrono, static text emits configured string. ESP32: connection status indicator (green/red dot bottom-right), per-widget clear before redraw, RenderEvent enum for local + server messages. Polling uses DataUpdate instead of ScreenUpdate to avoid wiping widget state. Empty mappings passthrough raw source data for internal sources.
This commit is contained in:
@@ -2,60 +2,128 @@ use domain::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum DataSourceConfigDto {
|
||||
#[serde(rename = "external")]
|
||||
External {
|
||||
#[serde(default)]
|
||||
url: Option<String>,
|
||||
#[serde(default)]
|
||||
api_key: Option<String>,
|
||||
#[serde(default)]
|
||||
headers: Vec<(String, String)>,
|
||||
},
|
||||
#[serde(rename = "clock")]
|
||||
Clock {
|
||||
#[serde(default = "default_clock_format")]
|
||||
format: String,
|
||||
#[serde(default = "default_timezone")]
|
||||
timezone: String,
|
||||
},
|
||||
#[serde(rename = "static_text")]
|
||||
StaticText {
|
||||
#[serde(default)]
|
||||
text: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn default_clock_format() -> String {
|
||||
"%H:%M:%S".into()
|
||||
}
|
||||
|
||||
fn default_timezone() -> String {
|
||||
"UTC".into()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DataSourceDto {
|
||||
pub id: u16,
|
||||
pub name: String,
|
||||
pub source_type: String,
|
||||
pub poll_interval_secs: u64,
|
||||
pub url: Option<String>,
|
||||
pub api_key: Option<String>,
|
||||
pub headers: Vec<(String, String)>,
|
||||
pub config: DataSourceConfigDto,
|
||||
}
|
||||
|
||||
fn source_type_to_str(t: &DataSourceType) -> &'static str {
|
||||
match t {
|
||||
DataSourceType::Weather => "weather",
|
||||
DataSourceType::Media => "media",
|
||||
DataSourceType::Rss => "rss",
|
||||
DataSourceType::HttpJson => "http_json",
|
||||
DataSourceType::Webhook => "webhook",
|
||||
DataSourceType::Clock => "clock",
|
||||
DataSourceType::StaticText => "static_text",
|
||||
}
|
||||
}
|
||||
|
||||
fn source_type_from_str(s: &str) -> Result<DataSourceType, String> {
|
||||
match s {
|
||||
"weather" => Ok(DataSourceType::Weather),
|
||||
"media" => Ok(DataSourceType::Media),
|
||||
"rss" => Ok(DataSourceType::Rss),
|
||||
"http_json" => Ok(DataSourceType::HttpJson),
|
||||
"webhook" => Ok(DataSourceType::Webhook),
|
||||
"clock" => Ok(DataSourceType::Clock),
|
||||
"static_text" => Ok(DataSourceType::StaticText),
|
||||
t => Err(format!("unknown source_type: {t}")),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DataSource> for DataSourceDto {
|
||||
fn from(ds: &DataSource) -> Self {
|
||||
let config = match &ds.config {
|
||||
DataSourceConfig::External {
|
||||
url,
|
||||
api_key,
|
||||
headers,
|
||||
} => DataSourceConfigDto::External {
|
||||
url: url.clone(),
|
||||
api_key: api_key.clone(),
|
||||
headers: headers.clone(),
|
||||
},
|
||||
DataSourceConfig::Clock { format, timezone } => DataSourceConfigDto::Clock {
|
||||
format: format.clone(),
|
||||
timezone: timezone.clone(),
|
||||
},
|
||||
DataSourceConfig::StaticText { text } => {
|
||||
DataSourceConfigDto::StaticText { text: text.clone() }
|
||||
}
|
||||
};
|
||||
Self {
|
||||
id: ds.id,
|
||||
name: ds.name.clone(),
|
||||
source_type: match ds.source_type {
|
||||
DataSourceType::Weather => "weather",
|
||||
DataSourceType::Media => "media",
|
||||
|
||||
DataSourceType::Rss => "rss",
|
||||
DataSourceType::HttpJson => "http_json",
|
||||
DataSourceType::Webhook => "webhook",
|
||||
}
|
||||
.into(),
|
||||
source_type: source_type_to_str(&ds.source_type).into(),
|
||||
poll_interval_secs: ds.poll_interval.as_secs(),
|
||||
url: ds.config.url.clone(),
|
||||
api_key: ds.config.api_key.clone(),
|
||||
headers: ds.config.headers.clone(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DataSourceDto {
|
||||
pub fn into_domain(self) -> Result<DataSource, String> {
|
||||
let source_type = match self.source_type.as_str() {
|
||||
"weather" => DataSourceType::Weather,
|
||||
"media" => DataSourceType::Media,
|
||||
|
||||
"rss" => DataSourceType::Rss,
|
||||
"http_json" => DataSourceType::HttpJson,
|
||||
"webhook" => DataSourceType::Webhook,
|
||||
t => return Err(format!("unknown source_type: {t}")),
|
||||
let source_type = source_type_from_str(&self.source_type)?;
|
||||
let config = match self.config {
|
||||
DataSourceConfigDto::External {
|
||||
url,
|
||||
api_key,
|
||||
headers,
|
||||
} => DataSourceConfig::External {
|
||||
url,
|
||||
api_key,
|
||||
headers,
|
||||
},
|
||||
DataSourceConfigDto::Clock { format, timezone } => {
|
||||
DataSourceConfig::Clock { format, timezone }
|
||||
}
|
||||
DataSourceConfigDto::StaticText { text } => DataSourceConfig::StaticText { text },
|
||||
};
|
||||
Ok(DataSource {
|
||||
id: self.id,
|
||||
name: self.name,
|
||||
source_type,
|
||||
poll_interval: Duration::from_secs(self.poll_interval_secs),
|
||||
config: DataSourceConfig {
|
||||
url: self.url,
|
||||
api_key: self.api_key,
|
||||
headers: self.headers,
|
||||
},
|
||||
config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user