add SPA config UI, wire media/rss adapters, event-driven layout push

- React SPA: dashboard, data sources CRUD, widgets CRUD, layout builder,
  presets. TanStack Router + Query, shadcn/ui, Vite proxy to :3000
- wire media + rss adapters into polling loop, remove xtb source type
- media adapter: read username/password from headers, proper subsonic auth
- event handler: subscribe to LayoutChanged, push screen update to clients
- fix clippy warnings across workspace (Default impls, collapsible ifs,
  redundant closures, is_none_or, unused imports)
This commit is contained in:
2026-06-19 00:12:42 +02:00
parent 21c08911df
commit 26ebfad3a2
175 changed files with 12338 additions and 801 deletions

View File

@@ -1,10 +1,9 @@
use domain::{
ConfigRepository, DataSource, DataSourceId, Layout, LayoutPreset, LayoutPresetId, WidgetConfig,
WidgetId,
};
use std::collections::HashMap;
use std::sync::RwLock;
use domain::{
ConfigRepository,
DataSource, DataSourceId, Layout, LayoutPreset, LayoutPresetId,
WidgetConfig, WidgetId,
};
#[derive(Debug, thiserror::Error)]
pub enum MemoryConfigError {
@@ -19,8 +18,8 @@ pub struct MemoryConfigStore {
presets: RwLock<HashMap<LayoutPresetId, LayoutPreset>>,
}
impl MemoryConfigStore {
pub fn new() -> Self {
impl Default for MemoryConfigStore {
fn default() -> Self {
Self {
widgets: RwLock::new(HashMap::new()),
data_sources: RwLock::new(HashMap::new()),
@@ -30,82 +29,130 @@ impl MemoryConfigStore {
}
}
impl MemoryConfigStore {
pub fn new() -> Self {
Self::default()
}
}
impl ConfigRepository for MemoryConfigStore {
type Error = MemoryConfigError;
async fn get_widget(&self, id: WidgetId) -> Result<Option<WidgetConfig>, Self::Error> {
let guard = self.widgets.read().map_err(|_| MemoryConfigError::LockPoisoned)?;
let guard = self
.widgets
.read()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
Ok(guard.get(&id).cloned())
}
async fn list_widgets(&self) -> Result<Vec<WidgetConfig>, Self::Error> {
let guard = self.widgets.read().map_err(|_| MemoryConfigError::LockPoisoned)?;
let guard = self
.widgets
.read()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
Ok(guard.values().cloned().collect())
}
async fn save_widget(&self, config: &WidgetConfig) -> Result<(), Self::Error> {
let mut guard = self.widgets.write().map_err(|_| MemoryConfigError::LockPoisoned)?;
let mut guard = self
.widgets
.write()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
guard.insert(config.id, config.clone());
Ok(())
}
async fn delete_widget(&self, id: WidgetId) -> Result<(), Self::Error> {
let mut guard = self.widgets.write().map_err(|_| MemoryConfigError::LockPoisoned)?;
let mut guard = self
.widgets
.write()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
guard.remove(&id);
Ok(())
}
async fn get_data_source(&self, id: DataSourceId) -> Result<Option<DataSource>, Self::Error> {
let guard = self.data_sources.read().map_err(|_| MemoryConfigError::LockPoisoned)?;
let guard = self
.data_sources
.read()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
Ok(guard.get(&id).cloned())
}
async fn list_data_sources(&self) -> Result<Vec<DataSource>, Self::Error> {
let guard = self.data_sources.read().map_err(|_| MemoryConfigError::LockPoisoned)?;
let guard = self
.data_sources
.read()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
Ok(guard.values().cloned().collect())
}
async fn save_data_source(&self, source: &DataSource) -> Result<(), Self::Error> {
let mut guard = self.data_sources.write().map_err(|_| MemoryConfigError::LockPoisoned)?;
let mut guard = self
.data_sources
.write()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
guard.insert(source.id, source.clone());
Ok(())
}
async fn delete_data_source(&self, id: DataSourceId) -> Result<(), Self::Error> {
let mut guard = self.data_sources.write().map_err(|_| MemoryConfigError::LockPoisoned)?;
let mut guard = self
.data_sources
.write()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
guard.remove(&id);
Ok(())
}
async fn get_layout(&self) -> Result<Option<Layout>, Self::Error> {
let guard = self.layout.read().map_err(|_| MemoryConfigError::LockPoisoned)?;
let guard = self
.layout
.read()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
Ok(guard.clone())
}
async fn save_layout(&self, layout: &Layout) -> Result<(), Self::Error> {
let mut guard = self.layout.write().map_err(|_| MemoryConfigError::LockPoisoned)?;
let mut guard = self
.layout
.write()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
*guard = Some(layout.clone());
Ok(())
}
async fn get_preset(&self, id: LayoutPresetId) -> Result<Option<LayoutPreset>, Self::Error> {
let guard = self.presets.read().map_err(|_| MemoryConfigError::LockPoisoned)?;
let guard = self
.presets
.read()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
Ok(guard.get(&id).cloned())
}
async fn list_presets(&self) -> Result<Vec<LayoutPreset>, Self::Error> {
let guard = self.presets.read().map_err(|_| MemoryConfigError::LockPoisoned)?;
let guard = self
.presets
.read()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
Ok(guard.values().cloned().collect())
}
async fn save_preset(&self, preset: &LayoutPreset) -> Result<(), Self::Error> {
let mut guard = self.presets.write().map_err(|_| MemoryConfigError::LockPoisoned)?;
let mut guard = self
.presets
.write()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
guard.insert(preset.id, preset.clone());
Ok(())
}
async fn delete_preset(&self, id: LayoutPresetId) -> Result<(), Self::Error> {
let mut guard = self.presets.write().map_err(|_| MemoryConfigError::LockPoisoned)?;
let mut guard = self
.presets
.write()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
guard.remove(&id);
Ok(())
}