diff --git a/.sqlx/query-3a0b307430ed264882e1f7b56ae9a4b6638a256e21edcf615c86cb53fba9c973.json b/.sqlx/query-0fc80b6949eaaeda77dabad7093bca70bd327c14eea4b8db1c9f11c722a00bf4.json similarity index 85% rename from .sqlx/query-3a0b307430ed264882e1f7b56ae9a4b6638a256e21edcf615c86cb53fba9c973.json rename to .sqlx/query-0fc80b6949eaaeda77dabad7093bca70bd327c14eea4b8db1c9f11c722a00bf4.json index 8f0ff1885..a86855aeb 100644 --- a/.sqlx/query-3a0b307430ed264882e1f7b56ae9a4b6638a256e21edcf615c86cb53fba9c973.json +++ b/.sqlx/query-0fc80b6949eaaeda77dabad7093bca70bd327c14eea4b8db1c9f11c722a00bf4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"location_id\",\"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"certificate\",\"certificate_expiry\",\"version\",\"modified_at\",\"modified_by\" FROM \"gateway\"", + "query": "SELECT id, \"location_id\",\"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"certificate\",\"certificate_expiry\",\"version\",\"enabled\",\"modified_at\",\"modified_by\" FROM \"gateway\"", "describe": { "columns": [ { @@ -55,11 +55,16 @@ }, { "ordinal": 10, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 11, "name": "modified_at", "type_info": "Timestamp" }, { - "ordinal": 11, + "ordinal": 12, "name": "modified_by", "type_info": "Int8" } @@ -79,8 +84,9 @@ true, true, false, + false, false ] }, - "hash": "3a0b307430ed264882e1f7b56ae9a4b6638a256e21edcf615c86cb53fba9c973" + "hash": "0fc80b6949eaaeda77dabad7093bca70bd327c14eea4b8db1c9f11c722a00bf4" } diff --git a/.sqlx/query-1e48e6b87c058b8dbc54b86c704ee3ecbfdfbd69f3d44e16d9a6e9ef25069614.json b/.sqlx/query-1e48e6b87c058b8dbc54b86c704ee3ecbfdfbd69f3d44e16d9a6e9ef25069614.json index 48914cbf1..9aa06dfed 100644 --- a/.sqlx/query-1e48e6b87c058b8dbc54b86c704ee3ecbfdfbd69f3d44e16d9a6e9ef25069614.json +++ b/.sqlx/query-1e48e6b87c058b8dbc54b86c704ee3ecbfdfbd69f3d44e16d9a6e9ef25069614.json @@ -60,11 +60,16 @@ }, { "ordinal": 11, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 12, "name": "modified_by_firstname", "type_info": "Text" }, { - "ordinal": 12, + "ordinal": 13, "name": "modified_by_lastname", "type_info": "Text" } @@ -85,6 +90,7 @@ false, true, false, + false, false ] }, diff --git a/.sqlx/query-2ca67025b051148efdb9e00e4bb48b883d72bc6c8f481ae2734b8b6fd25977ac.json b/.sqlx/query-2ce93887379d80ff03753caaf94ec1ab4c6f0ead212fc74bb881e1d5c0d96080.json similarity index 83% rename from .sqlx/query-2ca67025b051148efdb9e00e4bb48b883d72bc6c8f481ae2734b8b6fd25977ac.json rename to .sqlx/query-2ce93887379d80ff03753caaf94ec1ab4c6f0ead212fc74bb881e1d5c0d96080.json index 057c695d9..de746cbc6 100644 --- a/.sqlx/query-2ca67025b051148efdb9e00e4bb48b883d72bc6c8f481ae2734b8b6fd25977ac.json +++ b/.sqlx/query-2ce93887379d80ff03753caaf94ec1ab4c6f0ead212fc74bb881e1d5c0d96080.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"version\",\"certificate\",\"certificate_expiry\",\"modified_at\",\"modified_by\" FROM \"proxy\"", + "query": "SELECT * FROM proxy WHERE enabled", "describe": { "columns": [ { @@ -35,28 +35,33 @@ }, { "ordinal": 6, - "name": "version", - "type_info": "Text" + "name": "certificate_expiry", + "type_info": "Timestamp" }, { "ordinal": 7, - "name": "certificate", + "name": "version", "type_info": "Text" }, { "ordinal": 8, - "name": "certificate_expiry", + "name": "modified_at", "type_info": "Timestamp" }, { "ordinal": 9, - "name": "modified_at", - "type_info": "Timestamp" + "name": "modified_by", + "type_info": "Int8" }, { "ordinal": 10, - "name": "modified_by", - "type_info": "Int8" + "name": "certificate", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "enabled", + "type_info": "Bool" } ], "parameters": { @@ -71,10 +76,11 @@ true, true, true, - true, false, + false, + true, false ] }, - "hash": "2ca67025b051148efdb9e00e4bb48b883d72bc6c8f481ae2734b8b6fd25977ac" + "hash": "2ce93887379d80ff03753caaf94ec1ab4c6f0ead212fc74bb881e1d5c0d96080" } diff --git a/.sqlx/query-3c6a119f2f10046bd9e42314df953a0a0b3b44d0a87d43f69425729c15e1a400.json b/.sqlx/query-3c6a119f2f10046bd9e42314df953a0a0b3b44d0a87d43f69425729c15e1a400.json index 4e4de010f..9cd0911d7 100644 --- a/.sqlx/query-3c6a119f2f10046bd9e42314df953a0a0b3b44d0a87d43f69425729c15e1a400.json +++ b/.sqlx/query-3c6a119f2f10046bd9e42314df953a0a0b3b44d0a87d43f69425729c15e1a400.json @@ -62,6 +62,11 @@ "ordinal": 11, "name": "modified_by", "type_info": "Int8" + }, + { + "ordinal": 12, + "name": "enabled", + "type_info": "Bool" } ], "parameters": { @@ -81,6 +86,7 @@ false, false, false, + false, false ] }, diff --git a/.sqlx/query-4d9c4562a138038ba054b5b83b646341ee18e24f0d32399e6ce2ebaedef64cea.json b/.sqlx/query-4d9c4562a138038ba054b5b83b646341ee18e24f0d32399e6ce2ebaedef64cea.json index c18acfd6a..65ae6a26e 100644 --- a/.sqlx/query-4d9c4562a138038ba054b5b83b646341ee18e24f0d32399e6ce2ebaedef64cea.json +++ b/.sqlx/query-4d9c4562a138038ba054b5b83b646341ee18e24f0d32399e6ce2ebaedef64cea.json @@ -62,6 +62,11 @@ "ordinal": 11, "name": "modified_by", "type_info": "Int8" + }, + { + "ordinal": 12, + "name": "enabled", + "type_info": "Bool" } ], "parameters": { @@ -82,6 +87,7 @@ false, false, false, + false, false ] }, diff --git a/.sqlx/query-367b0ad00e37d5e0a001c8a2ff31db849ac5c6955d2d8e73938a37449d914bae.json b/.sqlx/query-6b1506441fd24aff832ee8ee9edb6d8423cfc61bf59ceaf0364c07ddde47127e.json similarity index 73% rename from .sqlx/query-367b0ad00e37d5e0a001c8a2ff31db849ac5c6955d2d8e73938a37449d914bae.json rename to .sqlx/query-6b1506441fd24aff832ee8ee9edb6d8423cfc61bf59ceaf0364c07ddde47127e.json index d76c52800..9ab462443 100644 --- a/.sqlx/query-367b0ad00e37d5e0a001c8a2ff31db849ac5c6955d2d8e73938a37449d914bae.json +++ b/.sqlx/query-6b1506441fd24aff832ee8ee9edb6d8423cfc61bf59ceaf0364c07ddde47127e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE \"gateway\" SET \"location_id\" = $2,\"name\" = $3,\"address\" = $4,\"port\" = $5,\"connected_at\" = $6,\"disconnected_at\" = $7,\"certificate\" = $8,\"certificate_expiry\" = $9,\"version\" = $10,\"modified_at\" = $11,\"modified_by\" = $12 WHERE id = $1", + "query": "UPDATE \"gateway\" SET \"location_id\" = $2,\"name\" = $3,\"address\" = $4,\"port\" = $5,\"connected_at\" = $6,\"disconnected_at\" = $7,\"certificate\" = $8,\"certificate_expiry\" = $9,\"version\" = $10,\"enabled\" = $11,\"modified_at\" = $12,\"modified_by\" = $13 WHERE id = $1", "describe": { "columns": [], "parameters": { @@ -15,11 +15,12 @@ "Text", "Timestamp", "Text", + "Bool", "Timestamp", "Int8" ] }, "nullable": [] }, - "hash": "367b0ad00e37d5e0a001c8a2ff31db849ac5c6955d2d8e73938a37449d914bae" + "hash": "6b1506441fd24aff832ee8ee9edb6d8423cfc61bf59ceaf0364c07ddde47127e" } diff --git a/.sqlx/query-d92e63295aa9d2302d5e4a8d205b35ab98de051c9aa9e5932b2c03a6118c2587.json b/.sqlx/query-780d66e4628d13c6c2f489cc87c7358945b93628104ac57aac207b8ec74be08a.json similarity index 66% rename from .sqlx/query-d92e63295aa9d2302d5e4a8d205b35ab98de051c9aa9e5932b2c03a6118c2587.json rename to .sqlx/query-780d66e4628d13c6c2f489cc87c7358945b93628104ac57aac207b8ec74be08a.json index b459ef3b3..84dc8a364 100644 --- a/.sqlx/query-d92e63295aa9d2302d5e4a8d205b35ab98de051c9aa9e5932b2c03a6118c2587.json +++ b/.sqlx/query-780d66e4628d13c6c2f489cc87c7358945b93628104ac57aac207b8ec74be08a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE \"proxy\" SET \"name\" = $2,\"address\" = $3,\"port\" = $4,\"connected_at\" = $5,\"disconnected_at\" = $6,\"version\" = $7,\"certificate\" = $8,\"certificate_expiry\" = $9,\"modified_at\" = $10,\"modified_by\" = $11 WHERE id = $1", + "query": "UPDATE \"proxy\" SET \"name\" = $2,\"address\" = $3,\"port\" = $4,\"connected_at\" = $5,\"disconnected_at\" = $6,\"version\" = $7,\"enabled\" = $8,\"certificate\" = $9,\"certificate_expiry\" = $10,\"modified_at\" = $11,\"modified_by\" = $12 WHERE id = $1", "describe": { "columns": [], "parameters": { @@ -12,6 +12,7 @@ "Timestamp", "Timestamp", "Text", + "Bool", "Text", "Timestamp", "Timestamp", @@ -20,5 +21,5 @@ }, "nullable": [] }, - "hash": "d92e63295aa9d2302d5e4a8d205b35ab98de051c9aa9e5932b2c03a6118c2587" + "hash": "780d66e4628d13c6c2f489cc87c7358945b93628104ac57aac207b8ec74be08a" } diff --git a/.sqlx/query-82bc29121a5a1426fd172de00194b73747f162221c6c84e7148e1aff3b5c53bc.json b/.sqlx/query-8d21a38672059e820d355590df83c1c9c5f75956f8b7c2a1a235189e1583a599.json similarity index 70% rename from .sqlx/query-82bc29121a5a1426fd172de00194b73747f162221c6c84e7148e1aff3b5c53bc.json rename to .sqlx/query-8d21a38672059e820d355590df83c1c9c5f75956f8b7c2a1a235189e1583a599.json index afdb6a5d2..592a365bb 100644 --- a/.sqlx/query-82bc29121a5a1426fd172de00194b73747f162221c6c84e7148e1aff3b5c53bc.json +++ b/.sqlx/query-8d21a38672059e820d355590df83c1c9c5f75956f8b7c2a1a235189e1583a599.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO \"gateway\" (\"location_id\",\"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"certificate\",\"certificate_expiry\",\"version\",\"modified_at\",\"modified_by\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING id", + "query": "INSERT INTO \"gateway\" (\"location_id\",\"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"certificate\",\"certificate_expiry\",\"version\",\"enabled\",\"modified_at\",\"modified_by\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING id", "describe": { "columns": [ { @@ -20,6 +20,7 @@ "Text", "Timestamp", "Text", + "Bool", "Timestamp", "Int8" ] @@ -28,5 +29,5 @@ false ] }, - "hash": "82bc29121a5a1426fd172de00194b73747f162221c6c84e7148e1aff3b5c53bc" + "hash": "8d21a38672059e820d355590df83c1c9c5f75956f8b7c2a1a235189e1583a599" } diff --git a/.sqlx/query-10a410ef8113994c19f1fd3c2c77fcb43d3be0bc795a3839207a11c9f24bf670.json b/.sqlx/query-938c250b35e5b2b46cff9efbe41fce3100fe0ff1a86be48b7a22b58ef3da5bf1.json similarity index 63% rename from .sqlx/query-10a410ef8113994c19f1fd3c2c77fcb43d3be0bc795a3839207a11c9f24bf670.json rename to .sqlx/query-938c250b35e5b2b46cff9efbe41fce3100fe0ff1a86be48b7a22b58ef3da5bf1.json index 996b13d99..989d93d99 100644 --- a/.sqlx/query-10a410ef8113994c19f1fd3c2c77fcb43d3be0bc795a3839207a11c9f24bf670.json +++ b/.sqlx/query-938c250b35e5b2b46cff9efbe41fce3100fe0ff1a86be48b7a22b58ef3da5bf1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO \"proxy\" (\"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"version\",\"certificate\",\"certificate_expiry\",\"modified_at\",\"modified_by\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) RETURNING id", + "query": "INSERT INTO \"proxy\" (\"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"version\",\"enabled\",\"certificate\",\"certificate_expiry\",\"modified_at\",\"modified_by\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING id", "describe": { "columns": [ { @@ -17,6 +17,7 @@ "Timestamp", "Timestamp", "Text", + "Bool", "Text", "Timestamp", "Timestamp", @@ -27,5 +28,5 @@ false ] }, - "hash": "10a410ef8113994c19f1fd3c2c77fcb43d3be0bc795a3839207a11c9f24bf670" + "hash": "938c250b35e5b2b46cff9efbe41fce3100fe0ff1a86be48b7a22b58ef3da5bf1" } diff --git a/.sqlx/query-a32d8d7edbd9e9054e082d1e98ec4d6f2ba85dcd98e84d177c3d3148143ad9fe.json b/.sqlx/query-a32d8d7edbd9e9054e082d1e98ec4d6f2ba85dcd98e84d177c3d3148143ad9fe.json index 196d30935..bda48d455 100644 --- a/.sqlx/query-a32d8d7edbd9e9054e082d1e98ec4d6f2ba85dcd98e84d177c3d3148143ad9fe.json +++ b/.sqlx/query-a32d8d7edbd9e9054e082d1e98ec4d6f2ba85dcd98e84d177c3d3148143ad9fe.json @@ -65,21 +65,26 @@ }, { "ordinal": 12, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 13, "name": "modified_by_firstname", "type_info": "Text" }, { - "ordinal": 13, + "ordinal": 14, "name": "modified_by_lastname", "type_info": "Text" }, { - "ordinal": 14, + "ordinal": 15, "name": "connected!", "type_info": "Bool" }, { - "ordinal": 15, + "ordinal": 16, "name": "location_name", "type_info": "Text" } @@ -104,6 +109,7 @@ false, false, false, + false, null, false ] diff --git a/.sqlx/query-a41787c8c8307414165ab23ef96d82a34d3bfa4364cbe9b8368e71445bc20877.json b/.sqlx/query-a41787c8c8307414165ab23ef96d82a34d3bfa4364cbe9b8368e71445bc20877.json index 4c6244cf5..a7235190f 100644 --- a/.sqlx/query-a41787c8c8307414165ab23ef96d82a34d3bfa4364cbe9b8368e71445bc20877.json +++ b/.sqlx/query-a41787c8c8307414165ab23ef96d82a34d3bfa4364cbe9b8368e71445bc20877.json @@ -57,6 +57,11 @@ "ordinal": 10, "name": "certificate", "type_info": "Text" + }, + { + "ordinal": 11, + "name": "enabled", + "type_info": "Bool" } ], "parameters": { @@ -76,7 +81,8 @@ true, false, false, - true + true, + false ] }, "hash": "a41787c8c8307414165ab23ef96d82a34d3bfa4364cbe9b8368e71445bc20877" diff --git a/.sqlx/query-fd05345c81860068b5013a07ca9187c2b96d0319ba0604c0313055eb1b2eea31.json b/.sqlx/query-bcb405dc3159cd72c5ccebf29bf4b6163ee0e324cd95cbf3e32d025b5ba7fcbb.json similarity index 81% rename from .sqlx/query-fd05345c81860068b5013a07ca9187c2b96d0319ba0604c0313055eb1b2eea31.json rename to .sqlx/query-bcb405dc3159cd72c5ccebf29bf4b6163ee0e324cd95cbf3e32d025b5ba7fcbb.json index 023832a35..67badd0ec 100644 --- a/.sqlx/query-fd05345c81860068b5013a07ca9187c2b96d0319ba0604c0313055eb1b2eea31.json +++ b/.sqlx/query-bcb405dc3159cd72c5ccebf29bf4b6163ee0e324cd95cbf3e32d025b5ba7fcbb.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"version\",\"certificate\",\"certificate_expiry\",\"modified_at\",\"modified_by\" FROM \"proxy\" WHERE id = $1", + "query": "SELECT id, \"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"version\",\"enabled\",\"certificate\",\"certificate_expiry\",\"modified_at\",\"modified_by\" FROM \"proxy\" WHERE id = $1", "describe": { "columns": [ { @@ -40,21 +40,26 @@ }, { "ordinal": 7, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 8, "name": "certificate", "type_info": "Text" }, { - "ordinal": 8, + "ordinal": 9, "name": "certificate_expiry", "type_info": "Timestamp" }, { - "ordinal": 9, + "ordinal": 10, "name": "modified_at", "type_info": "Timestamp" }, { - "ordinal": 10, + "ordinal": 11, "name": "modified_by", "type_info": "Int8" } @@ -72,11 +77,12 @@ true, true, true, + false, true, true, false, false ] }, - "hash": "fd05345c81860068b5013a07ca9187c2b96d0319ba0604c0313055eb1b2eea31" + "hash": "bcb405dc3159cd72c5ccebf29bf4b6163ee0e324cd95cbf3e32d025b5ba7fcbb" } diff --git a/.sqlx/query-c9ae7aea043b822a489deb2dcccaa834ecbdc4990149dd6ba61f34a96cbac235.json b/.sqlx/query-beffd1aad66ce9d9a179f14e224d9ca63f0c0aa378460bd344f7a7daa8985bad.json similarity index 84% rename from .sqlx/query-c9ae7aea043b822a489deb2dcccaa834ecbdc4990149dd6ba61f34a96cbac235.json rename to .sqlx/query-beffd1aad66ce9d9a179f14e224d9ca63f0c0aa378460bd344f7a7daa8985bad.json index 55befdedf..9f496db05 100644 --- a/.sqlx/query-c9ae7aea043b822a489deb2dcccaa834ecbdc4990149dd6ba61f34a96cbac235.json +++ b/.sqlx/query-beffd1aad66ce9d9a179f14e224d9ca63f0c0aa378460bd344f7a7daa8985bad.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, \"location_id\",\"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"certificate\",\"certificate_expiry\",\"version\",\"modified_at\",\"modified_by\" FROM \"gateway\" WHERE id = $1", + "query": "SELECT id, \"location_id\",\"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"certificate\",\"certificate_expiry\",\"version\",\"enabled\",\"modified_at\",\"modified_by\" FROM \"gateway\" WHERE id = $1", "describe": { "columns": [ { @@ -55,11 +55,16 @@ }, { "ordinal": 10, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 11, "name": "modified_at", "type_info": "Timestamp" }, { - "ordinal": 11, + "ordinal": 12, "name": "modified_by", "type_info": "Int8" } @@ -81,8 +86,9 @@ true, true, false, + false, false ] }, - "hash": "c9ae7aea043b822a489deb2dcccaa834ecbdc4990149dd6ba61f34a96cbac235" + "hash": "beffd1aad66ce9d9a179f14e224d9ca63f0c0aa378460bd344f7a7daa8985bad" } diff --git a/.sqlx/query-f14171d837b8ac91e765e9b86153186ac78bf78ce3cfc5af7441d84be52749d2.json b/.sqlx/query-f14171d837b8ac91e765e9b86153186ac78bf78ce3cfc5af7441d84be52749d2.json new file mode 100644 index 000000000..3b54687be --- /dev/null +++ b/.sqlx/query-f14171d837b8ac91e765e9b86153186ac78bf78ce3cfc5af7441d84be52749d2.json @@ -0,0 +1,86 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, \"name\",\"address\",\"port\",\"connected_at\",\"disconnected_at\",\"version\",\"enabled\",\"certificate\",\"certificate_expiry\",\"modified_at\",\"modified_by\" FROM \"proxy\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "address", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "port", + "type_info": "Int4" + }, + { + "ordinal": 4, + "name": "connected_at", + "type_info": "Timestamp" + }, + { + "ordinal": 5, + "name": "disconnected_at", + "type_info": "Timestamp" + }, + { + "ordinal": 6, + "name": "version", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "certificate", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "certificate_expiry", + "type_info": "Timestamp" + }, + { + "ordinal": 10, + "name": "modified_at", + "type_info": "Timestamp" + }, + { + "ordinal": 11, + "name": "modified_by", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + true, + true, + true, + false, + true, + true, + false, + false + ] + }, + "hash": "f14171d837b8ac91e765e9b86153186ac78bf78ce3cfc5af7441d84be52749d2" +} diff --git a/.sqlx/query-f22ab4ecf7a0146f3280290f55e9e989d80a8997c465cffadcc8dd6e587eb567.json b/.sqlx/query-f22ab4ecf7a0146f3280290f55e9e989d80a8997c465cffadcc8dd6e587eb567.json index de091db82..1beb4d2bb 100644 --- a/.sqlx/query-f22ab4ecf7a0146f3280290f55e9e989d80a8997c465cffadcc8dd6e587eb567.json +++ b/.sqlx/query-f22ab4ecf7a0146f3280290f55e9e989d80a8997c465cffadcc8dd6e587eb567.json @@ -65,21 +65,26 @@ }, { "ordinal": 12, + "name": "enabled", + "type_info": "Bool" + }, + { + "ordinal": 13, "name": "modified_by_firstname", "type_info": "Text" }, { - "ordinal": 13, + "ordinal": 14, "name": "modified_by_lastname", "type_info": "Text" }, { - "ordinal": 14, + "ordinal": 15, "name": "connected!", "type_info": "Bool" }, { - "ordinal": 15, + "ordinal": 16, "name": "location_name", "type_info": "Text" } @@ -102,6 +107,7 @@ false, false, false, + false, null, false ] diff --git a/Cargo.lock b/Cargo.lock index c27e0c4f8..86ddd2862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1827,9 +1827,9 @@ dependencies = [ [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags 2.11.0", "objc2", @@ -3129,9 +3129,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.90" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -3317,13 +3317,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ "bitflags 2.11.0", "libc", - "redox_syscall 0.7.2", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -3783,9 +3784,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] @@ -4418,18 +4419,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", @@ -4438,9 +4439,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -4475,6 +4476,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "png" version = "0.18.1" @@ -4695,12 +4702,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" -dependencies = [ - "num-traits", -] +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" [[package]] name = "qoi" @@ -4962,9 +4966,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d94dd2f7cd932d4dc02cc8b2b50dfd38bd079a4e5d79198b99743d7fcf9a4b4" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ "bitflags 2.11.0", ] @@ -7016,9 +7020,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -7029,9 +7033,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.63" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a89f4650b770e4521aa6573724e2aed4704372151bd0de9d16a3bbabb87441a" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -7043,9 +7047,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7053,9 +7057,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -7066,9 +7070,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -7122,9 +7126,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.90" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705eceb4ce901230f8625bd1d665128056ccbe4b7408faa625eec1ba80f59a97" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -7788,18 +7792,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", @@ -7896,9 +7900,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" diff --git a/crates/defguard_certs/src/lib.rs b/crates/defguard_certs/src/lib.rs index 1f458ac39..f92c10f96 100644 --- a/crates/defguard_certs/src/lib.rs +++ b/crates/defguard_certs/src/lib.rs @@ -39,7 +39,7 @@ impl CertificateAuthority<'_> { let cert_der = CertificateDer::from_pem_slice(ca_cert_pem.as_bytes()) .map_err(|e| CertificateError::ParsingError(e.to_string()))?; let issuer = Issuer::from_ca_cert_der(&cert_der, key_pair)?; - Ok(CertificateAuthority { issuer, cert_der }) + Ok(Self { issuer, cert_der }) } pub fn from_cert_der_key_pair( @@ -49,7 +49,7 @@ impl CertificateAuthority<'_> { let key_pair = KeyPair::try_from(ca_key_pair)?; let cert_der = CertificateDer::from(ca_cert_der.to_vec()); let issuer = Issuer::from_ca_cert_der(&cert_der, key_pair)?; - Ok(CertificateAuthority { issuer, cert_der }) + Ok(Self { issuer, cert_der }) } pub fn from_key_cert_params( @@ -59,7 +59,7 @@ impl CertificateAuthority<'_> { let cert = ca_cert_params.self_signed(&key_pair)?; let issuer = Issuer::new(ca_cert_params, key_pair); let cert_der = cert.der().clone(); - Ok(CertificateAuthority { issuer, cert_der }) + Ok(Self { issuer, cert_der }) } pub fn new( @@ -86,7 +86,7 @@ impl CertificateAuthority<'_> { let ca_key_pair = KeyPair::generate()?; - CertificateAuthority::from_key_cert_params(ca_key_pair, ca_params) + Self::from_key_cert_params(ca_key_pair, ca_params) } pub fn sign_csr(&self, csr: &Csr) -> Result { diff --git a/crates/defguard_common/src/db/models/gateway.rs b/crates/defguard_common/src/db/models/gateway.rs index 8de3a5be4..c0de1bee3 100644 --- a/crates/defguard_common/src/db/models/gateway.rs +++ b/crates/defguard_common/src/db/models/gateway.rs @@ -19,6 +19,7 @@ pub struct Gateway { pub certificate: Option, pub certificate_expiry: Option, pub version: Option, + pub enabled: bool, pub modified_at: NaiveDateTime, pub modified_by: Id, } @@ -63,6 +64,7 @@ impl Gateway { certificate: None, certificate_expiry: None, version: None, + enabled: true, modified_by, modified_at, } diff --git a/crates/defguard_common/src/db/models/proxy.rs b/crates/defguard_common/src/db/models/proxy.rs index 7685a6538..bffa67a29 100644 --- a/crates/defguard_common/src/db/models/proxy.rs +++ b/crates/defguard_common/src/db/models/proxy.rs @@ -20,6 +20,7 @@ pub struct Proxy { pub connected_at: Option, pub disconnected_at: Option, pub version: Option, + pub enabled: bool, pub certificate: Option, pub certificate_expiry: Option, pub modified_at: NaiveDateTime, @@ -58,6 +59,7 @@ impl Proxy { certificate: None, certificate_expiry: None, version: None, + enabled: true, modified_by, modified_at: Utc::now().naive_utc(), } @@ -65,6 +67,16 @@ impl Proxy { } impl Proxy { + /// Fetch all enabled Proxies. + pub async fn all_enabled<'e, E>(executor: E) -> Result, sqlx::Error> + where + E: sqlx::PgExecutor<'e>, + { + sqlx::query_as!(Self, "SELECT * FROM proxy WHERE enabled") + .fetch_all(executor) + .await + } + pub async fn find_by_address_port( pool: &PgPool, address: &str, diff --git a/crates/defguard_common/src/types/proxy.rs b/crates/defguard_common/src/types/proxy.rs index 0a4d1b5d0..4b39980ce 100644 --- a/crates/defguard_common/src/types/proxy.rs +++ b/crates/defguard_common/src/types/proxy.rs @@ -20,6 +20,7 @@ pub struct ProxyInfo { pub connected_at: Option, pub disconnected_at: Option, pub version: Option, + pub enabled: bool, pub certificate: Option, pub certificate_expiry: Option, pub modified_at: NaiveDateTime, diff --git a/crates/defguard_core/src/enterprise/handlers/acl/alias.rs b/crates/defguard_core/src/enterprise/handlers/acl/alias.rs index 73a9e86f4..e314b3e0a 100644 --- a/crates/defguard_core/src/enterprise/handlers/acl/alias.rs +++ b/crates/defguard_core/src/enterprise/handlers/acl/alias.rs @@ -350,7 +350,7 @@ pub(crate) async fn delete_acl_alias( _admin: AdminRole, State(appstate): State, session: SessionInfo, - Path(id): Path, + Path(id): Path, ) -> ApiResult { debug!("User {} deleting ACL alias {id}", session.user.username); AclAlias::delete_from_api(&appstate.pool, id) diff --git a/crates/defguard_core/src/handlers/gateway.rs b/crates/defguard_core/src/handlers/gateway.rs index fd3a488db..828c88d19 100644 --- a/crates/defguard_core/src/handlers/gateway.rs +++ b/crates/defguard_core/src/handlers/gateway.rs @@ -31,6 +31,7 @@ pub struct GatewayInfo { pub certificate: Option, pub certificate_expiry: Option, pub version: Option, + pub enabled: bool, pub modified_at: NaiveDateTime, pub modified_by: Id, pub modified_by_firstname: String, @@ -87,6 +88,7 @@ impl GatewayInfo { #[serde(deny_unknown_fields)] pub struct GatewayUpdateData { pub name: String, + pub enabled: bool, } #[utoipa::path( @@ -131,7 +133,7 @@ pub(crate) async fn gateway_list( ) )] pub(crate) async fn gateway_details( - Path(gateway_id): Path, + Path(gateway_id): Path, _role: AdminRole, session: SessionInfo, State(appstate): State, @@ -171,7 +173,7 @@ pub(crate) async fn gateway_details( )] pub(crate) async fn update_gateway( _role: AdminRole, - Path(gateway_id): Path, + Path(gateway_id): Path, State(appstate): State, session: SessionInfo, context: ApiRequestContext, @@ -199,6 +201,7 @@ pub(crate) async fn update_gateway( let before = gateway.clone(); gateway.name = data.name; + gateway.enabled = data.enabled; gateway.save(&appstate.pool).await?; info!( @@ -234,7 +237,7 @@ pub(crate) async fn update_gateway( )] pub(crate) async fn delete_gateway( _role: AdminRole, - Path(gateway_id): Path, + Path(gateway_id): Path, State(appstate): State, session: SessionInfo, context: ApiRequestContext, diff --git a/crates/defguard_core/src/handlers/location_stats.rs b/crates/defguard_core/src/handlers/location_stats.rs index 0ebbc5bbb..af0929ea9 100644 --- a/crates/defguard_core/src/handlers/location_stats.rs +++ b/crates/defguard_core/src/handlers/location_stats.rs @@ -2,11 +2,14 @@ use std::str::FromStr; use axum::extract::{Path, Query, State}; use chrono::{DateTime, NaiveDateTime, TimeDelta, Utc}; -use defguard_common::db::models::{ - WireguardNetwork, - wireguard::{ - DateTimeAggregation, LocationConnectedNetworkDevice, LocationConnectedUserStats, - WireguardNetworkStats, networks_stats, +use defguard_common::db::{ + Id, + models::{ + WireguardNetwork, + wireguard::{ + DateTimeAggregation, LocationConnectedNetworkDevice, LocationConnectedUserStats, + WireguardNetworkStats, networks_stats, + }, }, }; use reqwest::StatusCode; @@ -73,7 +76,7 @@ pub(crate) async fn locations_overview_stats( pub(crate) async fn location_stats( _role: AdminRole, State(appstate): State, - Path(network_id): Path, + Path(network_id): Path, Query(query_from): Query, ) -> ApiResult { debug!("Displaying WireGuard network stats for location {network_id}"); @@ -99,7 +102,7 @@ pub(crate) async fn location_stats( pub(crate) async fn location_connected_users( _role: AdminRole, State(appstate): State, - Path(location_id): Path, + Path(location_id): Path, Query(query_from): Query, pagination: Query, ) -> PaginatedApiResult { @@ -140,7 +143,7 @@ pub(crate) async fn location_connected_users( pub(crate) async fn location_connected_network_devices( _role: AdminRole, State(appstate): State, - Path(location_id): Path, + Path(location_id): Path, Query(query_from): Query, pagination: Query, ) -> PaginatedApiResult { diff --git a/crates/defguard_core/src/handlers/network_devices.rs b/crates/defguard_core/src/handlers/network_devices.rs index 10a3fa94e..06bc6e98e 100644 --- a/crates/defguard_core/src/handlers/network_devices.rs +++ b/crates/defguard_core/src/handlers/network_devices.rs @@ -113,7 +113,7 @@ impl NetworkDeviceInfo { pub async fn download_network_device_config( _admin_role: AdminRole, State(appstate): State, - Path(device_id): Path, + Path(device_id): Path, ) -> Result { debug!("Creating a WireGuard config for network device {device_id}."); let device = @@ -146,7 +146,7 @@ pub async fn download_network_device_config( pub async fn get_network_device( _admin_role: AdminRole, session: SessionInfo, - Path(device_id): Path, + Path(device_id): Path, State(appstate): State, ) -> ApiResult { debug!( @@ -234,7 +234,7 @@ impl IpAvailabilityCheckResult { pub(crate) async fn check_ip_availability( _admin_role: AdminRole, - Path(network_id): Path, + Path(network_id): Path, State(appstate): State, Json(check): Json, ) -> ApiResult { @@ -310,7 +310,7 @@ pub(crate) async fn check_ip_availability( pub(crate) async fn find_available_ips( _admin_role: AdminRole, - Path(network_id): Path, + Path(network_id): Path, State(appstate): State, ) -> ApiResult { let network = WireguardNetwork::find_by_id(&appstate.pool, network_id) @@ -481,7 +481,7 @@ pub(crate) async fn start_network_device_setup( pub(crate) async fn start_network_device_setup_for_device( _admin_role: AdminRole, session: SessionInfo, - Path(device_id): Path, + Path(device_id): Path, State(appstate): State, ) -> ApiResult { debug!( @@ -676,7 +676,7 @@ pub async fn modify_network_device( _admin_role: AdminRole, session: SessionInfo, context: ApiRequestContext, - Path(device_id): Path, + Path(device_id): Path, State(appstate): State, Json(data): Json, ) -> ApiResult { diff --git a/crates/defguard_core/src/handlers/proxy.rs b/crates/defguard_core/src/handlers/proxy.rs index 01810f473..cf862b119 100644 --- a/crates/defguard_core/src/handlers/proxy.rs +++ b/crates/defguard_core/src/handlers/proxy.rs @@ -4,7 +4,7 @@ use axum::{ }; use chrono::Utc; use defguard_common::{ - db::models::proxy::Proxy, + db::{Id, models::proxy::Proxy}, types::proxy::{ProxyControlMessage, ProxyInfo}, }; use reqwest::StatusCode; @@ -21,6 +21,7 @@ use crate::{ #[derive(Serialize, Deserialize, ToSchema)] pub struct ProxyUpdateData { pub name: String, + pub enabled: bool, } #[utoipa::path( @@ -65,7 +66,7 @@ pub(crate) async fn proxy_list( ) )] pub(crate) async fn proxy_details( - Path(proxy_id): Path, + Path(proxy_id): Path, _role: AdminRole, session: SessionInfo, State(appstate): State, @@ -105,7 +106,7 @@ pub(crate) async fn proxy_details( )] pub(crate) async fn update_proxy( _role: AdminRole, - Path(proxy_id): Path, + Path(proxy_id): Path, State(appstate): State, session: SessionInfo, context: ApiRequestContext, @@ -121,6 +122,21 @@ pub(crate) async fn update_proxy( let before = proxy.clone(); proxy.name = data.name; + if proxy.enabled != data.enabled { + if data.enabled { + // TODO: spawn Proxy + } else if let Err(err) = appstate + .proxy_control_tx + .send(ProxyControlMessage::ShutdownConnection(proxy.id)) + .await + { + error!( + "Failed to shutdown Proxy {}, it may be disconnected: {err:?}", + proxy.id + ); + } + } + proxy.enabled = data.enabled; proxy.modified_by = session.user.id; proxy.modified_at = Utc::now().naive_utc(); proxy.save(&appstate.pool).await?; @@ -156,7 +172,7 @@ pub(crate) async fn update_proxy( )] pub(crate) async fn delete_proxy( _role: AdminRole, - Path(proxy_id): Path, + Path(proxy_id): Path, State(appstate): State, session: SessionInfo, context: ApiRequestContext, @@ -176,7 +192,7 @@ pub(crate) async fn delete_proxy( .await { error!( - "Error shutting down proxy {}, it may be disconnected: {err:?}", + "Failed to purge Proxy {}, it may be disconnected: {err:?}", proxy.id ); } diff --git a/crates/defguard_core/src/handlers/settings.rs b/crates/defguard_core/src/handlers/settings.rs index 16fdbfb3b..bb43703df 100644 --- a/crates/defguard_core/src/handlers/settings.rs +++ b/crates/defguard_core/src/handlers/settings.rs @@ -3,9 +3,12 @@ use axum::{ extract::{Json, Path, State}, http::StatusCode, }; -use defguard_common::db::models::{ - Settings, SettingsEssentials, - settings::{LdapSyncStatus, SettingsPatch, update_current_settings}, +use defguard_common::db::{ + Id, + models::{ + Settings, SettingsEssentials, + settings::{LdapSyncStatus, SettingsPatch, update_current_settings}, + }, }; use sqlx::PgPool; use struct_patch::Patch; @@ -84,7 +87,7 @@ pub async fn get_settings_essentials(Extension(pool): Extension) -> ApiR pub async fn set_default_branding( _admin: AdminRole, State(appstate): State, - Path(_id): Path, // TODO: check with front-end and remove. + Path(_id): Path, // TODO: check with front-end and remove. session: SessionInfo, context: ApiRequestContext, ) -> ApiResult { diff --git a/crates/defguard_core/src/handlers/webhooks.rs b/crates/defguard_core/src/handlers/webhooks.rs index c13f28ccb..24c780b55 100644 --- a/crates/defguard_core/src/handlers/webhooks.rs +++ b/crates/defguard_core/src/handlers/webhooks.rs @@ -2,6 +2,7 @@ use axum::{ extract::{Json, Path, State}, http::StatusCode, }; +use defguard_common::db::Id; use super::{ApiResponse, ApiResult, WebHookData}; use crate::{ @@ -46,7 +47,7 @@ pub async fn list_webhooks(_admin: AdminRole, State(appstate): State) pub async fn get_webhook( _admin: AdminRole, State(appstate): State, - Path(id): Path, + Path(id): Path, ) -> ApiResult { match WebHook::find_by_id(&appstate.pool, id).await? { Some(webhook) => Ok(ApiResponse::json(webhook, StatusCode::OK)), @@ -59,7 +60,7 @@ pub async fn change_webhook( session: SessionInfo, context: ApiRequestContext, State(appstate): State, - Path(id): Path, + Path(id): Path, Json(data): Json, ) -> ApiResult { debug!("User {} updating webhook {id}", session.user.username); @@ -97,7 +98,7 @@ pub async fn delete_webhook( State(appstate): State, session: SessionInfo, context: ApiRequestContext, - Path(id): Path, + Path(id): Path, ) -> ApiResult { debug!("User {} deleting webhook {id}", session.user.username); let status = match WebHook::find_by_id(&appstate.pool, id).await? { @@ -125,7 +126,7 @@ pub async fn change_enabled( session: SessionInfo, context: ApiRequestContext, State(appstate): State, - Path(id): Path, + Path(id): Path, Json(data): Json, ) -> ApiResult { debug!( diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index 3a8adda26..47b3b30eb 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -296,7 +296,7 @@ async fn find_network(id: Id, pool: &PgPool) -> Result, Web )] pub(crate) async fn modify_network( _role: AdminRole, - Path(network_id): Path, + Path(network_id): Path, State(appstate): State, session: SessionInfo, context: ApiRequestContext, @@ -422,7 +422,7 @@ pub(crate) async fn modify_network( )] pub(crate) async fn delete_network( _role: AdminRole, - Path(network_id): Path, + Path(network_id): Path, State(appstate): State, session: SessionInfo, context: ApiRequestContext, @@ -524,7 +524,7 @@ pub(crate) async fn list_networks(_role: AdminRole, State(appstate): State, + Path(network_id): Path, _role: AdminRole, State(appstate): State, ) -> ApiResult { @@ -557,7 +557,7 @@ pub(crate) async fn network_details( /// # Returns /// Returns `Vec` for requested network. pub(crate) async fn gateway_status( - Path(network_id): Path, + Path(network_id): Path, _role: AdminRole, State(appstate): State, ) -> ApiResult { @@ -646,7 +646,7 @@ pub(crate) async fn add_user_devices( _role: AdminRole, session: SessionInfo, State(appstate): State, - Path(network_id): Path, + Path(network_id): Path, Json(request_data): Json, ) -> ApiResult { let mapped_devices = request_data.devices.clone(); @@ -957,7 +957,7 @@ pub(crate) async fn modify_device( _can_manage_devices: CanManageDevices, session: SessionInfo, context: ApiRequestContext, - Path(device_id): Path, + Path(device_id): Path, State(appstate): State, Json(data): Json, ) -> ApiResult { @@ -1074,7 +1074,7 @@ pub(crate) async fn modify_device( )] pub(crate) async fn get_device( session: SessionInfo, - Path(device_id): Path, + Path(device_id): Path, State(appstate): State, ) -> ApiResult { debug!("Retrieving device with id: {device_id}"); @@ -1112,7 +1112,7 @@ pub(crate) async fn delete_device( _can_manage_devices: CanManageDevices, session: SessionInfo, context: ApiRequestContext, - Path(device_id): Path, + Path(device_id): Path, State(appstate): State, ) -> ApiResult { // bind username to a variable for easier reference diff --git a/crates/defguard_core/tests/integration/api/gateway.rs b/crates/defguard_core/tests/integration/api/gateway.rs index f6c253102..e65b21ef8 100644 --- a/crates/defguard_core/tests/integration/api/gateway.rs +++ b/crates/defguard_core/tests/integration/api/gateway.rs @@ -52,6 +52,7 @@ async fn test_gateway_crud(_: PgPoolOptions, options: PgConnectOptions) { .put(format!("/api/v1/gateway/{}", gateway_1.id)) .json(&json!({ "name": "gateway-updated", + "enabled": true, })) .send() .await; @@ -112,6 +113,7 @@ async fn test_gateway_endpoints_require_admin(_: PgPoolOptions, options: PgConne .put(format!("/api/v1/gateway/{}", gateway.id)) .json(&json!({ "name": "gateway-updated", + "enabled": true, })) .send() .await; diff --git a/crates/defguard_core/tests/integration/api/proxy.rs b/crates/defguard_core/tests/integration/api/proxy.rs index 175eadb31..8e71c7509 100644 --- a/crates/defguard_core/tests/integration/api/proxy.rs +++ b/crates/defguard_core/tests/integration/api/proxy.rs @@ -55,6 +55,7 @@ async fn test_proxy_update(_: PgPoolOptions, options: PgConnectOptions) { // Modify name let data = ProxyUpdateData { name: "modified".to_string(), + enabled: true, }; let response = client .put(format!("/api/v1/proxy/{}", proxy.id)) diff --git a/crates/defguard_gateway_manager/src/lib.rs b/crates/defguard_gateway_manager/src/lib.rs index 8dc237830..e9d67d5cb 100644 --- a/crates/defguard_gateway_manager/src/lib.rs +++ b/crates/defguard_gateway_manager/src/lib.rs @@ -86,25 +86,7 @@ impl GatewayManager { TriggerOperation::Insert => { if let Some(new) = gateway_notification.new { let id = new.id; - let abort_handle = - self.run_handler(new, Arc::clone(&self.clients), certs_rx.clone())?; - abort_handles.insert(id, abort_handle); - } - } - TriggerOperation::Update => { - if let (Some(old), Some(new)) = - (gateway_notification.old, gateway_notification.new) - { - if old.address == new.address && old.port == new.port { - debug!( - "Gateway URL didn't change. Keeping the current gateway handler" - ); - } else if let Some(abort_handle) = abort_handles.remove(&old.id) { - info!( - "Aborting connection to {old}, it has changed in the database" - ); - abort_handle.abort(); - let id = new.id; + if new.enabled { let abort_handle = self.run_handler( new, Arc::clone(&self.clients), @@ -112,16 +94,47 @@ impl GatewayManager { )?; abort_handles.insert(id, abort_handle); } else { - warn!("Cannot find {old} on the list of connected gateways"); + debug!("New Gateway is disabled, so it won't be handled"); } } } + TriggerOperation::Update => { + let (Some(old), Some(new)) = + (gateway_notification.old, gateway_notification.new) + else { + continue; + }; + if old.address == new.address + && old.port == new.port + && old.enabled == new.enabled + { + debug!("Gateway address/port/state didn't change"); + continue; + } + if let Some(abort_handle) = abort_handles.remove(&old.id) { + info!( + "Aborting connection to Gateway {old}, it has changed in the \ + database" + ); + abort_handle.abort(); + } else if old.enabled { + warn!("Cannot find Gateway {old} on the list of connected gateways"); + } + if new.enabled { + let id = new.id; + let abort_handle = + self.run_handler(new, Arc::clone(&self.clients), certs_rx.clone())?; + abort_handles.insert(id, abort_handle); + } else { + debug!("Updated Gateway is disabled, so it won't be handled"); + } + } TriggerOperation::Delete => { let Some(old) = gateway_notification.old else { continue; }; - // Send purge request to the gateway. + // Send purge request to Gateway. let maybe_client = { self.clients .lock() @@ -130,26 +143,27 @@ impl GatewayManager { }; if let Some(mut client) = maybe_client { - debug!("Sending purge request to gateway {old}"); + debug!("Sending purge request to Gateway {old}"); if let Err(err) = client.purge(Request::new(())).await { - error!("Error sending purge request to gateway {old}: {err}"); + error!("Error sending purge request to Gateway {old}: {err}"); } else { - info!("Sent purge request to gateway {old}"); + info!("Sent purge request to Gateway {old}"); } } else { warn!( - "Cannot find gRPC client for gateway {old}, won't send purge request" + "Cannot find gRPC client for Gateway {old}; skipping purge request" ); } // Kill the `GatewayHandler` and the connection. if let Some(abort_handle) = abort_handles.remove(&old.id) { info!( - "Aborting connection to gateway {old}, it has disappeard from the database" + "Aborting connection to Gateway {old}, it has disappeard from the \ + database" ); abort_handle.abort(); - } else { - warn!("Cannot find abort handle for gateway {old}"); + } else if old.enabled { + warn!("Cannot find Gateway {old} on the list of connected gateways"); } } }, diff --git a/crates/defguard_proxy_manager/src/handler.rs b/crates/defguard_proxy_manager/src/handler.rs index d8c045c67..cec367cf6 100644 --- a/crates/defguard_proxy_manager/src/handler.rs +++ b/crates/defguard_proxy_manager/src/handler.rs @@ -69,7 +69,7 @@ use crate::{ servers::{EnrollmentServer, PasswordResetServer}, }; -static VERSION_ZERO: Version = Version::new(0, 0, 0); +const VERSION_ZERO: Version = Version::new(0, 0, 0); type ShutdownReceiver = tokio::sync::oneshot::Receiver; @@ -170,9 +170,9 @@ impl ProxyHandler { fn endpoint(&self) -> Result { let mut url = self.url.clone(); - // Using http here because the connector upgrades to TLS internally. + // Using HTTP here because the connector upgrades to TLS internally. url.set_scheme("http").map_err(|()| { - ProxyError::UrlError(format!("Failed to set http scheme on URL {url}")) + ProxyError::UrlError(format!("Failed to set HTTP scheme on URL {url}")) })?; let endpoint = Endpoint::from_shared(url.to_string())?; let endpoint = endpoint @@ -194,6 +194,7 @@ impl ProxyHandler { incompatible_components: Arc>, certs_rx: watch::Receiver>>, ) -> Result<(), ProxyError> { + let parsed_version = Version::parse(VERSION)?; loop { let endpoint = self.endpoint()?; let settings = Settings::get_current_settings(); @@ -213,7 +214,7 @@ impl ProxyHandler { let connector = HttpsSchemeConnector::new(connector); debug!("Connecting to proxy at {}", endpoint.uri()); - let interceptor = ClientVersionInterceptor::new(Version::parse(VERSION)?); + let interceptor = ClientVersionInterceptor::new(parsed_version.clone()); let channel = endpoint.connect_with_connector_lazy(connector); let mut client = ProxyClient::with_interceptor(channel, interceptor); self.client = Some(client.clone()); diff --git a/crates/defguard_proxy_manager/src/lib.rs b/crates/defguard_proxy_manager/src/lib.rs index b4bd75255..c4fa05553 100644 --- a/crates/defguard_proxy_manager/src/lib.rs +++ b/crates/defguard_proxy_manager/src/lib.rs @@ -76,7 +76,7 @@ impl ProxyManager { }); // Retrieve proxies from DB. let mut shutdown_channels = HashMap::new(); - let proxies = Proxy::all(&self.pool) + let proxies = Proxy::all_enabled(&self.pool) .await? .iter() .map(|proxy| { @@ -94,8 +94,8 @@ impl ProxyManager { .collect::, _>>()?; debug!("Retrieved {} proxies from the DB", proxies.len()); - // Connect to all proxies. - let mut tasks = JoinSet::>::new(); + // Connect to all enabled proxies. + let mut tasks = JoinSet::new(); for proxy in proxies { debug!("Spawning proxy task for proxy {}", proxy.url); tasks.spawn(proxy.run( diff --git a/migrations/20260219113714_[2.0.0]_update_gateway_model.down.sql b/migrations/20260219113714_[2.0.0]_update_gateway_model.down.sql index 4c11314cc..0c22a6865 100644 --- a/migrations/20260219113714_[2.0.0]_update_gateway_model.down.sql +++ b/migrations/20260219113714_[2.0.0]_update_gateway_model.down.sql @@ -1,4 +1,6 @@ ALTER TABLE gateway + DROP CONSTRAINT modified_by_fkey, + DROP COLUMN enabled, DROP COLUMN address, DROP COLUMN port, DROP COLUMN modified_at, @@ -7,4 +9,3 @@ ALTER TABLE gateway ADD COLUMN url text NOT NULL DEFAULT 'http://127.0.0.1:50051'; ALTER TABLE gateway RENAME COLUMN location_id TO network_id; - diff --git a/migrations/20260219113714_[2.0.0]_update_gateway_model.up.sql b/migrations/20260219113714_[2.0.0]_update_gateway_model.up.sql index 65819a671..e535125cc 100644 --- a/migrations/20260219113714_[2.0.0]_update_gateway_model.up.sql +++ b/migrations/20260219113714_[2.0.0]_update_gateway_model.up.sql @@ -8,6 +8,7 @@ ALTER TABLE gateway ADD COLUMN modified_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, -- FIXME: remove the default once we squash alpha migrations ADD COLUMN modified_by bigint NOT NULL DEFAULT 1, - ADD CONSTRAINT proxy_modified_by_fkey FOREIGN KEY (modified_by) REFERENCES "user"(id); + ADD COLUMN enabled bool NOT NULL DEFAULT true, + ADD CONSTRAINT modified_by_fkey FOREIGN KEY (modified_by) REFERENCES "user"(id); ALTER TABLE gateway RENAME COLUMN network_id TO location_id; diff --git a/migrations/20260302142347_[2.0.0]_proxy_enabled.down.sql b/migrations/20260302142347_[2.0.0]_proxy_enabled.down.sql new file mode 100644 index 000000000..c1bb0c3a9 --- /dev/null +++ b/migrations/20260302142347_[2.0.0]_proxy_enabled.down.sql @@ -0,0 +1 @@ +ALTER TABLE proxy DROP COLUMN enabled; diff --git a/migrations/20260302142347_[2.0.0]_proxy_enabled.up.sql b/migrations/20260302142347_[2.0.0]_proxy_enabled.up.sql new file mode 100644 index 000000000..acc3c2445 --- /dev/null +++ b/migrations/20260302142347_[2.0.0]_proxy_enabled.up.sql @@ -0,0 +1 @@ +ALTER TABLE proxy ADD COLUMN enabled bool NOT NULL DEFAULT true; diff --git a/web/package.json b/web/package.json index ab5afc1ce..7611295e4 100644 --- a/web/package.json +++ b/web/package.json @@ -61,17 +61,17 @@ "@types/byte-size": "^8.1.2", "@types/humanize-duration": "^3.27.4", "@types/lodash-es": "^4.17.12", - "@types/node": "^25.3.0", + "@types/node": "^25.3.1", "@types/qs": "^6.14.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react-swc": "^4.2.3", - "autoprefixer": "^10.4.24", + "autoprefixer": "^10.4.27", "globals": "^17.3.0", "prettier": "^3.8.1", "sass": "^1.97.3", "sharp": "^0.34.5", - "stylelint": "^17.3.0", + "stylelint": "^17.4.0", "stylelint-config-standard-scss": "^17.0.0", "stylelint-scss": "^7.0.0", "typescript": "~5.9.3", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 8485cd99e..39e12e483 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -122,7 +122,7 @@ importers: version: 2.4.4 '@tanstack/devtools-vite': specifier: ^0.5.1 - version: 0.5.1(vite@7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0)) + version: 0.5.1(vite@7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0)) '@tanstack/react-devtools': specifier: ^0.9.6 version: 0.9.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.9) @@ -134,7 +134,7 @@ importers: version: 1.163.2(@tanstack/react-router@1.163.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.163.2)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/router-plugin': specifier: ^1.163.2 - version: 1.163.2(@tanstack/react-router@1.163.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0)) + version: 1.163.2(@tanstack/react-router@1.163.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0)) '@types/byte-size': specifier: ^8.1.2 version: 8.1.2 @@ -145,8 +145,8 @@ importers: specifier: ^4.17.12 version: 4.17.12 '@types/node': - specifier: ^25.3.0 - version: 25.3.0 + specifier: ^25.3.1 + version: 25.3.1 '@types/qs': specifier: ^6.14.0 version: 6.14.0 @@ -158,10 +158,10 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react-swc': specifier: ^4.2.3 - version: 4.2.3(vite@7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0)) + version: 4.2.3(vite@7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0)) autoprefixer: - specifier: ^10.4.24 - version: 10.4.24(postcss@8.5.6) + specifier: ^10.4.27 + version: 10.4.27(postcss@8.5.6) globals: specifier: ^17.3.0 version: 17.3.0 @@ -175,23 +175,23 @@ importers: specifier: ^0.34.5 version: 0.34.5 stylelint: - specifier: ^17.3.0 - version: 17.3.0(typescript@5.9.3) + specifier: ^17.4.0 + version: 17.4.0(typescript@5.9.3) stylelint-config-standard-scss: specifier: ^17.0.0 - version: 17.0.0(postcss@8.5.6)(stylelint@17.3.0(typescript@5.9.3)) + version: 17.0.0(postcss@8.5.6)(stylelint@17.4.0(typescript@5.9.3)) stylelint-scss: specifier: ^7.0.0 - version: 7.0.0(stylelint@17.3.0(typescript@5.9.3)) + version: 7.0.0(stylelint@17.4.0(typescript@5.9.3)) typescript: specifier: ~5.9.3 version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0) + version: 7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0) vite-plugin-image-optimizer: specifier: ^2.0.3 - version: 2.0.3(sharp@0.34.5)(vite@7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0)) + version: 2.0.3(sharp@0.34.5)(vite@7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0)) packages: @@ -306,24 +306,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.4.4': resolution: {integrity: sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.4.4': resolution: {integrity: sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.4.4': resolution: {integrity: sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.4.4': resolution: {integrity: sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q==} @@ -592,89 +596,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -783,36 +803,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -899,66 +925,79 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -1087,24 +1126,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.15.13': resolution: {integrity: sha512-SmZ9m+XqCB35NddHCctvHFLqPZDAs5j8IgD36GoutufDJmeq2VNfgk5rQoqNqKmAK3Y7iFdEmI76QoHIWiCLyw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.15.13': resolution: {integrity: sha512-5rij+vB9a29aNkHq72EXI2ihDZPszJb4zlApJY4aCC/q6utgqFA6CkrfTfIb+O8hxtG3zP5KERETz8mfFK6A0A==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.15.13': resolution: {integrity: sha512-OlSlaOK9JplQ5qn07WiBLibkOw7iml2++ojEXhhR3rbWrNEKCD7sd8+6wSavsInyFdw4PhLA+Hy6YyDBIE23Yw==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.15.13': resolution: {integrity: sha512-zwQii5YVdsfG8Ti9gIKgBKZg8qMkRZxl+OlYWUT5D93Jl4NuNBRausP20tfEkQdAPSRrMCSUZBM6FhW7izAZRg==} @@ -1376,8 +1419,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@25.3.0': - resolution: {integrity: sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==} + '@types/node@25.3.1': + resolution: {integrity: sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw==} '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -1464,8 +1507,8 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - autoprefixer@10.4.24: - resolution: {integrity: sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==} + autoprefixer@10.4.27: + resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -1480,10 +1523,6 @@ packages: bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - balanced-match@3.0.1: - resolution: {integrity: sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==} - engines: {node: '>= 16'} - baseline-browser-mapping@2.10.0: resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} engines: {node: '>=6.0.0'} @@ -2588,8 +2627,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} style-to-js@1.1.21: @@ -2636,8 +2675,8 @@ packages: peerDependencies: stylelint: ^16.8.2 || ^17.0.0 - stylelint@17.3.0: - resolution: {integrity: sha512-1POV91lcEMhj6SLVaOeA0KlS9yattS+qq+cyWqP/nYzWco7K5jznpGH1ExngvPlTM9QF1Kjd2bmuzJu9TH2OcA==} + stylelint@17.4.0: + resolution: {integrity: sha512-3kQ2/cHv3Zt8OBg+h2B8XCx9evEABQIrv4hh3uXahGz/ZEHrTR80zxBiK2NfXNaSoyBzxO1pjsz1Vhdzwn5XSw==} engines: {node: '>=20.19.0'} hasBin: true @@ -3715,7 +3754,7 @@ snapshots: transitivePeerDependencies: - csstype - '@tanstack/devtools-vite@0.5.1(vite@7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0))': + '@tanstack/devtools-vite@0.5.1(vite@7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0))': dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -3727,7 +3766,7 @@ snapshots: chalk: 5.6.2 launch-editor: 2.13.1 picomatch: 4.0.3 - vite: 7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0) + vite: 7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0) transitivePeerDependencies: - bufferutil - supports-color @@ -3875,7 +3914,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.163.2(@tanstack/react-router@1.163.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0))': + '@tanstack/router-plugin@1.163.2(@tanstack/react-router@1.163.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -3892,7 +3931,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.163.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - vite: 7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0) + vite: 7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0) transitivePeerDependencies: - supports-color @@ -3974,7 +4013,7 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@25.3.0': + '@types/node@25.3.1': dependencies: undici-types: 7.18.2 @@ -4001,11 +4040,11 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react-swc@4.2.3(vite@7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0))': + '@vitejs/plugin-react-swc@4.2.3(vite@7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 '@swc/core': 1.15.13 - vite: 7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0) + vite: 7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0) transitivePeerDependencies: - '@swc/helpers' @@ -4047,7 +4086,7 @@ snapshots: asynckit@0.4.0: {} - autoprefixer@10.4.24(postcss@8.5.6): + autoprefixer@10.4.27(postcss@8.5.6): dependencies: browserslist: 4.28.1 caniuse-lite: 1.0.30001774 @@ -4075,8 +4114,6 @@ snapshots: bail@2.0.2: {} - balanced-match@3.0.1: {} - baseline-browser-mapping@2.10.0: {} binary-extensions@2.3.0: {} @@ -5301,7 +5338,7 @@ snapshots: string-width@8.2.0: dependencies: get-east-asian-width: 1.5.0 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 stringify-entities@4.0.4: dependencies: @@ -5312,7 +5349,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -5324,33 +5361,33 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - stylelint-config-recommended-scss@17.0.0(postcss@8.5.6)(stylelint@17.3.0(typescript@5.9.3)): + stylelint-config-recommended-scss@17.0.0(postcss@8.5.6)(stylelint@17.4.0(typescript@5.9.3)): dependencies: postcss-scss: 4.0.9(postcss@8.5.6) - stylelint: 17.3.0(typescript@5.9.3) - stylelint-config-recommended: 18.0.0(stylelint@17.3.0(typescript@5.9.3)) - stylelint-scss: 7.0.0(stylelint@17.3.0(typescript@5.9.3)) + stylelint: 17.4.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.4.0(typescript@5.9.3)) + stylelint-scss: 7.0.0(stylelint@17.4.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.6 - stylelint-config-recommended@18.0.0(stylelint@17.3.0(typescript@5.9.3)): + stylelint-config-recommended@18.0.0(stylelint@17.4.0(typescript@5.9.3)): dependencies: - stylelint: 17.3.0(typescript@5.9.3) + stylelint: 17.4.0(typescript@5.9.3) - stylelint-config-standard-scss@17.0.0(postcss@8.5.6)(stylelint@17.3.0(typescript@5.9.3)): + stylelint-config-standard-scss@17.0.0(postcss@8.5.6)(stylelint@17.4.0(typescript@5.9.3)): dependencies: - stylelint: 17.3.0(typescript@5.9.3) - stylelint-config-recommended-scss: 17.0.0(postcss@8.5.6)(stylelint@17.3.0(typescript@5.9.3)) - stylelint-config-standard: 40.0.0(stylelint@17.3.0(typescript@5.9.3)) + stylelint: 17.4.0(typescript@5.9.3) + stylelint-config-recommended-scss: 17.0.0(postcss@8.5.6)(stylelint@17.4.0(typescript@5.9.3)) + stylelint-config-standard: 40.0.0(stylelint@17.4.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.6 - stylelint-config-standard@40.0.0(stylelint@17.3.0(typescript@5.9.3)): + stylelint-config-standard@40.0.0(stylelint@17.4.0(typescript@5.9.3)): dependencies: - stylelint: 17.3.0(typescript@5.9.3) - stylelint-config-recommended: 18.0.0(stylelint@17.3.0(typescript@5.9.3)) + stylelint: 17.4.0(typescript@5.9.3) + stylelint-config-recommended: 18.0.0(stylelint@17.4.0(typescript@5.9.3)) - stylelint-scss@7.0.0(stylelint@17.3.0(typescript@5.9.3)): + stylelint-scss@7.0.0(stylelint@17.4.0(typescript@5.9.3)): dependencies: css-tree: 3.1.0 is-plain-object: 5.0.0 @@ -5360,9 +5397,9 @@ snapshots: postcss-resolve-nested-selector: 0.1.6 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - stylelint: 17.3.0(typescript@5.9.3) + stylelint: 17.4.0(typescript@5.9.3) - stylelint@17.3.0(typescript@5.9.3): + stylelint@17.4.0(typescript@5.9.3): dependencies: '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) @@ -5371,7 +5408,6 @@ snapshots: '@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.1) '@csstools/selector-specificity': 6.0.0(postcss-selector-parser@7.1.1) - balanced-match: 3.0.1 colord: 2.9.3 cosmiconfig: 9.0.0(typescript@5.9.3) css-functions-list: 3.3.3 @@ -5388,7 +5424,6 @@ snapshots: import-meta-resolve: 4.2.0 imurmurhash: 0.1.4 is-plain-object: 5.0.0 - known-css-properties: 0.37.0 mathml-tag-names: 4.0.0 meow: 14.1.0 micromatch: 4.0.8 @@ -5641,15 +5676,15 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-plugin-image-optimizer@2.0.3(sharp@0.34.5)(vite@7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0)): + vite-plugin-image-optimizer@2.0.3(sharp@0.34.5)(vite@7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0)): dependencies: ansi-colors: 4.1.3 pathe: 2.0.3 - vite: 7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0) + vite: 7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0) optionalDependencies: sharp: 0.34.5 - vite@7.3.1(@types/node@25.3.0)(sass@1.97.3)(tsx@4.21.0): + vite@7.3.1(@types/node@25.3.1)(sass@1.97.3)(tsx@4.21.0): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -5658,7 +5693,7 @@ snapshots: rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.3.0 + '@types/node': 25.3.1 fsevents: 2.3.3 sass: 1.97.3 tsx: 4.21.0 diff --git a/web/src/pages/EdgesPage/EdgesTable.tsx b/web/src/pages/EdgesPage/EdgesTable.tsx index 80b3d67da..5311f6688 100644 --- a/web/src/pages/EdgesPage/EdgesTable.tsx +++ b/web/src/pages/EdgesPage/EdgesTable.tsx @@ -46,6 +46,29 @@ const isConnected = (edge: EdgeInfo) => { const displayModifiedBy = (edge: EdgeInfo) => `${edge.modified_by_firstname} ${edge.modified_by_lastname}`; +const getStatusBadge = (edge: EdgeInfo) => { + if (!edge.enabled) { + return ( + + ); + } + + if (isConnected(edge)) { + return ( + + ); + } + + return ( + + ); +}; + export const EdgesTable = () => { const { data: edges } = useSuspenseQuery(getEdgesQueryOptions); const { data: licenseInfo } = useSuspenseQuery(getLicenseInfoQueryOptions); @@ -59,6 +82,13 @@ export const EdgesTable = () => { }, }); + const { mutate: toggleEdge } = useMutation({ + mutationFn: api.edge.editEdge, + meta: { + invalidate: ['edge'], + }, + }); + const addButtonProps = useMemo( (): ButtonProps => ({ variant: 'primary', @@ -163,26 +193,7 @@ export const EdgesTable = () => { minSize: 175, header: m.edges_col_status(), enableSorting: false, - cell: (info) => ( - - {isConnected(info.row.original) && ( - - )} - {!isConnected(info.row.original) && ( - - )} - - ), + cell: (info) => {getStatusBadge(info.row.original)}, }), columnHelper.display({ id: 'edit', @@ -206,6 +217,16 @@ export const EdgesTable = () => { }); }, }, + { + text: rowData.enabled ? m.controls_disable() : m.controls_enable(), + icon: rowData.enabled ? 'disabled' : 'check-circle', + onClick: () => { + toggleEdge({ + ...rowData, + enabled: !rowData.enabled, + }); + }, + }, ], }, { @@ -230,7 +251,7 @@ export const EdgesTable = () => { }, }), ], - [deleteEdge, navigate], + [deleteEdge, navigate, toggleEdge], ); const table = useReactTable({ diff --git a/web/src/pages/EditEdgePage/EditEdgePage.tsx b/web/src/pages/EditEdgePage/EditEdgePage.tsx index f2266be0d..03dcb19c9 100644 --- a/web/src/pages/EditEdgePage/EditEdgePage.tsx +++ b/web/src/pages/EditEdgePage/EditEdgePage.tsx @@ -48,6 +48,7 @@ const formSchema = z.object({ modified_at: z.string(), modified_by: z.number(), version: z.string().nullable(), + enabled: z.boolean(), }); type FormFields = z.infer; @@ -125,6 +126,10 @@ const EditEdgeForm = ({ edge }: { edge: Edge }) => { {(field) => } + + {(field) => } + + ({ diff --git a/web/src/pages/EditGatewayPage/EditGatewayPage.tsx b/web/src/pages/EditGatewayPage/EditGatewayPage.tsx index 1ec5f8860..584bf542d 100644 --- a/web/src/pages/EditGatewayPage/EditGatewayPage.tsx +++ b/web/src/pages/EditGatewayPage/EditGatewayPage.tsx @@ -45,6 +45,7 @@ const formSchema = z.object({ port: z.number().nullable(), connected_at: z.string().nullable(), disconnected_at: z.string().nullable(), + enabled: z.boolean(), modified_at: z.string(), modified_by: z.number(), version: z.string().nullable(), @@ -97,8 +98,9 @@ const EditGatewayForm = ({ gateway }: { gateway: Gateway }) => { }, onSubmit: async ({ value }) => { await editGateway({ - ...value, id: gateway.id, + name: value.name, + enabled: value.enabled, }); form.reset(value); }, @@ -126,6 +128,10 @@ const EditGatewayForm = ({ gateway }: { gateway: Gateway }) => { {(field) => } + + {(field) => } + + ({ diff --git a/web/src/pages/LocationsPage/components/GatewaysTable.tsx b/web/src/pages/LocationsPage/components/GatewaysTable.tsx index 3f33bec86..325912af7 100644 --- a/web/src/pages/LocationsPage/components/GatewaysTable.tsx +++ b/web/src/pages/LocationsPage/components/GatewaysTable.tsx @@ -1,4 +1,4 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; +import { useMutation, useSuspenseQuery } from '@tanstack/react-query'; import { useNavigate } from '@tanstack/react-router'; import { createColumnHelper, @@ -8,6 +8,7 @@ import { } from '@tanstack/react-table'; import { useMemo, useState } from 'react'; import { m } from '../../../paraglide/messages'; +import api from '../../../shared/api/api'; import type { GatewayInfo } from '../../../shared/api/types'; import { Badge } from '../../../shared/defguard-ui/components/Badge/Badge'; import { EmptyStateFlexible } from '../../../shared/defguard-ui/components/EmptyStateFlexible/EmptyStateFlexible'; @@ -31,6 +32,11 @@ const displayModifiedBy = (gateway: GatewayInfo) => `${gateway.modified_by_firstname} ${gateway.modified_by_lastname}`; const getStatusBadge = (gateway: GatewayInfo) => { + if (!gateway.enabled) { + return ( + + ); + } if (gateway.connected) { return ; } @@ -49,6 +55,12 @@ const getStatusBadge = (gateway: GatewayInfo) => { export const GatewaysTable = () => { const { data: gateways } = useSuspenseQuery(getGatewaysQueryOptions); const navigate = useNavigate(); + const { mutate: toggleGateway } = useMutation({ + mutationFn: api.gateway.editGateway, + meta: { + invalidate: ['gateway'], + }, + }); const [search, setSearch] = useState(''); @@ -179,6 +191,21 @@ export const GatewaysTable = () => { }); }, }, + { + text: rowData.enabled ? m.controls_disable() : m.controls_enable(), + icon: rowData.enabled ? 'disabled' : 'check-circle', + onClick: () => { + toggleGateway({ + id: rowData.id, + name: rowData.name, + enabled: !rowData.enabled, + }); + }, + }, + ], + }, + { + items: [ { text: m.controls_delete(), icon: 'delete', @@ -203,7 +230,7 @@ export const GatewaysTable = () => { }, }), ], - [navigate], + [navigate, toggleGateway], ); const table = useReactTable({ diff --git a/web/src/shared/api/api.ts b/web/src/shared/api/api.ts index b0aec8570..9e5b7ddbf 100644 --- a/web/src/shared/api/api.ts +++ b/web/src/shared/api/api.ts @@ -445,8 +445,8 @@ const api = { getGateways: () => client.get('/gateway'), getGateway: (gatewayId: number | string) => client.get(`/gateway/${gatewayId}`), - editGateway: (data: Gateway) => - client.put(`/gateway/${data.id}`, { name: data.name }), + editGateway: (data: { id: number | string; name: string; enabled: boolean }) => + client.put(`/gateway/${data.id}`, { name: data.name, enabled: data.enabled }), deleteGateway: (gatewayId: number | string) => client.delete(`/gateway/${gatewayId}`), }, acl: { diff --git a/web/src/shared/api/types.ts b/web/src/shared/api/types.ts index 4cd865a13..70035dc81 100644 --- a/web/src/shared/api/types.ts +++ b/web/src/shared/api/types.ts @@ -1070,6 +1070,7 @@ export interface Edge { version: string | null; connected_at: string | null; disconnected_at: string | null; + enabled: boolean; modified_at: string; modified_by: number; } @@ -1088,6 +1089,7 @@ export interface Gateway { version: string | null; connected_at: string | null; disconnected_at: string | null; + enabled: boolean; modified_at: string; modified_by: number; }