v0.12.6-hotfix
This commit is contained in:
+11
-11
@@ -5,7 +5,7 @@ dc8d8412b74e19f2d32570ca0ef176c0036a140e47e41d020cb4d140ad9bfe39 ./Cargo.toml
|
||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||
079ddac7ba983a3d679b8c748bd43c731eb078e91a40c5d8a683bd76e14f06c5 ./README.md
|
||||
d56cfff4179a6511aa764a91c6ddebe21fcd35f36b7298e62d012e11f62bfeb9 ./build.rs
|
||||
514065eefa7428afbcacaca7095605495f95339a2ae8b23707315dc434f9c4c5 ./docs/API.md
|
||||
ed543caace807645561db525c19ae2c3745600af146e1c1ea9b89a39a7f597a3 ./docs/API.md
|
||||
2e1e18fd8167dabfe2469c26e85cce62486c7cb6a502c63c6f6b0cd74d5885e0 ./docs/FLOW.md
|
||||
cb62e2ea377bdf4785c139ee169eec4914e04ceac6883c0afcb4c96fe60427b0 ./docs/openapi.json
|
||||
b11e581dd916676e24402112fe6896bc681abf3c94cafc85c4d3ded3f3a272a3 ./home-assistant/README.md
|
||||
@@ -27,8 +27,8 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./home-assista
|
||||
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./home-assistant/custom_components/gree_controller/translations/pl.json
|
||||
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./home-assistant/generated/gree_controller_entities.example.json
|
||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||
00cd78873d3a3290419a83ea736a9ec5f0d93f590159cbf40c54d5ea6c3c89fb ./lang/en.json
|
||||
a088acc585a863e204100af7f5465a5d761ba94c0afff66b5ed329e7d204ec35 ./lang/pl.json
|
||||
a5412c879ac0896ae6f9c2d66dd861789897b8c8610098e3fb9b5462068acd74 ./lang/en.json
|
||||
615d7297c5fa6228c017a7814aa17898aa990bfbde649d9c7c43b7b49d046ebe ./lang/pl.json
|
||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
||||
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
||||
@@ -109,11 +109,11 @@ e0340b0e5945d5a38a1054e549c819566d5c1ef6034ab0e4d44c68a3b9a249cf ./src/db.rs
|
||||
be4082b142fe16ba40c5978b287b7ef808e67c13edbd8f2d6637ccad974b8847 ./src/db/configuration.rs
|
||||
bc10f1a66ddcaacbb08b096a02998482da547024dd0ba3144d00f993b4351cb0 ./src/db/core_devices.rs
|
||||
4c20289c08b0cb9c3c4324d9dbc6a47c7dd953aff6fd1746f04b6024043d3bc5 ./src/db/device_history.rs
|
||||
eb07d94b58bcc8ea17b5b086cc88c165a35666f84c3d9a1d74f72dc30b979829 ./src/db/events_tokens.rs
|
||||
88cb4b7ff61a8a558949faa7157d4bc2c45facb951c57240412c4c9d3befdb86 ./src/db/events_tokens.rs
|
||||
bb5048ee60053f39ce3c1203dc1f24c7047310538d2357f105a052c016d21ec4 ./src/db/flows.rs
|
||||
5f4b567c6bc866d0674b3b0c66f4aeccf561774623f575b872ff25bf015b91c2 ./src/db/ha_history.rs
|
||||
c0a7fa3eac198737d7261ad00c542a614e6d7291f6581af8e7f7aef9dec1a049 ./src/db/schedules_automations.rs
|
||||
1a209dc6d8df864778bb03c980f309a2ec86fc62df274880fbb95eff8470174e ./src/db/tests.rs
|
||||
12c4f69011b22ec0af65121100e5cc717fe7bd5a139f4180d1982a9045c6918e ./src/db/tests.rs
|
||||
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
|
||||
4d2c6053586558afbca6b60fbbc9b1cd397f74f8284d7c01f3337eb42bf6d4f7 ./src/engine.rs
|
||||
6046bdbc9c02ac74f47068229c59af69abc8bfa46cfeff6fe7ba573b493e128c ./src/engine/automations.rs
|
||||
@@ -152,7 +152,7 @@ e1621255217ee6128151cc898373dd404123cb6d54017beec6c7e3e5c3030486 ./src/models/i
|
||||
887eb80a58998e90023e952c1fde67435eb843493387ae64536613b74c084534 ./src/models/settings_api.rs
|
||||
c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs
|
||||
7d798228a8d4d4e60a112167c001535f4b8a5fa32ec7a33554c73125c42256c2 ./src/models/zone.rs
|
||||
48e5d0e0c8b2863daf35c64ad49bf5333fb3b807568323cf0958efbc3aee7433 ./src/notifications.rs
|
||||
62e696df8b86835df7ad127f03c257a78580769dc82bc66daa5041f71e8440a5 ./src/notifications.rs
|
||||
cca742bb2b500eb9269f69385fcf88053cd08aabf6a98b7669b8a7a2fd67df13 ./src/protocol/crypto.rs
|
||||
28969da06a454ddd1ebea5fa606e8446a83dfd21e21352767c189013c508cb90 ./src/protocol/gree.rs
|
||||
7907c33e6396c7e0d61705b735b21c84d6ed784012fb61022b872c6745540f1d ./src/protocol/gree/binding.rs
|
||||
@@ -169,14 +169,14 @@ c868422c02ba457df21ae92acbd00698c243855ddd1d4e95ac2166796eba9a44 ./src/protocol
|
||||
7fac65a9dcbcecee09816f5447c7792dc94f736b1efb66ac85629db75f6280be ./src/queries/device_history.rs
|
||||
3fb50f57d30ac23ef122c45dae4bd96c7cb7184df02d99aef00d9890b66479e2 ./src/queries/entities.rs
|
||||
2c5fba462b72158b04dbaaa53c8536263f8d72a1338a6c4ff06cb28a36f6ae27 ./src/queries/ha_history.rs
|
||||
c91e4b8b250b30cbe76f313b70c16244e9003bce92e02c82283a1831f91974c5 ./src/queries/maintenance.rs
|
||||
3112452367b68aeda0a5e3143b1a51ce62e3159859fb41e92c35642a3cd72322 ./src/queries/maintenance.rs
|
||||
3a49bad74a2f047893e726b5e00a6e833c228ad268bdf50cd60f786ba164ea54 ./src/queries/schema.rs
|
||||
f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/zone_history.rs
|
||||
008be170ed418f8701c512f521828b8e6c0071ff195cd6f7aa39fade6c7c3fa9 ./src/state.rs
|
||||
7af6db4d826f8cb5f2305f743bffe4e112d4e1c1772366a83ba75953e60db1c3 ./src/state.rs
|
||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||
62e0a01a47a2d30e5265010aef5a089b0055d824fede940b7665d76f92018522 ./web/index.html
|
||||
9e98df7a759bd76bc7682b23c3c4b7e29f7bde972f03ae316ba7f8159301e2c6 ./web/index.html
|
||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
||||
45fb54541a117a8ee0dc484cd263b6af4aafab75d94d269b126bb603b5f0392c ./web/js/bootstrap.js
|
||||
badde8a5214a0322b2c45803c02a1cb723d53b25e57c687369cd804f509d9744 ./web/js/charts.js
|
||||
@@ -189,11 +189,11 @@ cf1ebf3b9d666f309a1add070b4da86be86d990b02c8d6474f6d121c6d332746 ./web/js/forms
|
||||
dbf36223863ba882c03eccea4516ba8db7acf0c2e72c6c64490cb994f1e20f96 ./web/js/history.js
|
||||
7eaa04992ea828dc89f5eaebf674cbbfd160f53eb8d0d6ef0d4e47bd8c28c435 ./web/js/main.js
|
||||
733fcc595ed9c03c9639acc6caba46f0588f29e0b95bf72891e34443e7aa9db8 ./web/js/navigation.js
|
||||
e3e01511a34175b88eb01dac2c557e8b112fcad5826f02a0bc3cd1e8dfb7ff91 ./web/js/realtime.js
|
||||
0f0ecb9fcc2b47aece715de4ededdc1cf512daf404c37efe33bf9069027221e3 ./web/js/realtime.js
|
||||
c8c82e88c3715b1bfabf155e36266a4c94f5e2fc03eb39dcdd9d08a1985a997c ./web/js/router.js
|
||||
25f5c9a0aa8622f419b05f64712c9eca4896f0e79818616904b83051fd77b07b ./web/js/settings-ui.js
|
||||
7399a951ae585d7c9ee55cc57064c64a2806ec725e2456cfe6b09bc61f8f4166 ./web/js/settings.js
|
||||
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
||||
801bb151f7b6220587e22c3114fb224a540dcfbddd1c09870007b7b893a01301 ./web/styles.css
|
||||
0b9774bb388158e23f8c90297d70659a015e0db58d25eadcae316a49e3c9fcb7 ./web/styles.css
|
||||
ae1b03f30b494f474a781a5d32072f1e73eba2b7c768109fc1cc8028cb6662a4 ./web/sw.js
|
||||
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
||||
|
||||
@@ -1012,6 +1012,8 @@ Response:
|
||||
}
|
||||
```
|
||||
|
||||
For events that are eligible for external notification, `metadata.notification` records the delivery result. `status` is `sent`, `silent` or `failed`. A `silent` event includes a `reason` such as `alert_type_disabled`, `notifications_disabled`, `mode_filtered` or `cooldown`; the Web UI shows these rows with a **SILENT** badge. Notification-status changes are also emitted as `log.updated` WebSocket events.
|
||||
|
||||
---
|
||||
|
||||
## Runtime settings — 0.12.0
|
||||
|
||||
@@ -192,6 +192,12 @@
|
||||
"logs.diagnostics": "Diagnostics",
|
||||
"logs.emptyTitle": "No events",
|
||||
"logs.emptyText": "The event log is empty.",
|
||||
"logs.silent": "SILENT",
|
||||
"logs.silentHint": "No notification was sent.",
|
||||
"logs.silentAlertTypeDisabled": "No notification was sent: this alert type is disabled.",
|
||||
"logs.silentNotificationsDisabled": "No notification was sent: notifications are disabled.",
|
||||
"logs.silentModeFiltered": "No notification was sent: the notification mode filtered this event.",
|
||||
"logs.silentCooldown": "No notification was sent: notification cooldown is active.",
|
||||
"common.name": "Name",
|
||||
"common.zone": "Zone",
|
||||
"common.device": "Device",
|
||||
@@ -877,6 +883,7 @@
|
||||
"flow.emptyCanvas": "Start by adding blocks",
|
||||
"flow.emptyCanvasHint": "Put conditions on the left and actions on the right.",
|
||||
"flow.interpretation": "Interpretation",
|
||||
"flow.previewDetails": "Interpretation and Flow cycle",
|
||||
"flow.selectBlock": "Select a block",
|
||||
"flow.selectBlockHint": "Its settings will appear here.",
|
||||
"flow.newDefaultName": "New logic",
|
||||
|
||||
@@ -192,6 +192,12 @@
|
||||
"logs.diagnostics": "Diagnostyka",
|
||||
"logs.emptyTitle": "Brak zdarzeń",
|
||||
"logs.emptyText": "Log jest pusty.",
|
||||
"logs.silent": "SILENT",
|
||||
"logs.silentHint": "Powiadomienie nie zostało wysłane.",
|
||||
"logs.silentAlertTypeDisabled": "Powiadomienie nie zostało wysłane: ten typ alertu jest wyłączony.",
|
||||
"logs.silentNotificationsDisabled": "Powiadomienie nie zostało wysłane: powiadomienia są wyłączone.",
|
||||
"logs.silentModeFiltered": "Powiadomienie nie zostało wysłane: zdarzenie odfiltrował tryb powiadomień.",
|
||||
"logs.silentCooldown": "Powiadomienie nie zostało wysłane: aktywny jest cooldown.",
|
||||
"common.name": "Nazwa",
|
||||
"common.zone": "Strefa",
|
||||
"common.device": "Urządzenie",
|
||||
@@ -877,6 +883,7 @@
|
||||
"flow.emptyCanvas": "Zacznij od dodania bloków",
|
||||
"flow.emptyCanvasHint": "Warunki układaj po lewej, akcje po prawej.",
|
||||
"flow.interpretation": "Interpretacja",
|
||||
"flow.previewDetails": "Interpretacja i cykl Flow",
|
||||
"flow.selectBlock": "Wybierz blok",
|
||||
"flow.selectBlockHint": "Tutaj pojawią się jego ustawienia.",
|
||||
"flow.newDefaultName": "Nowa logika",
|
||||
|
||||
@@ -29,6 +29,14 @@ impl Db {
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
|
||||
pub fn update_event_metadata(&self, id: i64, metadata: &Value) -> Result<bool> {
|
||||
let conn = self.lock()?;
|
||||
Ok(conn.execute(
|
||||
queries::UPDATE_EVENT_METADATA,
|
||||
params![serde_json::to_string(metadata)?, id],
|
||||
)? > 0)
|
||||
}
|
||||
|
||||
pub fn list_events(&self, limit: u32) -> Result<Vec<EventLog>> {
|
||||
let conn = self.lock()?;
|
||||
let mut stmt = conn.prepare(queries::LIST_EVENTS)?;
|
||||
|
||||
+11
-1
@@ -38,9 +38,19 @@ mod tests {
|
||||
let loaded = db.get_device(&device.id).unwrap().unwrap();
|
||||
assert_eq!(loaded.mac, device.mac);
|
||||
assert_eq!(db.list_devices().unwrap().len(), 1);
|
||||
db.log_event("info", "test", "ok", &serde_json::json!({"a":1}))
|
||||
let event_id = db
|
||||
.log_event("info", "test", "ok", &serde_json::json!({"a":1}))
|
||||
.unwrap();
|
||||
assert_eq!(db.list_events(10).unwrap().len(), 1);
|
||||
db.update_event_metadata(
|
||||
event_id,
|
||||
&serde_json::json!({"a":1,"notification":{"status":"silent"}}),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
db.list_events(10).unwrap()[0].metadata["notification"]["status"],
|
||||
"silent"
|
||||
);
|
||||
{
|
||||
let conn = db.lock().unwrap();
|
||||
conn.execute(
|
||||
|
||||
+132
-8
@@ -64,14 +64,28 @@ fn alert_type_enabled(cfg: &NotificationSettings, kind: &str) -> bool {
|
||||
types.other
|
||||
}
|
||||
|
||||
fn should_send(cfg: &NotificationSettings, level: &str, kind: &str) -> bool {
|
||||
if !cfg.enabled || !alert_type_enabled(cfg, kind) {
|
||||
return false;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum DeliveryDecision {
|
||||
NotApplicable,
|
||||
Silent(&'static str),
|
||||
Send,
|
||||
}
|
||||
if level == "error" || level == "warn" {
|
||||
return true;
|
||||
|
||||
fn delivery_decision(cfg: &NotificationSettings, level: &str, kind: &str) -> DeliveryDecision {
|
||||
let candidate = level == "error" || level == "warn" || important_kind(kind);
|
||||
if !candidate {
|
||||
return DeliveryDecision::NotApplicable;
|
||||
}
|
||||
cfg.mode == "important" && important_kind(kind)
|
||||
if !cfg.enabled {
|
||||
return DeliveryDecision::Silent("notifications_disabled");
|
||||
}
|
||||
if !alert_type_enabled(cfg, kind) {
|
||||
return DeliveryDecision::Silent("alert_type_disabled");
|
||||
}
|
||||
if level != "error" && level != "warn" && cfg.mode != "important" {
|
||||
return DeliveryDecision::Silent("mode_filtered");
|
||||
}
|
||||
DeliveryDecision::Send
|
||||
}
|
||||
|
||||
fn cooldown_key(cfg: &NotificationSettings, kind: &str, metadata: &Value) -> String {
|
||||
@@ -100,15 +114,85 @@ fn take_cooldown(cfg: &NotificationSettings, kind: &str, metadata: &Value) -> bo
|
||||
true
|
||||
}
|
||||
|
||||
fn with_notification_status(
|
||||
metadata: &Value,
|
||||
status: &str,
|
||||
reason: Option<&str>,
|
||||
provider: &str,
|
||||
) -> Value {
|
||||
let mut updated = match metadata {
|
||||
Value::Object(map) => Value::Object(map.clone()),
|
||||
other => json!({"data": other}),
|
||||
};
|
||||
let mut notification = serde_json::Map::new();
|
||||
notification.insert("status".into(), Value::String(status.into()));
|
||||
if let Some(reason) = reason {
|
||||
notification.insert("reason".into(), Value::String(reason.into()));
|
||||
}
|
||||
if !provider.trim().is_empty() {
|
||||
notification.insert("provider".into(), Value::String(provider.into()));
|
||||
}
|
||||
if let Some(object) = updated.as_object_mut() {
|
||||
object.insert("notification".into(), Value::Object(notification));
|
||||
}
|
||||
updated
|
||||
}
|
||||
|
||||
fn persist_notification_status(
|
||||
state: &AppState,
|
||||
event_id: Option<i64>,
|
||||
metadata: &Value,
|
||||
status: &str,
|
||||
reason: Option<&str>,
|
||||
provider: &str,
|
||||
) {
|
||||
let Some(event_id) = event_id else {
|
||||
return;
|
||||
};
|
||||
let updated = with_notification_status(metadata, status, reason, provider);
|
||||
match state.db.update_event_metadata(event_id, &updated) {
|
||||
Ok(true) => state.broadcast(
|
||||
"log.updated",
|
||||
json!({"id": event_id, "metadata": updated}),
|
||||
),
|
||||
Ok(false) => tracing::warn!(event_id, "cannot update missing event log metadata"),
|
||||
Err(err) => tracing::warn!(event_id, error=?err, "cannot update event log metadata"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn dispatch(
|
||||
state: AppState,
|
||||
event_id: Option<i64>,
|
||||
level: String,
|
||||
kind: String,
|
||||
message: String,
|
||||
metadata: Value,
|
||||
) {
|
||||
let cfg = state.settings.read().await.notifications.clone();
|
||||
if !should_send(&cfg, &level, &kind) || !take_cooldown(&cfg, &kind, &metadata) {
|
||||
match delivery_decision(&cfg, &level, &kind) {
|
||||
DeliveryDecision::NotApplicable => return,
|
||||
DeliveryDecision::Silent(reason) => {
|
||||
persist_notification_status(
|
||||
&state,
|
||||
event_id,
|
||||
&metadata,
|
||||
"silent",
|
||||
Some(reason),
|
||||
&cfg.provider,
|
||||
);
|
||||
return;
|
||||
}
|
||||
DeliveryDecision::Send => {}
|
||||
}
|
||||
if !take_cooldown(&cfg, &kind, &metadata) {
|
||||
persist_notification_status(
|
||||
&state,
|
||||
event_id,
|
||||
&metadata,
|
||||
"silent",
|
||||
Some("cooldown"),
|
||||
&cfg.provider,
|
||||
);
|
||||
return;
|
||||
}
|
||||
let title = format!(
|
||||
@@ -139,10 +223,28 @@ pub async fn dispatch(
|
||||
}
|
||||
_ => Err("unsupported notification provider".into()),
|
||||
};
|
||||
if let Err(err) = result {
|
||||
match result {
|
||||
Ok(()) => persist_notification_status(
|
||||
&state,
|
||||
event_id,
|
||||
&metadata,
|
||||
"sent",
|
||||
None,
|
||||
&cfg.provider,
|
||||
),
|
||||
Err(err) => {
|
||||
persist_notification_status(
|
||||
&state,
|
||||
event_id,
|
||||
&metadata,
|
||||
"failed",
|
||||
Some("delivery_failed"),
|
||||
&cfg.provider,
|
||||
);
|
||||
tracing::warn!(provider=%cfg.provider, error=%err, "notification delivery failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -161,6 +263,28 @@ mod tests {
|
||||
assert!(alert_type_enabled(&cfg, "zone.sensor_discrepancy"));
|
||||
assert!(!alert_type_enabled(&cfg, "zone.action_error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_alert_type_is_recorded_as_silent() {
|
||||
let mut cfg = NotificationSettings::default();
|
||||
cfg.enabled = true;
|
||||
cfg.mode = "important".into();
|
||||
cfg.alert_types.sensor_discrepancy = false;
|
||||
|
||||
assert_eq!(
|
||||
delivery_decision(&cfg, "warn", "zone.sensor_discrepancy"),
|
||||
DeliveryDecision::Silent("alert_type_disabled")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordinary_info_event_has_no_notification_status() {
|
||||
let cfg = NotificationSettings::default();
|
||||
assert_eq!(
|
||||
delivery_decision(&cfg, "info", "zone.quick_control"),
|
||||
DeliveryDecision::NotApplicable
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_pushover(
|
||||
|
||||
@@ -51,6 +51,7 @@ SELECT
|
||||
|
||||
pub const INSERT_EVENT: &str =
|
||||
"INSERT INTO event_log(timestamp,level,kind,message,metadata) VALUES(?1,?2,?3,?4,?5)";
|
||||
pub const UPDATE_EVENT_METADATA: &str = "UPDATE event_log SET metadata=?1 WHERE id=?2";
|
||||
pub const LIST_EVENTS: &str =
|
||||
"SELECT id,timestamp,level,kind,message,metadata FROM event_log ORDER BY id DESC LIMIT ?1";
|
||||
pub const PRUNE_EVENTS: &str = "DELETE FROM event_log WHERE timestamp < ?1";
|
||||
|
||||
+10
-2
@@ -165,12 +165,17 @@ impl AppState {
|
||||
}
|
||||
|
||||
pub fn log(&self, level: &str, kind: &str, message: &str, metadata: Value) {
|
||||
if let Err(err) = self.db.log_event(level, kind, message, &metadata) {
|
||||
let event_id = match self.db.log_event(level, kind, message, &metadata) {
|
||||
Ok(id) => Some(id),
|
||||
Err(err) => {
|
||||
tracing::warn!(error=?err, "cannot persist event log");
|
||||
None
|
||||
}
|
||||
};
|
||||
self.broadcast(
|
||||
"log.created",
|
||||
serde_json::json!({
|
||||
"id": event_id,
|
||||
"level": level,
|
||||
"kind": kind,
|
||||
"message": message,
|
||||
@@ -183,7 +188,10 @@ impl AppState {
|
||||
let kind = kind.to_string();
|
||||
let message = message.to_string();
|
||||
handle.spawn(async move {
|
||||
crate::notifications::dispatch(state, level, kind, message, metadata).await;
|
||||
crate::notifications::dispatch(
|
||||
state, event_id, level, kind, message, metadata,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -891,8 +891,8 @@
|
||||
</div>
|
||||
<div class="flow-natural-preview" id="flowNaturalPreview">
|
||||
<button type="button" class="flow-preview-toggle" data-action="flow-preview-toggle"
|
||||
aria-expanded="false"><strong data-i18n="flow.interpretation">Interpretation</strong><span
|
||||
class="flow-preview-toggle-icon" aria-hidden="true">⌃</span></button>
|
||||
aria-expanded="false"><strong data-i18n="flow.previewDetails">Interpretation and Flow cycle</strong><span
|
||||
class="flow-preview-toggle-icon" aria-hidden="true">⌄</span></button>
|
||||
<div class="flow-preview-details">
|
||||
<div class="flow-preview-item flow-preview-interpretation"><strong
|
||||
data-i18n="flow.interpretation">Interpretation</strong><span id="flowInterpretation"></span></div>
|
||||
|
||||
+14
-1
@@ -1,3 +1,15 @@
|
||||
function logNotificationBadge(item) {
|
||||
const notification = item?.metadata?.notification;
|
||||
if (notification?.status !== 'silent') return '';
|
||||
const reasonKey = {
|
||||
alert_type_disabled: 'logs.silentAlertTypeDisabled',
|
||||
notifications_disabled: 'logs.silentNotificationsDisabled',
|
||||
mode_filtered: 'logs.silentModeFiltered',
|
||||
cooldown: 'logs.silentCooldown',
|
||||
}[notification.reason] || 'logs.silentHint';
|
||||
return `<span class="badge silent log-notification-badge" title="${esc(tr(reasonKey))}">${esc(tr('logs.silent'))}</span>`;
|
||||
}
|
||||
|
||||
async function loadLogs() {
|
||||
renderLogRetention();
|
||||
try {
|
||||
@@ -5,7 +17,7 @@ async function loadLogs() {
|
||||
const level = $('#logLevelFilter')?.value || 'all', category = $('#logCategoryFilter')?.value || 'all';
|
||||
const logs = (data.events || []).filter(item => (level === 'all' || item.level === level) && (category === 'all' || logCategory(item.kind) === category));
|
||||
$('#logList').innerHTML = logs.length
|
||||
? logs.map(item => `<div class="log-row ${esc(item.level)} category-${esc(logCategory(item.kind))}"><time>${esc(new Date(item.timestamp).toLocaleTimeString(locale()))}</time><span class="log-category">${esc(logCategory(item.kind))}</span><span class="kind">${esc(item.kind)}</span><span class="message">${esc(item.message)}</span></div>`).join('')
|
||||
? logs.map(item => `<div class="log-row ${esc(item.level)} category-${esc(logCategory(item.kind))}"><time>${esc(new Date(item.timestamp).toLocaleTimeString(locale()))}</time><span class="log-category">${esc(logCategory(item.kind))}</span><span class="kind">${esc(item.kind)}${logNotificationBadge(item)}</span><span class="message">${esc(item.message)}</span></div>`).join('')
|
||||
: `<div class="empty"><strong>${esc(tr('logs.emptyTitle'))}</strong>${esc(tr('logs.emptyText'))}</div>`;
|
||||
} catch (error) { toast(error.message, true); }
|
||||
}
|
||||
@@ -70,6 +82,7 @@ async function handleWebSocketMessage(event) {
|
||||
else if (message.event === 'gree.frame') { if (app.settings?.debug?.overlay_enabled) debugLine('GREE', `${data.direction || '?'} ${data.protocol_version || ''}`, data.device_name || data.device_id || data.target || '', message.timestamp, data.payload); }
|
||||
else if (message.event === 'api.request') { if (app.settings?.debug?.overlay_enabled) debugLine('HTTP', `${data.method || '?'} ${data.status || ''}`, `${data.path || ''} · ${data.duration_ms ?? '?'} ms`, message.timestamp); }
|
||||
else if (message.event === 'log.created') { if (app.settings?.debug?.overlay_enabled) debugLine('API', data.kind || data.level || 'log', data.message || '', message.timestamp, data.metadata); if (app.currentView === 'logs') loadLogs(); }
|
||||
else if (message.event === 'log.updated') { if (app.currentView === 'logs') loadLogs(); }
|
||||
} catch (_) { }
|
||||
}
|
||||
|
||||
|
||||
@@ -9256,3 +9256,85 @@ body.flow-editor-open {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
/* Notification delivery audit in Events. */
|
||||
.log-row .kind .log-notification-badge {
|
||||
margin-left: .45rem;
|
||||
padding: 3px 7px;
|
||||
border: 1px solid var(--line-strong);
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
font-size: .64rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .06em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Keep the Flow footer compact before the full mobile layout kicks in. */
|
||||
@media (min-width:761px) and (max-width:1200px) {
|
||||
.flow-natural-preview {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flow-preview-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 8px 14px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.flow-preview-toggle strong {
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.flow-preview-toggle-icon {
|
||||
color: var(--muted);
|
||||
font-size: 1rem;
|
||||
transition: transform .15s ease;
|
||||
}
|
||||
|
||||
.flow-preview-details {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
max-height: 32dvh;
|
||||
padding: 9px 14px 11px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.flow-natural-preview.is-expanded .flow-preview-details {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flow-natural-preview.is-expanded .flow-preview-toggle {
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.flow-natural-preview.is-expanded .flow-preview-toggle-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.flow-preview-item {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.flow-preview-runtime {
|
||||
justify-content: start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.flow-preview-runtime span {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user