Compare commits

...

2 Commits

6 changed files with 116 additions and 87 deletions

View File

@ -1,5 +1,6 @@
default: default:
loglevel: "DEBUG" loglevel: "DEBUG"
darkmode: true
mqtt: mqtt:
broker: "192.168.178.2" broker: "192.168.178.2"

View File

@ -1,6 +1,21 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DefaultConfig {
pub loglevel: Option<String>,
pub darkmode: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ModbusConfig {
pub host: String,
pub port: u16,
pub max_coils_addr: Option<u16>,
pub max_input_addr: Option<u16>,
pub max_holding_addr: Option<u16>,
}
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MqttConfig { pub struct MqttConfig {
pub broker: String, pub broker: String,
@ -30,20 +45,6 @@ pub struct ModbusRegisterConfig {
pub comment: Option<String>, pub comment: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ModbusConfig {
pub host: String,
pub port: u16,
pub max_coils_addr: Option<u16>,
pub max_input_addr: Option<u16>,
pub max_holding_addr: Option<u16>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DefaultConfig {
pub loglevel: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppConfig { pub struct AppConfig {
pub default: DefaultConfig, pub default: DefaultConfig,

View File

@ -1,4 +1,3 @@
use actix_web::{web, App, HttpResponse, HttpServer, Result}; use actix_web::{web, App, HttpResponse, HttpServer, Result};
use actix_files as actix_fs; use actix_files as actix_fs;
use std::sync::{Mutex, Arc}; use std::sync::{Mutex, Arc};
@ -12,9 +11,6 @@ mod modbus;
use crate::config::{AppConfig, ModbusRegisterConfig, ModbusValueMaps}; use crate::config::{AppConfig, ModbusRegisterConfig, ModbusValueMaps};
// ...existing code...
struct AppState { struct AppState {
config: Mutex<AppConfig>, config: Mutex<AppConfig>,
value_maps: Arc<Mutex<ModbusValueMaps>>, value_maps: Arc<Mutex<ModbusValueMaps>>,
@ -44,9 +40,6 @@ impl AppState {
} }
} }
// ...existing code...
async fn index(data: web::Data<AppState>) -> Result<HttpResponse> { async fn index(data: web::Data<AppState>) -> Result<HttpResponse> {
let config = data.config.lock().unwrap(); let config = data.config.lock().unwrap();
let value_maps = data.value_maps.lock().unwrap(); let value_maps = data.value_maps.lock().unwrap();
@ -168,7 +161,17 @@ async fn save_settings(
settings: web::Json<AppConfig>, settings: web::Json<AppConfig>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let mut config = data.config.lock().unwrap(); let mut config = data.config.lock().unwrap();
let new_config = settings.into_inner(); let mut new_config = settings.into_inner();
// Tabellenwerte erhalten, falls sie im Request null sind
if new_config.modbus_coils.is_none() {
new_config.modbus_coils = config.modbus_coils.clone();
}
if new_config.modbus_input_register.is_none() {
new_config.modbus_input_register = config.modbus_input_register.clone();
}
if new_config.modbus_holding_register.is_none() {
new_config.modbus_holding_register = config.modbus_holding_register.clone();
}
// Value-Maps neu initialisieren // Value-Maps neu initialisieren
let mut value_maps = data.value_maps.lock().unwrap(); let mut value_maps = data.value_maps.lock().unwrap();
*value_maps = ModbusValueMaps::from_config(&new_config); *value_maps = ModbusValueMaps::from_config(&new_config);
@ -184,6 +187,11 @@ async fn save_settings(
Ok(HttpResponse::Ok().body("success")) Ok(HttpResponse::Ok().body("success"))
} }
async fn get_config(data: web::Data<AppState>) -> HttpResponse {
let config = data.config.lock().unwrap();
HttpResponse::Ok().json(&*config)
}
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
@ -215,6 +223,7 @@ async fn main() -> std::io::Result<()> {
.route("/settings", web::get().to(settings_page)) .route("/settings", web::get().to(settings_page))
.route("/api/save", web::post().to(save_table)) .route("/api/save", web::post().to(save_table))
.route("/api/save-settings", web::post().to(save_settings)) .route("/api/save-settings", web::post().to(save_settings))
.route("/api/config", web::get().to(get_config))
.service(actix_fs::Files::new("/static", "./static")) .service(actix_fs::Files::new("/static", "./static"))
}) })
.bind("0.0.0.0:8080")? .bind("0.0.0.0:8080")?

View File

@ -1,11 +1,15 @@
// Sortiere die Tabelle nach Adresse (addr) beim Laden der Seite // Sortiere die Tabelle nach Adresse (addr) beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Darkmode-Status aus localStorage übernehmen // Darkmode-Status aus Konfiguration übernehmen
if (localStorage.getItem('darkmode') === 'true') { fetch('/api/config')
.then(response => response.json())
.then(config => {
if (config && config.default && config.default.darkmode === true) {
document.body.classList.add('darkmode'); document.body.classList.add('darkmode');
} else { } else {
document.body.classList.remove('darkmode'); document.body.classList.remove('darkmode');
} }
});
const tableBody = document.getElementById('tableBody'); const tableBody = document.getElementById('tableBody');
if (tableBody) { if (tableBody) {
// Extrahiere alle Zeilen und deren addr // Extrahiere alle Zeilen und deren addr

View File

@ -1,4 +1,44 @@
// Setze den Wert des Darkmode-Schalters beim Laden der Seite, sobald das Element existiert
document.addEventListener('DOMContentLoaded', function() {
fetch('/api/config')
.then(response => response.json())
.then(config => {
function setSwitch() {
const el = document.getElementById('darkmode_switch');
if (el) {
if (config && config.default && (config.default.darkmode === true || config.default.darkmode === 'true')) {
el.checked = true;
} else {
el.checked = false;
}
} else {
// Falls das Element noch nicht existiert, erneut versuchen
setTimeout(setSwitch, 50);
}
}
setSwitch();
});
});
async function saveSettings() { async function saveSettings() {
// Default (für [default])
const darkmode = document.getElementById('darkmode_switch')?.checked ?? null;
const defaultConfig = {
loglevel: document.getElementById('loglevel')?.value || null,
darkmode: darkmode
};
// Modbus
const modbus = {
host: document.getElementById('modbus_host').value,
port: parseInt(document.getElementById('modbus_port').value, 10),
max_coils_addr: parseInt(document.getElementById('modbus_max_coils_addr').value, 10) || null,
max_input_addr: parseInt(document.getElementById('modbus_max_input_addr').value, 10) || null,
max_holding_addr: parseInt(document.getElementById('modbus_max_holding_addr').value, 10) || null,
modbus_coils: null,
modbus_input_register: null,
modbus_holding_register: null
};
// MQTT // MQTT
const mqtt = { const mqtt = {
broker: document.getElementById('mqtt_broker').value, broker: document.getElementById('mqtt_broker').value,
@ -18,28 +58,11 @@ async function saveSettings() {
measurement: document.getElementById('influxdb_measurement').value || null measurement: document.getElementById('influxdb_measurement').value || null
}; };
// Modbus
const modbus = {
host: document.getElementById('modbus_host').value,
port: parseInt(document.getElementById('modbus_port').value, 10),
max_coils_addr: parseInt(document.getElementById('modbus_max_coils_addr').value, 10) || null,
max_input_addr: parseInt(document.getElementById('modbus_max_input_addr').value, 10) || null,
max_holding_addr: parseInt(document.getElementById('modbus_max_holding_addr').value, 10) || null,
modbus_coils: null,
modbus_input_register: null,
modbus_holding_register: null
};
// Default (für [default])
const defaultConfig = {
loglevel: document.getElementById('loglevel')?.value || null
};
const settings = { const settings = {
default: defaultConfig, default: defaultConfig,
modbus,
mqtt, mqtt,
influxdb, influxdb
modbus
}; };
try { try {
@ -70,16 +93,3 @@ async function saveSettings() {
} }
} }
// Darkmode Toggle
document.addEventListener('DOMContentLoaded', function() {
const btn = document.getElementById('darkmode-toggle');
if (!btn) return;
// Initialer Zustand aus LocalStorage
if (localStorage.getItem('darkmode') === 'true') {
document.body.classList.add('darkmode');
}
btn.addEventListener('click', function() {
document.body.classList.toggle('darkmode');
localStorage.setItem('darkmode', document.body.classList.contains('darkmode'));
});
});

View File

@ -42,8 +42,36 @@
<option value="ERROR">ERROR</option> <option value="ERROR">ERROR</option>
</select> </select>
</div> </div>
<div class="form-group" style="text-align:right;"> <div class="form-group" style="display: flex; align-items: center; gap: 12px;">
<button id="darkmode-toggle" class="add-btn" type="button">🌙 Darkmode umschalten</button> <label for="darkmode_switch">Darkmode:</label>
<label class="switch">
<input type="checkbox" id="darkmode_switch">
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-section">
<h2>Modbus Konfiguration</h2>
<div class="form-group">
<label for="modbus_host">Host:</label>
<input type="text" id="modbus_host" class="text-input" value="{{ modbus.host }}" />
</div>
<div class="form-group">
<label for="modbus_port">Port:</label>
<input type="text" id="modbus_port" class="text-input" value="{{ modbus.port }}" />
</div>
<div class="form-group">
<label for="modbus_max_coils_addr">Max Coils Addr:</label>
<input type="text" id="modbus_max_coils_addr" class="text-input" value="{{ modbus.max_coils_addr | default(value="") }}" />
</div>
<div class="form-group">
<label for="modbus_max_input_addr">Max Input Addr:</label>
<input type="text" id="modbus_max_input_addr" class="text-input" value="{{ modbus.max_input_addr | default(value="") }}" />
</div>
<div class="form-group">
<label for="modbus_max_holding_addr">Max Holding Addr:</label>
<input type="text" id="modbus_max_holding_addr" class="text-input" value="{{ modbus.max_holding_addr | default(value="") }}" />
</div> </div>
</div> </div>
@ -99,30 +127,6 @@
</div> </div>
</div> </div>
<div class="settings-section">
<h2>Modbus Konfiguration</h2>
<div class="form-group">
<label for="modbus_host">Host:</label>
<input type="text" id="modbus_host" class="text-input" value="{{ modbus.host }}" />
</div>
<div class="form-group">
<label for="modbus_port">Port:</label>
<input type="text" id="modbus_port" class="text-input" value="{{ modbus.port }}" />
</div>
<div class="form-group">
<label for="modbus_max_coils_addr">Max Coils Addr:</label>
<input type="text" id="modbus_max_coils_addr" class="text-input" value="{{ modbus.max_coils_addr | default(value="") }}" />
</div>
<div class="form-group">
<label for="modbus_max_input_addr">Max Input Addr:</label>
<input type="text" id="modbus_max_input_addr" class="text-input" value="{{ modbus.max_input_addr | default(value="") }}" />
</div>
<div class="form-group">
<label for="modbus_max_holding_addr">Max Holding Addr:</label>
<input type="text" id="modbus_max_holding_addr" class="text-input" value="{{ modbus.max_holding_addr | default(value="") }}" />
</div>
</div>
<button class="save-btn" onclick="saveSettings()">💾 Einstellungen speichern</button> <button class="save-btn" onclick="saveSettings()">💾 Einstellungen speichern</button>
</div> </div>