Skip to content

Commit 1e9ad90

Browse files
authored
feat: network config (#63)
## Summary Add network configuration management UI and backend with support for static IP and DHCP configuration. ### Backend - Add `network` module with support for static IP and DHCP configuration - Implement network rollback mechanism with 90-second timeout - Add API endpoint for setting network configuration with validation - Refactor certificate management to use `DeviceServiceClient` reference - Move certificate creation to server startup after service client initialization - Remove certificate regeneration from network rollback flow - Add server restart channel for handling network changes - Cancel pending rollback on successful authentication ### Frontend - Add Network page with network settings UI - Add `NetworkSettings` component for editing network configuration - Add `NetworkActions` component for network-related actions - Add `useWaitForNewIp` composable for handling IP changes - Update `DeviceNetworks` component to show network details - Refactor `DeviceActions` to use composable pattern - Update Vuetify and Biome configurations - Add route for `/network` page --------- Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
1 parent 6986e22 commit 1e9ad90

28 files changed

+1391
-532
lines changed

Cargo.lock

Lines changed: 300 additions & 139 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@ license = "MIT OR Apache-2.0"
77
name = "omnect-ui"
88
readme = "README.md"
99
repository = "git@github.com:omnect/omnect-ui.git"
10-
version = "1.0.6"
10+
version = "1.1.0"
1111
build = "src/build.rs"
1212

1313
[dependencies]
14+
actix-cors = { version = "0.7", default-features = false }
1415
actix-files = { version = "0.6", default-features = false }
1516
actix-multipart = { version = "0.7", default-features = false, features = [
1617
"tempfile",
1718
"derive"
18-
]}
19-
actix-session = { version = "0.10", features = ["cookie-session"] }
19+
] }
2020
actix-server = { version = "2.6", default-features = false }
21+
actix-session = { version = "0.11", features = ["cookie-session"] }
2122
actix-web = { version = "4.11", default-features = false, features = [
2223
"macros",
2324
"rustls-0_23",
@@ -37,6 +38,7 @@ log-panics = { version = "2.1", default-features = false, features = [
3738
mockall = { version = "0.13", optional = true, default-features = false }
3839
rand_core = { version = "0.9", default-features = false, features = ["std"] }
3940
reqwest = { version = "0.12.23", default-features = false, features = ["json", "rustls-tls"] }
41+
rust-ini = { version = "0.21", default-features = false }
4042
rustls = { version = "0.23", default-features = false, features = [
4143
"aws_lc_rs",
4244
"std",
@@ -51,11 +53,13 @@ serde_json = { version = "1.0", default-features = false, features = [
5153
"raw_value",
5254
] }
5355
serde_repr = { version = "0.1", default-features = false }
56+
serde_valid = { version = "2.0", default-features = false }
5457
tokio = { version = "1.45", default-features = false, features = [
5558
"macros",
5659
"net",
5760
"process",
5861
] }
62+
trait-variant = { version = "0.1", default-features = false }
5963
uuid = { version = "1.17", default-features = false, features = [
6064
"v4",
6165
] }
@@ -66,5 +70,5 @@ mock = ["dep:mockall"]
6670
[dev-dependencies]
6771
actix-http = "3.11"
6872
actix-service = "2.0"
69-
tempfile = "3.20"
7073
mockall_double = "0.3"
74+
tempfile = "3.20"

biome.json

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
33
"linter": {
44
"enabled": true,
55
"rules": {
@@ -10,23 +10,32 @@
1010
},
1111
"style": {
1212
"noParameterAssign": "off",
13-
"useImportType": "off"
13+
"useImportType": "off",
14+
"useAsConstAssertion": "error",
15+
"useDefaultParameterLast": "error",
16+
"useEnumInitializers": "error",
17+
"useSelfClosingElements": "error",
18+
"useSingleVarDeclarator": "error",
19+
"noUnusedTemplateLiteral": "error",
20+
"useNumberNamespace": "error",
21+
"noInferrableTypes": "error",
22+
"noUselessElse": "error"
1423
},
1524
"complexity": {
1625
"noStaticOnlyClass": "off",
1726
"useLiteralKeys": "off",
1827
"noForEach": "off"
1928
}
2029
},
21-
"ignore": []
30+
"includes": ["**"]
2231
},
2332
"formatter": {
2433
"enabled": true,
2534
"formatWithErrors": false,
2635
"indentStyle": "tab",
2736
"indentWidth": 2,
2837
"lineWidth": 150,
29-
"ignore": []
38+
"includes": ["**"]
3039
},
3140
"javascript": {
3241
"formatter": {
@@ -39,7 +48,7 @@
3948
}
4049
},
4150
"files": {
42-
"ignore": ["./.vscode*"],
51+
"includes": ["**", "!.vscode*"],
4352
"maxSize": 31457280
4453
}
4554
}

build-and-run-image.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ docker run --rm \
1818
-v $(pwd)/temp:/cert \
1919
-v /tmp:/socket \
2020
-v $(pwd)/temp/data:/data \
21+
-v $(pwd)/temp/network:/network \
2122
-u $(id -u):$(id -g) \
2223
-e RUST_LOG=debug \
2324
-e UI_PORT=1977 \

src/api.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::{
22
auth::TokenManager,
33
common::{config_path, data_path, host_data_path, tmp_path, validate_password},
44
keycloak_client::SingleSignOnProvider,
5+
network::{NetworkConfig, NetworkConfigService},
56
omnect_device_service_client::{DeviceServiceClient, FactoryReset, LoadUpdate, RunUpdate},
67
};
78
use actix_files::NamedFile;
@@ -146,6 +147,7 @@ where
146147
pub async fn token(session: Session, token_manager: web::Data<TokenManager>) -> impl Responder {
147148
debug!("token() called");
148149

150+
NetworkConfigService::cancel_rollback();
149151
Self::session_token(session, token_manager)
150152
}
151153

@@ -275,6 +277,18 @@ where
275277
HttpResponse::Ok().finish()
276278
}
277279

280+
pub async fn set_network_config(
281+
network_config: web::Json<NetworkConfig>,
282+
api: web::Data<Self>,
283+
) -> impl Responder {
284+
debug!("set_network_config() called");
285+
286+
Self::handle_service_result(
287+
NetworkConfigService::set_network_config(&api.service_client, &network_config).await,
288+
"set_network_config",
289+
)
290+
}
291+
278292
async fn validate_token_and_claims(&self, token: &str) -> Result<()> {
279293
let claims = self.single_sign_on.verify_token(token).await?;
280294
let Some(tenant_list) = &claims.tenant_list else {

src/common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ pub fn create_frontend_config_file() -> Result<()> {
115115
.context("failed to create frontend config file")?;
116116

117117
config_file
118-
.write_all(keycloak_client::config().as_bytes())
118+
.write_all(keycloak_client::KeycloakProvider::config().as_bytes())
119119
.context("failed to write frontend config file")
120120
}
121121

src/keycloak_client.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use base64::{Engine, prelude::BASE64_STANDARD};
33
use jwt_simple::prelude::{RS256PublicKey, RSAPublicKeyLike};
44
#[cfg(feature = "mock")]
55
use mockall::automock;
6+
use reqwest::Client;
67
use serde::{Deserialize, Serialize};
8+
use trait_variant::make;
79

810
#[derive(Debug, Deserialize, Serialize, Clone)]
911
pub struct TokenClaims {
@@ -25,9 +27,9 @@ macro_rules! keycloak_url {
2527
}};
2628
}
2729

30+
#[make(Send + Sync)]
2831
#[cfg_attr(feature = "mock", automock)]
29-
#[allow(async_fn_in_trait)]
30-
pub trait SingleSignOnProvider: Send + Sync {
32+
pub trait SingleSignOnProvider {
3133
async fn verify_token(&self, token: &str) -> anyhow::Result<TokenClaims>;
3234
}
3335

@@ -39,12 +41,17 @@ pub struct KeycloakProvider {
3941
impl Default for KeycloakProvider {
4042
fn default() -> Self {
4143
Self {
42-
client: reqwest::Client::new(),
44+
client: Client::new(),
4345
}
4446
}
4547
}
4648

4749
impl KeycloakProvider {
50+
pub fn config() -> String {
51+
let keycloak_url = &keycloak_url!();
52+
format!("window.__APP_CONFIG__ = {{KEYCLOAK_URL:\"{keycloak_url}\"}};")
53+
}
54+
4855
async fn realm_public_key(&self) -> Result<RS256PublicKey> {
4956
let resp = self
5057
.client
@@ -71,8 +78,3 @@ impl SingleSignOnProvider for KeycloakProvider {
7178
Ok(claims.custom)
7279
}
7380
}
74-
75-
pub fn config() -> String {
76-
let keycloak_url = &keycloak_url!();
77-
format!("window.__APP_CONFIG__ = {{KEYCLOAK_URL:\"{keycloak_url}\"}};")
78-
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ pub mod common;
55
pub mod http_client;
66
pub mod keycloak_client;
77
pub mod middleware;
8+
pub mod network;
89
pub mod omnect_device_service_client;

0 commit comments

Comments
 (0)