mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Wifi optional (#2249)
* begin work * allow enable and disable wifi * nice styling * done except for popover not dismissing * update wifi.ts * address comments
This commit is contained in:
committed by
Aiden McClelland
parent
f6c09109ba
commit
9499ea8ca9
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { WifiPage } from './wifi.page'
|
||||
import { WifiPage, ToWifiIconPipe } from './wifi.page'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -19,6 +19,6 @@ const routes: Routes = [
|
||||
RouterModule.forChild(routes),
|
||||
SharedPipesModule,
|
||||
],
|
||||
declarations: [WifiPage],
|
||||
declarations: [WifiPage, ToWifiIconPipe],
|
||||
})
|
||||
export class WifiPageModule {}
|
||||
|
||||
@@ -4,171 +4,156 @@
|
||||
<ion-back-button defaultHref="system"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<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-header>
|
||||
|
||||
<ion-content class="ion-padding-top with-widgets">
|
||||
<ion-item-group>
|
||||
<!-- always -->
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>
|
||||
Adding WiFi credentials to your StartOS allows you to remove the
|
||||
Ethernet cable and move the device anywhere you want. StartOS will
|
||||
automatically connect to available networks.
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/wifi"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
<ion-content class="with-widgets">
|
||||
<div class="smaller" *tuiLet="enabled$ | async as enabled">
|
||||
<ion-item-group>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>
|
||||
Adding WiFi credentials to your Embassy allows you to remove the
|
||||
Ethernet cable and move the device anywhere you want. Embassy will
|
||||
automatically connect to available networks.
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/wifi"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View instructions
|
||||
</a>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label><b>Wi-Fi</b></ion-label>
|
||||
<ion-toggle
|
||||
slot="end"
|
||||
[checked]="enabled"
|
||||
(ionChange)="toggleWifi($any($event))"
|
||||
></ion-toggle>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
<ng-container *ngIf="enabled">
|
||||
<ng-container *ngIf="wifi$ | async as wifi; else loading">
|
||||
<!-- known networks -->
|
||||
<ion-item-divider *ngIf="!(wifi.ssids | empty)">
|
||||
Known Networks
|
||||
</ion-item-divider>
|
||||
<ion-item-group>
|
||||
<ion-item *ngFor="let ssid of wifi.ssids | keyvalue">
|
||||
<ion-label>
|
||||
<h2>{{ ssid.key }}</h2>
|
||||
<p *ngIf="ssid.key === wifi.connected" class="inline">
|
||||
<ion-icon
|
||||
color="success"
|
||||
name="ellipse"
|
||||
style="padding-right: 4px"
|
||||
></ion-icon>
|
||||
<ion-text color="dark"><b>Connected</b></ion-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
<div slot="end" class="inline slot-end">
|
||||
<img [src]="ssid.value | toWifiIcon" style="max-width: 32px" />
|
||||
<ion-button
|
||||
[id]="ssid.key"
|
||||
style="--padding-start: 6px; --padding-end: 4px"
|
||||
fill="clear"
|
||||
>
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
name="ellipsis-horizontal-circle"
|
||||
></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
<ion-popover [trigger]="ssid.key" triggerAction="click">
|
||||
<ng-template>
|
||||
<ion-content>
|
||||
<ion-item
|
||||
button
|
||||
*ngIf="ssid.key !== wifi.connected"
|
||||
(click)="connect(ssid.key)"
|
||||
>
|
||||
<ion-label>Connect</ion-label>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
button
|
||||
lines="none"
|
||||
(click)="forget(ssid.key, wifi)"
|
||||
>
|
||||
<ion-label>Forget this network</ion-label>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
</ng-template>
|
||||
</ion-popover>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
<!-- other networks -->
|
||||
<ion-item-divider>Other Networks</ion-item-divider>
|
||||
<ion-item-group>
|
||||
<ng-container *ngFor="let avWifi of wifi['available-wifi']">
|
||||
<ion-item *ngIf="avWifi.ssid">
|
||||
<ion-label>{{ avWifi.ssid }}</ion-label>
|
||||
<div slot="end" class="inline slot-end">
|
||||
<ion-button
|
||||
color="dark"
|
||||
class="connect-button"
|
||||
(click)="presentModalAdd(avWifi)"
|
||||
>
|
||||
Connect
|
||||
</ion-button>
|
||||
<ion-icon
|
||||
style="margin-right: 12px"
|
||||
[name]="avWifi.security.length ? 'lock-closed-outline' : 'lock-open-outline'"
|
||||
></ion-icon>
|
||||
<img
|
||||
[src]="avWifi.strength | toWifiIcon"
|
||||
style="max-width: 32px"
|
||||
/>
|
||||
</div>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
<div class="ion-text-end ion-padding-top">
|
||||
<ion-button
|
||||
(click)="presentModalAddOther(wifi)"
|
||||
color="dark"
|
||||
strong
|
||||
size="small"
|
||||
style="font-size: 12px"
|
||||
>
|
||||
View instructions
|
||||
</a>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider>Country</ion-item-divider>
|
||||
|
||||
<!-- not loading -->
|
||||
<ion-item
|
||||
button
|
||||
detail="false"
|
||||
(click)="presentAlertCountry()"
|
||||
[disabled]="loading"
|
||||
>
|
||||
<ion-icon slot="start" name="earth-outline" size="large"></ion-icon>
|
||||
<ion-label *ngIf="wifi.country">
|
||||
{{ wifi.country }} - {{ this.countries[wifi.country] }}
|
||||
</ion-label>
|
||||
<ion-label *ngIf="!wifi.country">Select Country</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- loading -->
|
||||
<ng-container *ngIf="loading">
|
||||
<ion-item-divider>Saved 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>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- not loading -->
|
||||
<ng-container *ngIf="!loading && wifi.country">
|
||||
<ion-item-divider *ngIf="!(wifi.ssids | empty)">
|
||||
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>
|
||||
<ng-container *ngFor="let avWifi of wifi['available-wifi']">
|
||||
<ion-item
|
||||
button
|
||||
detail="false"
|
||||
(click)="presentModalAdd(avWifi.ssid, !!avWifi.security.length)"
|
||||
*ngIf="avWifi.ssid"
|
||||
>
|
||||
<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>
|
||||
Other...
|
||||
</ion-button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ion-item button detail="false" (click)="presentModalAdd()">
|
||||
<ion-icon slot="start" name="add" color="dark"></ion-icon>
|
||||
<ion-label>
|
||||
<b>Join Another Network</b>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- loading -->
|
||||
<ng-template #loading>
|
||||
<ion-item-divider>Known Networks</ion-item-divider>
|
||||
<ion-item-group>
|
||||
<ion-item *ngFor="let entry of ['', '']" class="skeleton-parts">
|
||||
<ion-label>
|
||||
<ion-skeleton-text
|
||||
animated
|
||||
style="width: 25%"
|
||||
></ion-skeleton-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
<ion-item-divider>Other Networks</ion-item-divider>
|
||||
<ion-item-group>
|
||||
<ion-item *ngFor="let entry of ['', '', '']" class="skeleton-parts">
|
||||
<ion-label>
|
||||
<ion-skeleton-text
|
||||
animated
|
||||
style="width: 25%"
|
||||
></ion-skeleton-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,7 +1,41 @@
|
||||
.smaller {
|
||||
padding: 32px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.no-padding {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.skeleton-parts {
|
||||
ion-button::part(native) {
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: 0;
|
||||
};
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.connect-button {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.slot-end {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
ion-item-divider {
|
||||
text-transform: unset;
|
||||
padding-bottom: 12px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
ion-item-group {
|
||||
background-color: #1e2024;
|
||||
border: 1px solid #717171;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--background: #1e2024;
|
||||
}
|
||||
@@ -1,20 +1,30 @@
|
||||
import { Component } from '@angular/core'
|
||||
import {
|
||||
ActionSheetController,
|
||||
AlertController,
|
||||
ToastController,
|
||||
} from '@ionic/angular'
|
||||
import { AlertInput } from '@ionic/core'
|
||||
import { ToastController } from '@ionic/angular'
|
||||
import { TuiDialogOptions } from '@taiga-ui/core'
|
||||
import { ToggleCustomEvent } from '@ionic/core'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ActionSheetButton } from '@ionic/core'
|
||||
import { ValueSpecObject } from 'start-sdk/lib/config/configTypes'
|
||||
import { RR } from 'src/app/services/api/api.types'
|
||||
import { AvailableWifi, RR } from 'src/app/services/api/api.types'
|
||||
import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { FormContext, FormPage } from 'src/app/modals/form/form.page'
|
||||
import { LoadingService } from 'src/app/modals/loading/loading.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { ConnectionService } from 'src/app/services/connection.service'
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import {
|
||||
BehaviorSubject,
|
||||
catchError,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
from,
|
||||
merge,
|
||||
Observable,
|
||||
Subject,
|
||||
switchMap,
|
||||
tap,
|
||||
} from 'rxjs'
|
||||
import { wifiSpec } from './wifiSpec'
|
||||
|
||||
interface WiFiForm {
|
||||
ssid: string
|
||||
@@ -27,101 +37,106 @@ interface WiFiForm {
|
||||
styleUrls: ['wifi.page.scss'],
|
||||
})
|
||||
export class WifiPage {
|
||||
loading = true
|
||||
wifi: RR.GetWifiRes = {} as any
|
||||
countries = require('../../../util/countries.json') as {
|
||||
[key: string]: string
|
||||
}
|
||||
readonly connected$ = this.connectionService.connected$.pipe(filter(Boolean))
|
||||
readonly enabled$ = this.patch.watch$('server-info', 'wifi-enabled').pipe(
|
||||
distinctUntilChanged(),
|
||||
tap(enabled => {
|
||||
if (enabled) this.trigger$.next('')
|
||||
}),
|
||||
)
|
||||
readonly trigger$ = new BehaviorSubject('')
|
||||
readonly localChanges$ = new Subject<RR.GetWifiRes>()
|
||||
readonly wifi$ = merge(
|
||||
this.trigger$.pipe(switchMap(() => this.getWifi$())),
|
||||
this.localChanges$,
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly loader: LoadingService,
|
||||
private readonly formDialog: FormDialogService,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly actionCtrl: ActionSheetController,
|
||||
private readonly config: ConfigService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly connectionService: ConnectionService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.getWifi()
|
||||
}
|
||||
async toggleWifi(e: ToggleCustomEvent): Promise<void> {
|
||||
const enable = e.detail.checked
|
||||
const loader = this.loader
|
||||
.open(enable ? 'Enabling Wifi' : 'Disabling WiFi')
|
||||
.subscribe()
|
||||
|
||||
async getWifi(timeout: number = 0): Promise<void> {
|
||||
this.loading = true
|
||||
try {
|
||||
this.wifi = await this.api.getWifi({}, timeout)
|
||||
if (!this.wifi.country) {
|
||||
await this.presentAlertCountry()
|
||||
}
|
||||
await this.api.enableWifi({ enable })
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
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 server via LAN to change the country.',
|
||||
buttons: [
|
||||
{
|
||||
text: 'OK',
|
||||
role: 'cancel',
|
||||
},
|
||||
],
|
||||
cssClass: 'enter-click',
|
||||
})
|
||||
await alert.present()
|
||||
return
|
||||
async connect(ssid: string): Promise<void> {
|
||||
const loader = this.loader
|
||||
.open('Connecting. This could take a while...')
|
||||
.subscribe()
|
||||
|
||||
try {
|
||||
await this.api.connectWifi({ ssid })
|
||||
await this.confirmWifi(ssid)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
|
||||
const inputs: AlertInput[] = Object.entries(this.countries).map(
|
||||
([country, fullName]) => {
|
||||
return {
|
||||
name: fullName,
|
||||
type: 'radio',
|
||||
label: `${country} - ${fullName}`,
|
||||
value: country,
|
||||
checked: country === this.wifi.country,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Select Country',
|
||||
subHeader:
|
||||
'Warning: Changing the country will delete all saved networks from StartOS.',
|
||||
inputs,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Save',
|
||||
handler: (country: string) => this.setCountry(country),
|
||||
},
|
||||
],
|
||||
cssClass: 'enter-click select-warning',
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
presentModalAdd(ssid?: string, needsPW: boolean = true) {
|
||||
const { name, spec } = getWifiValueSpec(ssid, needsPW)
|
||||
async forget(ssid: string, wifi: RR.GetWifiRes): Promise<void> {
|
||||
const loader = this.loader.open('Deleting...').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.deleteWifi({ ssid })
|
||||
delete wifi.ssids[ssid]
|
||||
this.localChanges$.next(wifi)
|
||||
this.trigger$.next('')
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
async presentModalAdd(network: AvailableWifi) {
|
||||
if (!network.security.length) {
|
||||
this.connect(network.ssid)
|
||||
} else {
|
||||
const options: Partial<TuiDialogOptions<FormContext<WiFiForm>>> = {
|
||||
label: 'Password Needed',
|
||||
data: {
|
||||
spec: wifiSpec.spec,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Connect',
|
||||
handler: async ({ ssid, password }) =>
|
||||
this.saveAndConnect(ssid, password),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
this.formDialog.open(FormPage, options)
|
||||
}
|
||||
}
|
||||
|
||||
presentModalAddOther(wifi: RR.GetWifiRes) {
|
||||
const options: Partial<TuiDialogOptions<FormContext<WiFiForm>>> = {
|
||||
label: name,
|
||||
label: wifiSpec.name,
|
||||
data: {
|
||||
spec,
|
||||
spec: wifiSpec.spec,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Save for Later',
|
||||
handler: async ({ ssid, password }) => this.save(ssid, password),
|
||||
handler: async ({ ssid, password }) =>
|
||||
this.save(ssid, password, wifi),
|
||||
},
|
||||
{
|
||||
text: 'Save and Connect',
|
||||
@@ -131,107 +146,36 @@ export class WifiPage {
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
this.formDialog.open(FormPage, options)
|
||||
}
|
||||
|
||||
async presentAction(ssid: string) {
|
||||
const buttons: ActionSheetButton[] = [
|
||||
{
|
||||
text: 'Forget',
|
||||
icon: 'trash',
|
||||
role: 'destructive',
|
||||
handler: () => {
|
||||
this.delete(ssid)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
if (ssid !== this.wifi.connected) {
|
||||
buttons.unshift({
|
||||
text: 'Connect',
|
||||
icon: 'wifi',
|
||||
handler: () => {
|
||||
this.connect(ssid)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const action = await this.actionCtrl.create({
|
||||
header: ssid,
|
||||
subHeader: 'Manage network',
|
||||
mode: 'ios',
|
||||
buttons,
|
||||
})
|
||||
|
||||
await action.present()
|
||||
private getWifi$(): Observable<RR.GetWifiRes> {
|
||||
return from(this.api.getWifi({}, 10000)).pipe(
|
||||
catchError((e: any) => {
|
||||
this.errToast.present(e)
|
||||
return []
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
private async setCountry(country: string): Promise<void> {
|
||||
const loader = this.loader.open('Setting country...').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.setWifiCountry({ country })
|
||||
await this.getWifi()
|
||||
this.wifi.country = country
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
private async confirmWifi(
|
||||
ssid: string,
|
||||
deleteOnFailure = false,
|
||||
): Promise<void> {
|
||||
const maxAttempts = 5
|
||||
let attempts = 0
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
if (attempts > maxAttempts) {
|
||||
this.presentToastFail()
|
||||
if (deleteOnFailure) {
|
||||
delete this.wifi.ssids[ssid]
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
try {
|
||||
const start = new Date().valueOf()
|
||||
await this.getWifi()
|
||||
const end = new Date().valueOf()
|
||||
if (this.wifi.connected === ssid) {
|
||||
this.presentAlertSuccess(ssid)
|
||||
break
|
||||
} else {
|
||||
attempts++
|
||||
const diff = end - start
|
||||
// depending on the response time, wait a min of 1000 ms, and a max of 4000 ms in between retries. Both 1000 and 4000 are arbitrary
|
||||
await pauseFor(Math.max(1000, 4000 - diff))
|
||||
}
|
||||
} catch (e) {
|
||||
attempts++
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async presentAlertSuccess(ssid: string): Promise<void> {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: `Connected to "${ssid}"`,
|
||||
message:
|
||||
'Note. It may take several minutes to an hour for StartOS to reconnect over Tor.',
|
||||
private async presentToastSuccess(): Promise<void> {
|
||||
const toast = await this.toastCtrl.create({
|
||||
header: 'Connection successful!',
|
||||
position: 'bottom',
|
||||
duration: 4000,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Ok',
|
||||
role: 'cancel',
|
||||
cssClass: 'enter-click',
|
||||
side: 'start',
|
||||
icon: 'close',
|
||||
handler: () => {
|
||||
return true
|
||||
},
|
||||
},
|
||||
],
|
||||
cssClass: 'success-toast',
|
||||
})
|
||||
|
||||
await alert.present()
|
||||
await toast.present()
|
||||
}
|
||||
|
||||
private async presentToastFail(): Promise<void> {
|
||||
@@ -255,36 +199,11 @@ export class WifiPage {
|
||||
await toast.present()
|
||||
}
|
||||
|
||||
private async connect(ssid: string): Promise<void> {
|
||||
const loader = this.loader
|
||||
.open('Connecting. This could take a while...')
|
||||
.subscribe()
|
||||
|
||||
try {
|
||||
await this.api.connectWifi({ ssid })
|
||||
await this.confirmWifi(ssid)
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
private async delete(ssid: string): Promise<void> {
|
||||
const loader = this.loader.open('Deleting...').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.deleteWifi({ ssid })
|
||||
await this.getWifi()
|
||||
delete this.wifi.ssids[ssid]
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
private async save(ssid: string, password: string): Promise<boolean> {
|
||||
private async save(
|
||||
ssid: string,
|
||||
password: string,
|
||||
wifi: RR.GetWifiRes,
|
||||
): Promise<boolean> {
|
||||
const loader = this.loader.open('Saving...').subscribe()
|
||||
|
||||
try {
|
||||
@@ -294,7 +213,9 @@ export class WifiPage {
|
||||
priority: 0,
|
||||
connect: false,
|
||||
})
|
||||
await this.getWifi()
|
||||
wifi.ssids[ssid] = 0
|
||||
this.localChanges$.next(wifi)
|
||||
this.trigger$.next('')
|
||||
return true
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
@@ -319,7 +240,7 @@ export class WifiPage {
|
||||
priority: 0,
|
||||
connect: true,
|
||||
})
|
||||
await this.confirmWifi(ssid, true)
|
||||
await this.confirmWifi(ssid)
|
||||
return true
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
@@ -328,52 +249,52 @@ export class WifiPage {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getWifiValueSpec(
|
||||
ssid?: string,
|
||||
needsPW: boolean = true,
|
||||
): ValueSpecObject {
|
||||
return {
|
||||
type: 'object',
|
||||
name: 'WiFi Credentials',
|
||||
description:
|
||||
'Enter the network SSID and password. You can connect now or save the network for later.',
|
||||
warning: null,
|
||||
spec: {
|
||||
ssid: {
|
||||
type: 'text',
|
||||
name: 'Network SSID',
|
||||
description: null,
|
||||
inputmode: 'text',
|
||||
placeholder: null,
|
||||
patterns: [],
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
required: true,
|
||||
masked: false,
|
||||
default: ssid || null,
|
||||
warning: null,
|
||||
},
|
||||
password: {
|
||||
type: 'text',
|
||||
name: 'Password',
|
||||
description: null,
|
||||
inputmode: 'text',
|
||||
placeholder: null,
|
||||
required: needsPW,
|
||||
masked: true,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [
|
||||
{
|
||||
regex: '^.{8,}$',
|
||||
description: 'Must be longer than 8 characters',
|
||||
},
|
||||
],
|
||||
default: null,
|
||||
warning: null,
|
||||
},
|
||||
},
|
||||
private async confirmWifi(ssid: string): Promise<void> {
|
||||
const maxAttempts = 5
|
||||
let attempts = 0
|
||||
|
||||
while (true) {
|
||||
if (attempts > maxAttempts) {
|
||||
this.presentToastFail()
|
||||
break
|
||||
}
|
||||
|
||||
try {
|
||||
const start = new Date().valueOf()
|
||||
const newWifi = await this.api.getWifi({}, 10000)
|
||||
const end = new Date().valueOf()
|
||||
if (newWifi.connected === ssid) {
|
||||
this.localChanges$.next(newWifi)
|
||||
this.presentToastSuccess()
|
||||
break
|
||||
} else {
|
||||
attempts++
|
||||
const diff = end - start
|
||||
// depending on the response time, wait a min of 1000 ms, and a max of 4000 ms in between retries. Both 1000 and 4000 are arbitrary
|
||||
await pauseFor(Math.max(1000, 4000 - diff))
|
||||
}
|
||||
} catch (e) {
|
||||
attempts++
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'toWifiIcon',
|
||||
})
|
||||
export class ToWifiIconPipe implements PipeTransform {
|
||||
transform(signal: number): string {
|
||||
if (signal >= 0 && signal < 5) {
|
||||
return 'assets/img/icons/wifi-0.png'
|
||||
} else if (signal >= 5 && signal < 50) {
|
||||
return 'assets/img/icons/wifi-1.png'
|
||||
} else if (signal >= 50 && signal < 90) {
|
||||
return 'assets/img/icons/wifi-2.png'
|
||||
} else {
|
||||
return 'assets/img/icons/wifi-3.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ValueSpecObject } from 'start-sdk/lib/config/configTypes'
|
||||
|
||||
export const wifiSpec: ValueSpecObject = {
|
||||
type: 'object',
|
||||
name: 'WiFi Credentials',
|
||||
description:
|
||||
'Enter the network SSID and password. You can connect now or save the network for later.',
|
||||
warning: null,
|
||||
spec: {
|
||||
ssid: {
|
||||
type: 'text',
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [],
|
||||
name: 'Network SSID',
|
||||
description: null,
|
||||
inputmode: 'text',
|
||||
placeholder: null,
|
||||
required: true,
|
||||
masked: false,
|
||||
default: null,
|
||||
warning: null,
|
||||
},
|
||||
password: {
|
||||
type: 'text',
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [
|
||||
{
|
||||
regex: '^.{8,}$',
|
||||
description: 'Must be longer than 8 characters',
|
||||
},
|
||||
],
|
||||
name: 'Password',
|
||||
description: null,
|
||||
inputmode: 'text',
|
||||
placeholder: null,
|
||||
required: true,
|
||||
masked: true,
|
||||
default: null,
|
||||
warning: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -104,9 +104,6 @@ export module RR {
|
||||
|
||||
// wifi
|
||||
|
||||
export type SetWifiCountryReq = { country: string }
|
||||
export type SetWifiCountryRes = null
|
||||
|
||||
export type GetWifiReq = {}
|
||||
export type GetWifiRes = {
|
||||
ssids: {
|
||||
@@ -127,6 +124,9 @@ export module RR {
|
||||
}
|
||||
export type AddWifiRes = null
|
||||
|
||||
export type EnableWifiReq = { enable: boolean } // wifi.enable
|
||||
export type EnableWifiRes = null
|
||||
|
||||
export type ConnectWifiReq = { ssid: string } // wifi.connect
|
||||
export type ConnectWifiRes = null
|
||||
|
||||
|
||||
@@ -147,15 +147,13 @@ export abstract class ApiService {
|
||||
|
||||
// wifi
|
||||
|
||||
abstract enableWifi(params: RR.EnableWifiReq): Promise<RR.EnableWifiRes>
|
||||
|
||||
abstract getWifi(
|
||||
params: RR.GetWifiReq,
|
||||
timeout: number,
|
||||
): Promise<RR.GetWifiRes>
|
||||
|
||||
abstract setWifiCountry(
|
||||
params: RR.SetWifiCountryReq,
|
||||
): Promise<RR.SetWifiCountryRes>
|
||||
|
||||
abstract addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes>
|
||||
|
||||
abstract connectWifi(params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes>
|
||||
|
||||
@@ -266,6 +266,10 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
// wifi
|
||||
|
||||
async enableWifi(params: RR.EnableWifiReq): Promise<RR.EnableWifiRes> {
|
||||
return this.rpcRequest({ method: 'wifi.enable', params })
|
||||
}
|
||||
|
||||
async getWifi(
|
||||
params: RR.GetWifiReq,
|
||||
timeout?: number,
|
||||
@@ -273,12 +277,6 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'wifi.get', params, timeout })
|
||||
}
|
||||
|
||||
async setWifiCountry(
|
||||
params: RR.SetWifiCountryReq,
|
||||
): Promise<RR.SetWifiCountryRes> {
|
||||
return this.rpcRequest({ method: 'wifi.country.set', params })
|
||||
}
|
||||
|
||||
async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
|
||||
return this.rpcRequest({ method: 'wifi.add', params })
|
||||
}
|
||||
|
||||
@@ -418,18 +418,23 @@ export class MockApiService extends ApiService {
|
||||
|
||||
// wifi
|
||||
|
||||
async enableWifi(params: RR.EnableWifiReq): Promise<RR.EnableWifiRes> {
|
||||
await pauseFor(2000)
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: '/server-info/wifi-enabled',
|
||||
value: params.enable,
|
||||
},
|
||||
]
|
||||
return this.withRevision(patch, null)
|
||||
}
|
||||
|
||||
async getWifi(params: RR.GetWifiReq): Promise<RR.GetWifiRes> {
|
||||
await pauseFor(2000)
|
||||
return Mock.Wifi
|
||||
}
|
||||
|
||||
async setWifiCountry(
|
||||
params: RR.SetWifiCountryReq,
|
||||
): Promise<RR.SetWifiCountryRes> {
|
||||
await pauseFor(2000)
|
||||
return null
|
||||
}
|
||||
|
||||
async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
|
||||
await pauseFor(2000)
|
||||
return null
|
||||
|
||||
@@ -41,7 +41,8 @@ export const mockPatchData: DataModel = {
|
||||
},
|
||||
'server-info': {
|
||||
id: 'abcdefgh',
|
||||
version: '0.3.4.3',
|
||||
version: '0.3.4',
|
||||
country: 'us',
|
||||
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
|
||||
'lan-address': 'https://adjective-noun.local',
|
||||
'tor-address': 'http://myveryownspecialtoraddress.onion',
|
||||
@@ -56,6 +57,7 @@ export const mockPatchData: DataModel = {
|
||||
},
|
||||
},
|
||||
'last-wifi-region': null,
|
||||
'wifi-enabled': false,
|
||||
'unread-notification-count': 4,
|
||||
// password is asdfasdf
|
||||
'password-hash':
|
||||
|
||||
@@ -64,11 +64,13 @@ export interface DevProjectData {
|
||||
export interface ServerInfo {
|
||||
id: string
|
||||
version: string
|
||||
country: string
|
||||
'last-backup': string | null
|
||||
'lan-address': Url
|
||||
'tor-address': Url
|
||||
'ip-info': IpInfo
|
||||
'last-wifi-region': string | null
|
||||
'wifi-enabled': boolean
|
||||
'unread-notification-count': number
|
||||
'status-info': ServerStatusInfo
|
||||
'eos-version-compat': string
|
||||
|
||||
Reference in New Issue
Block a user