feature: Swapping to use nmcli (#1015)

The reason is that we get better errors and that we get signal strength.
Reworking all the commands to use nmcli instead.
Feat: Wifi List Available
Feat: strength sort for available
fix: Backend to match the frontend asking
feat: New get with all information
chore: Make backend changing country not for NonWire

Co-authored-by: Drew Ansbacher <drew.ansbacher@spiredigital.com>

Co-authored-by: Drew Ansbacher <drew.ansbacher@spiredigital.com>
This commit is contained in:
J M
2022-01-10 12:38:49 -07:00
committed by Aiden McClelland
parent 689b449d7a
commit 1086ce13d2
12 changed files with 795 additions and 412 deletions

View File

@@ -35,7 +35,7 @@ clean:
eos.img: $(EMBASSY_SRC) system-images/compat/compat.tar system-images/utils/utils.tar eos.img: $(EMBASSY_SRC) system-images/compat/compat.tar system-images/utils/utils.tar
! test -f eos.img || rm eos.img ! test -f eos.img || rm eos.img
if [ $(NO_KEY) -eq 1 ]; then NO_KEY=1 ./build/make-image.sh; else ./build/make-image.sh; fi if [ $(NO_KEY) = 1 ]; then NO_KEY=1 ./build/make-image.sh; else ./build/make-image.sh; fi
system-images/compat/compat.tar: $(COMPAT_SRC) system-images/compat/compat.tar: $(COMPAT_SRC)
cd system-images/compat && ./build.sh cd system-images/compat && ./build.sh

View File

@@ -206,10 +206,7 @@ impl RpcContext {
notification_manager, notification_manager,
open_authed_websockets: Mutex::new(BTreeMap::new()), open_authed_websockets: Mutex::new(BTreeMap::new()),
rpc_stream_continuations: Mutex::new(BTreeMap::new()), rpc_stream_continuations: Mutex::new(BTreeMap::new()),
wifi_manager: Arc::new(RwLock::new(WpaCli::init( wifi_manager: Arc::new(RwLock::new(WpaCli::init("wlan0".to_string()))),
"wlan0".to_string(),
base.datadir().join("main"),
))),
}); });
let metrics_seed = seed.clone(); let metrics_seed = seed.clone();
tokio::spawn(async move { tokio::spawn(async move {

View File

@@ -3,6 +3,7 @@ use std::sync::Arc;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use emver::VersionRange; use emver::VersionRange;
use isocountry::CountryCode;
use patch_db::json_ptr::JsonPointer; use patch_db::json_ptr::JsonPointer;
use patch_db::{HasModel, Map, MapModel, OptionModel}; use patch_db::{HasModel, Map, MapModel, OptionModel};
use reqwest::Url; use reqwest::Url;
@@ -44,6 +45,7 @@ impl Database {
id, id,
version: Current::new().semver().into(), version: Current::new().semver().into(),
last_backup: None, last_backup: None,
last_wifi_region: None,
eos_version_compat: Current::new().compat().clone(), eos_version_compat: Current::new().compat().clone(),
lan_address: format!("https://{}.local", hostname).parse().unwrap(), lan_address: format!("https://{}.local", hostname).parse().unwrap(),
tor_address: format!("http://{}", tor_key.public().get_onion_address()) tor_address: format!("http://{}", tor_key.public().get_onion_address())
@@ -85,6 +87,8 @@ pub struct ServerInfo {
pub id: String, pub id: String,
pub version: Version, pub version: Version,
pub last_backup: Option<DateTime<Utc>>, pub last_backup: Option<DateTime<Utc>>,
/// Used in the wifi to determine the region to set the system to
pub last_wifi_region: Option<CountryCode>,
pub eos_version_compat: VersionRange, pub eos_version_compat: VersionRange,
pub lan_address: Url, pub lan_address: Url,
pub tor_address: Url, pub tor_address: Url,

View File

@@ -59,12 +59,26 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<(), Error> {
crate::ssh::sync_keys_from_db(&secret_store, "/root/.ssh/authorized_keys").await?; crate::ssh::sync_keys_from_db(&secret_store, "/root/.ssh/authorized_keys").await?;
tracing::info!("Synced SSH Keys"); tracing::info!("Synced SSH Keys");
crate::net::wifi::synchronize_wpa_supplicant_conf(&cfg.datadir().join("main")).await?;
tracing::info!("Synchronized wpa_supplicant.conf");
let db = cfg.db(&secret_store).await?; let db = cfg.db(&secret_store).await?;
let mut handle = db.handle(); let mut handle = db.handle();
crate::net::wifi::synchronize_wpa_supplicant_conf(
&cfg.datadir().join("main"),
&*crate::db::DatabaseModel::new()
.server_info()
.last_wifi_region()
.get(&mut handle, false)
.await
.map_err(|_e| {
Error::new(
color_eyre::eyre::eyre!("Could not find the last wifi region"),
crate::ErrorKind::NotFound,
)
})?,
)
.await?;
tracing::info!("Synchronized wpa_supplicant.conf");
let mut info = crate::db::DatabaseModel::new() let mut info = crate::db::DatabaseModel::new()
.server_info() .server_info()
.get_mut(&mut handle) .get_mut(&mut handle)

View File

@@ -1,10 +1,13 @@
use std::collections::BTreeMap; use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf}; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use clap::ArgMatches; use clap::ArgMatches;
use isocountry::CountryCode; use isocountry::CountryCode;
use lazy_static::lazy_static;
use patch_db::DbHandle;
use regex::Regex;
use rpc_toolkit::command; use rpc_toolkit::command;
use tokio::process::Command; use tokio::process::Command;
use tokio::sync::RwLock; use tokio::sync::RwLock;
@@ -15,13 +18,23 @@ use crate::util::serde::{display_serializable, IoFormat};
use crate::util::{display_none, Invoke}; use crate::util::{display_none, Invoke};
use crate::{Error, ErrorKind}; use crate::{Error, ErrorKind};
#[command(subcommands(add, connect, delete, get, set_country))] #[command(subcommands(add, connect, delete, get, country, available))]
pub async fn wifi() -> Result<(), Error> { pub async fn wifi() -> Result<(), Error> {
Ok(()) Ok(())
} }
#[command(subcommands(get_available))]
pub async fn available() -> Result<(), Error> {
Ok(())
}
#[command(subcommands(set_country))]
pub async fn country() -> Result<(), Error> {
Ok(())
}
#[command(display(display_none))] #[command(display(display_none))]
#[instrument(skip(ctx))] #[instrument(skip(ctx, password))]
pub async fn add( pub async fn add(
#[context] ctx: RpcContext, #[context] ctx: RpcContext,
#[arg] ssid: String, #[arg] ssid: String,
@@ -42,48 +55,32 @@ pub async fn add(
)); ));
} }
async fn add_procedure( async fn add_procedure(
db: impl DbHandle,
wifi_manager: Arc<RwLock<WpaCli>>, wifi_manager: Arc<RwLock<WpaCli>>,
ssid: &str, ssid: &Ssid,
password: &str, password: &Psk,
priority: isize, priority: isize,
connect: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
tracing::info!("Adding new WiFi network: '{}'", ssid); tracing::info!("Adding new WiFi network: '{}'", ssid.0);
let mut wpa_supplicant = wifi_manager.write().await; let mut wpa_supplicant = wifi_manager.write().await;
wpa_supplicant.add_network(ssid, password, priority).await?; wpa_supplicant
.add_network(db, ssid, password, priority)
.await?;
drop(wpa_supplicant); drop(wpa_supplicant);
if connect {
let wpa_supplicant = wifi_manager.read().await;
let current = wpa_supplicant.get_current_network().await?;
drop(wpa_supplicant);
let mut wpa_supplicant = wifi_manager.write().await;
let connected = wpa_supplicant.select_network(ssid).await?;
if !connected {
tracing::info!("Failed to add new WiFi network: '{}'", ssid);
wpa_supplicant.remove_network(ssid).await?;
match current {
None => {}
Some(current) => {
wpa_supplicant.select_network(&current).await?;
}
}
}
}
Ok(()) Ok(())
} }
tokio::spawn(async move { tokio::spawn(async move {
match add_procedure( match add_procedure(
&mut ctx.db.handle(),
ctx.wifi_manager.clone(), ctx.wifi_manager.clone(),
&ssid, &Ssid(ssid.clone()),
&password, &Psk(password.clone()),
priority, priority,
connect,
) )
.await .await
{ {
Err(e) => { Err(e) => {
tracing::info!("Failed to add new WiFi network '{}': {}", ssid, e); tracing::info!("Failed to add new WiFi network '{}': {}", ssid, e);
tracing::debug!("{:?}", e);
} }
Ok(_) => {} Ok(_) => {}
} }
@@ -101,31 +98,38 @@ pub async fn connect(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result<
)); ));
} }
async fn connect_procedure( async fn connect_procedure(
mut db: impl DbHandle,
wifi_manager: Arc<RwLock<WpaCli>>, wifi_manager: Arc<RwLock<WpaCli>>,
ssid: &String, ssid: &Ssid,
) -> Result<(), Error> { ) -> Result<(), Error> {
let wpa_supplicant = wifi_manager.read().await; let wpa_supplicant = wifi_manager.read().await;
let current = wpa_supplicant.get_current_network().await?; let current = wpa_supplicant.get_current_network().await?;
drop(wpa_supplicant); drop(wpa_supplicant);
let mut wpa_supplicant = wifi_manager.write().await; let mut wpa_supplicant = wifi_manager.write().await;
let connected = wpa_supplicant.select_network(&ssid).await?; let connected = wpa_supplicant.select_network(&mut db, &ssid).await?;
if connected { if connected {
tracing::info!("Successfully connected to WiFi: '{}'", ssid); tracing::info!("Successfully connected to WiFi: '{}'", ssid.0);
} else { } else {
tracing::info!("Failed to connect to WiFi: '{}'", ssid); tracing::info!("Failed to connect to WiFi: '{}'", ssid.0);
match current { match current {
None => { None => {
tracing::info!("No WiFi to revert to!"); tracing::info!("No WiFi to revert to!");
} }
Some(current) => { Some(current) => {
wpa_supplicant.select_network(&current).await?; wpa_supplicant.select_network(&mut db, &current).await?;
} }
} }
} }
Ok(()) Ok(())
} }
tokio::spawn(async move { tokio::spawn(async move {
match connect_procedure(ctx.wifi_manager.clone(), &ssid).await { match connect_procedure(
&mut ctx.db.handle(),
ctx.wifi_manager.clone(),
&Ssid(ssid.clone()),
)
.await
{
Err(e) => { Err(e) => {
tracing::info!("Failed to connect to WiFi network '{}': {}", &ssid, e); tracing::info!("Failed to connect to WiFi network '{}': {}", &ssid, e);
} }
@@ -148,31 +152,42 @@ pub async fn delete(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result<(
let current = wpa_supplicant.get_current_network().await?; let current = wpa_supplicant.get_current_network().await?;
drop(wpa_supplicant); drop(wpa_supplicant);
let mut wpa_supplicant = ctx.wifi_manager.write().await; let mut wpa_supplicant = ctx.wifi_manager.write().await;
match current { let ssid = Ssid(ssid);
None => { let is_current_being_removed = matches!(current, Some(current) if current == ssid);
wpa_supplicant.remove_network(&ssid).await?; let is_current_removed_and_no_hardwire =
} is_current_being_removed && !interface_connected("eth0").await?;
Some(current) => { if is_current_removed_and_no_hardwire {
if current == ssid { return Err(Error::new(color_eyre::eyre::eyre!("Forbidden: Deleting this Network would make your Embassy Unreachable. Either connect to ethernet or connect to a different WiFi network to remedy this."), ErrorKind::Wifi));
if interface_connected("eth0").await? {
wpa_supplicant.remove_network(&ssid).await?;
} else {
return Err(Error::new(color_eyre::eyre::eyre!("Forbidden: Deleting this Network would make your Embassy Unreachable. Either connect to ethernet or connect to a different WiFi network to remedy this."), ErrorKind::Wifi));
}
}
}
} }
wpa_supplicant
.remove_network(&mut ctx.db.handle(), &ssid)
.await?;
Ok(()) Ok(())
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct WiFiInfo { pub struct WiFiInfo {
ssids: Vec<String>, ssids: HashMap<Ssid, SignalStrength>,
connected: Option<String>, connected: Option<Ssid>,
country: CountryCode, country: CountryCode,
ethernet: bool, ethernet: bool,
signal_strength: Option<usize>, available_wifi: Vec<WifiListOut>,
} }
#[derive(serde::Serialize, serde::Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct WifiListInfo {
strength: SignalStrength,
security: Vec<String>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct WifiListOut {
ssid: Ssid,
strength: SignalStrength,
security: Vec<String>,
}
pub type WifiList = HashMap<Ssid, WifiListInfo>;
fn display_wifi_info(info: WiFiInfo, matches: &ArgMatches<'_>) { fn display_wifi_info(info: WiFiInfo, matches: &ArgMatches<'_>) {
use prettytable::*; use prettytable::*;
@@ -191,34 +206,74 @@ fn display_wifi_info(info: WiFiInfo, matches: &ArgMatches<'_>) {
&info &info
.connected .connected
.as_ref() .as_ref()
.map_or("[N/A]".to_owned(), |c| format!("{}", c)), .map_or("[N/A]".to_owned(), |c| format!("{}", c.0)),
&info &info
.signal_strength .connected
.as_ref() .as_ref()
.map_or("[N/A]".to_owned(), |ss| format!("{}", ss)), .and_then(|x| info.ssids.get(x))
.map_or("[N/A]".to_owned(), |ss| format!("{}", ss.0)),
&format!("{}", info.country.alpha2()), &format!("{}", info.country.alpha2()),
&format!("{}", info.ethernet) &format!("{}", info.ethernet)
]); ]);
table_global.print_tty(false); table_global.print_tty(false);
let mut table_ssids = Table::new(); let mut table_ssids = Table::new();
table_ssids.add_row(row![bc => "SSID",]); table_ssids.add_row(row![bc => "SSID", "STRENGTH"]);
for ssid in &info.ssids { for (ssid, signal_strength) in &info.ssids {
let mut row = row![ssid]; let mut row = row![&ssid.0, format!("{}", signal_strength.0)];
if Some(ssid) == info.connected.as_ref() { row.iter_mut()
row.iter_mut() .map(|c| {
.map(|c| { c.style(Attr::ForegroundColor(match &signal_strength.0 {
c.style(Attr::ForegroundColor(match &info.signal_strength { x if x >= &90 => color::GREEN,
Some(100) => color::GREEN, x if x == &50 => color::MAGENTA,
Some(0) => color::RED, x if x == &0 => color::RED,
_ => color::YELLOW, _ => color::YELLOW,
})) }))
}) })
.collect::<()>() .for_each(drop);
}
table_ssids.add_row(row); table_ssids.add_row(row);
} }
table_ssids.print_tty(false); table_ssids.print_tty(false);
let mut table_global = Table::new();
table_global.add_row(row![bc =>
"SSID",
"STRENGTH",
"SECURITY",
]);
for table_info in info.available_wifi {
table_global.add_row(row![
&table_info.ssid.0,
&format!("{}", table_info.strength.0),
&format!("{}", table_info.security.join(" "))
]);
}
table_global.print_tty(false);
}
fn display_wifi_list(info: Vec<WifiListOut>, matches: &ArgMatches<'_>) {
use prettytable::*;
if matches.is_present("format") {
return display_serializable(info, matches);
}
let mut table_global = Table::new();
table_global.add_row(row![bc =>
"SSID",
"STRENGTH",
"SECURITY",
]);
for table_info in info {
table_global.add_row(row![
&table_info.ssid.0,
&format!("{}", table_info.strength.0),
&format!("{}", table_info.security.join(" "))
]);
}
table_global.print_tty(false);
} }
#[command(display(display_wifi_info))] #[command(display(display_wifi_info))]
@@ -230,261 +285,327 @@ pub async fn get(
format: Option<IoFormat>, format: Option<IoFormat>,
) -> Result<WiFiInfo, Error> { ) -> Result<WiFiInfo, Error> {
let wpa_supplicant = ctx.wifi_manager.read().await; let wpa_supplicant = ctx.wifi_manager.read().await;
let ssids_task = async { let (list_networks, current_res, country_res, ethernet_res, signal_strengths) = tokio::join!(
Result::<Vec<String>, Error>::Ok( wpa_supplicant.list_networks_low(),
wpa_supplicant wpa_supplicant.get_current_network(),
.list_networks_low() wpa_supplicant.get_country_low(),
.await? interface_connected("eth0"), // TODO: pull from config
.into_keys() wpa_supplicant.list_wifi_low()
.collect::<Vec<String>>(),
)
};
let current_task = wpa_supplicant.get_current_network();
let country_task = wpa_supplicant.get_country_low();
let ethernet_task = interface_connected("eth0"); // TODO: pull from config
let rssi_task = wpa_supplicant.signal_poll_low();
let (ssids_res, current_res, country_res, ethernet_res, rssi_res) = tokio::join!(
ssids_task,
current_task,
country_task,
ethernet_task,
rssi_task
); );
let current = current_res?; let signal_strengths = signal_strengths?;
let signal_strength = match rssi_res? { let list_networks = list_networks?;
None => None, let available_wifi = {
Some(x) if x <= -100 => Some(0 as usize), let mut wifi_list: Vec<WifiListOut> = signal_strengths
Some(x) if x >= -50 => Some(100 as usize), .clone()
Some(x) => Some(2 * (x + 100) as usize), .into_iter()
.filter(|(ssid, _)| !list_networks.contains_key(ssid))
.map(|(ssid, info)| WifiListOut {
ssid,
strength: info.strength,
security: info.security,
})
.collect();
wifi_list.sort_by_key(|x| x.strength);
wifi_list.reverse();
wifi_list
}; };
let ssids: HashMap<Ssid, SignalStrength> = list_networks
.into_keys()
.map(|x| {
let signal_strength = signal_strengths
.get(&x)
.map(|x| x.strength)
.unwrap_or_default();
(x, signal_strength)
})
.collect();
let current = current_res?;
Ok(WiFiInfo { Ok(WiFiInfo {
ssids: ssids_res?, ssids,
connected: current, connected: current.map(|x| x),
country: country_res?, country: country_res?,
ethernet: ethernet_res?, ethernet: ethernet_res?,
signal_strength, available_wifi,
}) })
} }
#[command(display(display_none))] #[command(rename = "get", display(display_wifi_list))]
#[instrument(skip(ctx))] #[instrument(skip(ctx))]
pub async fn get_available(
#[context] ctx: RpcContext,
#[allow(unused_variables)]
#[arg(long = "format")]
format: Option<IoFormat>,
) -> Result<Vec<WifiListOut>, Error> {
let wpa_supplicant = ctx.wifi_manager.read().await;
let (wifi_list, network_list) = tokio::join!(
wpa_supplicant.list_wifi_low(),
wpa_supplicant.list_networks_low()
);
let network_list = network_list?;
let mut wifi_list: Vec<WifiListOut> = wifi_list?
.into_iter()
.filter(|(ssid, _)| !network_list.contains_key(ssid))
.map(|(ssid, info)| WifiListOut {
ssid,
strength: info.strength,
security: info.security,
})
.collect();
wifi_list.sort_by_key(|x| x.strength);
wifi_list.reverse();
Ok(wifi_list)
}
#[command(rename = "set", display(display_none))]
pub async fn set_country( pub async fn set_country(
#[context] ctx: RpcContext, #[context] ctx: RpcContext,
#[arg(parse(country_code_parse))] country: CountryCode, #[arg(parse(country_code_parse))] country: CountryCode,
) -> Result<(), Error> { ) -> Result<(), Error> {
if !interface_connected("eth0").await? {
return Err(Error::new(
color_eyre::eyre::eyre!("Won't change country without hardwire connection"),
crate::ErrorKind::Wifi,
));
}
let mut wpa_supplicant = ctx.wifi_manager.write().await; let mut wpa_supplicant = ctx.wifi_manager.write().await;
wpa_supplicant.set_country_low(country.alpha2()).await wpa_supplicant.set_country_low(country.alpha2()).await?;
for (_ssid, network_id) in wpa_supplicant.list_networks_low().await? {
wpa_supplicant.remove_network_low(network_id).await?;
}
wpa_supplicant.remove_all_connections().await?;
wpa_supplicant.save_config(&mut ctx.db.handle()).await?;
Ok(())
} }
#[derive(Debug)] #[derive(Debug)]
pub struct WpaCli { pub struct WpaCli {
datadir: PathBuf,
interface: String, interface: String,
} }
#[derive(Clone)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct NetworkId(String); pub struct NetworkId(String);
pub enum NetworkAttr {
Ssid(String), /// Ssid are the names of the wifis, usually human readable.
Psk(String), #[derive(
Priority(isize), Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
ScanSsid(bool), )]
} pub struct Ssid(String);
impl NetworkAttr {
fn name(&self) -> &'static str { /// So a signal strength is a number between 0-100, I want the null option to be 0 since there is no signal
use NetworkAttr::*; #[derive(
match self { Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
Ssid(_) => "ssid", )]
Psk(_) => "psk", pub struct SignalStrength(u8);
Priority(_) => "priority",
ScanSsid(_) => "scan_ssid", impl SignalStrength {
} fn new(size: Option<u8>) -> Self {
} let size = match size {
fn value(&self) -> String { None => return Self(0),
use NetworkAttr::*; Some(x) => x,
match self { };
Ssid(s) => format!("\"{}\"", s), if size >= 100 {
Psk(s) => format!("\"{}\"", s), return Self(100);
Priority(n) => format!("{}", n),
ScanSsid(b) => {
if *b {
String::from("1")
} else {
String::from("0")
}
}
} }
Self(size)
} }
} }
impl Default for SignalStrength {
fn default() -> Self {
Self(0)
}
}
#[derive(Clone, Debug)]
pub struct Psk(String);
impl WpaCli { impl WpaCli {
pub fn init(interface: String, datadir: PathBuf) -> Self { pub fn init(interface: String) -> Self {
WpaCli { interface, datadir } WpaCli { interface }
} }
// Low Level #[instrument(skip(self, psk))]
pub async fn add_network_low(&mut self) -> Result<NetworkId, Error> { pub async fn set_add_network_low(&mut self, ssid: &Ssid, psk: &Psk) -> Result<(), Error> {
let r = Command::new("wpa_cli") let _ = Command::new("nmcli")
.arg("-i") .arg("-a")
.arg(&self.interface) .arg("-w")
.arg("add_network") .arg("30")
.arg("d")
.arg("wifi")
.arg("con")
.arg(&ssid.0)
.arg("password")
.arg(&psk.0)
.invoke(ErrorKind::Wifi) .invoke(ErrorKind::Wifi)
.await?; .await?;
let s = std::str::from_utf8(&r)?; Ok(())
Ok(NetworkId(s.trim().to_owned()))
} }
pub async fn set_network_low( #[instrument(skip(self, psk))]
&mut self, pub async fn add_network_low(&mut self, ssid: &Ssid, psk: &Psk) -> Result<(), Error> {
id: &NetworkId, let _ = Command::new("nmcli")
attr: &NetworkAttr, .arg("con")
) -> Result<(), Error> { .arg("add")
let _ = Command::new("wpa_cli") .arg("con-name")
.arg("-i") .arg(&ssid.0)
.arg("ifname")
.arg(&self.interface) .arg(&self.interface)
.arg("set_network") .arg("type")
.arg(&id.0) .arg("wifi")
.arg(attr.name()) .arg("ssid")
.arg(attr.value()) .arg(&ssid.0)
.invoke(ErrorKind::Wifi)
.await?;
let _ = Command::new("nmcli")
.arg("con")
.arg("modify")
.arg(&ssid.0)
.arg("wifi-sec.key-mgmt")
.arg("wpa-psk")
.invoke(ErrorKind::Wifi)
.await?;
let _ = Command::new("nmcli")
.arg("con")
.arg("modify")
.arg(&ssid.0)
.arg("wifi-sec.psk")
.arg(&psk.0)
.invoke(ErrorKind::Wifi) .invoke(ErrorKind::Wifi)
.await?; .await?;
Ok(()) Ok(())
} }
pub async fn set_country_low(&mut self, country_code: &str) -> Result<(), Error> { pub async fn set_country_low(&mut self, country_code: &str) -> Result<(), Error> {
let _ = Command::new("wpa_cli") let _ = Command::new("iw")
.arg("-i") .arg("reg")
.arg(&self.interface)
.arg("set") .arg("set")
.arg("country")
.arg(country_code) .arg(country_code)
.invoke(ErrorKind::Wifi) .invoke(ErrorKind::Wifi)
.await?; .await?;
Ok(()) Ok(())
} }
pub async fn get_country_low(&self) -> Result<CountryCode, Error> { pub async fn get_country_low(&self) -> Result<CountryCode, Error> {
let r = Command::new("wpa_cli") let r = Command::new("iw")
.arg("-i") .arg("reg")
.arg(&self.interface)
.arg("get") .arg("get")
.arg("country")
.invoke(ErrorKind::Wifi) .invoke(ErrorKind::Wifi)
.await?; .await?;
Ok(CountryCode::for_alpha2(&String::from_utf8(r)?).unwrap()) let r = String::from_utf8(r)?;
} lazy_static! {
pub async fn enable_network_low(&mut self, id: &NetworkId) -> Result<(), Error> { static ref RE: Regex = Regex::new("country (\\w+):").unwrap();
let _ = Command::new("wpa_cli") }
.arg("-i") let first_country = r
.arg(&self.interface) .lines()
.arg("enable_network") .filter(|s| s.contains("country"))
.arg(&id.0) .next()
.invoke(ErrorKind::Wifi) .ok_or_else(|| {
.await?; Error::new(
Ok(()) color_eyre::eyre::eyre!("Could not find a country config lines"),
} ErrorKind::Wifi,
pub async fn save_config_low(&mut self) -> Result<(), Error> { )
let _ = Command::new("wpa_cli") })?;
.arg("-i") let country = &RE.captures(first_country).ok_or_else(|| {
.arg(&self.interface) Error::new(
.arg("save_config") color_eyre::eyre::eyre!("Could not find a country config with regex"),
.invoke(ErrorKind::Wifi) ErrorKind::Wifi,
.await?; )
Ok(()) })?[1];
Ok(CountryCode::for_alpha2(country).or(Err(Error::new(
color_eyre::eyre::eyre!("Invalid Country Code: {}", country),
ErrorKind::Wifi,
)))?)
} }
pub async fn remove_network_low(&mut self, id: NetworkId) -> Result<(), Error> { pub async fn remove_network_low(&mut self, id: NetworkId) -> Result<(), Error> {
let _ = Command::new("wpa_cli") let _ = Command::new("nmcli")
.arg("-i") .arg("c")
.arg(&self.interface) .arg("del")
.arg("remove_network")
.arg(&id.0) .arg(&id.0)
.invoke(ErrorKind::Wifi) .invoke(ErrorKind::Wifi)
.await?; .await?;
Ok(()) Ok(())
} }
pub async fn reconfigure_low(&mut self) -> Result<(), Error> {
let _ = Command::new("wpa_cli")
.arg("-i")
.arg(&self.interface)
.arg("reconfigure")
.invoke(ErrorKind::Wifi)
.await?;
Ok(())
}
#[instrument] #[instrument]
pub async fn list_networks_low(&self) -> Result<BTreeMap<String, NetworkId>, Error> { pub async fn list_networks_low(&self) -> Result<BTreeMap<Ssid, NetworkId>, Error> {
let r = Command::new("wpa_cli") let r = Command::new("nmcli")
.arg("-i") .arg("-t")
.arg(&self.interface) .arg("c")
.arg("list_networks") .arg("show")
.invoke(ErrorKind::Wifi) .invoke(ErrorKind::Wifi)
.await?; .await?;
Ok(String::from_utf8(r)? Ok(String::from_utf8(r)?
.lines() .lines()
.skip(1)
.filter_map(|l| { .filter_map(|l| {
let mut cs = l.split("\t"); let mut cs = l.split(":");
let nid = NetworkId(cs.next()?.to_owned()); let name = Ssid(cs.next()?.to_owned());
let ssid = cs.next()?.to_owned(); let uuid = NetworkId(cs.next()?.to_owned());
Some((ssid, nid)) let _connection_type = cs.next()?;
let _device = cs.next()?;
Some((name, uuid))
}) })
.collect::<BTreeMap<String, NetworkId>>()) .collect::<BTreeMap<Ssid, NetworkId>>())
}
pub async fn select_network_low(&mut self, id: &NetworkId) -> Result<(), Error> {
let _ = Command::new("wpa_cli")
.arg("-i")
.arg(&self.interface)
.arg("select_network")
.arg(&id.0)
.invoke(ErrorKind::Wifi)
.await?;
Ok(())
}
pub async fn new_password_low(&mut self, id: &NetworkId, pass: &str) -> Result<(), Error> {
let _ = Command::new("wpa_cli")
.arg("-i")
.arg(&self.interface)
.arg("new_password")
.arg(&id.0)
.arg(pass)
.invoke(ErrorKind::Wifi)
.await?;
Ok(())
}
#[instrument]
pub async fn signal_poll_low(&self) -> Result<Option<isize>, Error> {
let r = Command::new("wpa_cli")
.arg("-i")
.arg(&self.interface)
.arg("signal_poll")
.invoke(ErrorKind::Wifi)
.await?;
let e = || {
Error::new(
color_eyre::eyre::eyre!("Invalid output from wpa_cli signal_poll"),
ErrorKind::Wifi,
)
};
let output = String::from_utf8(r)?;
Ok(if output.trim() == "FAIL" {
None
} else {
let l = output.lines().next().ok_or_else(e)?;
let rssi = l.split("=").nth(1).ok_or_else(e)?.parse()?;
Some(rssi)
})
} }
// High Level #[instrument]
pub async fn save_config(&mut self) -> Result<(), Error> { pub async fn list_wifi_low(&self) -> Result<WifiList, Error> {
self.save_config_low().await?; let r = Command::new("nmcli")
tokio::fs::copy( .arg("-g")
"/etc/wpa_supplicant.conf", .arg("SSID,SIGNAL,security")
self.datadir.join("wpa_supplicant.conf"), .arg("d")
) .arg("wifi")
.await?; .arg("list")
.invoke(ErrorKind::Wifi)
.await?;
Ok(String::from_utf8(r)?
.lines()
.filter_map(|l| {
let mut values = l.split(":");
let ssid = Ssid(values.next()?.to_owned());
let signal = SignalStrength::new(std::str::FromStr::from_str(values.next()?).ok());
let security: Vec<String> =
values.next()?.split(" ").map(|x| x.to_owned()).collect();
Some((
ssid,
WifiListInfo {
strength: signal,
security,
},
))
})
.collect::<WifiList>())
}
pub async fn select_network_low(&mut self, id: &NetworkId) -> Result<(), Error> {
let _ = Command::new("nmcli")
.arg("c")
.arg("up")
.arg(&id.0)
.invoke(ErrorKind::Wifi)
.await?;
Ok(()) Ok(())
} }
pub async fn check_network(&self, ssid: &str) -> Result<Option<NetworkId>, Error> { pub async fn remove_all_connections(&mut self) -> Result<(), Error> {
let location_connections = Path::new("/etc/NetworkManager/system-connections");
let mut connections = tokio::fs::read_dir(&location_connections).await?;
while let Some(connection) = connections.next_entry().await? {
let path = connection.path();
if path.is_file() {
let _ = tokio::fs::remove_file(&path).await?;
}
}
Ok(())
}
pub async fn save_config(&mut self, mut db: impl DbHandle) -> Result<(), Error> {
crate::db::DatabaseModel::new()
.server_info()
.last_wifi_region()
.put(&mut db, &Some(self.get_country_low().await?))
.await?;
Ok(())
}
pub async fn check_network(&self, ssid: &Ssid) -> Result<Option<NetworkId>, Error> {
Ok(self.list_networks_low().await?.remove(ssid)) Ok(self.list_networks_low().await?.remove(ssid))
} }
#[instrument] #[instrument(skip(db))]
pub async fn select_network(&mut self, ssid: &str) -> Result<bool, Error> { pub async fn select_network(&mut self, db: impl DbHandle, ssid: &Ssid) -> Result<bool, Error> {
let m_id = self.check_network(ssid).await?; let m_id = self.check_network(ssid).await?;
match m_id { match m_id {
None => Err(Error::new( None => Err(Error::new(
@@ -493,14 +614,14 @@ impl WpaCli {
)), )),
Some(x) => { Some(x) => {
self.select_network_low(&x).await?; self.select_network_low(&x).await?;
self.save_config().await?; self.save_config(db).await?;
let connect = async { let connect = async {
let mut current; let mut current;
loop { loop {
current = self.get_current_network().await; current = self.get_current_network().await;
match &current { match &current {
Ok(Some(ssid)) => { Ok(Some(ssid)) => {
tracing::debug!("Connected to: {}", ssid); tracing::debug!("Connected to: {}", ssid.0);
break; break;
} }
_ => {} _ => {}
@@ -517,13 +638,13 @@ impl WpaCli {
tracing::debug!("{:?}", res); tracing::debug!("{:?}", res);
Ok(match res { Ok(match res {
None => false, None => false,
Some(net) => net == ssid, Some(net) => &net == ssid,
}) })
} }
} }
} }
#[instrument] #[instrument]
pub async fn get_current_network(&self) -> Result<Option<String>, Error> { pub async fn get_current_network(&self) -> Result<Option<Ssid>, Error> {
let r = Command::new("iwgetid") let r = Command::new("iwgetid")
.arg(&self.interface) .arg(&self.interface)
.arg("--raw") .arg("--raw")
@@ -535,45 +656,42 @@ impl WpaCli {
if network.is_empty() { if network.is_empty() {
Ok(None) Ok(None)
} else { } else {
Ok(Some(network.to_owned())) Ok(Some(Ssid(network.to_owned())))
} }
} }
#[instrument] #[instrument(skip(db))]
pub async fn remove_network(&mut self, ssid: &str) -> Result<bool, Error> { pub async fn remove_network(&mut self, db: impl DbHandle, ssid: &Ssid) -> Result<bool, Error> {
match self.check_network(ssid).await? { match self.check_network(ssid).await? {
None => Ok(false), None => Ok(false),
Some(x) => { Some(x) => {
self.remove_network_low(x).await?; self.remove_network_low(x).await?;
self.save_config().await?; self.save_config(db).await?;
self.reconfigure_low().await?;
Ok(true) Ok(true)
} }
} }
} }
#[instrument] #[instrument(skip(psk, db))]
pub async fn add_network( pub async fn set_add_network(
&mut self, &mut self,
ssid: &str, db: impl DbHandle,
psk: &str, ssid: &Ssid,
psk: &Psk,
priority: isize, priority: isize,
) -> Result<(), Error> { ) -> Result<(), Error> {
use NetworkAttr::*; self.set_add_network_low(&ssid, &psk).await?;
let nid = match self.check_network(ssid).await? { self.save_config(db).await?;
None => { Ok(())
let nid = self.add_network_low().await?; }
self.set_network_low(&nid, &Ssid(ssid.to_owned())).await?; #[instrument(skip(psk, db))]
self.set_network_low(&nid, &Psk(psk.to_owned())).await?; pub async fn add_network(
self.set_network_low(&nid, &Priority(priority)).await?; &mut self,
self.set_network_low(&nid, &ScanSsid(true)).await?; db: impl DbHandle,
Result::<NetworkId, Error>::Ok(nid) ssid: &Ssid,
} psk: &Psk,
Some(nid) => { priority: isize,
self.new_password_low(&nid, psk).await?; ) -> Result<(), Error> {
Ok(nid) self.add_network_low(&ssid, &psk).await?;
} self.save_config(db).await?;
}?;
self.enable_network_low(&nid).await?;
self.save_config().await?;
Ok(()) Ok(())
} }
} }
@@ -599,21 +717,26 @@ pub fn country_code_parse(code: &str, _matches: &ArgMatches<'_>) -> Result<Count
} }
#[instrument(skip(main_datadir))] #[instrument(skip(main_datadir))]
pub async fn synchronize_wpa_supplicant_conf<P: AsRef<Path>>(main_datadir: P) -> Result<(), Error> { pub async fn synchronize_wpa_supplicant_conf<P: AsRef<Path>>(
let persistent = main_datadir.as_ref().join("wpa_supplicant.conf"); main_datadir: P,
last_country_code: &Option<CountryCode>,
) -> Result<(), Error> {
let persistent = main_datadir.as_ref().join("system-connections");
tracing::debug!("persistent: {:?}", persistent); tracing::debug!("persistent: {:?}", persistent);
let supplicant = Path::new("/etc/wpa_supplicant.conf");
if tokio::fs::metadata(&persistent).await.is_err() { if tokio::fs::metadata(&persistent).await.is_err() {
tokio::fs::write(&persistent, include_str!("wpa_supplicant.conf.base")).await?; tokio::fs::create_dir_all(&persistent).await?;
} }
let volatile = Path::new("/etc/wpa_supplicant.conf"); crate::disk::mount::util::bind(&persistent, "/etc/NetworkManager/system-connections", false)
tracing::debug!("link: {:?}", volatile); .await?;
if tokio::fs::metadata(&volatile).await.is_ok() { if tokio::fs::metadata(&supplicant).await.is_err() {
tokio::fs::remove_file(&volatile).await? tokio::fs::write(&supplicant, include_str!("wpa_supplicant.conf.base")).await?;
} }
tokio::fs::copy(&persistent, volatile).await?;
Command::new("systemctl") Command::new("systemctl")
.arg("restart") .arg("restart")
.arg("wpa_supplicant") .arg("NetworkManager")
.invoke(ErrorKind::Wifi) .invoke(ErrorKind::Wifi)
.await?; .await?;
Command::new("ifconfig") Command::new("ifconfig")
@@ -622,5 +745,14 @@ pub async fn synchronize_wpa_supplicant_conf<P: AsRef<Path>>(main_datadir: P) ->
.invoke(ErrorKind::Wifi) .invoke(ErrorKind::Wifi)
.await?; .await?;
Command::new("dhclient").invoke(ErrorKind::Wifi).await?; Command::new("dhclient").invoke(ErrorKind::Wifi).await?;
if let Some(last_country_code) = last_country_code {
tracing::info!("Setting the region");
let _ = Command::new("iw")
.arg("reg")
.arg("set")
.arg(last_country_code.alpha2())
.invoke(ErrorKind::Wifi)
.await?;
}
Ok(()) Ok(())
} }

View File

@@ -1,3 +1,5 @@
server {{ server {{
listen 443 ssl default_server; listen 443 ssl default_server;
listen [::]:443 ssl default_server; listen [::]:443 ssl default_server;
@@ -19,7 +21,8 @@ server {{
gzip on; gzip on;
gzip_vary on; gzip_vary on;
gzip_min_length 1024; gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml; gzip_types text/plain text/css text/xml text/javascript application/javascript image/svg+xml font/tts font/otf font/eot font/openttype application/x-javascript application/xml;
location /rpc/ {{ location /rpc/ {{
proxy_pass http://127.0.0.1:5959/; proxy_pass http://127.0.0.1:5959/;

View File

@@ -26,7 +26,8 @@ apt-get install -y \
ecryptfs-utils \ ecryptfs-utils \
cifs-utils \ cifs-utils \
samba-common-bin \ samba-common-bin \
ntp ntp \
network-manager
apt-get autoremove -y apt-get autoremove -y
apt-get upgrade -y apt-get upgrade -y
@@ -60,5 +61,6 @@ EOF
passwd -l ubuntu passwd -l ubuntu
echo 'overlayroot="tmpfs":swap=1,recurse=0' > /etc/overlayroot.local.conf echo 'overlayroot="tmpfs":swap=1,recurse=0' > /etc/overlayroot.local.conf
systemctl disable initialization.service systemctl disable initialization.service
sudo systemctl restart NetworkManager
sync sync
reboot reboot

270
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,12 @@
<ion-back-button defaultHref="embassy"></ion-back-button> <ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons> </ion-buttons>
<ion-title>WiFi Settings</ion-title> <ion-title>WiFi Settings</ion-title>
<ion-buttons slot="end">
<ion-button (click)="getWifi()">
Refresh
<ion-icon slot="end" name="refresh"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -40,25 +46,43 @@
<ion-skeleton-text animated style="width: 18%;"></ion-skeleton-text> <ion-skeleton-text animated style="width: 18%;"></ion-skeleton-text>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item-divider>Available Networks</ion-item-divider>
<ion-item *ngFor="let entry of ['', '']" class="skeleton-parts">
<ion-button slot="start" fill="clear">
<ion-skeleton-text animated style="width: 30px; height: 30px; border-radius: 0;"></ion-skeleton-text>
</ion-button>
<ion-label>
<ion-skeleton-text animated style="width: 18%;"></ion-skeleton-text>
</ion-label>
</ion-item>
</ng-container> </ng-container>
<!-- not loading --> <!-- not loading -->
<ng-container *ngIf="!loading && wifi.country"> <ng-container *ngIf="!loading && wifi.country">
<ion-item-divider>Saved Networks</ion-item-divider> <ion-item-divider>Saved Networks</ion-item-divider>
<ion-item button detail="false" *ngFor="let ssid of wifi.ssids | keyvalue" (click)="presentAction(ssid.key)">
<div *ngIf="ssid.key !== wifi.connected" slot="start" style="padding-right: 32px;"></div>
<ion-icon *ngIf="ssid.key === wifi.connected" slot="start" size="large" name="checkmark" color="success"></ion-icon>
<ion-label>{{ ssid.key }}</ion-label>
<img *ngIf="ssid.value > 0 && ssid.value < 5" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="ssid.value >= 5 && ssid.value < 50" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="ssid.value >= 50 && ssid.value < 90" slot="end" src="assets/img/icons/wifi-2.png" style="max-width: 32px;" />
<img *ngIf="ssid.value >= 90" slot="end" src="assets/img/icons/wifi-3.png" style="max-width: 32px;" />
</ion-item>
<ion-item-divider>Available Networks</ion-item-divider>
<ion-item button detail="false" *ngFor="let avWifi of wifi['available-wifi']" (click)="presentModalAdd(avWifi.ssid, !!avWifi.security.length)">
<ion-icon slot="start" name="add" size="large"></ion-icon>
<ion-label>{{ avWifi.ssid }}</ion-label>
<img *ngIf="avWifi.strength < 5" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="avWifi.strength >= 5 && avWifi.strength < 50" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="avWifi.strength >= 50 && avWifi.strength < 90" slot="end" src="assets/img/icons/wifi-2.png" style="max-width: 32px;" />
<img *ngIf="avWifi.strength >= 90" slot="end" src="assets/img/icons/wifi-3.png" style="max-width: 32px;" />
</ion-item>
<ion-item button detail="false" (click)="presentModalAdd()"> <ion-item button detail="false" (click)="presentModalAdd()">
<ion-icon slot="start" name="add" size="large"></ion-icon> <ion-icon slot="start" name="add" size="large"></ion-icon>
<ion-label>Add new network</ion-label> <ion-label>Other</ion-label>
</ion-item>
<ion-item button detail="false" *ngFor="let ssid of wifi.ssids; let i = index;" (click)="presentAction(ssid, i)">
<div *ngIf="ssid !== wifi.connected" slot="start" style="padding-right: 32px;"></div>
<ion-icon *ngIf="ssid === wifi.connected" slot="start" size="large" name="checkmark" color="success"></ion-icon>
<ion-label>{{ ssid }}</ion-label>
<ng-container *ngIf="ssid === wifi.connected && wifi['signal-strength'] as strength">
<img *ngIf="strength < 5" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="strength >= 5 && strength < 50" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="strength >= 50 && strength < 90" slot="end" src="assets/img/icons/wifi-2.png" style="max-width: 32px;" />
<img *ngIf="strength >= 90" slot="end" src="assets/img/icons/wifi-3.png" style="max-width: 32px;" />
</ng-container>
</ion-item> </ion-item>
</ng-container> </ng-container>
</ion-item-group> </ion-item-group>

View File

@@ -8,6 +8,7 @@ import { ValueSpecObject } from 'src/app/pkg-config/config-types'
import { RR } from 'src/app/services/api/api.types' import { RR } from 'src/app/services/api/api.types'
import { pauseFor } from 'src/app/util/misc.util' import { pauseFor } from 'src/app/util/misc.util'
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
import { ConfigService } from 'src/app/services/config.service'
@Component({ @Component({
selector: 'wifi', selector: 'wifi',
@@ -27,6 +28,7 @@ export class WifiPage {
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly errToast: ErrorToastService, private readonly errToast: ErrorToastService,
private readonly actionCtrl: ActionSheetController, private readonly actionCtrl: ActionSheetController,
private readonly config: ConfigService,
) { } ) { }
async ngOnInit () { async ngOnInit () {
@@ -47,6 +49,23 @@ export class WifiPage {
} }
async presentAlertCountry (): Promise<void> { async presentAlertCountry (): Promise<void> {
if (!this.config.isLan) {
const alert = await this.alertCtrl.create({
header: 'Cannot Complete Action',
message: 'You must be connected to your Emassy via LAN to change the country.',
buttons: [
{
text: 'OK',
role: 'cancel',
},
],
cssClass: 'wide-alert enter-click',
})
await alert.present()
return
}
const inputs: AlertInput[] = Object.entries(this.countries).map(([country, fullName]) => { const inputs: AlertInput[] = Object.entries(this.countries).map(([country, fullName]) => {
return { return {
name: fullName, name: fullName,
@@ -59,6 +78,7 @@ export class WifiPage {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: 'Select Country', header: 'Select Country',
message: 'Warning: Changing the country will delete all saved networks from the Embassy.',
inputs, inputs,
buttons: [ buttons: [
{ {
@@ -77,7 +97,8 @@ export class WifiPage {
await alert.present() await alert.present()
} }
async presentModalAdd () { async presentModalAdd (ssid?: string, needsPW: boolean = true) {
const wifiSpec = getWifiValueSpec(ssid, needsPW)
const modal = await this.modalCtrl.create({ const modal = await this.modalCtrl.create({
component: GenericFormPage, component: GenericFormPage,
componentProps: { componentProps: {
@@ -104,14 +125,14 @@ export class WifiPage {
await modal.present() await modal.present()
} }
async presentAction (ssid: string, i: number) { async presentAction (ssid: string) {
const buttons: ActionSheetButton[] = [ const buttons: ActionSheetButton[] = [
{ {
text: 'Forget', text: 'Forget',
icon: 'trash', icon: 'trash',
role: 'destructive', role: 'destructive',
handler: () => { handler: () => {
this.delete(ssid, i) this.delete(ssid)
}, },
}, },
] ]
@@ -147,6 +168,7 @@ export class WifiPage {
try { try {
await this.api.setWifiCountry({ country }) await this.api.setWifiCountry({ country })
await this.getWifi(4000)
this.wifi.country = country this.wifi.country = country
} catch (e) { } catch (e) {
this.errToast.present(e) this.errToast.present(e)
@@ -164,7 +186,7 @@ export class WifiPage {
if (attempts > maxAttempts) { if (attempts > maxAttempts) {
this.presentToastFail() this.presentToastFail()
if (deleteOnFailure) { if (deleteOnFailure) {
this.wifi.ssids = this.wifi.ssids.filter(s => s !== ssid) delete this.wifi.ssids[ssid]
} }
break break
} }
@@ -243,7 +265,7 @@ export class WifiPage {
} }
} }
private async delete (ssid: string, i: number): Promise<void> { private async delete (ssid: string): Promise<void> {
const loader = await this.loadingCtrl.create({ const loader = await this.loadingCtrl.create({
spinner: 'lines', spinner: 'lines',
message: 'Deleting...', message: 'Deleting...',
@@ -253,7 +275,8 @@ export class WifiPage {
try { try {
await this.api.deleteWifi({ ssid }) await this.api.deleteWifi({ ssid })
this.wifi.ssids = this.wifi.ssids.filter((w, index) => index !== i) await this.getWifi(4000)
delete this.wifi.ssids[ssid]
} catch (e) { } catch (e) {
this.errToast.present(e) this.errToast.present(e)
} finally { } finally {
@@ -276,7 +299,7 @@ export class WifiPage {
priority: 0, priority: 0,
connect: false, connect: false,
}) })
await this.getWifi() await this.getWifi(4000)
} catch (e) { } catch (e) {
this.errToast.present(e) this.errToast.present(e)
} finally { } finally {
@@ -310,25 +333,29 @@ export class WifiPage {
} }
} }
const wifiSpec: ValueSpecObject = { function getWifiValueSpec (ssid?: string, needsPW: boolean = true): ValueSpecObject {
type: 'object', return {
name: 'WiFi Credentials', type: 'object',
description: 'Enter the network SSID and password. You can connect now or save the network for later.', name: 'WiFi Credentials',
'unique-by': null, description: 'Enter the network SSID and password. You can connect now or save the network for later.',
spec: { 'unique-by': null,
ssid: { spec: {
type: 'string', ssid: {
name: 'Network SSID', type: 'string',
nullable: false, name: 'Network SSID',
masked: false, nullable: false,
copyable: false, masked: false,
copyable: false,
default: ssid,
},
password: {
type: 'string',
name: 'Password',
nullable: !needsPW,
masked: true,
copyable: false,
},
}, },
password: { }
type: 'string',
name: 'Password',
nullable: false,
masked: true,
copyable: false,
},
},
} }

View File

@@ -983,10 +983,24 @@ export module Mock {
export const Wifi: RR.GetWifiRes = { export const Wifi: RR.GetWifiRes = {
ethernet: true, ethernet: true,
ssids: ['Goosers', 'Goosers5G'], ssids: {
'Goosers': 50,
'Goosers5G': 0,
},
connected: 'Goosers', connected: 'Goosers',
country: 'US', country: 'US',
'signal-strength': 50, 'available-wifi': [
{
ssid: 'Goosers a billion',
strength: 40,
security: [],
},
{
ssid: 'Bill nye the wifi guy',
strength: 99,
security: ['1', '2', '3'],
},
],
} }
export const BackupTargets: RR.GetBackupTargetsRes = { export const BackupTargets: RR.GetBackupTargetsRes = {

View File

@@ -83,13 +83,15 @@ export module RR {
export type SetWifiCountryRes = null export type SetWifiCountryRes = null
export type GetWifiReq = { } export type GetWifiReq = { }
export type GetWifiRes = { // wifi.get export type GetWifiRes = {
ethernet: boolean ssids: {
ssids: string[] [ssid: string]: number
connected: string | null },
country: string | null connected?: string,
'signal-strength': number country: string,
} ethernet: boolean,
'available-wifi': AvailableWifi[]
}
export type AddWifiReq = { // wifi.add export type AddWifiReq = { // wifi.add
ssid: string ssid: string
@@ -442,3 +444,9 @@ export interface BackupReport {
} }
} }
} }
export interface AvailableWifi {
ssid: string
strength: number
security: string []
}