Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ edition = "2024"
homepage = "https://www.omnect.io/home"
license = "MIT OR Apache-2.0"
repository = "git@github.com:omnect/omnect-ui.git"
version = "1.1.2"
version = "1.2.1"
6 changes: 6 additions & 0 deletions project-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ omnect-ui/
│ │ ├── wasm.rs # WASM FFI bindings
│ │ ├── macros.rs # URL and log macros
│ │ ├── http_helpers.rs # HTTP request utilities
│ │ ├── wifi_psk.rs # WiFi PSK utilities
│ │ ├── commands/ # Custom side-effect commands
│ │ │ ├── mod.rs
│ │ │ └── centrifugo.rs # Centrifugo WebSocket commands
Expand All @@ -164,13 +165,15 @@ omnect-ui/
│ │ │ ├── common.rs # Common shared types
│ │ │ ├── device.rs # Device information types
│ │ │ ├── network.rs # Network configuration types
│ │ │ ├── wifi.rs # WiFi types
│ │ │ ├── ods.rs # ODS-specific DTOs
│ │ │ ├── factory_reset.rs
│ │ │ └── update.rs # Update validation types
│ │ └── update/ # Domain-based event handlers
│ │ ├── mod.rs # Main dispatcher
│ │ ├── auth.rs # Auth event handlers
│ │ ├── ui.rs # UI state handlers
│ │ ├── wifi.rs # WiFi event handlers
│ │ ├── websocket.rs # WebSocket state handlers
│ │ └── device/ # Device domain handlers
│ │ ├── mod.rs
Expand All @@ -187,11 +190,13 @@ omnect-ui/
│ │ │ ├── http_client.rs # Internal HTTP client
│ │ │ ├── keycloak_client.rs
│ │ │ ├── omnect_device_service_client.rs
│ │ │ ├── wifi_commissioning_client.rs
│ │ │ └── services/ # Business logic services
│ │ │ ├── mod.rs
│ │ │ ├── certificate.rs
│ │ │ ├── firmware.rs
│ │ │ ├── network.rs
│ │ │ ├── marker.rs
│ │ │ └── auth/ # Auth logic
│ │ │ ├── mod.rs
│ │ │ ├── authorization.rs # JWT/SSO validation
Expand Down Expand Up @@ -242,6 +247,7 @@ omnect-ui/
│ ├── smoke.spec.ts
│ ├── update.spec.ts
│ ├── version-mismatch.spec.ts
│ ├── wifi.spec.ts
│ └── fixtures/
└── project-context.md # This file
```
Expand Down
8 changes: 4 additions & 4 deletions scripts/build-and-deploy-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,9 @@ if [[ "$DEPLOY" == "true" ]]; then

echo "Copying image to device $DEVICE_HOST..."
if [ -n "$DEVICE_PASS" ]; then
sshpass -p "$DEVICE_PASS" scp "$IMAGE_TAR" "${DEVICE_USER}@${DEVICE_HOST}:/tmp/"
sshpass -p "$DEVICE_PASS" scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR "$IMAGE_TAR" "${DEVICE_USER}@${DEVICE_HOST}:/tmp/"
else
scp "$IMAGE_TAR" "${DEVICE_USER}@${DEVICE_HOST}:/tmp/"
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR "$IMAGE_TAR" "${DEVICE_USER}@${DEVICE_HOST}:/tmp/"
fi

echo "Loading image on device and restarting container..."
Expand All @@ -203,9 +203,9 @@ if [[ "$DEPLOY" == "true" ]]; then
sudo iotedge system restart"

if [ -n "$DEVICE_PASS" ]; then
sshpass -p "$DEVICE_PASS" ssh "${DEVICE_USER}@${DEVICE_HOST}" "$CMD"
sshpass -p "$DEVICE_PASS" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR "${DEVICE_USER}@${DEVICE_HOST}" "$CMD"
else
ssh "${DEVICE_USER}@${DEVICE_HOST}" "$CMD"
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR "${DEVICE_USER}@${DEVICE_HOST}" "$CMD"
fi

echo "Cleaning up local tar file..."
Expand Down
4 changes: 4 additions & 0 deletions src/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_json = { version = "1.0", default-features = false }
serde_repr = { version = "0.1", default-features = false }
serde_valid = { version = "2.0", default-features = false }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
hmac = { version = "0.12", default-features = false }
pbkdf2 = { version = "0.12", default-features = false, features = ["hmac"] }
sha1 = { version = "0.10", default-features = false }
wasm-bindgen = { version = "0.2", default-features = false }

[target.'cfg(target_arch = "wasm32")'.dependencies]
Expand Down
121 changes: 121 additions & 0 deletions src/app/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum AuthEvent {
password: String,
},
CheckRequiresPasswordSet,
RestoreSession(String),
#[serde(skip)]
LoginResponse(Result<AuthToken, String>),
#[serde(skip)]
Expand Down Expand Up @@ -60,6 +61,7 @@ pub enum DeviceEvent {
RunUpdate {
validate_iothub_connection: bool,
},
FetchInitialHealthcheck,
ReconnectionCheckTick,
ReconnectionTimeout,
NewIpCheckTick,
Expand Down Expand Up @@ -102,6 +104,122 @@ pub enum WebSocketEvent {
Disconnected,
}

/// WiFi management events
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum WifiEvent {
// User actions
CheckAvailability,
Scan,
Connect {
ssid: String,
password: String,
},
Disconnect,
GetStatus,
GetSavedNetworks,
ForgetNetwork {
ssid: String,
},
// Timer ticks (driven by Shell)
ScanPollTick,
ConnectPollTick,
// Responses from HTTP effects
#[serde(skip)]
CheckAvailabilityResponse(Result<WifiAvailability, String>),
#[serde(skip)]
ScanResponse(Result<(), String>),
#[serde(skip)]
ScanResultsResponse(Result<WifiScanResultsApiResponse, String>),
#[serde(skip)]
ConnectResponse(Result<(), String>),
#[serde(skip)]
DisconnectResponse(Result<(), String>),
#[serde(skip)]
StatusResponse(Result<WifiStatusApiResponse, String>),
#[serde(skip)]
SavedNetworksResponse(Result<WifiSavedNetworksApiResponse, String>),
#[serde(skip)]
ForgetNetworkResponse(Result<(), String>),
}

/// API response types for WiFi (match backend JSON shapes)
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct WifiScanResultsApiResponse {
pub status: String,
pub state: String,
pub networks: Vec<WifiNetworkApiResponse>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct WifiNetworkApiResponse {
pub ssid: String,
pub mac: String,
pub ch: u16,
pub rssi: i16,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct WifiStatusApiResponse {
pub status: String,
pub state: String,
pub ssid: Option<String>,
pub ip_address: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct WifiSavedNetworksApiResponse {
pub status: String,
pub networks: Vec<WifiSavedNetworkApiResponse>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct WifiSavedNetworkApiResponse {
pub ssid: String,
pub flags: String,
}

/// Custom Debug for WifiEvent to redact password
impl fmt::Debug for WifiEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WifiEvent::Connect { ssid, .. } => f
.debug_struct("Connect")
.field("ssid", ssid)
.field("password", &"<redacted>")
.finish(),
WifiEvent::CheckAvailability => write!(f, "CheckAvailability"),
WifiEvent::Scan => write!(f, "Scan"),
WifiEvent::Disconnect => write!(f, "Disconnect"),
WifiEvent::GetStatus => write!(f, "GetStatus"),
WifiEvent::GetSavedNetworks => write!(f, "GetSavedNetworks"),
WifiEvent::ForgetNetwork { ssid } => {
f.debug_struct("ForgetNetwork").field("ssid", ssid).finish()
}
WifiEvent::ScanPollTick => write!(f, "ScanPollTick"),
WifiEvent::ConnectPollTick => write!(f, "ConnectPollTick"),
WifiEvent::CheckAvailabilityResponse(r) => {
f.debug_tuple("CheckAvailabilityResponse").field(r).finish()
}
WifiEvent::ScanResponse(r) => f.debug_tuple("ScanResponse").field(r).finish(),
WifiEvent::ScanResultsResponse(r) => {
f.debug_tuple("ScanResultsResponse").field(r).finish()
}
WifiEvent::ConnectResponse(r) => f.debug_tuple("ConnectResponse").field(r).finish(),
WifiEvent::DisconnectResponse(r) => {
f.debug_tuple("DisconnectResponse").field(r).finish()
}
WifiEvent::StatusResponse(r) => f.debug_tuple("StatusResponse").field(r).finish(),
WifiEvent::SavedNetworksResponse(r) => {
f.debug_tuple("SavedNetworksResponse").field(r).finish()
}
WifiEvent::ForgetNetworkResponse(r) => {
f.debug_tuple("ForgetNetworkResponse").field(r).finish()
}
}
}
}

/// UI action events
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum UiEvent {
Expand All @@ -118,6 +236,7 @@ pub enum Event {
Device(DeviceEvent),
WebSocket(WebSocketEvent),
Ui(UiEvent),
Wifi(WifiEvent),
}

/// Custom Debug implementation for AuthEvent to redact sensitive data
Expand Down Expand Up @@ -149,6 +268,7 @@ impl fmt::Debug for AuthEvent {
},
AuthEvent::Logout => write!(f, "Logout"),
AuthEvent::CheckRequiresPasswordSet => write!(f, "CheckRequiresPasswordSet"),
AuthEvent::RestoreSession(_) => write!(f, "RestoreSession(<redacted token>)"),
AuthEvent::LogoutResponse(r) => f.debug_tuple("LogoutResponse").field(r).finish(),
AuthEvent::SetPasswordResponse(result) => match result {
Ok(_) => f
Expand Down Expand Up @@ -180,6 +300,7 @@ impl fmt::Debug for Event {
Event::Device(e) => write!(f, "Device({e:?})"),
Event::WebSocket(e) => write!(f, "WebSocket({e:?})"),
Event::Ui(e) => write!(f, "Ui({e:?})"),
Event::Wifi(e) => write!(f, "Wifi({e:?})"),
}
}
}
Loading
Loading