v0.14.18
This commit is contained in:
Generated
+1
-1
@@ -700,7 +700,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.14.15"
|
version = "0.14.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.14.15"
|
version = "0.14.18"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["GREE Controller contributors"]
|
authors = ["GREE Controller contributors"]
|
||||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||||
|
|||||||
+27
-27
@@ -1,14 +1,14 @@
|
|||||||
f809050a6074ba8e98e08c89af1174dffef679114ffae5c2d200150a9588b8d9 ./.env.example
|
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||||
597f6fb6e6b7306d2b22fa68cf4cc9c438b1d67537db29519a1de57a8775a426 ./Cargo.lock
|
ae81cbdcd9800a5b705f5321034f001571cecc8124671c7ebb77de8ce9d0033e ./Cargo.lock
|
||||||
8839d028f8c3facf329b97d8e2623b6c89b27aa25716ccd4848dff4a5e4041bf ./Cargo.toml
|
5190ac715681fbb1aa4b2bcebf1478c58791794507e3f4f70af37fc6bf1b29bc ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
699e00f944554597b1684b7892cb43dc96eb3c7b6f04ed8e28fc3a8784765ff2 ./README.md
|
414ce67f34d7eccde894167b383a2cf4f18ae9f275e782d61f48569b1297b0a8 ./README.md
|
||||||
bdc52c361ff7a2e0fc3e96ab2b1599f4002cc6490064e4064a5688604cde83bc ./SOURCE_MANIFEST.sha256
|
a1a12263a9409f687783d79b83e285785cc735ab80288f37b57cf6cccca967d4 ./SOURCE_MANIFEST.sha256
|
||||||
5302570299af2c2650bada12e123f598c5db8009949c75761385f73ed57343c5 ./build.rs
|
5302570299af2c2650bada12e123f598c5db8009949c75761385f73ed57343c5 ./build.rs
|
||||||
fa74b515483caeea518e392adfe8f235fb3f08131fb2fbd12bebe58c665bd81f ./docs/API.md
|
a2ce53408ca7ea559da735b1d7d0bb6d3d050eca220a5fcfe43f264b336e583f ./docs/API.md
|
||||||
f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md
|
f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md
|
||||||
809adad678983ed8572dd558768be2f20835ebd92dc527da7207e6b5df096d47 ./docs/openapi.json
|
9dc6076abac0bd394682206a365a179b64a798104b5f300135de5a74ed6a580c ./docs/openapi.json
|
||||||
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
||||||
ddec80cf2b5da73fd1bae50e46398033c95aa088afdb7c07d4889b5987a17a3f ./ha-addon/Dockerfile
|
ddec80cf2b5da73fd1bae50e46398033c95aa088afdb7c07d4889b5987a17a3f ./ha-addon/Dockerfile
|
||||||
3702707b5fd4f3a3d5d73e2dc827a6a43c144a22ff94861ab38720207bfcd94b ./ha-addon/README.md
|
3702707b5fd4f3a3d5d73e2dc827a6a43c144a22ff94861ab38720207bfcd94b ./ha-addon/README.md
|
||||||
@@ -22,7 +22,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom
|
|||||||
41a8958a6fe6d10e5679c95ac34832583a2a15ecfd67d09b4e094a83d06f5c47 ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
41a8958a6fe6d10e5679c95ac34832583a2a15ecfd67d09b4e094a83d06f5c47 ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
||||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
||||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
||||||
c62ac791ec1abefe26ea97e001a92db2f2905ee603b50aab0e76242b45e863e7 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
3af12a645805dd8f70be900d1f962be1dfef1c252ba5d2f72bef89c1e4ed7587 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||||
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
||||||
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
||||||
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
||||||
@@ -30,10 +30,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom
|
|||||||
6bddb7b4620021ecd2099a86a77ef5c7f2c2dcd3d07d5db4e7b4c4ce6d3e8c03 ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
6bddb7b4620021ecd2099a86a77ef5c7f2c2dcd3d07d5db4e7b4c4ce6d3e8c03 ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
||||||
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
||||||
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
||||||
59f420dcff7b7712ee4fb2afcda8573d2f420b593cc333989b4537179703508c ./ha-addon/repository/gree-controller/CHANGELOG.md
|
6339b12ea19bcde3628ac34cc7b65fd971e3e44bcaf28c7a144fa8042a25aada ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||||
1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md
|
1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md
|
||||||
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
||||||
bc356f1ea4f367084677ae05887a256056d6b403e5a0f0e57c86c11636fb20ea ./ha-addon/repository/gree-controller/config.yaml
|
a07415454295755b8920fe305deff439e6ebc79f0f4ba184cd3ae91d41c8d420 ./ha-addon/repository/gree-controller/config.yaml
|
||||||
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
||||||
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
||||||
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
||||||
@@ -43,8 +43,8 @@ cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run
|
|||||||
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
||||||
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
||||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||||
ec091f3cf6745995b1a677df5b064e9ed70e67b61f2c300e146ac8d64d6af570 ./lang/en.json
|
9477f8e45e8471867acf9af2b1554c9dc9490808c11507e4ce9cb9101c0ce2df ./lang/en.json
|
||||||
b14581a982441986df693849feccfcdfe4769b245a2adac0558fc86420d17f82 ./lang/pl.json
|
1270413ebdc2ace15ba62f365484207299f2de480bbb87d2b42959dc9ce6fd34 ./lang/pl.json
|
||||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||||
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
||||||
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
||||||
@@ -85,7 +85,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
|
|||||||
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
||||||
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
||||||
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
||||||
8bf77e97e4789242924aa030aa63c63912f5cd7334de0778f2ea9800c6912f8e ./scripts/api_dev_test.py
|
81f2d3f512f21d167c6d18bd2bda9d9e1557c0b234aa7826148ea557d726ddc2 ./scripts/api_dev_test.py
|
||||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||||
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
||||||
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
||||||
@@ -102,7 +102,7 @@ a70ddd9d4c9fca073ff494db73ec13acc7cff1830c9675d0541518d814da3772 ./src/api.rs
|
|||||||
f6ed359370dacd691344402307843a0ad31bbeb46085f99e81cfa46a618ee87d ./src/api/assets.rs
|
f6ed359370dacd691344402307843a0ad31bbeb46085f99e81cfa46a618ee87d ./src/api/assets.rs
|
||||||
b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs
|
b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs
|
||||||
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
||||||
1f2e3df47e532c1a222125d3790ec97772cf535af679589fde0ef9217f2db018 ./src/api/configuration.rs
|
af078ccc0bfcf0c7f2a4a8c1e7ee2f21dc1966ac209d17f17de12bb4d5276244 ./src/api/configuration.rs
|
||||||
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
||||||
32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs
|
32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs
|
||||||
fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs
|
fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs
|
||||||
@@ -110,7 +110,7 @@ fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devi
|
|||||||
d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs
|
d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs
|
||||||
2cfee7029f065e81628a8942e3dad00a03c3a5cf4f8492d07f12b75d05c07359 ./src/api/gree_cloud.rs
|
2cfee7029f065e81628a8942e3dad00a03c3a5cf4f8492d07f12b75d05c07359 ./src/api/gree_cloud.rs
|
||||||
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
||||||
e41ddb47f46ccad81baab1d25ddba910c087350b4b521844a33eae010cdaaa0b ./src/api/history.rs
|
92de677265216adb3154f6558684aec44cebff6bf6c57044f37d20f7d5a4a10f ./src/api/history.rs
|
||||||
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
||||||
c71540b2b68e805e3a79ac787db9127b87585968a205fc307e93bf68d5b028e0 ./src/api/integrations.rs
|
c71540b2b68e805e3a79ac787db9127b87585968a205fc307e93bf68d5b028e0 ./src/api/integrations.rs
|
||||||
7658111f7d125db55a6e23f88157339b110b2cd1ed13ceabd8f860b42dba5101 ./src/api/middleware.rs
|
7658111f7d125db55a6e23f88157339b110b2cd1ed13ceabd8f860b42dba5101 ./src/api/middleware.rs
|
||||||
@@ -200,20 +200,20 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
|||||||
7e7037a059b585c5709367b9431a225789e7039796e3ed19fc132776ac234099 ./src/state.rs
|
7e7037a059b585c5709367b9431a225789e7039796e3ed19fc132776ac234099 ./src/state.rs
|
||||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||||
4c88441675f8dec968e1a086e0c4514c131a15dce19801ca24c2de0b2e3086f2 ./web/custom-chart.html
|
fbd8595c339d245453427c7cb9bca937bd2f60852ba25a15956295abda26878c ./web/custom-chart.html
|
||||||
f0c3b6ed0cb99a7271dbad6d4003ff98fa1a4314a384e7341441bf988b30c0ba ./web/custom-chart.js
|
e13dc8c936b39c9d1f808d6e0e2ed1e9171c4da62ce6ba526220e8aaf478f571 ./web/custom-chart.js
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
019af8f410a20704e7445d0013fc37728d56b11b2772d679fe12a6c896972d87 ./web/index.html
|
217a9f99be2356bb15d5fa177bd08c7527eaeff827e572557464dd4e1dfa8fe4 ./web/index.html
|
||||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
||||||
27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js/bootstrap.js
|
27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js/bootstrap.js
|
||||||
5970f3d05006ab975c7c88baaed4cc73eb014fc40dbfb3f07a191db3534211ea ./web/js/charts.js
|
1612896d22fd61747175cd54174a283002ac9911f4164e593c1079f767790cda ./web/js/charts.js
|
||||||
1f1b7d1fdc2755a0888a14a314668a80ed7095763eba9c821e5e0968ec54fd73 ./web/js/core.js
|
5c46d271550f3db38ecbfc09e3a620f6f0e6a907a3d6d56feae9b08e894df196 ./web/js/core.js
|
||||||
c4ef56259052ec75d362fcffbb42cdd5062485a177108c175d89614527d421e6 ./web/js/dashboard.js
|
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js/dashboard.js
|
||||||
c5476aaa91123bc9d2b0ab1cb2870cba04fd67ae0067f86b3edb415b76cd4864 ./web/js/entities.js
|
0f975542ae1d9bd7216123dd3718be6e9a25dbe643eb899c67024d293940fae5 ./web/js/entities.js
|
||||||
a59fd5b822029d0c1e74ba8078f55f13122347e036a96dec383293ae386f6667 ./web/js/events.js
|
7c08eb200684e6fff521db589fd4417eea050f4eac02f9a2e6b53d212dd33f75 ./web/js/events.js
|
||||||
b08b0fe301642d1daa084b322a02e351d284d2465231505d45079a52ccb4028f ./web/js/flows.js
|
473511ca11a0412a794b20430c077843072e9abe0d2510552ac83b226c381453 ./web/js/flows.js
|
||||||
8e6798405332eec8439c9ea28e967ee2b1d62b57012452c03089aff01dcd6452 ./web/js/forms.js
|
a3d9f77a40a2574a668855125c781c067289944c6f93179ff23e3dae150e7dac ./web/js/forms.js
|
||||||
1868a8afbfc35623b538a1c6cd73246c8cc0aada4881e56941167958c0276b18 ./web/js/history.js
|
edde2969c9c1376f4df37224daa291256ebbbe713903d98e26b6aaa6ca467911 ./web/js/history.js
|
||||||
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js/main.js
|
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js/main.js
|
||||||
6bb9386707dcf601111fea15a1a366d8798313b973ca1624878618f4c3095add ./web/js/navigation.js
|
6bb9386707dcf601111fea15a1a366d8798313b973ca1624878618f4c3095add ./web/js/navigation.js
|
||||||
799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js/realtime.js
|
799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js/realtime.js
|
||||||
@@ -223,6 +223,6 @@ c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js/selec
|
|||||||
9f50794a92059357301b4c920b066bdb20ce1f64fbccea42effcd196d0a00cc4 ./web/js/settings.js
|
9f50794a92059357301b4c920b066bdb20ce1f64fbccea42effcd196d0a00cc4 ./web/js/settings.js
|
||||||
3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js
|
3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js
|
||||||
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
||||||
e25c0f0419611c47a1870d0f37b5db1fe5632c12eeba0b3556179e13f222e02a ./web/styles.css
|
0efe0cdff18beff496017a984f6d189ea6463e14b517f2f5c5fc46923579ab15 ./web/styles.css
|
||||||
fefdf12cbbb0a5d8a2cb89275cae28455aa42aaf179542305cc8f5e0f4825898 ./web/sw.js
|
fefdf12cbbb0a5d8a2cb89275cae28455aa42aaf179542305cc8f5e0f4825898 ./web/sw.js
|
||||||
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API.
|
Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API.
|
||||||
|
|
||||||
**Version: 0.14.15**
|
**Version: 0.14.18**
|
||||||
|
|
||||||
Version 0.14.15 hardens Home Assistant add-on publishing and direct-access behavior. The add-on keeps a fixed internal HTTP/ingress port (`8787`), validates that contract at startup/build time, resolves the primary Home Assistant host IPv4 through the Supervisor API for chart-only share links, and supports an explicit `public_chart_base_url` override for reverse proxies or unusual network layouts. Standalone port handling remains dynamic through `GREE_CONTROLLER_BIND`.
|
Version 0.14.18 refactors shared Web UI CSS classes to generic component names. Segmented navigation, configuration forms/sections, chart cards, enable toggles, diagram nodes/ports and Flow block-category styling now use reusable names instead of classes tied to one feature.
|
||||||
|
|
||||||
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
||||||
|
|
||||||
|
|||||||
+26
-26
@@ -1,13 +1,13 @@
|
|||||||
f809050a6074ba8e98e08c89af1174dffef679114ffae5c2d200150a9588b8d9 ./.env.example
|
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||||
597f6fb6e6b7306d2b22fa68cf4cc9c438b1d67537db29519a1de57a8775a426 ./Cargo.lock
|
ae81cbdcd9800a5b705f5321034f001571cecc8124671c7ebb77de8ce9d0033e ./Cargo.lock
|
||||||
8839d028f8c3facf329b97d8e2623b6c89b27aa25716ccd4848dff4a5e4041bf ./Cargo.toml
|
5190ac715681fbb1aa4b2bcebf1478c58791794507e3f4f70af37fc6bf1b29bc ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
699e00f944554597b1684b7892cb43dc96eb3c7b6f04ed8e28fc3a8784765ff2 ./README.md
|
414ce67f34d7eccde894167b383a2cf4f18ae9f275e782d61f48569b1297b0a8 ./README.md
|
||||||
5302570299af2c2650bada12e123f598c5db8009949c75761385f73ed57343c5 ./build.rs
|
5302570299af2c2650bada12e123f598c5db8009949c75761385f73ed57343c5 ./build.rs
|
||||||
fa74b515483caeea518e392adfe8f235fb3f08131fb2fbd12bebe58c665bd81f ./docs/API.md
|
a2ce53408ca7ea559da735b1d7d0bb6d3d050eca220a5fcfe43f264b336e583f ./docs/API.md
|
||||||
f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md
|
f21d732eb1147f8f3167d2172cdf0b57e61edabdfecbaf97811d46c5bff7e47a ./docs/FLOW.md
|
||||||
809adad678983ed8572dd558768be2f20835ebd92dc527da7207e6b5df096d47 ./docs/openapi.json
|
9dc6076abac0bd394682206a365a179b64a798104b5f300135de5a74ed6a580c ./docs/openapi.json
|
||||||
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
c65aa543d2e99f8f7d52736307b07ebd182597d80124661d53a16bb23f6f1118 ./ha-addon/.env.example
|
||||||
ddec80cf2b5da73fd1bae50e46398033c95aa088afdb7c07d4889b5987a17a3f ./ha-addon/Dockerfile
|
ddec80cf2b5da73fd1bae50e46398033c95aa088afdb7c07d4889b5987a17a3f ./ha-addon/Dockerfile
|
||||||
3702707b5fd4f3a3d5d73e2dc827a6a43c144a22ff94861ab38720207bfcd94b ./ha-addon/README.md
|
3702707b5fd4f3a3d5d73e2dc827a6a43c144a22ff94861ab38720207bfcd94b ./ha-addon/README.md
|
||||||
@@ -21,7 +21,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom
|
|||||||
41a8958a6fe6d10e5679c95ac34832583a2a15ecfd67d09b4e094a83d06f5c47 ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
41a8958a6fe6d10e5679c95ac34832583a2a15ecfd67d09b4e094a83d06f5c47 ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
||||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
||||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
||||||
c62ac791ec1abefe26ea97e001a92db2f2905ee603b50aab0e76242b45e863e7 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
3af12a645805dd8f70be900d1f962be1dfef1c252ba5d2f72bef89c1e4ed7587 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||||
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
||||||
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
||||||
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
||||||
@@ -29,10 +29,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom
|
|||||||
6bddb7b4620021ecd2099a86a77ef5c7f2c2dcd3d07d5db4e7b4c4ce6d3e8c03 ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
6bddb7b4620021ecd2099a86a77ef5c7f2c2dcd3d07d5db4e7b4c4ce6d3e8c03 ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
||||||
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
13f30e2dcdcedbd1b6c3f99c2335e0487108fd72c8e86922368b84f2fa2038ae ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
||||||
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
||||||
59f420dcff7b7712ee4fb2afcda8573d2f420b593cc333989b4537179703508c ./ha-addon/repository/gree-controller/CHANGELOG.md
|
6339b12ea19bcde3628ac34cc7b65fd971e3e44bcaf28c7a144fa8042a25aada ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||||
1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md
|
1b5599195329cc3e643c23aa1caadcec80f133e8e515eec7d92b9f3cbda5b80b ./ha-addon/repository/gree-controller/DOCS.md
|
||||||
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
c3db302f38d26dc9f8caf37a1a8a07a1334cc07ae301d452f2940a5ec5915719 ./ha-addon/repository/gree-controller/README.md
|
||||||
bc356f1ea4f367084677ae05887a256056d6b403e5a0f0e57c86c11636fb20ea ./ha-addon/repository/gree-controller/config.yaml
|
a07415454295755b8920fe305deff439e6ebc79f0f4ba184cd3ae91d41c8d420 ./ha-addon/repository/gree-controller/config.yaml
|
||||||
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
||||||
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
||||||
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
||||||
@@ -42,8 +42,8 @@ cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run
|
|||||||
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
||||||
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
||||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||||
ec091f3cf6745995b1a677df5b064e9ed70e67b61f2c300e146ac8d64d6af570 ./lang/en.json
|
9477f8e45e8471867acf9af2b1554c9dc9490808c11507e4ce9cb9101c0ce2df ./lang/en.json
|
||||||
b14581a982441986df693849feccfcdfe4769b245a2adac0558fc86420d17f82 ./lang/pl.json
|
1270413ebdc2ace15ba62f365484207299f2de480bbb87d2b42959dc9ce6fd34 ./lang/pl.json
|
||||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||||
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
||||||
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
||||||
@@ -84,7 +84,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
|
|||||||
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
||||||
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
||||||
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
||||||
8bf77e97e4789242924aa030aa63c63912f5cd7334de0778f2ea9800c6912f8e ./scripts/api_dev_test.py
|
81f2d3f512f21d167c6d18bd2bda9d9e1557c0b234aa7826148ea557d726ddc2 ./scripts/api_dev_test.py
|
||||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||||
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
||||||
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
||||||
@@ -101,7 +101,7 @@ a70ddd9d4c9fca073ff494db73ec13acc7cff1830c9675d0541518d814da3772 ./src/api.rs
|
|||||||
f6ed359370dacd691344402307843a0ad31bbeb46085f99e81cfa46a618ee87d ./src/api/assets.rs
|
f6ed359370dacd691344402307843a0ad31bbeb46085f99e81cfa46a618ee87d ./src/api/assets.rs
|
||||||
b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs
|
b24092f34a46626d64628b510eba82193f0b203fd097f6a621b91af5d91eb571 ./src/api/auth.rs
|
||||||
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
|
||||||
1f2e3df47e532c1a222125d3790ec97772cf535af679589fde0ef9217f2db018 ./src/api/configuration.rs
|
af078ccc0bfcf0c7f2a4a8c1e7ee2f21dc1966ac209d17f17de12bb4d5276244 ./src/api/configuration.rs
|
||||||
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
|
||||||
32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs
|
32ade46ec45d6faf7852242b9f5ba1be6eacbacbeb33d2deabfab59bf1610ce0 ./src/api/device_groups.rs
|
||||||
fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs
|
fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devices.rs
|
||||||
@@ -109,7 +109,7 @@ fcbb3da0538aab451f71810f829cc248d6bb67e586dc418d71a572980583823d ./src/api/devi
|
|||||||
d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs
|
d59b080b392b574ec94276c7b485b455d0d1abacc7de2ef29dcbfcf7d86dbdb0 ./src/api/flows.rs
|
||||||
2cfee7029f065e81628a8942e3dad00a03c3a5cf4f8492d07f12b75d05c07359 ./src/api/gree_cloud.rs
|
2cfee7029f065e81628a8942e3dad00a03c3a5cf4f8492d07f12b75d05c07359 ./src/api/gree_cloud.rs
|
||||||
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
|
||||||
e41ddb47f46ccad81baab1d25ddba910c087350b4b521844a33eae010cdaaa0b ./src/api/history.rs
|
92de677265216adb3154f6558684aec44cebff6bf6c57044f37d20f7d5a4a10f ./src/api/history.rs
|
||||||
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
e8edfbb725d6de650442444b0a929ad2be0bd2c2d7d227d2b3fdd99d05c94f94 ./src/api/house.rs
|
||||||
c71540b2b68e805e3a79ac787db9127b87585968a205fc307e93bf68d5b028e0 ./src/api/integrations.rs
|
c71540b2b68e805e3a79ac787db9127b87585968a205fc307e93bf68d5b028e0 ./src/api/integrations.rs
|
||||||
7658111f7d125db55a6e23f88157339b110b2cd1ed13ceabd8f860b42dba5101 ./src/api/middleware.rs
|
7658111f7d125db55a6e23f88157339b110b2cd1ed13ceabd8f860b42dba5101 ./src/api/middleware.rs
|
||||||
@@ -199,20 +199,20 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
|||||||
7e7037a059b585c5709367b9431a225789e7039796e3ed19fc132776ac234099 ./src/state.rs
|
7e7037a059b585c5709367b9431a225789e7039796e3ed19fc132776ac234099 ./src/state.rs
|
||||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||||
4c88441675f8dec968e1a086e0c4514c131a15dce19801ca24c2de0b2e3086f2 ./web/custom-chart.html
|
fbd8595c339d245453427c7cb9bca937bd2f60852ba25a15956295abda26878c ./web/custom-chart.html
|
||||||
f0c3b6ed0cb99a7271dbad6d4003ff98fa1a4314a384e7341441bf988b30c0ba ./web/custom-chart.js
|
e13dc8c936b39c9d1f808d6e0e2ed1e9171c4da62ce6ba526220e8aaf478f571 ./web/custom-chart.js
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
019af8f410a20704e7445d0013fc37728d56b11b2772d679fe12a6c896972d87 ./web/index.html
|
217a9f99be2356bb15d5fa177bd08c7527eaeff827e572557464dd4e1dfa8fe4 ./web/index.html
|
||||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md
|
||||||
27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js/bootstrap.js
|
27eff2821c40395369be15e197fa830535d2327fcb27c93a0e8e65a1c62de3ad ./web/js/bootstrap.js
|
||||||
5970f3d05006ab975c7c88baaed4cc73eb014fc40dbfb3f07a191db3534211ea ./web/js/charts.js
|
1612896d22fd61747175cd54174a283002ac9911f4164e593c1079f767790cda ./web/js/charts.js
|
||||||
1f1b7d1fdc2755a0888a14a314668a80ed7095763eba9c821e5e0968ec54fd73 ./web/js/core.js
|
5c46d271550f3db38ecbfc09e3a620f6f0e6a907a3d6d56feae9b08e894df196 ./web/js/core.js
|
||||||
c4ef56259052ec75d362fcffbb42cdd5062485a177108c175d89614527d421e6 ./web/js/dashboard.js
|
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js/dashboard.js
|
||||||
c5476aaa91123bc9d2b0ab1cb2870cba04fd67ae0067f86b3edb415b76cd4864 ./web/js/entities.js
|
0f975542ae1d9bd7216123dd3718be6e9a25dbe643eb899c67024d293940fae5 ./web/js/entities.js
|
||||||
a59fd5b822029d0c1e74ba8078f55f13122347e036a96dec383293ae386f6667 ./web/js/events.js
|
7c08eb200684e6fff521db589fd4417eea050f4eac02f9a2e6b53d212dd33f75 ./web/js/events.js
|
||||||
b08b0fe301642d1daa084b322a02e351d284d2465231505d45079a52ccb4028f ./web/js/flows.js
|
473511ca11a0412a794b20430c077843072e9abe0d2510552ac83b226c381453 ./web/js/flows.js
|
||||||
8e6798405332eec8439c9ea28e967ee2b1d62b57012452c03089aff01dcd6452 ./web/js/forms.js
|
a3d9f77a40a2574a668855125c781c067289944c6f93179ff23e3dae150e7dac ./web/js/forms.js
|
||||||
1868a8afbfc35623b538a1c6cd73246c8cc0aada4881e56941167958c0276b18 ./web/js/history.js
|
edde2969c9c1376f4df37224daa291256ebbbe713903d98e26b6aaa6ca467911 ./web/js/history.js
|
||||||
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js/main.js
|
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js/main.js
|
||||||
6bb9386707dcf601111fea15a1a366d8798313b973ca1624878618f4c3095add ./web/js/navigation.js
|
6bb9386707dcf601111fea15a1a366d8798313b973ca1624878618f4c3095add ./web/js/navigation.js
|
||||||
799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js/realtime.js
|
799613c6d465cb2f92ecb57cb082d0c33729a03d88ab4ca7fa12a3cbd7e38e75 ./web/js/realtime.js
|
||||||
@@ -222,6 +222,6 @@ c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js/selec
|
|||||||
9f50794a92059357301b4c920b066bdb20ce1f64fbccea42effcd196d0a00cc4 ./web/js/settings.js
|
9f50794a92059357301b4c920b066bdb20ce1f64fbccea42effcd196d0a00cc4 ./web/js/settings.js
|
||||||
3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js
|
3815fea7ae1694568c2f249bc8460e0563ced6bde89214f74f21b41fe8cad9a6 ./web/lang-init.js
|
||||||
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
6b653e4fe2d4db4ffae6ff8f39b8ce57a10a990dc0000ee4aae51d949afadd52 ./web/manifest.webmanifest
|
||||||
e25c0f0419611c47a1870d0f37b5db1fe5632c12eeba0b3556179e13f222e02a ./web/styles.css
|
0efe0cdff18beff496017a984f6d189ea6463e14b517f2f5c5fc46923579ab15 ./web/styles.css
|
||||||
fefdf12cbbb0a5d8a2cb89275cae28455aa42aaf179542305cc8f5e0f4825898 ./web/sw.js
|
fefdf12cbbb0a5d8a2cb89275cae28455aa42aaf179542305cc8f5e0f4825898 ./web/sw.js
|
||||||
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
# GREE Controller API reference
|
# GREE Controller API reference
|
||||||
|
|
||||||
HTTP and WebSocket API for GREE Controller **0.14.15**.
|
HTTP and WebSocket API for GREE Controller **0.14.18**.
|
||||||
|
|
||||||
[← Main documentation](../README.md)
|
[← Main documentation](../README.md)
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ Response:
|
|||||||
{
|
{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"name": "gree-controller",
|
"name": "gree-controller",
|
||||||
"version": "0.14.15",
|
"version": "0.14.18",
|
||||||
"uptime_seconds": 1234,
|
"uptime_seconds": 1234,
|
||||||
"control_ready": true,
|
"control_ready": true,
|
||||||
"time": "2026-08-30T06:54:00Z"
|
"time": "2026-08-30T06:54:00Z"
|
||||||
@@ -302,7 +302,7 @@ Returns the initial Web UI snapshot:
|
|||||||
"control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []},
|
"control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []},
|
||||||
"control_plan_revision": 42,
|
"control_plan_revision": 42,
|
||||||
"system": {
|
"system": {
|
||||||
"version": "0.14.15",
|
"version": "0.14.18",
|
||||||
"uptime_seconds": 1234,
|
"uptime_seconds": 1234,
|
||||||
"auth_required": false,
|
"auth_required": false,
|
||||||
"control_ready": true,
|
"control_ready": true,
|
||||||
@@ -326,7 +326,7 @@ Returns the initial Web UI snapshot:
|
|||||||
|
|
||||||
`POST /api/charts/custom/share` is an administrator endpoint that persists a selected Custom Chart definition and returns a random path such as `/charts/custom/chart_<token>`. Only a hash of the share token is stored. The URL does not contain device names or metric selectors.
|
`POST /api/charts/custom/share` is an administrator endpoint that persists a selected Custom Chart definition and returns a random path such as `/charts/custom/chart_<token>`. Only a hash of the share token is stored. The URL does not contain device names or metric selectors.
|
||||||
|
|
||||||
`GET /charts/custom/:token` renders only the shared chart, without the dashboard. `GET /api/public/charts/custom/:token` returns only the series configured for that share and is intentionally unauthenticated. Possession of the unguessable share URL is the authorization for this narrow read-only endpoint.
|
`GET /charts/custom/:token` renders only the shared chart, without the dashboard, and lets the viewer switch the history range. `GET /api/public/charts/custom/:token` returns only the series configured for that share and is intentionally unauthenticated; an optional `hours` query parameter changes only the time range, not the shared series definition. Possession of the unguessable share URL is the authorization for this narrow read-only endpoint.
|
||||||
|
|
||||||
In the Home Assistant add-on, TCP `8787` is the fixed internal application/ingress port. The startup script validates that Supervisor reports the same `ingress_port`; a manually modified mismatched package fails fast instead of starting partially. For generated links, the add-on discovers the primary Home Assistant host IPv4 through the Supervisor API and publishes `http://<HA-IP>:8787/charts/custom/...` by default. The optional `public_chart_base_url` add-on setting overrides that base for reverse proxies or unusual routing. Standalone installations use their current origin/configured base path and therefore follow any port configured in `GREE_CONTROLLER_BIND`.
|
In the Home Assistant add-on, TCP `8787` is the fixed internal application/ingress port. The startup script validates that Supervisor reports the same `ingress_port`; a manually modified mismatched package fails fast instead of starting partially. For generated links, the add-on discovers the primary Home Assistant host IPv4 through the Supervisor API and publishes `http://<HA-IP>:8787/charts/custom/...` by default. The optional `public_chart_base_url` add-on setting overrides that base for reverse proxies or unusual routing. Standalone installations use their current origin/configured base path and therefore follow any port configured in `GREE_CONTROLLER_BIND`.
|
||||||
|
|
||||||
|
|||||||
+15
-4
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "gree_controller",
|
"domain": "gree_controller",
|
||||||
"name": "GREE Controller",
|
"name": "GREE Controller",
|
||||||
"version": "0.14.15",
|
"version": "0.14.18",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.14.18
|
||||||
|
|
||||||
|
- Renames the shared segmented navigation class from feature-specific `automation-tabs` to generic `segmented-tabs`.
|
||||||
|
- Generalizes shared configuration layout classes to `config-form`, `form-section`, `form-section-head`, `form-grid`, `sticky-form-actions` and `full-width-form`.
|
||||||
|
- Generalizes reusable chart, enable-toggle and diagram classes used across History/public charts, Zones/Flow and Simulator/Flow editor.
|
||||||
|
- Replaces Flow-palette-specific category modifiers with shared `block-category-*` classes also used by the block library.
|
||||||
|
- Removes all references to the replaced shared class names without changing component behavior or visual styling.
|
||||||
|
|
||||||
|
## 0.14.17
|
||||||
|
|
||||||
|
- Makes History navigation visually identical to the Flow / Schedules / Automations segmented navigation.
|
||||||
|
- Reuses the shared `automation-tabs` component for Overview, Zones, GREE devices, Energy, Pings, HA sensors and Custom chart.
|
||||||
|
- Removes redundant History-only tab CSS, including its separate mobile overrides.
|
||||||
|
|
||||||
|
## 0.14.16
|
||||||
|
|
||||||
|
- Adds an explicit Saved Charts list to History → Custom Chart with load, edit, update and delete actions.
|
||||||
|
- Keeps chart name, selected series and range editable when updating an existing saved chart instead of creating duplicates.
|
||||||
|
- Adds a range selector to standalone public Custom Chart links; the public data endpoint accepts an optional `hours` override while keeping the shared series definition fixed.
|
||||||
|
- Fits the standalone public chart to the viewport and prevents vertical page scrolling, including compact/mobile layouts.
|
||||||
|
|
||||||
## 0.14.15
|
## 0.14.15
|
||||||
|
|
||||||
- Treats TCP `8787` as the fixed internal Home Assistant add-on application/ingress port and validates the Supervisor-reported ingress port at startup.
|
- Treats TCP `8787` as the fixed internal Home Assistant add-on application/ingress port and validates the Supervisor-reported ingress port at startup.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "GREE Controller"
|
name: "GREE Controller"
|
||||||
version: "0.14.15"
|
version: "0.14.18"
|
||||||
slug: "gree_controller"
|
slug: "gree_controller"
|
||||||
description: "Local GREE HVAC controller with Web UI and Home Assistant integration"
|
description: "Local GREE HVAC controller with Web UI and Home Assistant integration"
|
||||||
url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/"
|
url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/"
|
||||||
|
|||||||
@@ -422,9 +422,13 @@
|
|||||||
"history.copyLink": "Copy link",
|
"history.copyLink": "Copy link",
|
||||||
"history.series": "series",
|
"history.series": "series",
|
||||||
"history.noSavedCharts": "No saved custom charts yet.",
|
"history.noSavedCharts": "No saved custom charts yet.",
|
||||||
|
"history.savedCharts": "Saved charts",
|
||||||
|
"history.editingChart": "Editing chart",
|
||||||
|
"history.saveChanges": "Save changes",
|
||||||
"history.customChart": "Custom climate chart",
|
"history.customChart": "Custom climate chart",
|
||||||
"history.customChartHint": "Your selected data sources on one timeline.",
|
"history.customChartHint": "Your selected data sources on one timeline.",
|
||||||
"history.chartSaved": "Custom chart saved",
|
"history.chartSaved": "Custom chart saved",
|
||||||
|
"history.chartUpdated": "Custom chart updated",
|
||||||
"history.linkCopied": "Chart link copied",
|
"history.linkCopied": "Chart link copied",
|
||||||
"toast.successTitle": "Done",
|
"toast.successTitle": "Done",
|
||||||
"toast.errorTitle": "Something went wrong",
|
"toast.errorTitle": "Something went wrong",
|
||||||
|
|||||||
@@ -422,9 +422,13 @@
|
|||||||
"history.copyLink": "Kopiuj link",
|
"history.copyLink": "Kopiuj link",
|
||||||
"history.series": "serie",
|
"history.series": "serie",
|
||||||
"history.noSavedCharts": "Brak zapisanych własnych wykresów.",
|
"history.noSavedCharts": "Brak zapisanych własnych wykresów.",
|
||||||
|
"history.savedCharts": "Zapisane wykresy",
|
||||||
|
"history.editingChart": "Edytujesz wykres",
|
||||||
|
"history.saveChanges": "Zapisz zmiany",
|
||||||
"history.customChart": "Własny wykres klimatu",
|
"history.customChart": "Własny wykres klimatu",
|
||||||
"history.customChartHint": "Wybrane źródła danych na wspólnej osi czasu.",
|
"history.customChartHint": "Wybrane źródła danych na wspólnej osi czasu.",
|
||||||
"history.chartSaved": "Własny wykres zapisany",
|
"history.chartSaved": "Własny wykres zapisany",
|
||||||
|
"history.chartUpdated": "Własny wykres zaktualizowany",
|
||||||
"history.linkCopied": "Link do wykresu skopiowany",
|
"history.linkCopied": "Link do wykresu skopiowany",
|
||||||
"toast.successTitle": "Gotowe",
|
"toast.successTitle": "Gotowe",
|
||||||
"toast.errorTitle": "Wystąpił błąd",
|
"toast.errorTitle": "Wystąpił błąd",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Developer smoke/integration tests for GREE Controller API 0.14.15.
|
"""Developer smoke/integration tests for GREE Controller API 0.14.18.
|
||||||
|
|
||||||
Default mode is read-only and safe to run against a real controller.
|
Default mode is read-only and safe to run against a real controller.
|
||||||
Use --settings-write to additionally round-trip all split settings resources and
|
Use --settings-write to additionally round-trip all split settings resources and
|
||||||
@@ -26,7 +26,7 @@ from typing import Any, Callable, Iterable, Optional
|
|||||||
|
|
||||||
DEFAULT_BASE_URL = os.environ.get("GREE_API_URL", "http://127.0.0.1:8787")
|
DEFAULT_BASE_URL = os.environ.get("GREE_API_URL", "http://127.0.0.1:8787")
|
||||||
DEFAULT_TOKEN = os.environ.get("GREE_API_TOKEN", "")
|
DEFAULT_TOKEN = os.environ.get("GREE_API_TOKEN", "")
|
||||||
DEFAULT_EXPECTED_VERSION = "0.14.15"
|
DEFAULT_EXPECTED_VERSION = "0.14.18"
|
||||||
|
|
||||||
SETTINGS_PATHS: dict[str, set[str]] = {
|
SETTINGS_PATHS: dict[str, set[str]] = {
|
||||||
"/api/settings/application": {"simulator_enabled"},
|
"/api/settings/application": {"simulator_enabled"},
|
||||||
@@ -593,7 +593,7 @@ def run_suite(args: argparse.Namespace) -> int:
|
|||||||
print(f"Settings write tests: {'ENABLED' if args.settings_write else 'disabled'}\n")
|
print(f"Settings write tests: {'ENABLED' if args.settings_write else 'disabled'}\n")
|
||||||
|
|
||||||
runner.run("public health", lambda: test_health(client, args.expected_version))
|
runner.run("public health", lambda: test_health(client, args.expected_version))
|
||||||
runner.run("OpenAPI 0.14.15 contract", lambda: test_openapi(client, args.expected_version))
|
runner.run("OpenAPI 0.14.18 contract", lambda: test_openapi(client, args.expected_version))
|
||||||
runner.run("bootstrap snapshot contract", lambda: test_bootstrap_contract(client))
|
runner.run("bootstrap snapshot contract", lambda: test_bootstrap_contract(client))
|
||||||
runner.run("protected API requires auth", lambda: test_protected_auth(client))
|
runner.run("protected API requires auth", lambda: test_protected_auth(client))
|
||||||
|
|
||||||
@@ -662,7 +662,7 @@ def run_suite(args: argparse.Namespace) -> int:
|
|||||||
|
|
||||||
def parse_args(argv: Optional[Iterable[str]] = None) -> argparse.Namespace:
|
def parse_args(argv: Optional[Iterable[str]] = None) -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Developer smoke/integration tests for GREE Controller API 0.14.15.",
|
description="Developer smoke/integration tests for GREE Controller API 0.14.18.",
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
epilog="""Examples:
|
epilog="""Examples:
|
||||||
python3 scripts/api_dev_test.py
|
python3 scripts/api_dev_test.py
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ async fn export_configuration(
|
|||||||
|
|
||||||
fn validate_configuration_header(export: &ConfigurationExport) -> Result<(), AppError> {
|
fn validate_configuration_header(export: &ConfigurationExport) -> Result<(), AppError> {
|
||||||
if export.format_version != 3 {
|
if export.format_version != 3 {
|
||||||
return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.15".into()));
|
return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.18".into()));
|
||||||
}
|
}
|
||||||
if export.settings.control_strategy != "setpoint" {
|
if export.settings.control_strategy != "setpoint" {
|
||||||
return Err(AppError::BadRequest(
|
return Err(AppError::BadRequest(
|
||||||
|
|||||||
+7
-1
@@ -511,6 +511,11 @@ struct PublicCustomChartShare {
|
|||||||
lang: String,
|
lang: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct PublicCustomChartQuery {
|
||||||
|
hours: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
struct PublicChartPoint {
|
struct PublicChartPoint {
|
||||||
timestamp: chrono::DateTime<Utc>,
|
timestamp: chrono::DateTime<Utc>,
|
||||||
@@ -652,6 +657,7 @@ fn zone_reading_points(rows: Vec<ZoneReading>, field: &str) -> Vec<PublicChartPo
|
|||||||
async fn public_custom_chart(
|
async fn public_custom_chart(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(token): Path<String>,
|
Path(token): Path<String>,
|
||||||
|
Query(query): Query<PublicCustomChartQuery>,
|
||||||
) -> Result<Json<Value>, AppError> {
|
) -> Result<Json<Value>, AppError> {
|
||||||
if token.len() < 32 || token.len() > 128 || !token.starts_with("chart_") {
|
if token.len() < 32 || token.len() > 128 || !token.starts_with("chart_") {
|
||||||
return Err(AppError::NotFound("custom chart".into()));
|
return Err(AppError::NotFound("custom chart".into()));
|
||||||
@@ -663,7 +669,7 @@ async fn public_custom_chart(
|
|||||||
let share: PublicCustomChartShare = serde_json::from_value(payload)?;
|
let share: PublicCustomChartShare = serde_json::from_value(payload)?;
|
||||||
validate_public_chart_spec(&share.series)?;
|
validate_public_chart_spec(&share.series)?;
|
||||||
|
|
||||||
let hours = share.hours.clamp(1, 24 * 3650);
|
let hours = query.hours.unwrap_or(share.hours).clamp(1, 24 * 3650);
|
||||||
let lang = if share.lang == "pl" { "pl" } else { "en" };
|
let lang = if share.lang == "pl" { "pl" } else { "en" };
|
||||||
let since = Utc::now() - ChronoDuration::hours(hours);
|
let since = Utc::now() - ChronoDuration::hours(hours);
|
||||||
let bucket_seconds = history_bucket_seconds(hours);
|
let bucket_seconds = history_bucket_seconds(hours);
|
||||||
|
|||||||
+14
-3
@@ -12,12 +12,23 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="public-custom-chart-page">
|
<body class="public-custom-chart-page">
|
||||||
<main class="public-custom-chart-shell">
|
<main class="public-custom-chart-shell">
|
||||||
<section class="panel chart-panel history-chart-card public-custom-chart-card" aria-live="polite">
|
<section class="panel chart-panel chart-card public-custom-chart-card" aria-live="polite">
|
||||||
<div class="chart-title-row">
|
<div class="chart-title-row public-chart-title-row">
|
||||||
<div>
|
<div class="public-chart-heading">
|
||||||
<h3 id="publicChartTitle">Custom chart</h3>
|
<h3 id="publicChartTitle">Custom chart</h3>
|
||||||
<p id="publicChartHint">Loading…</p>
|
<p id="publicChartHint">Loading…</p>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="public-chart-range-control">
|
||||||
|
<span id="publicChartRangeLabel">Range</span>
|
||||||
|
<select id="publicChartHours" aria-label="Chart range">
|
||||||
|
<option value="6">6 hours</option>
|
||||||
|
<option value="24">24 hours</option>
|
||||||
|
<option value="168">7 days</option>
|
||||||
|
<option value="720">30 days</option>
|
||||||
|
<option value="2160">90 days</option>
|
||||||
|
<option value="8760">1 year</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-wrap public-custom-chart-wrap">
|
<div class="chart-wrap public-custom-chart-wrap">
|
||||||
<canvas id="publicCustomChart" width="1200" height="520" tabindex="0" aria-label="Custom chart"></canvas>
|
<canvas id="publicCustomChart" width="1200" height="520" tabindex="0" aria-label="Custom chart"></canvas>
|
||||||
|
|||||||
+86
-5
@@ -3,6 +3,7 @@
|
|||||||
const PUBLIC_CHART_COLORS = ['--accent', '--info', '--warning', '--purple', '--danger', '--teal', '--orange', '--blue'];
|
const PUBLIC_CHART_COLORS = ['--accent', '--info', '--warning', '--purple', '--danger', '--teal', '--orange', '--blue'];
|
||||||
const $ = selector => document.querySelector(selector);
|
const $ = selector => document.querySelector(selector);
|
||||||
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
||||||
|
const PUBLIC_CHART_RANGES = [6, 24, 168, 720, 2160, 8760];
|
||||||
|
|
||||||
function basePathFromScript() {
|
function basePathFromScript() {
|
||||||
const src = document.currentScript?.src || '';
|
const src = document.currentScript?.src || '';
|
||||||
@@ -41,7 +42,7 @@ function preciseTime(timestamp, locale) {
|
|||||||
function prepareCanvas(canvas, height = 520) {
|
function prepareCanvas(canvas, height = 520) {
|
||||||
const wrap = canvas.parentElement;
|
const wrap = canvas.parentElement;
|
||||||
const width = Math.max(720, Math.floor(wrap?.clientWidth || canvas.getBoundingClientRect().width || 720));
|
const width = Math.max(720, Math.floor(wrap?.clientWidth || canvas.getBoundingClientRect().width || 720));
|
||||||
const actualHeight = Math.max(320, Math.floor(wrap?.clientHeight || height));
|
const actualHeight = Math.max(180, Math.floor(wrap?.clientHeight || height));
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
canvas.width = Math.floor(width * dpr);
|
canvas.width = Math.floor(width * dpr);
|
||||||
canvas.height = Math.floor(actualHeight * dpr);
|
canvas.height = Math.floor(actualHeight * dpr);
|
||||||
@@ -194,12 +195,64 @@ function drawChart(canvas, payload, locale) {
|
|||||||
bindTooltip(canvas, series, { pad, width, height, firstTs, lastTs }, locale);
|
bindTooltip(canvas, series, { pad, width, height, firstTs, lastTs }, locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPublicChart() {
|
let publicChartPayload = null;
|
||||||
const token = decodeURIComponent(location.pathname.split('/').filter(Boolean).pop() || '');
|
let publicChartLocale = 'en-GB';
|
||||||
|
let publicChartResizeFrame = 0;
|
||||||
|
|
||||||
|
function publicChartToken() {
|
||||||
|
return decodeURIComponent(location.pathname.split('/').filter(Boolean).pop() || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestedPublicChartHours() {
|
||||||
|
const value = Number(new URLSearchParams(location.search).get('hours'));
|
||||||
|
return Number.isInteger(value) && value >= 1 && value <= 87600 ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPublicRangeLanguage(lang) {
|
||||||
|
const pl = lang === 'pl';
|
||||||
|
const label = $('#publicChartRangeLabel');
|
||||||
|
const select = $('#publicChartHours');
|
||||||
|
if (label) label.textContent = pl ? 'Zakres' : 'Range';
|
||||||
|
if (!select) return;
|
||||||
|
const labels = pl
|
||||||
|
? ['6 godzin', '24 godziny', '7 dni', '30 dni', '90 dni', '1 rok']
|
||||||
|
: ['6 hours', '24 hours', '7 days', '30 days', '90 days', '1 year'];
|
||||||
|
[...select.options].forEach((option, index) => { if (labels[index]) option.textContent = labels[index]; });
|
||||||
|
select.setAttribute('aria-label', pl ? 'Zakres wykresu' : 'Chart range');
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncPublicRangeControl(hours) {
|
||||||
|
const select = $('#publicChartHours');
|
||||||
|
if (!select) return;
|
||||||
|
const value = String(hours);
|
||||||
|
if (![...select.options].some(option => option.value === value)) {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = value;
|
||||||
|
option.textContent = `${value} h`;
|
||||||
|
select.append(option);
|
||||||
|
}
|
||||||
|
select.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicChartApiPath(token, hours) {
|
||||||
|
const value = Number(hours);
|
||||||
|
const hasHours = hours !== null && hours !== undefined && Number.isInteger(value) && value >= 1 && value <= 87600;
|
||||||
|
const query = hasHours ? `?hours=${encodeURIComponent(String(value))}` : '';
|
||||||
|
return `/api/public/charts/custom/${encodeURIComponent(token)}${query}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function redrawPublicChart() {
|
||||||
|
if (!publicChartPayload) return;
|
||||||
|
drawChart($('#publicCustomChart'), publicChartPayload, publicChartLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPublicChart(hours = requestedPublicChartHours()) {
|
||||||
|
const token = publicChartToken();
|
||||||
const title = $('#publicChartTitle');
|
const title = $('#publicChartTitle');
|
||||||
const hint = $('#publicChartHint');
|
const hint = $('#publicChartHint');
|
||||||
const error = $('#publicChartError');
|
const error = $('#publicChartError');
|
||||||
const canvas = $('#publicCustomChart');
|
const canvas = $('#publicCustomChart');
|
||||||
|
const range = $('#publicChartHours');
|
||||||
|
|
||||||
if (!token.startsWith('chart_')) {
|
if (!token.startsWith('chart_')) {
|
||||||
title.textContent = 'Custom chart';
|
title.textContent = 'Custom chart';
|
||||||
@@ -210,7 +263,9 @@ async function loadPublicChart() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(withBase(`/api/public/charts/custom/${encodeURIComponent(token)}`), { headers: { Accept: 'application/json' }, cache: 'no-store' });
|
if (range) range.disabled = true;
|
||||||
|
error.hidden = true;
|
||||||
|
const response = await fetch(withBase(publicChartApiPath(token, hours)), { headers: { Accept: 'application/json' }, cache: 'no-store' });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
let message = `${response.status}`;
|
let message = `${response.status}`;
|
||||||
try { message = (await response.json()).error || message; } catch (_) { }
|
try { message = (await response.json()).error || message; } catch (_) { }
|
||||||
@@ -223,14 +278,40 @@ async function loadPublicChart() {
|
|||||||
title.textContent = payload.title || (lang === 'pl' ? 'Wykres niestandardowy' : 'Custom chart');
|
title.textContent = payload.title || (lang === 'pl' ? 'Wykres niestandardowy' : 'Custom chart');
|
||||||
hint.textContent = payload.hint || '';
|
hint.textContent = payload.hint || '';
|
||||||
canvas.setAttribute('aria-label', title.textContent);
|
canvas.setAttribute('aria-label', title.textContent);
|
||||||
|
setPublicRangeLanguage(lang);
|
||||||
|
syncPublicRangeControl(payload.hours);
|
||||||
|
publicChartPayload = payload;
|
||||||
|
publicChartLocale = locale;
|
||||||
drawChart(canvas, payload, locale);
|
drawChart(canvas, payload, locale);
|
||||||
window.addEventListener('resize', () => drawChart(canvas, payload, locale));
|
if (range) range.disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$('#publicChartHours')?.addEventListener('change', async event => {
|
||||||
|
const hours = Number(event.currentTarget.value);
|
||||||
|
if (!Number.isInteger(hours) || hours < 1 || hours > 87600) return;
|
||||||
|
const url = new URL(location.href);
|
||||||
|
url.searchParams.set('hours', String(hours));
|
||||||
|
history.replaceState(null, '', url);
|
||||||
|
try {
|
||||||
|
await loadPublicChart(hours);
|
||||||
|
} catch (error) {
|
||||||
|
const host = $('#publicChartError');
|
||||||
|
host.hidden = false;
|
||||||
|
host.textContent = `${document.documentElement.lang === 'pl' ? 'Nie udało się wczytać wykresu' : 'Unable to load chart'}: ${error.message}`;
|
||||||
|
event.currentTarget.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
cancelAnimationFrame(publicChartResizeFrame);
|
||||||
|
publicChartResizeFrame = requestAnimationFrame(redrawPublicChart);
|
||||||
|
});
|
||||||
|
|
||||||
loadPublicChart().catch(error => {
|
loadPublicChart().catch(error => {
|
||||||
const host = $('#publicChartError');
|
const host = $('#publicChartError');
|
||||||
host.hidden = false;
|
host.hidden = false;
|
||||||
host.textContent = `Unable to load chart: ${error.message}`;
|
host.textContent = `Unable to load chart: ${error.message}`;
|
||||||
$('#publicChartHint').textContent = '';
|
$('#publicChartHint').textContent = '';
|
||||||
|
$('#publicChartHours').disabled = false;
|
||||||
drawEmpty($('#publicCustomChart'), host.textContent);
|
drawEmpty($('#publicCustomChart'), host.textContent);
|
||||||
});
|
});
|
||||||
|
|||||||
+78
-78
@@ -189,7 +189,7 @@
|
|||||||
data-i18n="flow.import">Import Flow</button><button class="primary" type="button" data-action="new-flow"
|
data-i18n="flow.import">Import Flow</button><button class="primary" type="button" data-action="new-flow"
|
||||||
data-i18n="flow.new">New Flow</button></div>
|
data-i18n="flow.new">New Flow</button></div>
|
||||||
</div>
|
</div>
|
||||||
<nav class="automation-tabs" data-i18n-aria="automationHub.navigation" aria-label="Automation navigation">
|
<nav class="segmented-tabs" data-i18n-aria="automationHub.navigation" aria-label="Automation navigation">
|
||||||
<button type="button" class="active" data-go="flows" data-i18n="nav.flows">Flow</button>
|
<button type="button" class="active" data-go="flows" data-i18n="nav.flows">Flow</button>
|
||||||
<button type="button" data-go="schedules" data-i18n="nav.schedules">Schedules</button>
|
<button type="button" data-go="schedules" data-i18n="nav.schedules">Schedules</button>
|
||||||
<button type="button" data-go="automations" data-i18n="nav.automations">Automations</button>
|
<button type="button" data-go="automations" data-i18n="nav.automations">Automations</button>
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
<h1 data-i18n="nav.schedules">Schedules</h1>
|
<h1 data-i18n="nav.schedules">Schedules</h1>
|
||||||
</div><button class="secondary" data-open="scheduleDialog" data-i18n="actions.add">Add</button>
|
</div><button class="secondary" data-open="scheduleDialog" data-i18n="actions.add">Add</button>
|
||||||
</div>
|
</div>
|
||||||
<nav class="automation-tabs" data-i18n-aria="automationHub.navigation" aria-label="Automation navigation">
|
<nav class="segmented-tabs" data-i18n-aria="automationHub.navigation" aria-label="Automation navigation">
|
||||||
<button type="button" data-go="flows" data-i18n="nav.flows">Flow</button>
|
<button type="button" data-go="flows" data-i18n="nav.flows">Flow</button>
|
||||||
<button type="button" class="active" data-go="schedules" data-i18n="nav.schedules">Schedules</button>
|
<button type="button" class="active" data-go="schedules" data-i18n="nav.schedules">Schedules</button>
|
||||||
<button type="button" data-go="automations" data-i18n="nav.automations">Automations</button>
|
<button type="button" data-go="automations" data-i18n="nav.automations">Automations</button>
|
||||||
@@ -248,7 +248,7 @@
|
|||||||
<h1 data-i18n="nav.automations">Automations</h1>
|
<h1 data-i18n="nav.automations">Automations</h1>
|
||||||
</div><button class="secondary" data-open="automationDialog" data-i18n="actions.add">Add</button>
|
</div><button class="secondary" data-open="automationDialog" data-i18n="actions.add">Add</button>
|
||||||
</div>
|
</div>
|
||||||
<nav class="automation-tabs" data-i18n-aria="automationHub.navigation" aria-label="Automation navigation">
|
<nav class="segmented-tabs" data-i18n-aria="automationHub.navigation" aria-label="Automation navigation">
|
||||||
<button type="button" data-go="flows" data-i18n="nav.flows">Flow</button>
|
<button type="button" data-go="flows" data-i18n="nav.flows">Flow</button>
|
||||||
<button type="button" data-go="schedules" data-i18n="nav.schedules">Schedules</button>
|
<button type="button" data-go="schedules" data-i18n="nav.schedules">Schedules</button>
|
||||||
<button type="button" class="active" data-go="automations" data-i18n="nav.automations">Automations</button>
|
<button type="button" class="active" data-go="automations" data-i18n="nav.automations">Automations</button>
|
||||||
@@ -322,15 +322,15 @@
|
|||||||
<h1 data-i18n="history.title">Climate history</h1>
|
<h1 data-i18n="history.title">Climate history</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="history-tabs" id="historyTabs" role="tablist">
|
<nav class="segmented-tabs" id="historyTabs" role="tablist" aria-label="History navigation">
|
||||||
<button data-history-tab="overview" data-i18n="history.tabOverview">Overview</button>
|
<button type="button" data-history-tab="overview" data-i18n="history.tabOverview">Overview</button>
|
||||||
<button data-history-tab="zones" data-i18n="history.tabZones">Zones</button>
|
<button type="button" data-history-tab="zones" data-i18n="history.tabZones">Zones</button>
|
||||||
<button data-history-tab="devices" data-i18n="history.tabDevices">GREE devices</button>
|
<button type="button" data-history-tab="devices" data-i18n="history.tabDevices">GREE devices</button>
|
||||||
<button data-history-tab="energy" data-i18n="energy.title">Energy</button>
|
<button type="button" data-history-tab="energy" data-i18n="energy.title">Energy</button>
|
||||||
<button data-history-tab="network" data-i18n="history.tabNetwork">Pings</button>
|
<button type="button" data-history-tab="network" data-i18n="history.tabNetwork">Pings</button>
|
||||||
<button data-history-tab="sensors" data-i18n="history.tabSensors">HA sensors</button>
|
<button type="button" data-history-tab="sensors" data-i18n="history.tabSensors">HA sensors</button>
|
||||||
<button data-history-tab="custom" data-i18n="history.tabCustom">Custom chart</button>
|
<button type="button" data-history-tab="custom" data-i18n="history.tabCustom">Custom chart</button>
|
||||||
</div>
|
</nav>
|
||||||
<div class="panel chart-panel history-toolbar-panel">
|
<div class="panel chart-panel history-toolbar-panel">
|
||||||
<div class="chart-toolbar">
|
<div class="chart-toolbar">
|
||||||
<div id="historyContextControls" class="history-context-controls"></div>
|
<div id="historyContextControls" class="history-context-controls"></div>
|
||||||
@@ -361,16 +361,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="lead" data-i18n="night.description">Configure quieter thermostat behavior for sleeping hours without
|
<p class="lead" data-i18n="night.description">Configure quieter thermostat behavior for sleeping hours without
|
||||||
mixing it with application settings.</p>
|
mixing it with application settings.</p>
|
||||||
<form class="settings-form standalone-settings-form" id="nightModeForm">
|
<form class="config-form full-width-form" id="nightModeForm">
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.nightMode">Night mode</h3>
|
<h3 data-i18n="settings.nightMode">Night mode</h3>
|
||||||
<p data-i18n="settings.nightModeHint">During selected hours the thermostat limits fan speed and can
|
<p data-i18n="settings.nightModeHint">During selected hours the thermostat limits fan speed and can
|
||||||
request Quiet on supported units.</p>
|
request Quiet on supported units.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="check wide"><input type="checkbox" name="night_mode_enabled"> <span
|
<label class="check wide"><input type="checkbox" name="night_mode_enabled"> <span
|
||||||
data-i18n="settings.nightModeEnabled">Enable night mode</span></label>
|
data-i18n="settings.nightModeEnabled">Enable night mode</span></label>
|
||||||
<label><span data-i18n="settings.nightStart">Start</span><input type="time" name="night_mode_start"
|
<label><span data-i18n="settings.nightStart">Start</span><input type="time" name="night_mode_start"
|
||||||
@@ -391,7 +391,7 @@
|
|||||||
data-i18n="settings.nightNativeSleep">Use native Sleep when supported</span></label>
|
data-i18n="settings.nightNativeSleep">Use native Sleep when supported</span></label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div class="settings-save-bar"><span data-i18n="night.saveHint">Save night-mode changes.</span><button
|
<div class="sticky-form-actions"><span data-i18n="night.saveHint">Save night-mode changes.</span><button
|
||||||
class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
@@ -404,16 +404,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="lead" data-i18n="homeAssistant.description">Connection, temperature sensors, friendly aliases and access
|
<p class="lead" data-i18n="homeAssistant.description">Connection, temperature sensors, friendly aliases and access
|
||||||
tokens for the Home Assistant integration.</p>
|
tokens for the Home Assistant integration.</p>
|
||||||
<form class="settings-form standalone-settings-form" id="homeAssistantForm">
|
<form class="config-form full-width-form" id="homeAssistantForm">
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.haConnection">Połączenie z Home Assistant</h3>
|
<h3 data-i18n="settings.haConnection">Połączenie z Home Assistant</h3>
|
||||||
<p data-i18n="settings.haConnectionHint">Adres serwera i dane dostępu używane przez wszystkie funkcje Home
|
<p data-i18n="settings.haConnectionHint">Adres serwera i dane dostępu używane przez wszystkie funkcje Home
|
||||||
Assistant.</p>
|
Assistant.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<div id="haSupervisorStatus" class="ha-supervisor-status wide" role="status" aria-live="polite" hidden></div>
|
<div id="haSupervisorStatus" class="ha-supervisor-status wide" role="status" aria-live="polite" hidden></div>
|
||||||
<label class="wide" data-ha-manual-field><span>URL</span><input type="url" name="ha_url"
|
<label class="wide" data-ha-manual-field><span>URL</span><input type="url" name="ha_url"
|
||||||
placeholder="http://homeassistant.local:8123"></label>
|
placeholder="http://homeassistant.local:8123"></label>
|
||||||
@@ -430,15 +430,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.haThermostatSources">Źródła temperatury sterowania</h3>
|
<h3 data-i18n="settings.haThermostatSources">Źródła temperatury sterowania</h3>
|
||||||
<p data-i18n="settings.haThermostatSourcesHint">Te ustawienia należą do logiki termostatów i źródeł
|
<p data-i18n="settings.haThermostatSourcesHint">Te ustawienia należą do logiki termostatów i źródeł
|
||||||
temperatury, nie do wspólnych wejść Flow.</p>
|
temperatury, nie do wspólnych wejść Flow.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="wide"><span data-i18n="settings.outdoorEntity">Global outdoor temperature entity_id</span><input
|
<label class="wide"><span data-i18n="settings.outdoorEntity">Global outdoor temperature entity_id</span><input
|
||||||
name="ha_outdoor_entity_id" list="haEntitySuggestions" placeholder="sensor.outdoor_temperature"></label>
|
name="ha_outdoor_entity_id" list="haEntitySuggestions" placeholder="sensor.outdoor_temperature"></label>
|
||||||
<p class="field-note wide" data-i18n="settings.outdoorEntityHint">Global Home Assistant outdoor temperature sensor. Zones use it by default and may override it with another outdoor sensor in zone settings.</p>
|
<p class="field-note wide" data-i18n="settings.outdoorEntityHint">Global Home Assistant outdoor temperature sensor. Zones use it by default and may override it with another outdoor sensor in zone settings.</p>
|
||||||
@@ -452,8 +452,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel settings-block flow-shared-settings" id="flowSharedInputsSettings">
|
<section class="panel form-section flow-shared-settings" id="flowSharedInputsSettings">
|
||||||
<div class="settings-block-head flow-shared-settings-head">
|
<div class="form-section-head flow-shared-settings-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="flow.sharedInputsTitle">Wspólne wejścia Flow</h3>
|
<h3 data-i18n="flow.sharedInputsTitle">Wspólne wejścia Flow</h3>
|
||||||
<p data-i18n="flow.sharedInputsHint">Zdefiniuj wspólne źródła wartości raz i używaj ich w wielu Flow.
|
<p data-i18n="flow.sharedInputsHint">Zdefiniuj wspólne źródła wartości raz i używaj ich w wielu Flow.
|
||||||
@@ -465,8 +465,8 @@
|
|||||||
<div id="flowSharedInputList" class="flow-shared-input-list"></div>
|
<div id="flowSharedInputList" class="flow-shared-input-list"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.sensorAliases">Sensor aliases</h3>
|
<h3 data-i18n="settings.sensorAliases">Sensor aliases</h3>
|
||||||
<p data-i18n="settings.sensorAliasesHint">Badges show whether an entity is collected as metrics, used by
|
<p data-i18n="settings.sensorAliasesHint">Badges show whether an entity is collected as metrics, used by
|
||||||
@@ -481,8 +481,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.haIntegrationAccess">Home Assistant integration access</h3>
|
<h3 data-i18n="settings.haIntegrationAccess">Home Assistant integration access</h3>
|
||||||
<p data-i18n="settings.haIntegrationHint">Create a controller token and paste it into the GREE Controller
|
<p data-i18n="settings.haIntegrationHint">Create a controller token and paste it into the GREE Controller
|
||||||
@@ -495,7 +495,7 @@
|
|||||||
data-i18n="settings.newToken">Create new token</button></div>
|
data-i18n="settings.newToken">Create new token</button></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div class="settings-save-bar"><span data-i18n="homeAssistant.saveHint">Save Home Assistant and sensor
|
<div class="sticky-form-actions"><span data-i18n="homeAssistant.saveHint">Save Home Assistant and sensor
|
||||||
changes.</span><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
changes.</span><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
@@ -514,31 +514,31 @@
|
|||||||
data-i18n="settings.greeSettings">GREE</button>
|
data-i18n="settings.greeSettings">GREE</button>
|
||||||
<button type="button" role="tab" aria-selected="false" data-settings-tab="cloud">GREE Cloud</button>
|
<button type="button" role="tab" aria-selected="false" data-settings-tab="cloud">GREE Cloud</button>
|
||||||
</div>
|
</div>
|
||||||
<form class="settings-form" id="settingsForm" novalidate>
|
<form class="config-form" id="settingsForm" novalidate>
|
||||||
<div class="settings-pane" data-settings-pane="app">
|
<div class="settings-pane" data-settings-pane="app">
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.applicationRuntime">Application runtime</h3>
|
<h3 data-i18n="settings.applicationRuntime">Application runtime</h3>
|
||||||
<p data-i18n="settings.applicationRuntimeHint">Application-wide behavior that does not change GREE
|
<p data-i18n="settings.applicationRuntimeHint">Application-wide behavior that does not change GREE
|
||||||
transport or discovery.</p>
|
transport or discovery.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="check wide"><input type="checkbox" name="simulator_enabled"> <span
|
<label class="check wide"><input type="checkbox" name="simulator_enabled"> <span
|
||||||
data-i18n="settings.simulationMode">Simulation mode</span></label>
|
data-i18n="settings.simulationMode">Simulation mode</span></label>
|
||||||
<p class="field-note wide warning-note" data-i18n="settings.simulationModeHint">When enabled, a prominent
|
<p class="field-note wide warning-note" data-i18n="settings.simulationModeHint">When enabled, a prominent
|
||||||
warning is shown throughout the application.</p>
|
warning is shown throughout the application.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="notifications.title">Notifications</h3>
|
<h3 data-i18n="notifications.title">Notifications</h3>
|
||||||
<p data-i18n="notifications.hint">Send important events or only problems/anomalies.</p>
|
<p data-i18n="notifications.hint">Send important events or only problems/anomalies.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="check wide"><input type="checkbox" name="notifications_enabled"> <span
|
<label class="check wide"><input type="checkbox" name="notifications_enabled"> <span
|
||||||
data-i18n="notifications.enable">Enable notifications</span></label>
|
data-i18n="notifications.enable">Enable notifications</span></label>
|
||||||
<label><span data-i18n="notifications.mode">Mode</span><select name="notifications_mode">
|
<label><span data-i18n="notifications.mode">Mode</span><select name="notifications_mode">
|
||||||
@@ -600,15 +600,15 @@
|
|||||||
data-i18n="notifications.test">Send test notification</button></div>
|
data-i18n="notifications.test">Send test notification</button></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.metricsAndLogs">Metrics and logs</h3>
|
<h3 data-i18n="settings.metricsAndLogs">Metrics and logs</h3>
|
||||||
<p data-i18n="settings.compactionHint">SQLite keeps recent data locally and compacts older samples to
|
<p data-i18n="settings.compactionHint">SQLite keeps recent data locally and compacts older samples to
|
||||||
the resolution used by charts.</p>
|
the resolution used by charts.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label><span data-i18n="settings.retentionDays">Local metric retention (days)</span><input type="number"
|
<label><span data-i18n="settings.retentionDays">Local metric retention (days)</span><input type="number"
|
||||||
name="history_retention_days" min="1" max="3650" required></label>
|
name="history_retention_days" min="1" max="3650" required></label>
|
||||||
<label><span data-i18n="settings.eventRetentionDays">Event/log retention (days)</span><input type="number"
|
<label><span data-i18n="settings.eventRetentionDays">Event/log retention (days)</span><input type="number"
|
||||||
@@ -617,15 +617,15 @@
|
|||||||
data-i18n="settings.compaction">Compact old metrics</span></label>
|
data-i18n="settings.compaction">Compact old metrics</span></label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.influx">Long-term InfluxDB history</h3>
|
<h3 data-i18n="settings.influx">Long-term InfluxDB history</h3>
|
||||||
<p data-i18n="settings.influxHint">Optional archive for older history. InfluxDB 1.x and 2.x are
|
<p data-i18n="settings.influxHint">Optional archive for older history. InfluxDB 1.x and 2.x are
|
||||||
supported.</p>
|
supported.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="check wide"><input type="checkbox" name="influx_enabled"> <span
|
<label class="check wide"><input type="checkbox" name="influx_enabled"> <span
|
||||||
data-i18n="settings.influxEnabled">Enable InfluxDB archive</span></label>
|
data-i18n="settings.influxEnabled">Enable InfluxDB archive</span></label>
|
||||||
<label><span data-i18n="settings.influxVersion">InfluxDB version</span><select name="influx_version">
|
<label><span data-i18n="settings.influxVersion">InfluxDB version</span><select name="influx_version">
|
||||||
@@ -654,14 +654,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.debug">On-screen debug</h3>
|
<h3 data-i18n="settings.debug">On-screen debug</h3>
|
||||||
<p data-i18n="settings.debugHint">Live diagnostics displayed on every application page.</p>
|
<p data-i18n="settings.debugHint">Live diagnostics displayed on every application page.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="check"><input type="checkbox" name="debug_overlay_enabled"> <span
|
<label class="check"><input type="checkbox" name="debug_overlay_enabled"> <span
|
||||||
data-i18n="settings.debugOverlay">Show debug window on every page</span></label>
|
data-i18n="settings.debugOverlay">Show debug window on every page</span></label>
|
||||||
<label class="check"><input type="checkbox" name="debug_gree_frames"> <span
|
<label class="check"><input type="checkbox" name="debug_gree_frames"> <span
|
||||||
@@ -672,8 +672,8 @@
|
|||||||
data-i18n="settings.debugCloudMqtt">Trace GREE Cloud MQTT</span></label>
|
data-i18n="settings.debugCloudMqtt">Trace GREE Cloud MQTT</span></label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.backup">Configuration backup</h3>
|
<h3 data-i18n="settings.backup">Configuration backup</h3>
|
||||||
<p class="warning-note" data-i18n="settings.backupHint">Export/import application configuration.
|
<p class="warning-note" data-i18n="settings.backupHint">Export/import application configuration.
|
||||||
@@ -689,15 +689,15 @@
|
|||||||
<div class="panel system-panel" id="systemInfo"></div>
|
<div class="panel system-panel" id="systemInfo"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-pane" data-settings-pane="gree" hidden>
|
<div class="settings-pane" data-settings-pane="gree" hidden>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.greeConnection">GREE connection</h3>
|
<h3 data-i18n="settings.greeConnection">GREE connection</h3>
|
||||||
<p data-i18n="settings.greeConnectionHint">Controller identity, polling intervals and LAN discovery
|
<p data-i18n="settings.greeConnectionHint">Controller identity, polling intervals and LAN discovery
|
||||||
transport.</p>
|
transport.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label><span data-i18n="settings.clientId">Client identifier</span><input name="controller_id"
|
<label><span data-i18n="settings.clientId">Client identifier</span><input name="controller_id"
|
||||||
required></label>
|
required></label>
|
||||||
<label><span data-i18n="settings.pollInterval">Poll interval (s)</span><input type="number"
|
<label><span data-i18n="settings.pollInterval">Poll interval (s)</span><input type="number"
|
||||||
@@ -710,29 +710,29 @@
|
|||||||
name="discovery_timeout_ms" min="300" max="30000" required></label>
|
name="discovery_timeout_ms" min="300" max="30000" required></label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.connectivityTitle">Connectivity metrics</h3>
|
<h3 data-i18n="settings.connectivityTitle">Connectivity metrics</h3>
|
||||||
<p data-i18n="settings.connectivityHint">Periodic GREE UDP round-trip measurements for Local/LAN units. Results are stored in History.</p>
|
<p data-i18n="settings.connectivityHint">Periodic GREE UDP round-trip measurements for Local/LAN units. Results are stored in History.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="check wide"><input type="checkbox" name="ping_metrics_enabled"> <span data-i18n="settings.connectivityEnable">Enable local unit ping measurements</span></label>
|
<label class="check wide"><input type="checkbox" name="ping_metrics_enabled"> <span data-i18n="settings.connectivityEnable">Enable local unit ping measurements</span></label>
|
||||||
<label><span data-i18n="settings.connectivityInterval">Measurement interval (s)</span><input type="number" name="ping_interval_seconds" min="10" max="3600" value="60"></label>
|
<label><span data-i18n="settings.connectivityInterval">Measurement interval (s)</span><input type="number" name="ping_interval_seconds" min="10" max="3600" value="60"></label>
|
||||||
<label><span data-i18n="settings.connectivitySamples">Samples per measurement</span><input type="number" name="ping_sample_count" min="1" max="10" value="3"></label>
|
<label><span data-i18n="settings.connectivitySamples">Samples per measurement</span><input type="number" name="ping_sample_count" min="1" max="10" value="3"></label>
|
||||||
<p class="field-note wide" data-i18n="settings.connectivityLocalOnly">Only Local/LAN installations are probed. Cloud devices are not pinged directly.</p>
|
<p class="field-note wide" data-i18n="settings.connectivityLocalOnly">Only Local/LAN installations are probed. Cloud devices are not pinged directly.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.greeCommands">GREE commands</h3>
|
<h3 data-i18n="settings.greeCommands">GREE commands</h3>
|
||||||
<p data-i18n="settings.suppressBeepHint">Only changed properties are sent. Buzzer suppression is also
|
<p data-i18n="settings.suppressBeepHint">Only changed properties are sent. Buzzer suppression is also
|
||||||
requested when supported by the unit firmware.</p>
|
requested when supported by the unit firmware.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="check wide"><input type="checkbox" name="suppress_device_beep"> <span
|
<label class="check wide"><input type="checkbox" name="suppress_device_beep"> <span
|
||||||
data-i18n="settings.suppressBeep">Try to suppress command beeps</span></label>
|
data-i18n="settings.suppressBeep">Try to suppress command beeps</span></label>
|
||||||
<label class="check wide"><input type="checkbox" name="compressor_protection_enabled"> <span
|
<label class="check wide"><input type="checkbox" name="compressor_protection_enabled"> <span
|
||||||
@@ -744,8 +744,8 @@
|
|||||||
Thermostats.</p>
|
Thermostats.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.greeTraffic">GREE traffic</h3>
|
<h3 data-i18n="settings.greeTraffic">GREE traffic</h3>
|
||||||
<p data-i18n="settings.greeTrafficHint">Live counters of UDP frames received from configured air
|
<p data-i18n="settings.greeTrafficHint">Live counters of UDP frames received from configured air
|
||||||
@@ -756,9 +756,9 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-pane" data-settings-pane="cloud" hidden>
|
<div class="settings-pane" data-settings-pane="cloud" hidden>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head"><div><h3 data-i18n="settings.cloudAccountTitle">GREE Cloud account</h3><p data-i18n="settings.cloudAccountHint">REST login and device discovery use the selected regional GREE service. The password is never returned by the API after it is saved.</p></div></div>
|
<div class="form-section-head"><div><h3 data-i18n="settings.cloudAccountTitle">GREE Cloud account</h3><p data-i18n="settings.cloudAccountHint">REST login and device discovery use the selected regional GREE service. The password is never returned by the API after it is saved.</p></div></div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="check wide"><input type="checkbox" name="gree_cloud_enabled"> <span data-i18n="settings.cloudEnable">Enable GREE Cloud</span></label>
|
<label class="check wide"><input type="checkbox" name="gree_cloud_enabled"> <span data-i18n="settings.cloudEnable">Enable GREE Cloud</span></label>
|
||||||
<label><span data-i18n="settings.cloudRegion">Region</span><select name="gree_cloud_region">
|
<label><span data-i18n="settings.cloudRegion">Region</span><select name="gree_cloud_region">
|
||||||
<option>Australia</option><option>China Mainland</option><option>East South Asia</option><option selected>Europe</option><option>India</option><option>Latin American</option><option>Middle East</option><option>North American</option><option>Russia</option><option>South American</option>
|
<option>Australia</option><option>China Mainland</option><option>East South Asia</option><option selected>Europe</option><option>India</option><option>Latin American</option><option>Middle East</option><option>North American</option><option>Russia</option><option>South American</option>
|
||||||
@@ -787,14 +787,14 @@
|
|||||||
<div id="greeCloudTestResult" class="inline-alert wide" hidden></div>
|
<div id="greeCloudTestResult" class="inline-alert wide" hidden></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel settings-block">
|
<section class="panel form-section">
|
||||||
<div class="settings-block-head">
|
<div class="form-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 data-i18n="settings.cloudConnectivityTitle">Cloud connectivity metrics</h3>
|
<h3 data-i18n="settings.cloudConnectivityTitle">Cloud connectivity metrics</h3>
|
||||||
<p data-i18n="settings.cloudConnectivityHint">Optional. Disabled by default. Measures REST login response and MQTT PINGRESP round-trip.</p>
|
<p data-i18n="settings.cloudConnectivityHint">Optional. Disabled by default. Measures REST login response and MQTT PINGRESP round-trip.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label class="check wide"><input type="checkbox" name="gree_cloud_connectivity_metrics_enabled"> <span data-i18n="settings.cloudConnectivityEnable">Measure Cloud REST/MQTT connectivity</span></label>
|
<label class="check wide"><input type="checkbox" name="gree_cloud_connectivity_metrics_enabled"> <span data-i18n="settings.cloudConnectivityEnable">Measure Cloud REST/MQTT connectivity</span></label>
|
||||||
<label><span data-i18n="settings.connectivityInterval">Measurement interval (s)</span><input type="number" name="gree_cloud_connectivity_metrics_interval_seconds" min="30" max="3600" value="300"></label>
|
<label><span data-i18n="settings.connectivityInterval">Measurement interval (s)</span><input type="number" name="gree_cloud_connectivity_metrics_interval_seconds" min="30" max="3600" value="300"></label>
|
||||||
<label><span data-i18n="settings.connectivitySamples">Samples per measurement</span><input type="number" name="gree_cloud_connectivity_metrics_sample_count" min="1" max="10" value="3"></label>
|
<label><span data-i18n="settings.connectivitySamples">Samples per measurement</span><input type="number" name="gree_cloud_connectivity_metrics_sample_count" min="1" max="10" value="3"></label>
|
||||||
@@ -802,7 +802,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-save-bar"><span data-i18n="settings.saveHint">Save changes made in the blocks
|
<div class="sticky-form-actions"><span data-i18n="settings.saveHint">Save changes made in the blocks
|
||||||
above.</span><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
above.</span><button class="primary" type="submit" data-i18n="actions.save">Save</button></div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
@@ -878,16 +878,16 @@
|
|||||||
<aside class="flow-palette">
|
<aside class="flow-palette">
|
||||||
<div class="flow-palette-head"><strong data-i18n="flow.blocks">Blocks</strong><small
|
<div class="flow-palette-head"><strong data-i18n="flow.blocks">Blocks</strong><small
|
||||||
data-i18n="flow.paletteHint">Add a block, then connect its output to the next block.</small></div>
|
data-i18n="flow.paletteHint">Add a block, then connect its output to the next block.</small></div>
|
||||||
<div class="flow-palette-group flow-palette-trigger"><span data-i18n="flow.triggers">Wyzwalacze</span>
|
<div class="flow-palette-group block-category-trigger"><span data-i18n="flow.triggers">Wyzwalacze</span>
|
||||||
<button type="button" data-flow-add="cron_trigger" data-i18n="flow.node.cronTrigger">CRON</button>
|
<button type="button" data-flow-add="cron_trigger" data-i18n="flow.node.cronTrigger">CRON</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flow-palette-group flow-palette-time"><span data-i18n="flow.time">Czas</span>
|
<div class="flow-palette-group block-category-time"><span data-i18n="flow.time">Czas</span>
|
||||||
<button type="button" data-flow-add="weekday" data-i18n="flow.node.weekday">Dni tygodnia</button><button
|
<button type="button" data-flow-add="weekday" data-i18n="flow.node.weekday">Dni tygodnia</button><button
|
||||||
type="button" data-flow-add="time_range" data-i18n="flow.node.timeRange">Przedział godzin</button><button
|
type="button" data-flow-add="time_range" data-i18n="flow.node.timeRange">Przedział godzin</button><button
|
||||||
type="button" data-flow-add="date_range" data-i18n="flow.node.dateRange">Zakres dat</button><button
|
type="button" data-flow-add="date_range" data-i18n="flow.node.dateRange">Zakres dat</button><button
|
||||||
type="button" data-flow-add="night_mode" data-i18n="flow.node.nightMode">Tryb nocny</button>
|
type="button" data-flow-add="night_mode" data-i18n="flow.node.nightMode">Tryb nocny</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flow-palette-group flow-palette-timeop"><span data-i18n="flow.timeOps">Operacje czasu</span>
|
<div class="flow-palette-group block-category-timeop"><span data-i18n="flow.timeOps">Operacje czasu</span>
|
||||||
<button type="button" data-flow-add="stable_for" data-i18n="flow.node.stableFor">Utrzymuje się
|
<button type="button" data-flow-add="stable_for" data-i18n="flow.node.stableFor">Utrzymuje się
|
||||||
przez…</button><button type="button" data-flow-add="state_duration" data-i18n="flow.node.stateDuration">Czas
|
przez…</button><button type="button" data-flow-add="state_duration" data-i18n="flow.node.stateDuration">Czas
|
||||||
trwania stanu</button><button type="button" data-flow-add="on_change" data-i18n="flow.node.onChange">Tylko
|
trwania stanu</button><button type="button" data-flow-add="on_change" data-i18n="flow.node.onChange">Tylko
|
||||||
@@ -895,7 +895,7 @@
|
|||||||
X razy / okres</button><button type="button" data-flow-add="delay"
|
X razy / okres</button><button type="button" data-flow-add="delay"
|
||||||
data-i18n="flow.node.delay">Odczekaj…</button>
|
data-i18n="flow.node.delay">Odczekaj…</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flow-palette-group flow-palette-sensor"><span data-i18n="flow.sensors">Sensory / wartości</span>
|
<div class="flow-palette-group block-category-sensor"><span data-i18n="flow.sensors">Sensory / wartości</span>
|
||||||
<button type="button" data-flow-add="outdoor_temperature" data-i18n="flow.node.outdoorTemperature">Temperatura
|
<button type="button" data-flow-add="outdoor_temperature" data-i18n="flow.node.outdoorTemperature">Temperatura
|
||||||
zewn.</button><button type="button" data-flow-add="device_temperature"
|
zewn.</button><button type="button" data-flow-add="device_temperature"
|
||||||
data-i18n="flow.node.deviceTemperature">Temperatura urządzenia</button><button type="button"
|
data-i18n="flow.node.deviceTemperature">Temperatura urządzenia</button><button type="button"
|
||||||
@@ -913,17 +913,17 @@
|
|||||||
data-flow-add="rolling_stat" data-i18n="flow.node.rollingStat">Średnia / mediana</button><button
|
data-flow-add="rolling_stat" data-i18n="flow.node.rollingStat">Średnia / mediana</button><button
|
||||||
type="button" data-flow-add="oscillates" data-i18n="flow.node.oscillates">Wartość oscyluje</button>
|
type="button" data-flow-add="oscillates" data-i18n="flow.node.oscillates">Wartość oscyluje</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flow-palette-group flow-palette-logic"><span data-i18n="flow.logic">Logika</span><button
|
<div class="flow-palette-group block-category-logic"><span data-i18n="flow.logic">Logika</span><button
|
||||||
type="button" data-flow-add="logic_and" data-i18n="flow.node.and">AND</button><button type="button"
|
type="button" data-flow-add="logic_and" data-i18n="flow.node.and">AND</button><button type="button"
|
||||||
data-flow-add="logic_or" data-i18n="flow.node.or">OR</button><button type="button" data-flow-add="logic_not"
|
data-flow-add="logic_or" data-i18n="flow.node.or">OR</button><button type="button" data-flow-add="logic_not"
|
||||||
data-i18n="flow.node.not">NOT</button></div>
|
data-i18n="flow.node.not">NOT</button></div>
|
||||||
<div class="flow-palette-group flow-palette-action"><span data-i18n="flow.actions">Akcje</span>
|
<div class="flow-palette-group block-category-action"><span data-i18n="flow.actions">Akcje</span>
|
||||||
<button type="button" data-flow-add="zone_thermostat"
|
<button type="button" data-flow-add="zone_thermostat"
|
||||||
data-i18n="flow.node.thermostat">Termostat</button><button type="button" data-flow-add="device_action"
|
data-i18n="flow.node.thermostat">Termostat</button><button type="button" data-flow-add="device_action"
|
||||||
data-i18n="flow.node.greeDevice">Urządzenie GREE</button><button type="button" data-flow-add="group_action"
|
data-i18n="flow.node.greeDevice">Urządzenie GREE</button><button type="button" data-flow-add="group_action"
|
||||||
data-i18n="flow.node.group">Grupa</button>
|
data-i18n="flow.node.group">Grupa</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flow-palette-group flow-palette-haaction"><span data-i18n="flow.haActions">Home Assistant</span>
|
<div class="flow-palette-group block-category-haaction"><span data-i18n="flow.haActions">Home Assistant</span>
|
||||||
<button type="button" data-flow-add="ha_service_action" data-i18n="flow.node.haServiceAction">Home Assistant:
|
<button type="button" data-flow-add="ha_service_action" data-i18n="flow.node.haServiceAction">Home Assistant:
|
||||||
usługa</button>
|
usługa</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1233,9 +1233,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<label><span data-i18n="common.name">Name</span><input name="name" required maxlength="80"></label>
|
<label><span data-i18n="common.name">Name</span><input name="name" required maxlength="80"></label>
|
||||||
<div class="technical-device-grid" id="deviceDetailsMeta"></div>
|
<div class="technical-device-grid" id="deviceDetailsMeta"></div>
|
||||||
<section class="settings-block">
|
<section class="form-section">
|
||||||
<div class="settings-block-head"><div><h3 data-i18n="energy.title">Energy</h3><p data-i18n="energy.sourceHint">Choose the cumulative energy source used for charts and period totals.</p></div></div>
|
<div class="form-section-head"><div><h3 data-i18n="energy.title">Energy</h3><p data-i18n="energy.sourceHint">Choose the cumulative energy source used for charts and period totals.</p></div></div>
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label><span data-i18n="energy.source">Energy source</span><select name="energy_source">
|
<label><span data-i18n="energy.source">Energy source</span><select name="energy_source">
|
||||||
<option value="auto" data-i18n="energy.auto">Auto</option>
|
<option value="auto" data-i18n="energy.auto">Auto</option>
|
||||||
<option value="gree_cloud">GREE Cloud</option>
|
<option value="gree_cloud">GREE Cloud</option>
|
||||||
@@ -1257,7 +1257,7 @@
|
|||||||
<div id="deviceGroupsList" class="installation-list"></div>
|
<div id="deviceGroupsList" class="installation-list"></div>
|
||||||
<form id="deviceGroupForm" class="installation-form">
|
<form id="deviceGroupForm" class="installation-form">
|
||||||
<input type="hidden" name="id">
|
<input type="hidden" name="id">
|
||||||
<div class="settings-grid">
|
<div class="form-grid">
|
||||||
<label><span data-i18n="devices.installationName">Installation name</span><input name="name" maxlength="80" required></label>
|
<label><span data-i18n="devices.installationName">Installation name</span><input name="name" maxlength="80" required></label>
|
||||||
<label><span data-i18n="devices.installationType">Installation type</span><select name="kind"><option value="split" data-i18n="devices.split">Split</option><option value="multisplit" data-i18n="devices.multisplit">Multisplit</option></select></label>
|
<label><span data-i18n="devices.installationType">Installation type</span><select name="kind"><option value="split" data-i18n="devices.split">Split</option><option value="multisplit" data-i18n="devices.multisplit">Multisplit</option></select></label>
|
||||||
<div class="wide"><span class="field-label" data-i18n="devices.installationDevices">Indoor units</span><div id="deviceGroupDeviceChoices" class="installation-device-choices"></div></div>
|
<div class="wide"><span class="field-label" data-i18n="devices.installationDevices">Indoor units</span><div id="deviceGroupDeviceChoices" class="installation-device-choices"></div></div>
|
||||||
|
|||||||
+6
-6
@@ -191,7 +191,7 @@ function setChartSeriesHidden(chartId, item, index, hidden) {
|
|||||||
|
|
||||||
function updateChartZoomControls(id) {
|
function updateChartZoomControls(id) {
|
||||||
const canvas = document.getElementById(id);
|
const canvas = document.getElementById(id);
|
||||||
const card = canvas?.closest('.history-chart-card');
|
const card = canvas?.closest('.chart-card');
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
const zoom = clamp(Number(app.chartZooms[id] || 1), 1, MAX_CHART_ZOOM);
|
const zoom = clamp(Number(app.chartZooms[id] || 1), 1, MAX_CHART_ZOOM);
|
||||||
const reset = card.querySelector('[data-chart-zoom="reset"]');
|
const reset = card.querySelector('[data-chart-zoom="reset"]');
|
||||||
@@ -319,11 +319,11 @@ function closeChartPreview(card) {
|
|||||||
function toggleChartFullscreen(id) {
|
function toggleChartFullscreen(id) {
|
||||||
if (!window.matchMedia('(min-width: 761px)').matches) return;
|
if (!window.matchMedia('(min-width: 761px)').matches) return;
|
||||||
const canvas = document.getElementById(id);
|
const canvas = document.getElementById(id);
|
||||||
const card = canvas?.closest('.history-chart-card');
|
const card = canvas?.closest('.chart-card');
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
|
|
||||||
const active = chartCardIsFullscreen(card);
|
const active = chartCardIsFullscreen(card);
|
||||||
const opened = document.querySelector('.history-chart-card.chart-fullscreen-fallback');
|
const opened = document.querySelector('.chart-card.chart-fullscreen-fallback');
|
||||||
if (opened && opened !== card) closeChartPreview(opened);
|
if (opened && opened !== card) closeChartPreview(opened);
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
@@ -356,7 +356,7 @@ function prepareCanvas(canvas, height) {
|
|||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
const wrap = canvas.parentElement;
|
const wrap = canvas.parentElement;
|
||||||
const wrapWidth = Math.floor(wrap?.clientWidth || rect.width || 720);
|
const wrapWidth = Math.floor(wrap?.clientWidth || rect.width || 720);
|
||||||
const fullscreen = chartCardIsFullscreen(canvas.closest('.history-chart-card'));
|
const fullscreen = chartCardIsFullscreen(canvas.closest('.chart-card'));
|
||||||
const wrapHeight = fullscreen ? Math.floor(wrap?.clientHeight || 0) : 0;
|
const wrapHeight = fullscreen ? Math.floor(wrap?.clientHeight || 0) : 0;
|
||||||
const actualHeight = wrapHeight || height;
|
const actualHeight = wrapHeight || height;
|
||||||
const zoom = clamp(Number(canvas.dataset.chartZoom || 1), 1, MAX_CHART_ZOOM);
|
const zoom = clamp(Number(canvas.dataset.chartZoom || 1), 1, MAX_CHART_ZOOM);
|
||||||
@@ -724,7 +724,7 @@ function renderLegend(host, series) {
|
|||||||
function historyChartMarkup(id, title, hint, compact = false) {
|
function historyChartMarkup(id, title, hint, compact = false) {
|
||||||
const zoom = clamp(Number(app.chartZooms[id] || 1), 1, MAX_CHART_ZOOM);
|
const zoom = clamp(Number(app.chartZooms[id] || 1), 1, MAX_CHART_ZOOM);
|
||||||
const percent = Math.round(zoom * 100);
|
const percent = Math.round(zoom * 100);
|
||||||
return `<div class="panel chart-panel history-chart-card"><div class="chart-title-row"><div><h3>${esc(title)}</h3><p>${esc(hint || '')}</p><small class="chart-hover-hint">${esc(tr('history.hoverHint'))}</small></div><div class="chart-title-actions"><div class="chart-zoom-controls" aria-label="${esc(tr('history.zoomControls'))}"><button type="button" data-chart-zoom="out" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomOut'))}" ${zoom <= 1 ? 'disabled' : ''}>${uiIcon('minus')}</button><button type="button" class="chart-zoom-reset" data-chart-zoom="reset" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomReset'))}">${percent}%</button><button type="button" data-chart-zoom="in" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomIn'))}" ${zoom >= MAX_CHART_ZOOM ? 'disabled' : ''}>${uiIcon('plus')}</button></div><button type="button" class="chart-fullscreen-button" data-chart-fullscreen="${esc(id)}" title="${esc(tr('history.fullscreen'))}" aria-label="${esc(tr('history.fullscreen'))}">${uiIcon('fullscreen')}</button></div></div><div class="chart-wrap ${compact ? 'compact-chart' : ''}"><canvas id="${esc(id)}" data-chart-zoom="${zoom}" width="1000" height="${compact ? 300 : 420}" tabindex="0" aria-label="${esc(title)}"></canvas><div class="chart-hover-line" hidden></div><div class="chart-selection" hidden></div><div class="chart-tooltip" hidden></div></div><div class="legend" id="${esc(id)}Legend"></div></div>`;
|
return `<div class="panel chart-panel chart-card"><div class="chart-title-row"><div><h3>${esc(title)}</h3><p>${esc(hint || '')}</p><small class="chart-hover-hint">${esc(tr('history.hoverHint'))}</small></div><div class="chart-title-actions"><div class="chart-zoom-controls" aria-label="${esc(tr('history.zoomControls'))}"><button type="button" data-chart-zoom="out" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomOut'))}" ${zoom <= 1 ? 'disabled' : ''}>${uiIcon('minus')}</button><button type="button" class="chart-zoom-reset" data-chart-zoom="reset" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomReset'))}">${percent}%</button><button type="button" data-chart-zoom="in" data-chart-id="${esc(id)}" title="${esc(tr('history.zoomIn'))}" ${zoom >= MAX_CHART_ZOOM ? 'disabled' : ''}>${uiIcon('plus')}</button></div><button type="button" class="chart-fullscreen-button" data-chart-fullscreen="${esc(id)}" title="${esc(tr('history.fullscreen'))}" aria-label="${esc(tr('history.fullscreen'))}">${uiIcon('fullscreen')}</button></div></div><div class="chart-wrap ${compact ? 'compact-chart' : ''}"><canvas id="${esc(id)}" data-chart-zoom="${zoom}" width="1000" height="${compact ? 300 : 420}" tabindex="0" aria-label="${esc(title)}"></canvas><div class="chart-hover-line" hidden></div><div class="chart-selection" hidden></div><div class="chart-tooltip" hidden></div></div><div class="legend" id="${esc(id)}Legend"></div></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function historySeriesColor(index) {
|
function historySeriesColor(index) {
|
||||||
@@ -733,6 +733,6 @@ function historySeriesColor(index) {
|
|||||||
|
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
if (event.key === 'Escape') closeChartPreview(document.querySelector('.history-chart-card.chart-fullscreen-fallback'));
|
if (event.key === 'Escape') closeChartPreview(document.querySelector('.chart-card.chart-fullscreen-fallback'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ const app = {
|
|||||||
languages: [], translations: {}, locales: {},
|
languages: [], translations: {}, locales: {},
|
||||||
historyTab: 'overview', historyData: { zones: [], devices: [], sensors: [] }, historyCounts: {},
|
historyTab: 'overview', historyData: { zones: [], devices: [], sensors: [] }, historyCounts: {},
|
||||||
historyZone: 'all', historyDevice: 'all', historySensor: 'all', historyNetworkTarget: 'all', historyNetworkShowJitter: true, historyNetwork: [], historyNetworkTargets: [], historyEnergyDevice: '', historyEnergyTargets: [], historyEnergyInterval: 'daily', historyEnergyCompare: 'none', historyEnergy: [], historyLoading: false, historyReloadPending: false,
|
historyZone: 'all', historyDevice: 'all', historySensor: 'all', historyNetworkTarget: 'all', historyNetworkShowJitter: true, historyNetwork: [], historyNetworkTargets: [], historyEnergyDevice: '', historyEnergyTargets: [], historyEnergyInterval: 'daily', historyEnergyCompare: 'none', historyEnergy: [], historyLoading: false, historyReloadPending: false,
|
||||||
customChartSeries: [], savedCharts: [], chartZooms: {}, chartHiddenSeries: {}, zoneControlSeq: {}, groupControlSeq: {}, zoneControlQueue: {}, groupControlQueue: {}, deviceControlQueue: {}, houseControlQueue: null, climateControlQueue: null, groupCustomDrafts: {}, zoneTemperatureTimers: {}, deviceTemperatureDrafts: {},
|
customChartSeries: [], savedCharts: [], customChartEditingId: null, customChartNameDraft: null, chartZooms: {}, chartHiddenSeries: {}, zoneControlSeq: {}, groupControlSeq: {}, zoneControlQueue: {}, groupControlQueue: {}, deviceControlQueue: {}, houseControlQueue: null, climateControlQueue: null, groupCustomDrafts: {}, zoneTemperatureTimers: {}, deviceTemperatureDrafts: {},
|
||||||
controlPlan: null, controlPlanRevision: null, controlPlanPushReady: false, controlPlanTimer: null, debugLines: [], debugBacklogLoaded: false, debugFilter: 'all', sensorAliases: {}, flowSharedInputs: [], flowSharedInputValueCache: {}, flowSharedInputValueRequests: {}, flowSharedInputValueTimer: null,
|
controlPlan: null, controlPlanRevision: null, controlPlanPushReady: false, controlPlanTimer: null, debugLines: [], debugBacklogLoaded: false, debugFilter: 'all', sensorAliases: {}, flowSharedInputs: [], flowSharedInputValueCache: {}, flowSharedInputValueRequests: {}, flowSharedInputValueTimer: null,
|
||||||
haManualFallbackVisible: false, haSupervisorTestState: null, haEntityCatalog: [], haEntityCatalogConfigured: false, haEntityCatalogLoadedAt: 0, haEntityCatalogLoading: false,
|
haManualFallbackVisible: false, haSupervisorTestState: null, haEntityCatalog: [], haEntityCatalogConfigured: false, haEntityCatalogLoadedAt: 0, haEntityCatalogLoading: false,
|
||||||
simulationScope: 'units', simulationTarget: 'all', standaloneSimulation: false, dashboardTab: 'main', settingsTab: 'app', systemSnapshotAt: Date.now(),
|
simulationScope: 'units', simulationTarget: 'all', standaloneSimulation: false, dashboardTab: 'main', settingsTab: 'app', systemSnapshotAt: Date.now(),
|
||||||
|
|||||||
+1
-1
@@ -377,7 +377,7 @@ function renderSimulationPage() {
|
|||||||
return `<path class="flow-link ${cls}" d="M ${ax} ${ay} C ${ax + bend} ${ay}, ${bx - bend} ${by}, ${bx} ${by}"/>`;
|
return `<path class="flow-link ${cls}" d="M ${ax} ${ay} C ${ax + bend} ${ay}, ${bx - bend} ${by}, ${bx} ${by}"/>`;
|
||||||
};
|
};
|
||||||
const verticalLink = (ax, ay, bx, by, cls = 'bus') => `<path class="flow-link ${cls}" d="M ${ax} ${ay} C ${ax} ${ay + 35}, ${bx} ${by - 35}, ${bx} ${by}"/>`;
|
const verticalLink = (ax, ay, bx, by, cls = 'bus') => `<path class="flow-link ${cls}" d="M ${ax} ${ay} C ${ax} ${ay + 35}, ${bx} ${by - 35}, ${bx} ${by}"/>`;
|
||||||
const node = ({ left, top, kind, eyebrow, title, value, meta = '', badge = '', badgeIcon = '', badgeClass = '' }) => `<article class="flow-node ${kind}" style="left:${left}px;top:${top}px;width:${nodeW}px;min-height:${nodeH}px"><div class="flow-node-top"><span>${esc(eyebrow)}</span>${badge || badgeIcon ? `<b class="flow-node-badge ${badgeClass}">${badgeIcon ? uiIcon(badgeIcon) : esc(badge)}</b>` : ''}</div><h3>${esc(title)}</h3><strong>${esc(value)}</strong>${meta ? `<p>${esc(meta)}</p>` : ''}<i class="flow-port in"></i><i class="flow-port out"></i></article>`;
|
const node = ({ left, top, kind, eyebrow, title, value, meta = '', badge = '', badgeIcon = '', badgeClass = '' }) => `<article class="diagram-node ${kind}" style="left:${left}px;top:${top}px;width:${nodeW}px;min-height:${nodeH}px"><div class="flow-node-top"><span>${esc(eyebrow)}</span>${badge || badgeIcon ? `<b class="flow-node-badge ${badgeClass}">${badgeIcon ? uiIcon(badgeIcon) : esc(badge)}</b>` : ''}</div><h3>${esc(title)}</h3><strong>${esc(value)}</strong>${meta ? `<p>${esc(meta)}</p>` : ''}<i class="diagram-port in"></i><i class="diagram-port out"></i></article>`;
|
||||||
|
|
||||||
const globalHouseLeft = 305, globalNightLeft = 565, globalTop = 28;
|
const globalHouseLeft = 305, globalNightLeft = 565, globalTop = 28;
|
||||||
nodes.push(node({ left: globalHouseLeft, top: globalTop, kind: 'logic global', eyebrow: tr('simulation.globalInput'), title: tr('simulation.houseMode'), value: houseModeLabel(plan.house_mode || 'off'), meta: tr('simulation.strategy', { strategy: (plan.control_strategy || 'setpoint') === 'setpoint' ? tr('plan.strategySetpoint') : (plan.control_strategy || 'setpoint') }), badge: tr('simulation.house') }));
|
nodes.push(node({ left: globalHouseLeft, top: globalTop, kind: 'logic global', eyebrow: tr('simulation.globalInput'), title: tr('simulation.houseMode'), value: houseModeLabel(plan.house_mode || 'off'), meta: tr('simulation.strategy', { strategy: (plan.control_strategy || 'setpoint') === 'setpoint' ? tr('plan.strategySetpoint') : (plan.control_strategy || 'setpoint') }), badge: tr('simulation.house') }));
|
||||||
|
|||||||
+1
-1
@@ -537,7 +537,7 @@ function zoneCard(zone, detailed = true) {
|
|||||||
const groupText = groupNames.length ? groupNames.join(' · ') : tr('zones.noGroup');
|
const groupText = groupNames.length ? groupNames.join(' · ') : tr('zones.noGroup');
|
||||||
const policy = zone.inherit_house_mode ? tr('zones.followHouse') : tr(zone.mode === 'heat' ? 'zones.heatOnly' : 'zones.coolOnly');
|
const policy = zone.inherit_house_mode ? tr('zones.followHouse') : tr(zone.mode === 'heat' ? 'zones.heatOnly' : 'zones.coolOnly');
|
||||||
return `<article class="list-card zone-config-card ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-config="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
return `<article class="list-card zone-config-card ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-config="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||||
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('zones.configuration'))}</span><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(tr('groups.group'))}: ${esc(groupText)}</p></div><button type="button" class="zone-enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}" title="${esc(tr('zones.automationToggleHint'))}"><span>${uiIcon(zone.enabled ? 'check' : 'circle')}</span>${esc(state)}</button></div>
|
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('zones.configuration'))}</span><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(tr('groups.group'))}: ${esc(groupText)}</p></div><button type="button" class="enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}" title="${esc(tr('zones.automationToggleHint'))}"><span>${uiIcon(zone.enabled ? 'check' : 'circle')}</span>${esc(state)}</button></div>
|
||||||
<div class="zone-config-status">
|
<div class="zone-config-status">
|
||||||
<div class="zone-config-temperature"><small>${esc(tr('zones.currentStatus'))}</small><div><span>${fmtTemp(roomTemperature)}</span><b>→</b><strong>${Number.isFinite(target) ? `${target.toFixed(1)}°C` : '—'}</strong></div></div>
|
<div class="zone-config-temperature"><small>${esc(tr('zones.currentStatus'))}</small><div><span>${fmtTemp(roomTemperature)}</span><b>→</b><strong>${Number.isFinite(target) ? `${target.toFixed(1)}°C` : '—'}</strong></div></div>
|
||||||
<div class="zone-config-runtime"><span class="badge ${workingDemand ? 'active' : ''}">${esc(zoneRuntimeStatusLabel(zone, effectiveMode, device))}</span><span>${esc(houseModeLabel(effectiveMode))} · ${esc(zonePresetLabel(displayPreset))}</span><small>${esc(override)}</small><small><strong>${esc(tr('zones.controlOwner'))}:</strong> ${esc(zoneControlOwnerLabel(zone))}${zoneControlOwnerMeta(zone) ? ` · ${esc(zoneControlOwnerMeta(zone))}` : ''}</small></div>
|
<div class="zone-config-runtime"><span class="badge ${workingDemand ? 'active' : ''}">${esc(zoneRuntimeStatusLabel(zone, effectiveMode, device))}</span><span>${esc(houseModeLabel(effectiveMode))} · ${esc(zonePresetLabel(displayPreset))}</span><small>${esc(override)}</small><small><strong>${esc(tr('zones.controlOwner'))}:</strong> ${esc(zoneControlOwnerLabel(zone))}${zoneControlOwnerMeta(zone) ? ` · ${esc(zoneControlOwnerMeta(zone))}` : ''}</small></div>
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ document.addEventListener('click', async event => {
|
|||||||
if (hours && $('#historyHours')) $('#historyHours').value = hours;
|
if (hours && $('#historyHours')) $('#historyHours').value = hours;
|
||||||
else if ($('#historyHours')) $('#historyHours').value = '24';
|
else if ($('#historyHours')) $('#historyHours').value = '24';
|
||||||
const dialog = historyRoute.closest('dialog');
|
const dialog = historyRoute.closest('dialog');
|
||||||
closeChartPreview(dialog?.querySelector('.history-chart-card.chart-fullscreen-fallback'));
|
closeChartPreview(dialog?.querySelector('.chart-card.chart-fullscreen-fallback'));
|
||||||
if (dialog?.open) dialog.close();
|
if (dialog?.open) dialog.close();
|
||||||
app.historyTab = 'overview';
|
app.historyTab = 'overview';
|
||||||
showView('history');
|
showView('history');
|
||||||
|
|||||||
+5
-5
@@ -98,7 +98,7 @@ function renderFlows() {
|
|||||||
const host = $('#flowList'); if (!host) return;
|
const host = $('#flowList'); if (!host) return;
|
||||||
const count = $('#flowListCount'); if (count) count.textContent = tr('flow.listCount', { count: app.flows.length });
|
const count = $('#flowListCount'); if (count) count.textContent = tr('flow.listCount', { count: app.flows.length });
|
||||||
host.innerHTML = app.flows.length ? app.flows.map(flow => `<article class="list-card flow-card ${flow.enabled && !flow.draft ? '' : 'is-disabled'} ${flow.draft ? 'is-draft' : ''}" data-flow-card-id="${esc(flow.id)}" tabindex="0" aria-label="${esc(`${tr('flow.openEditor')}: ${flow.name}`)}">
|
host.innerHTML = app.flows.length ? app.flows.map(flow => `<article class="list-card flow-card ${flow.enabled && !flow.draft ? '' : 'is-disabled'} ${flow.draft ? 'is-draft' : ''}" data-flow-card-id="${esc(flow.id)}" tabindex="0" aria-label="${esc(`${tr('flow.openEditor')}: ${flow.name}`)}">
|
||||||
<div class="list-card-head"><div><h3>${esc(flow.name)}${flow.draft ? ` <span class="badge flow-draft-badge">${esc(tr('flow.draft'))}</span>` : ''}</h3><p>${esc(flow.description || tr('flow.defaultDescription'))}</p></div>${flow.draft ? `<button type="button" class="zone-enable-toggle" disabled title="${esc(tr('flow.draftDisabledHint'))}"><span>${uiIcon('edit')}</span>${esc(tr('flow.disabledState'))}</button>` : `<button type="button" class="zone-enable-toggle ${flow.enabled ? 'active' : ''}" data-action="toggle-flow-enabled" data-id="${esc(flow.id)}" data-value="${flow.enabled ? 'false' : 'true'}" aria-label="${esc(tr(flow.enabled ? 'flow.disable' : 'flow.enable'))}" title="${esc(tr('flow.quickToggleHint'))}"><span>${uiIcon(flow.enabled ? 'check' : 'circle')}</span>${esc(flow.enabled ? tr('flow.enabledState') : tr('flow.disabledState'))}</button>`}</div>
|
<div class="list-card-head"><div><h3>${esc(flow.name)}${flow.draft ? ` <span class="badge flow-draft-badge">${esc(tr('flow.draft'))}</span>` : ''}</h3><p>${esc(flow.description || tr('flow.defaultDescription'))}</p></div>${flow.draft ? `<button type="button" class="enable-toggle" disabled title="${esc(tr('flow.draftDisabledHint'))}"><span>${uiIcon('edit')}</span>${esc(tr('flow.disabledState'))}</button>` : `<button type="button" class="enable-toggle ${flow.enabled ? 'active' : ''}" data-action="toggle-flow-enabled" data-id="${esc(flow.id)}" data-value="${flow.enabled ? 'false' : 'true'}" aria-label="${esc(tr(flow.enabled ? 'flow.disable' : 'flow.enable'))}" title="${esc(tr('flow.quickToggleHint'))}"><span>${uiIcon(flow.enabled ? 'check' : 'circle')}</span>${esc(flow.enabled ? tr('flow.enabledState') : tr('flow.disabledState'))}</button>`}</div>
|
||||||
<div class="card-stats"><div class="card-stat"><small>${esc(tr('flow.blocks'))}</small><strong>${flow.nodes?.length || 0}</strong></div><div class="card-stat"><small>${esc(tr('nav.schedules'))}</small><strong>${flow.compiled_schedule_ids?.length || 0}</strong></div><div class="card-stat"><small>${esc(tr('nav.automations'))}</small><strong>${flow.compiled_automation_ids?.length || 0}</strong></div></div>
|
<div class="card-stats"><div class="card-stat"><small>${esc(tr('flow.blocks'))}</small><strong>${flow.nodes?.length || 0}</strong></div><div class="card-stat"><small>${esc(tr('nav.schedules'))}</small><strong>${flow.compiled_schedule_ids?.length || 0}</strong></div><div class="card-stat"><small>${esc(tr('nav.automations'))}</small><strong>${flow.compiled_automation_ids?.length || 0}</strong></div></div>
|
||||||
<div class="card-footer"><small>${esc(flow.draft ? tr('flow.draftNoExecution') : tr('flow.compileCount', { schedules: flow.compiled_schedule_ids?.length || 0, automations: flow.compiled_automation_ids?.length || 0 }))}</small><div class="card-menu"><button class="card-primary-action" data-action="edit-flow" data-id="${esc(flow.id)}">${esc(tr('flow.openEditor'))}</button><details class="card-overflow"><summary aria-label="${esc(tr('actions.more'))}" title="${esc(tr('actions.more'))}">${uiIcon('more-horizontal')}</summary><div class="card-overflow-menu"><button class="danger" data-action="delete-flow" data-id="${esc(flow.id)}">${esc(tr('actions.delete'))}</button></div></details></div></div>
|
<div class="card-footer"><small>${esc(flow.draft ? tr('flow.draftNoExecution') : tr('flow.compileCount', { schedules: flow.compiled_schedule_ids?.length || 0, automations: flow.compiled_automation_ids?.length || 0 }))}</small><div class="card-menu"><button class="card-primary-action" data-action="edit-flow" data-id="${esc(flow.id)}">${esc(tr('flow.openEditor'))}</button><details class="card-overflow"><summary aria-label="${esc(tr('actions.more'))}" title="${esc(tr('actions.more'))}">${uiIcon('more-horizontal')}</summary><div class="card-overflow-menu"><button class="danger" data-action="delete-flow" data-id="${esc(flow.id)}">${esc(tr('actions.delete'))}</button></div></details></div></div>
|
||||||
</article>`).join('') : `<div class="empty"><strong>${esc(tr('flow.emptyTitle'))}</strong>${esc(tr('flow.emptyText'))}</div>`;
|
</article>`).join('') : `<div class="empty"><strong>${esc(tr('flow.emptyTitle'))}</strong>${esc(tr('flow.emptyText'))}</div>`;
|
||||||
@@ -426,7 +426,7 @@ function renderFlowBlockLibrary(filter = '') {
|
|||||||
const title = group.querySelector(':scope > span')?.textContent?.trim() || '';
|
const title = group.querySelector(':scope > span')?.textContent?.trim() || '';
|
||||||
const buttons = $$('[data-flow-add]', group).filter(button => !query || `${title} ${button.textContent}`.toLocaleLowerCase(locale()).includes(query));
|
const buttons = $$('[data-flow-add]', group).filter(button => !query || `${title} ${button.textContent}`.toLocaleLowerCase(locale()).includes(query));
|
||||||
if (!buttons.length) return '';
|
if (!buttons.length) return '';
|
||||||
const categoryClass = [...group.classList].find(name => name.startsWith('flow-palette-') && name !== 'flow-palette-group') || '';
|
const categoryClass = [...group.classList].find(name => name.startsWith('block-category-')) || '';
|
||||||
return `<section class="flow-block-library-group ${esc(categoryClass)}"><h3>${esc(title)}</h3><div>${buttons.map(button => `<button type="button" class="secondary" data-flow-add="${esc(button.dataset.flowAdd)}">${esc(button.textContent.trim())}</button>`).join('')}</div></section>`;
|
return `<section class="flow-block-library-group ${esc(categoryClass)}"><h3>${esc(title)}</h3><div>${buttons.map(button => `<button type="button" class="secondary" data-flow-add="${esc(button.dataset.flowAdd)}">${esc(button.textContent.trim())}</button>`).join('')}</div></section>`;
|
||||||
}).join('') || `<div class="empty compact"><strong>${esc(tr('flow.noBlocksFound'))}</strong><span>${esc(tr('flow.noBlocksFoundHint'))}</span></div>`;
|
}).join('') || `<div class="empty compact"><strong>${esc(tr('flow.noBlocksFound'))}</strong><span>${esc(tr('flow.noBlocksFoundHint'))}</span></div>`;
|
||||||
}
|
}
|
||||||
@@ -443,11 +443,11 @@ function renderFlowEditor() {
|
|||||||
const nodesHost = $('#flowNodes');
|
const nodesHost = $('#flowNodes');
|
||||||
nodesHost.innerHTML = draft.nodes.map(node => {
|
nodesHost.innerHTML = draft.nodes.map(node => {
|
||||||
const meta = FLOW_NODE_META[node.kind] || { title: node.kind, category: 'logic' };
|
const meta = FLOW_NODE_META[node.kind] || { title: node.kind, category: 'logic' };
|
||||||
return `<article class="flow-node flow-node-${esc(meta.category)} ${(app.flowSelectedNodeIds || []).includes(node.id) ? 'selected' : ''}" data-flow-node="${esc(node.id)}" style="left:${Number(node.x || 0)}px;top:${Number(node.y || 0)}px">
|
return `<article class="diagram-node flow-node-${esc(meta.category)} ${(app.flowSelectedNodeIds || []).includes(node.id) ? 'selected' : ''}" data-flow-node="${esc(node.id)}" style="left:${Number(node.x || 0)}px;top:${Number(node.y || 0)}px">
|
||||||
<button class="flow-port flow-port-in" type="button" data-flow-input="${esc(node.id)}" title="${esc(tr('flow.connectHere'))}"></button>
|
<button class="diagram-port flow-port-in" type="button" data-flow-input="${esc(node.id)}" title="${esc(tr('flow.connectHere'))}"></button>
|
||||||
<div class="flow-node-head"><span>${esc(flowNodeTitle(meta))}</span><button type="button" data-flow-remove="${esc(node.id)}" aria-label="${esc(tr('actions.delete'))}">${uiIcon('close')}</button></div>
|
<div class="flow-node-head"><span>${esc(flowNodeTitle(meta))}</span><button type="button" data-flow-remove="${esc(node.id)}" aria-label="${esc(tr('actions.delete'))}">${uiIcon('close')}</button></div>
|
||||||
<div class="flow-node-body">${esc(flowNodeSummary(node))}${node.kind === 'shared_input' && node.config?.input_id ? `<div class="flow-node-current" data-flow-shared-current="${esc(node.config.input_id)}"><span>${esc(tr('flow.sharedInputTestCurrent'))}</span><strong>—</strong></div>` : ''}</div>
|
<div class="flow-node-body">${esc(flowNodeSummary(node))}${node.kind === 'shared_input' && node.config?.input_id ? `<div class="flow-node-current" data-flow-shared-current="${esc(node.config.input_id)}"><span>${esc(tr('flow.sharedInputTestCurrent'))}</span><strong>—</strong></div>` : ''}</div>
|
||||||
<button class="flow-port flow-port-out ${app.flowConnectFrom === node.id ? 'armed' : ''}" type="button" data-flow-output="${esc(node.id)}" title="${esc(tr('flow.startConnection'))}"></button>
|
<button class="diagram-port flow-port-out ${app.flowConnectFrom === node.id ? 'armed' : ''}" type="button" data-flow-output="${esc(node.id)}" title="${esc(tr('flow.startConnection'))}"></button>
|
||||||
</article>`;
|
</article>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
$('#flowEmptyHint').hidden = draft.nodes.length > 0;
|
$('#flowEmptyHint').hidden = draft.nodes.length > 0;
|
||||||
|
|||||||
+3
-3
@@ -66,7 +66,7 @@ function showFormError(form, message) {
|
|||||||
summary.className = 'inline-alert error form-error-summary';
|
summary.className = 'inline-alert error form-error-summary';
|
||||||
summary.setAttribute('role', 'alert');
|
summary.setAttribute('role', 'alert');
|
||||||
summary.innerHTML = `<span>${esc(tr('devices.lastError'))}</span><strong>${esc(message)}</strong>`;
|
summary.innerHTML = `<span>${esc(tr('devices.lastError'))}</span><strong>${esc(message)}</strong>`;
|
||||||
const anchor = $('.settings-save-bar', form) || $('.form-actions', form);
|
const anchor = $('.sticky-form-actions', form) || $('.form-actions', form);
|
||||||
if (anchor?.parentElement) anchor.parentElement.insertBefore(summary, anchor);
|
if (anchor?.parentElement) anchor.parentElement.insertBefore(summary, anchor);
|
||||||
else form.appendChild(summary);
|
else form.appendChild(summary);
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ function confirmDiscardForm(form) {
|
|||||||
function activeDirtySettingsForm() {
|
function activeDirtySettingsForm() {
|
||||||
const active = $('.view.active');
|
const active = $('.view.active');
|
||||||
if (!active) return null;
|
if (!active) return null;
|
||||||
const form = $('form.settings-form', active);
|
const form = $('form.config-form', active);
|
||||||
return form && isFormDirty(form) ? form : null;
|
return form && isFormDirty(form) ? form : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ function requestDialogClose(dialog) {
|
|||||||
if (!dialog) return true;
|
if (!dialog) return true;
|
||||||
const form = $('form', dialog);
|
const form = $('form', dialog);
|
||||||
if (form && !confirmDiscardForm(form)) return false;
|
if (form && !confirmDiscardForm(form)) return false;
|
||||||
closeChartPreview(dialog.querySelector?.('.history-chart-card.chart-fullscreen-fallback'));
|
closeChartPreview(dialog.querySelector?.('.chart-card.chart-fullscreen-fallback'));
|
||||||
dialog.close();
|
dialog.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-4
@@ -272,17 +272,31 @@ function persistSavedCharts() {
|
|||||||
localStorage.setItem('gree_controller_saved_charts', JSON.stringify(app.savedCharts.slice(0, 30)));
|
localStorage.setItem('gree_controller_saved_charts', JSON.stringify(app.savedCharts.slice(0, 30)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function savedChartRangeLabel(hours) {
|
||||||
|
const value = Number(hours || 24);
|
||||||
|
if (value === 168) return tr('history.range7d');
|
||||||
|
if (value === 720) return tr('history.range30d');
|
||||||
|
if (value === 2160) return tr('history.range90d');
|
||||||
|
if (value === 8760) return tr('history.range1y');
|
||||||
|
return `${value} h`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderCustomBuilder() {
|
function renderCustomBuilder() {
|
||||||
const host = $('#historyCustomBuilder'); if (!host) return;
|
const host = $('#historyCustomBuilder'); if (!host) return;
|
||||||
if (app.historyTab !== 'custom') { host.innerHTML = ''; host.classList.remove('active'); return; }
|
if (app.historyTab !== 'custom') { host.innerHTML = ''; host.classList.remove('active'); return; }
|
||||||
host.classList.add('active');
|
host.classList.add('active');
|
||||||
const options = customSeriesOptions();
|
const options = customSeriesOptions();
|
||||||
const selected = app.customChartSeries.map((key, index) => customSeriesDefinition(key, index)).filter(Boolean);
|
const selected = app.customChartSeries.map((key, index) => customSeriesDefinition(key, index)).filter(Boolean);
|
||||||
|
const editing = app.savedCharts.find(item => item.id === app.customChartEditingId) || null;
|
||||||
|
const draftName = app.customChartNameDraft !== null ? app.customChartNameDraft : (editing?.name || '');
|
||||||
|
const editNote = editing ? `<span class="custom-chart-edit-note">${esc(tr('history.editingChart'))}: <strong>${esc(editing.name)}</strong></span>` : '';
|
||||||
|
const cancelEdit = editing ? `<button class="secondary" data-history-action="cancel-chart-edit">${esc(tr('actions.cancel'))}</button>` : '';
|
||||||
host.innerHTML = `<div class="panel custom-chart-panel"><div class="chart-title-row"><div><h3>${esc(tr('history.customTitle'))}</h3><p>${esc(tr('history.customDescription'))}</p></div></div>
|
host.innerHTML = `<div class="panel custom-chart-panel"><div class="chart-title-row"><div><h3>${esc(tr('history.customTitle'))}</h3><p>${esc(tr('history.customDescription'))}</p></div></div>
|
||||||
<div class="custom-chart-add"><select id="customSeriesSelect">${options.map(([value, label]) => `<option value="${esc(value)}">${esc(label)}</option>`).join('')}</select><button class="secondary" data-history-action="add-series">${esc(tr('history.addSeries'))}</button></div>
|
<div class="custom-chart-add"><select id="customSeriesSelect">${options.map(([value, label]) => `<option value="${esc(value)}">${esc(label)}</option>`).join('')}</select><button class="secondary" data-history-action="add-series">${esc(tr('history.addSeries'))}</button></div>
|
||||||
<div class="custom-series-list">${selected.length ? selected.map((item, index) => `<span class="custom-series-chip"><i style="--chip-color:${esc(item.color)}"></i>${esc(item.label)}<button data-history-action="remove-series" data-index="${index}" aria-label="${esc(tr('actions.remove'))}">${uiIcon('close')}</button></span>`).join('') : `<span class="field-note">${esc(tr('history.noCustomSeries'))}</span>`}</div>
|
<div class="custom-series-list">${selected.length ? selected.map((item, index) => `<span class="custom-series-chip"><i style="--chip-color:${esc(item.color)}"></i>${esc(item.label)}<button data-history-action="remove-series" data-index="${index}" aria-label="${esc(tr('actions.remove'))}">${uiIcon('close')}</button></span>`).join('') : `<span class="field-note">${esc(tr('history.noCustomSeries'))}</span>`}</div>
|
||||||
<div class="custom-chart-save"><input id="customChartName" placeholder="${esc(tr('history.chartName'))}"><button class="secondary" data-history-action="save-chart">${esc(tr('actions.save'))}</button><button class="primary" data-history-action="copy-chart-link">${esc(tr('history.copyLink'))}</button></div>
|
${editNote}
|
||||||
<div class="saved-chart-list">${app.savedCharts.length ? app.savedCharts.map(item => `<div class="saved-chart-row"><button data-history-action="load-chart" data-id="${esc(item.id)}"><strong>${esc(item.name)}</strong><small>${esc(item.series.length)} ${esc(tr('history.series'))}</small></button><button class="danger" data-history-action="delete-chart" data-id="${esc(item.id)}">${uiIcon('close')}</button></div>`).join('') : `<span class="field-note">${esc(tr('history.noSavedCharts'))}</span>`}</div>
|
<div class="custom-chart-save"><input id="customChartName" value="${esc(draftName)}" placeholder="${esc(tr('history.chartName'))}"><button class="secondary" data-history-action="save-chart">${esc(editing ? tr('history.saveChanges') : tr('actions.save'))}</button>${cancelEdit}<button class="primary" data-history-action="copy-chart-link">${esc(tr('history.copyLink'))}</button></div>
|
||||||
|
<div class="saved-chart-section"><div class="saved-chart-heading"><strong>${esc(tr('history.savedCharts'))}</strong><small>${app.savedCharts.length}</small></div><div class="saved-chart-list">${app.savedCharts.length ? app.savedCharts.map(item => `<div class="saved-chart-row${item.id === app.customChartEditingId ? ' is-editing' : ''}"><button class="saved-chart-open" data-history-action="load-chart" data-id="${esc(item.id)}"><strong>${esc(item.name)}</strong><small>${esc(item.series.length)} ${esc(tr('history.series'))} · ${esc(savedChartRangeLabel(item.hours))}</small></button><div class="saved-chart-actions"><button class="secondary" data-history-action="edit-chart" data-id="${esc(item.id)}" title="${esc(tr('actions.edit'))}" aria-label="${esc(tr('actions.edit'))}">${uiIcon('edit')}</button><button class="danger" data-history-action="delete-chart" data-id="${esc(item.id)}" title="${esc(tr('actions.delete'))}" aria-label="${esc(tr('actions.delete'))}">${uiIcon('close')}</button></div></div>`).join('') : `<span class="field-note">${esc(tr('history.noSavedCharts'))}</span>`}</div></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,26 +341,46 @@ function publicCustomChartUrl(path) {
|
|||||||
async function handleHistoryAction(button) {
|
async function handleHistoryAction(button) {
|
||||||
const action = button.dataset.historyAction;
|
const action = button.dataset.historyAction;
|
||||||
if (action === 'add-series') {
|
if (action === 'add-series') {
|
||||||
|
if ($('#customChartName')) app.customChartNameDraft = $('#customChartName').value;
|
||||||
const value = $('#customSeriesSelect')?.value;
|
const value = $('#customSeriesSelect')?.value;
|
||||||
if (value && !app.customChartSeries.includes(value)) app.customChartSeries.push(value);
|
if (value && !app.customChartSeries.includes(value)) app.customChartSeries.push(value);
|
||||||
renderCustomHistory(); updateBrowserUrl(currentHistoryPath(), true); return;
|
renderCustomHistory(); updateBrowserUrl(currentHistoryPath(), true); return;
|
||||||
}
|
}
|
||||||
if (action === 'remove-series') {
|
if (action === 'remove-series') {
|
||||||
|
if ($('#customChartName')) app.customChartNameDraft = $('#customChartName').value;
|
||||||
app.customChartSeries.splice(Number(button.dataset.index), 1);
|
app.customChartSeries.splice(Number(button.dataset.index), 1);
|
||||||
renderCustomHistory(); updateBrowserUrl(currentHistoryPath(), true); return;
|
renderCustomHistory(); updateBrowserUrl(currentHistoryPath(), true); return;
|
||||||
}
|
}
|
||||||
if (action === 'save-chart') {
|
if (action === 'save-chart') {
|
||||||
if (!app.customChartSeries.length) return toast(tr('history.noCustomSeries'), true);
|
if (!app.customChartSeries.length) return toast(tr('history.noCustomSeries'), true);
|
||||||
const name = $('#customChartName')?.value.trim() || tr('history.customChart');
|
const name = $('#customChartName')?.value.trim() || tr('history.customChart');
|
||||||
const item = { id: `chart-${Date.now()}`, name, series: [...app.customChartSeries], hours: $('#historyHours')?.value || '24' };
|
const hours = $('#historyHours')?.value || '24';
|
||||||
app.savedCharts.unshift(item); persistSavedCharts(); renderCustomHistory(); toast(tr('history.chartSaved')); return;
|
const editingIndex = app.savedCharts.findIndex(entry => entry.id === app.customChartEditingId);
|
||||||
|
if (editingIndex >= 0) {
|
||||||
|
app.savedCharts[editingIndex] = { ...app.savedCharts[editingIndex], name, series: [...app.customChartSeries], hours };
|
||||||
|
app.customChartEditingId = null; app.customChartNameDraft = null;
|
||||||
|
persistSavedCharts(); renderCustomHistory(); toast(tr('history.chartUpdated')); return;
|
||||||
|
}
|
||||||
|
const item = { id: `chart-${Date.now()}`, name, series: [...app.customChartSeries], hours };
|
||||||
|
app.savedCharts.unshift(item); app.customChartNameDraft = null; persistSavedCharts(); renderCustomHistory(); toast(tr('history.chartSaved')); return;
|
||||||
}
|
}
|
||||||
if (action === 'load-chart') {
|
if (action === 'load-chart') {
|
||||||
const item = app.savedCharts.find(entry => entry.id === button.dataset.id); if (!item) return;
|
const item = app.savedCharts.find(entry => entry.id === button.dataset.id); if (!item) return;
|
||||||
|
app.customChartEditingId = null; app.customChartNameDraft = item.name || '';
|
||||||
app.customChartSeries = [...item.series]; if ($('#historyHours') && item.hours) $('#historyHours').value = String(item.hours);
|
app.customChartSeries = [...item.series]; if ($('#historyHours') && item.hours) $('#historyHours').value = String(item.hours);
|
||||||
renderCustomHistory(); updateBrowserUrl(currentHistoryPath()); return;
|
renderCustomHistory(); updateBrowserUrl(currentHistoryPath()); return;
|
||||||
}
|
}
|
||||||
|
if (action === 'edit-chart') {
|
||||||
|
const item = app.savedCharts.find(entry => entry.id === button.dataset.id); if (!item) return;
|
||||||
|
app.customChartEditingId = item.id; app.customChartNameDraft = item.name || ''; app.customChartSeries = [...item.series];
|
||||||
|
if ($('#historyHours') && item.hours) $('#historyHours').value = String(item.hours);
|
||||||
|
renderCustomHistory(); updateBrowserUrl(currentHistoryPath()); $('#customChartName')?.focus(); return;
|
||||||
|
}
|
||||||
|
if (action === 'cancel-chart-edit') {
|
||||||
|
app.customChartEditingId = null; app.customChartNameDraft = null; renderCustomHistory(); return;
|
||||||
|
}
|
||||||
if (action === 'delete-chart') {
|
if (action === 'delete-chart') {
|
||||||
|
if (app.customChartEditingId === button.dataset.id) { app.customChartEditingId = null; app.customChartNameDraft = null; }
|
||||||
app.savedCharts = app.savedCharts.filter(entry => entry.id !== button.dataset.id); persistSavedCharts(); renderCustomHistory(); return;
|
app.savedCharts = app.savedCharts.filter(entry => entry.id !== button.dataset.id); persistSavedCharts(); renderCustomHistory(); return;
|
||||||
}
|
}
|
||||||
if (action === 'copy-chart-link') {
|
if (action === 'copy-chart-link') {
|
||||||
|
|||||||
+170
-138
@@ -736,7 +736,7 @@ h3 {
|
|||||||
padding: 7px 11px;
|
padding: 7px 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zone-enable-toggle {
|
.enable-toggle {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -749,13 +749,13 @@ h3 {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zone-enable-toggle.active {
|
.enable-toggle.active {
|
||||||
border-color: color-mix(in srgb, var(--accent) 34%, var(--line));
|
border-color: color-mix(in srgb, var(--accent) 34%, var(--line));
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
|
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
.zone-enable-toggle span {
|
.enable-toggle span {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
@@ -1639,16 +1639,16 @@ button.outside-pill:focus-visible {
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
.outdoor-history-dialog .history-chart-card {
|
.outdoor-history-dialog .chart-card {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.outdoor-history-dialog .history-chart-card:not(.chart-fullscreen-fallback) .chart-wrap {
|
.outdoor-history-dialog .chart-card:not(.chart-fullscreen-fallback) .chart-wrap {
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: none;
|
max-height: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.outdoor-history-dialog .history-chart-card:not(.chart-fullscreen-fallback) canvas {
|
.outdoor-history-dialog .chart-card:not(.chart-fullscreen-fallback) canvas {
|
||||||
height: 350px;
|
height: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2031,7 +2031,7 @@ button.outside-pill:focus-visible {
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-chart-card {
|
.chart-card {
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2188,35 +2188,6 @@ button.outside-pill:focus-visible {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-tabs {
|
|
||||||
display: flex;
|
|
||||||
gap: 7px;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: 0 0 14px;
|
|
||||||
padding: 3px;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-tabs::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-tabs button {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
padding: 9px 13px;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 999px;
|
|
||||||
color: var(--muted);
|
|
||||||
background: var(--surface);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-tabs button.active {
|
|
||||||
color: var(--accent-text);
|
|
||||||
border-color: var(--accent);
|
|
||||||
background: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-toolbar-panel {
|
.history-toolbar-panel {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
@@ -2309,23 +2280,49 @@ button.outside-pill:focus-visible {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-chart-edit-note {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-chart-edit-note strong { color: var(--text-soft); }
|
||||||
|
|
||||||
|
.saved-chart-section {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.saved-chart-heading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.saved-chart-heading small { color: var(--muted); }
|
||||||
|
|
||||||
.saved-chart-list {
|
.saved-chart-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
padding-top: 3px;
|
|
||||||
border-top: 1px solid var(--line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.saved-chart-row {
|
.saved-chart-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.saved-chart-row>button:first-child {
|
.saved-chart-row.is-editing .saved-chart-open {
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 65%, var(--line));
|
||||||
|
}
|
||||||
|
|
||||||
|
.saved-chart-open {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
@@ -2344,7 +2341,12 @@ button.outside-pill:focus-visible {
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.saved-chart-row>.danger {
|
.saved-chart-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.saved-chart-actions button {
|
||||||
width: 42px;
|
width: 42px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
@@ -2679,7 +2681,7 @@ button.outside-pill:focus-visible {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-chart-card canvas {
|
.chart-card canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 720px;
|
min-width: 720px;
|
||||||
}
|
}
|
||||||
@@ -2960,7 +2962,7 @@ button.outside-pill:focus-visible {
|
|||||||
align-self: end;
|
align-self: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-form {
|
.config-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
@@ -3001,39 +3003,39 @@ button.outside-pill:focus-visible {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-block {
|
.form-section {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-block-head {
|
.form-section-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-block-head h3 {
|
.form-section-head h3 {
|
||||||
margin: 1px 0 4px;
|
margin: 1px 0 4px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-block-head p {
|
.form-section-head p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-grid {
|
.form-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-grid .wide {
|
.form-grid .wide {
|
||||||
grid-column: 1/-1;
|
grid-column: 1/-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-save-bar {
|
.sticky-form-actions {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 18px;
|
bottom: 18px;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
@@ -3049,7 +3051,7 @@ button.outside-pill:focus-visible {
|
|||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-save-bar span {
|
.sticky-form-actions span {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
@@ -3240,7 +3242,7 @@ button.outside-pill:focus-visible {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node {
|
.diagram-node {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -3253,31 +3255,31 @@ button.outside-pill:focus-visible {
|
|||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, .15);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, .15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.input {
|
.diagram-node.input {
|
||||||
border-left: 3px solid var(--teal);
|
border-left: 3px solid var(--teal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.logic {
|
.diagram-node.logic {
|
||||||
border-left: 3px solid var(--purple);
|
border-left: 3px solid var(--purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.action {
|
.diagram-node.action {
|
||||||
border-left: 3px solid var(--accent);
|
border-left: 3px solid var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.event {
|
.diagram-node.event {
|
||||||
border-left: 3px solid var(--info);
|
border-left: 3px solid var(--info);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.decision.demand {
|
.diagram-node.decision.demand {
|
||||||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 28%, transparent), 0 8px 24px rgba(0, 0, 0, .15);
|
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 28%, transparent), 0 8px 24px rgba(0, 0, 0, .15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.global {
|
.diagram-node.global {
|
||||||
min-height: 106px;
|
min-height: 106px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.night-active {
|
.diagram-node.night-active {
|
||||||
border-color: color-mix(in srgb, var(--info) 45%, var(--line-strong));
|
border-color: color-mix(in srgb, var(--info) 45%, var(--line-strong));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3293,7 +3295,7 @@ button.outside-pill:focus-visible {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node h3 {
|
.diagram-node h3 {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
@@ -3302,13 +3304,13 @@ button.outside-pill:focus-visible {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node>strong {
|
.diagram-node>strong {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node p {
|
.diagram-node p {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -3336,7 +3338,7 @@ button.outside-pill:focus-visible {
|
|||||||
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
|
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-port {
|
.diagram-port {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
width: 9px;
|
width: 9px;
|
||||||
@@ -3347,27 +3349,27 @@ button.outside-pill:focus-visible {
|
|||||||
background: var(--muted);
|
background: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-port.in {
|
.diagram-port.in {
|
||||||
left: -6px;
|
left: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-port.out {
|
.diagram-port.out {
|
||||||
right: -6px;
|
right: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.input .flow-port {
|
.diagram-node.input .diagram-port {
|
||||||
background: var(--teal);
|
background: var(--teal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.logic .flow-port {
|
.diagram-node.logic .diagram-port {
|
||||||
background: var(--purple);
|
background: var(--purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.action .flow-port {
|
.diagram-node.action .diagram-port {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-node.event .flow-port {
|
.diagram-node.event .diagram-port {
|
||||||
background: var(--info);
|
background: var(--info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3384,15 +3386,15 @@ button.outside-pill:focus-visible {
|
|||||||
grid-column: auto;
|
grid-column: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-grid {
|
.form-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-grid .wide {
|
.form-grid .wide {
|
||||||
grid-column: auto;
|
grid-column: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-save-bar {
|
.sticky-form-actions {
|
||||||
bottom: 76px;
|
bottom: 76px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3416,7 +3418,7 @@ button.outside-pill:focus-visible {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-block-head {
|
.form-section-head {
|
||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3480,7 +3482,7 @@ button.outside-pill:focus-visible {
|
|||||||
border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
|
border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
|
||||||
}
|
}
|
||||||
|
|
||||||
.standalone-settings-form {
|
.full-width-form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
@@ -4135,7 +4137,7 @@ body.simulation-standalone [data-view="simulation"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.zone-config-card .zone-enable-toggle {
|
.zone-config-card .enable-toggle {
|
||||||
min-height: 28px;
|
min-height: 28px;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
@@ -4588,7 +4590,7 @@ body.simulation-standalone [data-view="simulation"] {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-thermostat-control .zone-enable-toggle {
|
.quick-thermostat-control .enable-toggle {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5326,7 +5328,7 @@ textarea[aria-invalid="true"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.settings-form>.form-error-summary {
|
.config-form>.form-error-summary {
|
||||||
margin: 0 0 12px;
|
margin: 0 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5334,7 +5336,7 @@ textarea[aria-invalid="true"] {
|
|||||||
margin-top: -2px;
|
margin-top: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-form.is-saving,
|
.config-form.is-saving,
|
||||||
.dialog-form.is-saving {
|
.dialog-form.is-saving {
|
||||||
cursor: progress;
|
cursor: progress;
|
||||||
}
|
}
|
||||||
@@ -5417,7 +5419,7 @@ textarea[aria-invalid="true"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (hover:hover) and (pointer:fine) {
|
@media (hover:hover) and (pointer:fine) {
|
||||||
.history-chart-card .chart-wrap canvas {
|
.chart-card .chart-wrap canvas {
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5492,7 +5494,7 @@ textarea[aria-invalid="true"] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-form.has-unsaved-changes .settings-save-bar {
|
.config-form.has-unsaved-changes .sticky-form-actions {
|
||||||
border-color: color-mix(in srgb, var(--accent) 48%, var(--line-strong));
|
border-color: color-mix(in srgb, var(--accent) 48%, var(--line-strong));
|
||||||
box-shadow: 0 8px 30px rgba(0, 0, 0, .18), 0 0 0 1px color-mix(in srgb, var(--accent) 10%, transparent);
|
box-shadow: 0 8px 30px rgba(0, 0, 0, .18), 0 0 0 1px color-mix(in srgb, var(--accent) 10%, transparent);
|
||||||
}
|
}
|
||||||
@@ -6172,7 +6174,7 @@ textarea[aria-invalid="true"] {
|
|||||||
stroke-width: 4;
|
stroke-width: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-editor .flow-node {
|
.flow-editor .diagram-node {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -6188,7 +6190,7 @@ textarea[aria-invalid="true"] {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-editor .flow-node.selected {
|
.flow-editor .diagram-node.selected {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
box-shadow: 0 0 0 2px var(--accent-soft), 0 12px 30px rgba(0, 0, 0, .22);
|
box-shadow: 0 0 0 2px var(--accent-soft), 0 12px 30px rgba(0, 0, 0, .22);
|
||||||
}
|
}
|
||||||
@@ -6236,7 +6238,7 @@ textarea[aria-invalid="true"] {
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-editor .flow-port {
|
.flow-editor .diagram-port {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@@ -6247,8 +6249,8 @@ textarea[aria-invalid="true"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.flow-editor .flow-port:hover,
|
.flow-editor .diagram-port:hover,
|
||||||
.flow-editor .flow-port.armed {
|
.flow-editor .diagram-port.armed {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
transform: scale(1.15);
|
transform: scale(1.15);
|
||||||
}
|
}
|
||||||
@@ -7230,31 +7232,31 @@ textarea[aria-invalid="true"] {
|
|||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-palette-trigger {
|
.block-category-trigger {
|
||||||
border-left-color: var(--warning);
|
border-left-color: var(--warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-palette-time {
|
.block-category-time {
|
||||||
border-left-color: var(--blue);
|
border-left-color: var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-palette-timeop {
|
.block-category-timeop {
|
||||||
border-left-color: var(--info);
|
border-left-color: var(--info);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-palette-sensor {
|
.block-category-sensor {
|
||||||
border-left-color: var(--teal);
|
border-left-color: var(--teal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-palette-logic {
|
.block-category-logic {
|
||||||
border-left-color: var(--purple);
|
border-left-color: var(--purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-palette-action {
|
.block-category-action {
|
||||||
border-left-color: var(--orange);
|
border-left-color: var(--orange);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-palette-haaction {
|
.block-category-haaction {
|
||||||
border-left-color: var(--accent);
|
border-left-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7380,7 +7382,7 @@ button {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.automation-tabs {
|
.segmented-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
@@ -7395,11 +7397,11 @@ button {
|
|||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.automation-tabs::-webkit-scrollbar {
|
.segmented-tabs::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.automation-tabs button {
|
.segmented-tabs button {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@@ -7409,7 +7411,7 @@ button {
|
|||||||
font-weight: 750;
|
font-weight: 750;
|
||||||
}
|
}
|
||||||
|
|
||||||
.automation-tabs button.active {
|
.segmented-tabs button.active {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
box-shadow: inset 0 0 0 1px var(--accent-border);
|
box-shadow: inset 0 0 0 1px var(--accent-border);
|
||||||
@@ -7614,31 +7616,31 @@ button {
|
|||||||
border-left: 3px solid var(--line-strong);
|
border-left: 3px solid var(--line-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-block-library-group.flow-palette-trigger {
|
.flow-block-library-group.block-category-trigger {
|
||||||
border-left-color: var(--warning);
|
border-left-color: var(--warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-block-library-group.flow-palette-time {
|
.flow-block-library-group.block-category-time {
|
||||||
border-left-color: var(--blue);
|
border-left-color: var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-block-library-group.flow-palette-timeop {
|
.flow-block-library-group.block-category-timeop {
|
||||||
border-left-color: var(--info);
|
border-left-color: var(--info);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-block-library-group.flow-palette-sensor {
|
.flow-block-library-group.block-category-sensor {
|
||||||
border-left-color: var(--teal);
|
border-left-color: var(--teal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-block-library-group.flow-palette-logic {
|
.flow-block-library-group.block-category-logic {
|
||||||
border-left-color: var(--purple);
|
border-left-color: var(--purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-block-library-group.flow-palette-action {
|
.flow-block-library-group.block-category-action {
|
||||||
border-left-color: var(--orange);
|
border-left-color: var(--orange);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-block-library-group.flow-palette-haaction {
|
.flow-block-library-group.block-category-haaction {
|
||||||
border-left-color: var(--accent);
|
border-left-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7661,7 +7663,7 @@ button {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-editor .flow-port {
|
.flow-editor .diagram-port {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-top: -8px;
|
margin-top: -8px;
|
||||||
@@ -7675,7 +7677,7 @@ button {
|
|||||||
right: -9px;
|
right: -9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flow-editor .flow-port::after {
|
.flow-editor .diagram-port::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -11px;
|
inset: -11px;
|
||||||
@@ -7822,12 +7824,12 @@ button {
|
|||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.automation-tabs {
|
.segmented-tabs {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
scroll-snap-type: x proximity;
|
scroll-snap-type: x proximity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.automation-tabs button {
|
.segmented-tabs button {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
scroll-snap-align: start;
|
scroll-snap-align: start;
|
||||||
}
|
}
|
||||||
@@ -7917,17 +7919,6 @@ button {
|
|||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-tabs {
|
|
||||||
margin-inline: -12px;
|
|
||||||
padding-inline: 12px;
|
|
||||||
scroll-padding-inline: 12px;
|
|
||||||
scroll-snap-type: x proximity;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-tabs button {
|
|
||||||
min-height: 40px;
|
|
||||||
scroll-snap-align: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-toolbar-panel {
|
.history-toolbar-panel {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
@@ -7942,17 +7933,17 @@ button {
|
|||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-save-bar {
|
.sticky-form-actions {
|
||||||
bottom: calc(74px + env(safe-area-inset-bottom));
|
bottom: calc(74px + env(safe-area-inset-bottom));
|
||||||
margin-inline: -4px;
|
margin-inline: -4px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-save-bar span {
|
.sticky-form-actions span {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-save-bar button {
|
.sticky-form-actions button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
@@ -8332,15 +8323,15 @@ button {
|
|||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-block {
|
.form-section {
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-block-head h3 {
|
.form-section-head h3 {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-block-head p,
|
.form-section-head p,
|
||||||
.field-note {
|
.field-note {
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
@@ -8531,7 +8522,7 @@ body.flow-editor-open {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-chart-card.chart-fullscreen-fallback {
|
.chart-card.chart-fullscreen-fallback {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 140;
|
z-index: 140;
|
||||||
@@ -8551,16 +8542,16 @@ body.flow-editor-open {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-chart-card.chart-fullscreen-fallback .chart-title-row {
|
.chart-card.chart-fullscreen-fallback .chart-title-row {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-chart-card.chart-fullscreen-fallback .chart-title-row p,
|
.chart-card.chart-fullscreen-fallback .chart-title-row p,
|
||||||
.history-chart-card.chart-fullscreen-fallback .chart-hover-hint {
|
.chart-card.chart-fullscreen-fallback .chart-hover-hint {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-chart-card.chart-fullscreen-fallback .chart-wrap {
|
.chart-card.chart-fullscreen-fallback .chart-wrap {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -8569,7 +8560,7 @@ body.flow-editor-open {
|
|||||||
background: var(--surface-muted);
|
background: var(--surface-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-chart-card.chart-fullscreen-fallback .legend {
|
.chart-card.chart-fullscreen-fallback .legend {
|
||||||
max-height: 88px;
|
max-height: 88px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -8611,7 +8602,7 @@ body.flow-editor-open {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.automation-tabs {
|
.segmented-tabs {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
@@ -9981,35 +9972,73 @@ select.select-native-proxy[aria-invalid="true"] + .custom-select .custom-select-
|
|||||||
|
|
||||||
/* Standalone public custom chart */
|
/* Standalone public custom chart */
|
||||||
.public-custom-chart-page {
|
.public-custom-chart-page {
|
||||||
min-height: 100vh;
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.public-custom-chart-shell {
|
.public-custom-chart-shell {
|
||||||
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
min-height: 0;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.public-custom-chart-card {
|
.public-custom-chart-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: calc(100vh - 32px);
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.public-chart-title-row {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-chart-heading { min-width: 0; }
|
||||||
|
|
||||||
|
.public-chart-range-control {
|
||||||
|
display: grid;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-chart-range-control select { min-width: 126px; }
|
||||||
|
|
||||||
.public-custom-chart-wrap {
|
.public-custom-chart-wrap {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 0;
|
||||||
min-height: 360px;
|
min-height: 180px;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#publicCustomChart {
|
#publicCustomChart {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 720px;
|
min-width: 720px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 420px;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.public-custom-chart-card .legend {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-custom-chart-card .legend::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
.public-chart-legend-item {
|
.public-chart-legend-item {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
@@ -10028,5 +10057,8 @@ select.select-native-proxy[aria-invalid="true"] + .custom-select .custom-select-
|
|||||||
|
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
.public-custom-chart-shell { padding: 8px; }
|
.public-custom-chart-shell { padding: 8px; }
|
||||||
.public-custom-chart-card { min-height: calc(100vh - 16px); padding: 14px; }
|
.public-custom-chart-card { padding: 12px; }
|
||||||
|
.public-chart-title-row { align-items: center; }
|
||||||
|
.public-chart-range-control select { min-width: 108px; }
|
||||||
|
.public-custom-chart-wrap { min-height: 140px; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user