From 220161a70aeaf09c72eea3aa5ac1c991d18b6a0d Mon Sep 17 00:00:00 2001 From: Eric Neuber Date: Thu, 20 Nov 2025 20:12:25 +0100 Subject: [PATCH] jetzt mit Header, 3 Tabellen --- .gitignore | 4 + Cargo.lock | 52 ++++++++++++ Cargo.toml | 1 + README.md | 116 +++++++++++++++++--------- src/main.rs | 166 ++++++++++++++++++++++++++++++------- static/script.js | 69 +++++++++++++--- static/settings.js | 40 +++++++++ static/style.css | 179 ++++++++++++++++++++++++++++++++++++---- templates/index.html | 50 ++++++++--- templates/settings.html | 61 ++++++++++++++ 10 files changed, 636 insertions(+), 102 deletions(-) create mode 100644 .gitignore create mode 100644 static/settings.js create mode 100644 templates/settings.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b5090b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +table_config.json +Cargo.lock +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index 350995f..e23b7d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "actix-files" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c0d87f10d70e2948ad40e8edea79c8e77c6c66e0250a4c1f09b690465199576" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + [[package]] name = "actix-http" version = "3.11.2" @@ -656,6 +679,12 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.10.1" @@ -935,6 +964,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1471,6 +1510,7 @@ dependencies = [ name = "table-server" version = "0.1.0" dependencies = [ + "actix-files", "actix-web", "serde", "serde_json", @@ -1626,6 +1666,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.22" @@ -1662,6 +1708,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 4789359..3c4c601 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] actix-web = "4.4.0" +actix-files = "0.6.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1", features = ["full"] } diff --git a/README.md b/README.md index 4256763..ec8f416 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,12 @@ table-server/ ├── src/ │ └── main.rs ├── templates/ -│ └── index.html +│ ├── index.html +│ └── settings.html ├── static/ │ ├── style.css -│ └── script.js +│ ├── script.js +│ └── settings.js ├── Cargo.toml ├── Dockerfile ├── .gitignore @@ -21,12 +23,17 @@ table-server/ ## Funktionen -- Webbasierte Sensor-Konfigurationstabelle mit 6 Spalten +- **3 separate Tabellen** für verschiedene Sensor-Gruppen +- **Navigation** mit aktivem Status-Indikator +- **Header mit Logo** für professionelles Erscheinungsbild +- **Zeilen hinzufügen/löschen** dynamisch zur Laufzeit +- **Einstellungsseite** für MQTT und InfluxDB Konfiguration - Editierbare Textfelder (Bezeichnung, Adresse, Type, Faktor) - Toggle-Schalter für Boolean-Werte (MQTT, InfluxDB) -- Persistierung in JSON-Datei -- Einfache REST-API +- **Zentrale JSON-Persistierung** für alle Tabellen und Einstellungen +- REST-API für Daten-Management - Docker-Unterstützung +- Responsive Design ## Lokale Entwicklung @@ -89,27 +96,39 @@ docker-compose up -d ## Verwendung 1. Öffne `http://localhost:8080` im Browser -2. Bearbeite die Sensor-Konfigurationen: - - **Bezeichnung**: Name des Sensors - - **Adresse**: IP-Adresse oder Identifier - - **Type**: Sensor-Typ (z.B. Temperatur, Luftfeuchtigkeit) - - **Faktor**: Numerischer Korrekturfaktor - - **MQTT**: Toggle-Schalter für MQTT-Aktivierung - - **InfluxDB**: Toggle-Schalter für InfluxDB-Aktivierung -3. Klicke auf "Speichern" um die Änderungen zu persistieren -4. Die Daten werden in `table_config.json` gespeichert +2. Navigiere zwischen den Tabellen über das Menü: + - **Tabelle 1, 2, 3**: Verschiedene Sensor-Gruppen + - **⚙️ Einstellungen**: MQTT und InfluxDB Konfiguration +3. In den Tabellen: + - **➕ Zeile hinzufügen**: Neue Sensor-Einträge erstellen + - **🗑️ Löschen**: Einzelne Zeilen entfernen + - **Felder bearbeiten**: + - Bezeichnung: Name des Sensors + - Adresse: IP-Adresse oder Identifier + - Type: Sensor-Typ (z.B. Temperatur, Luftfeuchtigkeit) + - Faktor: Numerischer Korrekturfaktor + - MQTT: Toggle-Schalter für MQTT-Aktivierung + - InfluxDB: Toggle-Schalter für InfluxDB-Aktivierung +4. **💾 Speichern**: Änderungen persistieren +5. Alle Daten werden zentral in `table_config.json` gespeichert ## API Endpoints -- `GET /` - Zeigt die HTML-Seite mit der Tabelle -- `POST /api/save` - Speichert die Tabellendaten +- `GET /` - Zeigt Tabelle 1 +- `GET /table/table2` - Zeigt Tabelle 2 +- `GET /table/table3` - Zeigt Tabelle 3 +- `GET /settings` - Zeigt Einstellungsseite +- `POST /api/save` - Speichert eine Tabelle +- `POST /api/save-settings` - Speichert die Einstellungen +- `GET /static/*` - Statische Dateien (CSS, JS) -### Beispiel API-Request +### Beispiel API-Request (Tabelle speichern) ```bash curl -X POST http://localhost:8080/api/save \ -H "Content-Type: application/json" \ -d '{ + "table_id": "table1", "rows": [ { "bezeichnung": "Sensor 1", @@ -118,28 +137,33 @@ curl -X POST http://localhost:8080/api/save \ "faktor": "1.0", "mqtt": true, "influxdb": false - }, - { - "bezeichnung": "Sensor 2", - "adresse": "192.168.1.101", - "type": "Luftfeuchtigkeit", - "faktor": "0.5", - "mqtt": false, - "influxdb": true } ] }' ``` +### Beispiel API-Request (Einstellungen speichern) + +```bash +curl -X POST http://localhost:8080/api/save-settings \ + -H "Content-Type: application/json" \ + -d '{ + "mqtt_broker": "localhost", + "mqtt_port": "1883", + "influxdb_url": "http://localhost:8086", + "influxdb_token": "your-token-here" + }' +``` + ## Konfigurationsdatei -Die Sensor-Daten werden in `table_config.json` gespeichert: +Die komplette Anwendungskonfiguration wird in `table_config.json` gespeichert: ```json { - "rows": [ + "table1": [ { - "bezeichnung": "Sensor 1", + "bezeichnung": "Temp Sensor 1", "adresse": "192.168.1.100", "type": "Temperatur", "faktor": "1.0", @@ -147,22 +171,40 @@ Die Sensor-Daten werden in `table_config.json` gespeichert: "influxdb": false }, { - "bezeichnung": "Sensor 2", + "bezeichnung": "Temp Sensor 2", "adresse": "192.168.1.101", - "type": "Luftfeuchtigkeit", - "faktor": "0.5", + "type": "Temperatur", + "faktor": "1.0", "mqtt": false, "influxdb": true - }, + } + ], + "table2": [ { - "bezeichnung": "Sensor 3", - "adresse": "192.168.1.102", - "type": "Druck", - "faktor": "2.0", + "bezeichnung": "Humidity Sensor 1", + "adresse": "192.168.1.200", + "type": "Luftfeuchtigkeit", + "faktor": "0.5", "mqtt": true, "influxdb": true } - ] + ], + "table3": [ + { + "bezeichnung": "Pressure Sensor 1", + "adresse": "192.168.1.300", + "type": "Druck", + "faktor": "2.0", + "mqtt": true, + "influxdb": false + } + ], + "settings": { + "mqtt_broker": "localhost", + "mqtt_port": "1883", + "influxdb_url": "http://localhost:8086", + "influxdb_token": "" + } } ``` diff --git a/src/main.rs b/src/main.rs index 8ef3d15..70cad4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use actix_web::{web, App, HttpResponse, HttpServer, Result}; +use actix_files as fs; use serde::{Deserialize, Serialize}; -use std::fs; +use std::fs as std_fs; use std::sync::Mutex; use tera::{Context, Tera}; @@ -15,16 +16,38 @@ struct TableRow { } #[derive(Debug, Serialize, Deserialize, Clone)] -struct TableData { - rows: Vec, +struct Settings { + mqtt_broker: String, + mqtt_port: String, + influxdb_url: String, + influxdb_token: String, } -impl Default for TableData { +impl Default for Settings { fn default() -> Self { - TableData { - rows: vec![ + Settings { + mqtt_broker: "localhost".to_string(), + mqtt_port: "1883".to_string(), + influxdb_url: "http://localhost:8086".to_string(), + influxdb_token: "".to_string(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +struct AppConfig { + table1: Vec, + table2: Vec, + table3: Vec, + settings: Settings, +} + +impl Default for AppConfig { + fn default() -> Self { + AppConfig { + table1: vec![ TableRow { - bezeichnung: "Sensor 1".to_string(), + bezeichnung: "Temp Sensor 1".to_string(), adresse: "192.168.1.100".to_string(), r#type: "Temperatur".to_string(), faktor: "1.0".to_string(), @@ -32,39 +55,52 @@ impl Default for TableData { influxdb: false, }, TableRow { - bezeichnung: "Sensor 2".to_string(), + bezeichnung: "Temp Sensor 2".to_string(), adresse: "192.168.1.101".to_string(), - r#type: "Luftfeuchtigkeit".to_string(), - faktor: "0.5".to_string(), + r#type: "Temperatur".to_string(), + faktor: "1.0".to_string(), mqtt: false, influxdb: true, }, + ], + table2: vec![ TableRow { - bezeichnung: "Sensor 3".to_string(), - adresse: "192.168.1.102".to_string(), - r#type: "Druck".to_string(), - faktor: "2.0".to_string(), + bezeichnung: "Humidity Sensor 1".to_string(), + adresse: "192.168.1.200".to_string(), + r#type: "Luftfeuchtigkeit".to_string(), + faktor: "0.5".to_string(), mqtt: true, influxdb: true, }, ], + table3: vec![ + TableRow { + bezeichnung: "Pressure Sensor 1".to_string(), + adresse: "192.168.1.300".to_string(), + r#type: "Druck".to_string(), + faktor: "2.0".to_string(), + mqtt: true, + influxdb: false, + }, + ], + settings: Settings::default(), } } } struct AppState { - table: Mutex, + config: Mutex, config_path: String, templates: Tera, } impl AppState { fn load_or_create(config_path: &str) -> Self { - let table = match fs::read_to_string(config_path) { + let config = match std_fs::read_to_string(config_path) { Ok(content) => serde_json::from_str(&content).unwrap_or_default(), Err(_) => { - let default = TableData::default(); - let _ = fs::write(config_path, serde_json::to_string_pretty(&default).unwrap()); + let default = AppConfig::default(); + let _ = std_fs::write(config_path, serde_json::to_string_pretty(&default).unwrap()); default } }; @@ -78,24 +114,26 @@ impl AppState { }; AppState { - table: Mutex::new(table), + config: Mutex::new(config), config_path: config_path.to_string(), templates: tera, } } fn save(&self) -> Result<(), std::io::Error> { - let table = self.table.lock().unwrap(); - let json = serde_json::to_string_pretty(&*table)?; - fs::write(&self.config_path, json) + let config = self.config.lock().unwrap(); + let json = serde_json::to_string_pretty(&*config)?; + std_fs::write(&self.config_path, json) } } async fn index(data: web::Data) -> Result { - let table = data.table.lock().unwrap(); + let config = data.config.lock().unwrap(); let mut context = Context::new(); - context.insert("rows", &table.rows); + context.insert("rows", &config.table1); + context.insert("table_id", "table1"); + context.insert("active_page", "table1"); let html = data.templates.render("index.html", &context) .map_err(|e| { @@ -106,13 +144,81 @@ async fn index(data: web::Data) -> Result { Ok(HttpResponse::Ok().content_type("text/html").body(html)) } +async fn table_page(data: web::Data, path: web::Path) -> Result { + let table_id = path.into_inner(); + let config = data.config.lock().unwrap(); + + let rows = match table_id.as_str() { + "table1" => &config.table1, + "table2" => &config.table2, + "table3" => &config.table3, + _ => return Ok(HttpResponse::NotFound().body("Table not found")), + }; + + let mut context = Context::new(); + context.insert("rows", rows); + context.insert("table_id", &table_id); + context.insert("active_page", &table_id); + + let html = data.templates.render("index.html", &context) + .map_err(|e| { + eprintln!("Template error: {}", e); + actix_web::error::ErrorInternalServerError("Template error") + })?; + + Ok(HttpResponse::Ok().content_type("text/html").body(html)) +} + +async fn settings_page(data: web::Data) -> Result { + let config = data.config.lock().unwrap(); + + let mut context = Context::new(); + context.insert("settings", &config.settings); + context.insert("active_page", "settings"); + + let html = data.templates.render("settings.html", &context) + .map_err(|e| { + eprintln!("Template error: {}", e); + actix_web::error::ErrorInternalServerError("Template error") + })?; + + Ok(HttpResponse::Ok().content_type("text/html").body(html)) +} + +#[derive(Deserialize)] +struct SaveTableRequest { + table_id: String, + rows: Vec, +} + async fn save_table( data: web::Data, - table_data: web::Json, + req: web::Json, ) -> Result { - let mut table = data.table.lock().unwrap(); - *table = table_data.into_inner(); - drop(table); + let mut config = data.config.lock().unwrap(); + + match req.table_id.as_str() { + "table1" => config.table1 = req.rows.clone(), + "table2" => config.table2 = req.rows.clone(), + "table3" => config.table3 = req.rows.clone(), + _ => return Ok(HttpResponse::BadRequest().json(serde_json::json!({"status": "error", "message": "Invalid table_id"}))), + } + + drop(config); + + match data.save() { + Ok(_) => Ok(HttpResponse::Ok().json(serde_json::json!({"status": "success"}))), + Err(_) => Ok(HttpResponse::InternalServerError().json(serde_json::json!({"status": "error"}))), + } +} + +async fn save_settings( + data: web::Data, + settings: web::Json, +) -> Result { + let mut config = data.config.lock().unwrap(); + config.settings = settings.into_inner(); + drop(config); match data.save() { Ok(_) => Ok(HttpResponse::Ok().json(serde_json::json!({"status": "success"}))), @@ -131,7 +237,11 @@ async fn main() -> std::io::Result<()> { App::new() .app_data(app_state.clone()) .route("/", web::get().to(index)) + .route("/table/{id}", web::get().to(table_page)) + .route("/settings", web::get().to(settings_page)) .route("/api/save", web::post().to(save_table)) + .route("/api/save-settings", web::post().to(save_settings)) + .service(fs::Files::new("/static", "./static")) }) .bind("0.0.0.0:8080")? .run() diff --git a/static/script.js b/static/script.js index 0109322..5da09e7 100644 --- a/static/script.js +++ b/static/script.js @@ -1,14 +1,60 @@ +function addRow() { + const tableBody = document.getElementById('tableBody'); + const rowCount = tableBody.querySelectorAll('tr').length; + + const newRow = document.createElement('tr'); + newRow.setAttribute('data-row', rowCount); + newRow.innerHTML = ` + + + + + + + + + + + + + + `; + + tableBody.appendChild(newRow); +} + +function deleteRow(button) { + const row = button.closest('tr'); + if (confirm('Möchten Sie diese Zeile wirklich löschen?')) { + row.remove(); + updateRowIndices(); + } +} + +function updateRowIndices() { + const rows = document.querySelectorAll('#tableBody tr'); + rows.forEach((row, index) => { + row.setAttribute('data-row', index); + }); +} + async function saveTable() { const rows = []; - const rowCount = document.querySelectorAll('tbody tr').length; + const tableRows = document.querySelectorAll('#tableBody tr'); - for (let i = 0; i < rowCount; i++) { - const bezeichnung = document.querySelector(`input[data-row='${i}'][data-field='bezeichnung']`).value; - const adresse = document.querySelector(`input[data-row='${i}'][data-field='adresse']`).value; - const type = document.querySelector(`input[data-row='${i}'][data-field='type']`).value; - const faktor = document.querySelector(`input[data-row='${i}'][data-field='faktor']`).value; - const mqtt = document.querySelector(`input[data-row='${i}'][data-field='mqtt']`).checked; - const influxdb = document.querySelector(`input[data-row='${i}'][data-field='influxdb']`).checked; + tableRows.forEach((row) => { + const bezeichnung = row.querySelector("input[data-field='bezeichnung']").value; + const adresse = row.querySelector("input[data-field='adresse']").value; + const type = row.querySelector("input[data-field='type']").value; + const faktor = row.querySelector("input[data-field='faktor']").value; + const mqtt = row.querySelector("input[data-field='mqtt']").checked; + const influxdb = row.querySelector("input[data-field='influxdb']").checked; rows.push({ bezeichnung, @@ -18,7 +64,7 @@ async function saveTable() { mqtt, influxdb }); - } + }); try { const response = await fetch('/api/save', { @@ -26,7 +72,10 @@ async function saveTable() { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ rows: rows }) + body: JSON.stringify({ + table_id: tableId, + rows: rows + }) }); const messageDiv = document.getElementById('message'); diff --git a/static/settings.js b/static/settings.js new file mode 100644 index 0000000..a4036da --- /dev/null +++ b/static/settings.js @@ -0,0 +1,40 @@ +async function saveSettings() { + const mqtt_broker = document.getElementById('mqtt_broker').value; + const mqtt_port = document.getElementById('mqtt_port').value; + const influxdb_url = document.getElementById('influxdb_url').value; + const influxdb_token = document.getElementById('influxdb_token').value; + + const settings = { + mqtt_broker, + mqtt_port, + influxdb_url, + influxdb_token + }; + + try { + const response = await fetch('/api/save-settings', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(settings) + }); + + const messageDiv = document.getElementById('message'); + if (response.ok) { + messageDiv.className = 'message success'; + messageDiv.textContent = '✓ Einstellungen erfolgreich gespeichert!'; + } else { + messageDiv.className = 'message error'; + messageDiv.textContent = '✗ Fehler beim Speichern der Einstellungen!'; + } + + setTimeout(() => { + messageDiv.style.display = 'none'; + }, 3000); + } catch (error) { + const messageDiv = document.getElementById('message'); + messageDiv.className = 'message error'; + messageDiv.textContent = '✗ Verbindungsfehler!'; + } +} diff --git a/static/style.css b/static/style.css index 6affd41..12ebc16 100644 --- a/static/style.css +++ b/static/style.css @@ -1,16 +1,79 @@ -body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - max-width: 1200px; - margin: 30px auto; - padding: 20px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - min-height: 100vh; +* { + margin: 0; + padding: 0; + box-sizing: border-box; } +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + padding-top: 80px; +} + +/* Header Styles */ +.header { + position: fixed; + top: 0; + left: 0; + right: 0; + background: white; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + z-index: 1000; +} + +.header-content { + max-width: 1400px; + margin: 0 auto; + padding: 15px 30px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + display: flex; + align-items: center; + gap: 12px; +} + +.logo-text { + font-size: 20px; + font-weight: 700; + color: #333; +} + +.nav { + display: flex; + gap: 5px; +} + +.nav-link { + padding: 10px 20px; + text-decoration: none; + color: #666; + border-radius: 6px; + transition: all 0.3s; + font-weight: 500; +} + +.nav-link:hover { + background-color: #f0f0f0; + color: #333; +} + +.nav-link.active { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +/* Container */ .container { + max-width: 1400px; + margin: 30px auto; + padding: 30px; background-color: white; border-radius: 12px; - padding: 30px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } @@ -21,8 +84,18 @@ h1 { font-size: 28px; } +h2 { + color: #333; + margin-bottom: 20px; + font-size: 20px; + border-bottom: 2px solid #667eea; + padding-bottom: 10px; +} + +/* Table Styles */ .table-wrapper { overflow-x: auto; + margin-bottom: 20px; } table { @@ -64,7 +137,7 @@ tr:hover { border-color: #667eea; } -/* Toggle Switch Styles */ +/* Toggle Switch */ .switch { position: relative; display: inline-block; @@ -114,11 +187,16 @@ input:checked + .slider:before { transform: translateX(26px); } -.save-btn { - display: block; - width: 200px; - margin: 30px auto 0; - padding: 14px; +/* Buttons */ +.button-group { + display: flex; + gap: 15px; + justify-content: center; + margin-top: 20px; +} + +.save-btn, .add-btn { + padding: 14px 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; @@ -129,15 +207,35 @@ input:checked + .slider:before { transition: transform 0.2s, box-shadow 0.2s; } -.save-btn:hover { +.save-btn:hover, .add-btn:hover { transform: translateY(-2px); box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4); } -.save-btn:active { +.save-btn:active, .add-btn:active { transform: translateY(0); } +.add-btn { + background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); +} + +.delete-btn { + padding: 8px 12px; + background-color: #f44336; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s; +} + +.delete-btn:hover { + background-color: #da190b; +} + +/* Messages */ .message { text-align: center; padding: 12px; @@ -160,3 +258,52 @@ input:checked + .slider:before { border: 1px solid #f5c6cb; display: block; } + +/* Settings Page */ +.settings-section { + margin-bottom: 40px; + padding: 20px; + background-color: #f9f9f9; + border-radius: 8px; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + color: #333; + font-weight: 500; +} + +.form-group .text-input { + max-width: 600px; +} + +/* Responsive */ +@media (max-width: 768px) { + .header-content { + flex-direction: column; + gap: 15px; + } + + .nav { + flex-wrap: wrap; + justify-content: center; + } + + .container { + padding: 15px; + margin: 15px; + } + + .button-group { + flex-direction: column; + } + + .save-btn, .add-btn { + width: 100%; + } +} diff --git a/templates/index.html b/templates/index.html index 57d8a31..30f6bf6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -7,11 +7,29 @@ +
+
+ + +
+
+
-

🔧 Sensor Konfiguration

+

🔧 Sensor Konfiguration - {{ table_id | upper }}

- +
@@ -20,35 +38,45 @@ + - + {% for row in rows %} - - - - - + + + + + + {% endfor %}
BezeichnungFaktor MQTT InfluxDBAktionen
+ +
- +
+ + +
+ diff --git a/templates/settings.html b/templates/settings.html new file mode 100644 index 0000000..f1469e4 --- /dev/null +++ b/templates/settings.html @@ -0,0 +1,61 @@ + + + + + + Einstellungen + + + +
+
+ + +
+
+ +
+

⚙️ Einstellungen

+
+ +
+

MQTT Konfiguration

+
+ + +
+
+ + +
+
+ +
+

InfluxDB Konfiguration

+
+ + +
+
+ + +
+
+ + +
+ + + +