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:
Matt Hill
2023-05-09 07:40:56 -06:00
committed by Aiden McClelland
parent f6c09109ba
commit 9499ea8ca9
11 changed files with 422 additions and 433 deletions

View File

@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { RouterModule, Routes } from '@angular/router' import { RouterModule, Routes } from '@angular/router'
import { WifiPage } from './wifi.page' import { WifiPage, ToWifiIconPipe } from './wifi.page'
import { SharedPipesModule } from '@start9labs/shared' import { SharedPipesModule } from '@start9labs/shared'
const routes: Routes = [ const routes: Routes = [
@@ -19,6 +19,6 @@ const routes: Routes = [
RouterModule.forChild(routes), RouterModule.forChild(routes),
SharedPipesModule, SharedPipesModule,
], ],
declarations: [WifiPage], declarations: [WifiPage, ToWifiIconPipe],
}) })
export class WifiPageModule {} export class WifiPageModule {}

View File

@@ -4,171 +4,156 @@
<ion-back-button defaultHref="system"></ion-back-button> <ion-back-button defaultHref="system"></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>
<ion-content class="ion-padding-top with-widgets"> <ion-content class="with-widgets">
<ion-item-group> <div class="smaller" *tuiLet="enabled$ | async as enabled">
<!-- always --> <ion-item-group>
<ion-item> <ion-item>
<ion-label> <ion-label>
<h2> <h2>
Adding WiFi credentials to your StartOS allows you to remove the Adding WiFi credentials to your Embassy allows you to remove the
Ethernet cable and move the device anywhere you want. StartOS will Ethernet cable and move the device anywhere you want. Embassy will
automatically connect to available networks. automatically connect to available networks.
<a <a
href="https://docs.start9.com/latest/user-manual/wifi" href="https://docs.start9.com/latest/user-manual/wifi"
target="_blank" target="_blank"
rel="noreferrer" 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 Other...
</a> </ion-button>
</h2> </div>
</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>
</ng-container> </ng-container>
<ion-item button detail="false" (click)="presentModalAdd()">
<ion-icon slot="start" name="add" color="dark"></ion-icon> <!-- loading -->
<ion-label> <ng-template #loading>
<b>Join Another Network</b> <ion-item-divider>Known Networks</ion-item-divider>
</ion-label> <ion-item-group>
</ion-item> <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> </ng-container>
</ion-item-group> </div>
</ion-content> </ion-content>

View File

@@ -1,7 +1,41 @@
.smaller {
padding: 32px;
max-width: 800px;
}
.no-padding {
padding-right: 0;
}
.skeleton-parts { .skeleton-parts {
ion-button::part(native) { ion-button::part(native) {
padding-inline-start: 0; padding-inline-start: 0;
padding-inline-end: 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;
} }

View File

@@ -1,20 +1,30 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { import { ToastController } from '@ionic/angular'
ActionSheetController,
AlertController,
ToastController,
} from '@ionic/angular'
import { AlertInput } from '@ionic/core'
import { TuiDialogOptions } from '@taiga-ui/core' import { TuiDialogOptions } from '@taiga-ui/core'
import { ToggleCustomEvent } from '@ionic/core'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ActionSheetButton } from '@ionic/core' import { AvailableWifi, RR } from 'src/app/services/api/api.types'
import { ValueSpecObject } from 'start-sdk/lib/config/configTypes'
import { RR } from 'src/app/services/api/api.types'
import { pauseFor, ErrorToastService } from '@start9labs/shared' import { pauseFor, ErrorToastService } from '@start9labs/shared'
import { ConfigService } from 'src/app/services/config.service'
import { FormDialogService } from 'src/app/services/form-dialog.service' import { FormDialogService } from 'src/app/services/form-dialog.service'
import { FormContext, FormPage } from 'src/app/modals/form/form.page' import { FormContext, FormPage } from 'src/app/modals/form/form.page'
import { LoadingService } from 'src/app/modals/loading/loading.service' 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 { interface WiFiForm {
ssid: string ssid: string
@@ -27,101 +37,106 @@ interface WiFiForm {
styleUrls: ['wifi.page.scss'], styleUrls: ['wifi.page.scss'],
}) })
export class WifiPage { export class WifiPage {
loading = true readonly connected$ = this.connectionService.connected$.pipe(filter(Boolean))
wifi: RR.GetWifiRes = {} as any readonly enabled$ = this.patch.watch$('server-info', 'wifi-enabled').pipe(
countries = require('../../../util/countries.json') as { distinctUntilChanged(),
[key: string]: string 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( constructor(
private readonly api: ApiService, private readonly api: ApiService,
private readonly toastCtrl: ToastController, private readonly toastCtrl: ToastController,
private readonly alertCtrl: AlertController,
private readonly loader: LoadingService, private readonly loader: LoadingService,
private readonly formDialog: FormDialogService, private readonly formDialog: FormDialogService,
private readonly errToast: ErrorToastService, private readonly errToast: ErrorToastService,
private readonly actionCtrl: ActionSheetController, private readonly patch: PatchDB<DataModel>,
private readonly config: ConfigService, private readonly connectionService: ConnectionService,
) {} ) {}
async ngOnInit() { async toggleWifi(e: ToggleCustomEvent): Promise<void> {
await this.getWifi() 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 { try {
this.wifi = await this.api.getWifi({}, timeout) await this.api.enableWifi({ enable })
if (!this.wifi.country) {
await this.presentAlertCountry()
}
} catch (e: any) { } catch (e: any) {
this.errToast.present(e) this.errToast.present(e)
} finally { } finally {
this.loading = false loader.unsubscribe()
} }
} }
async presentAlertCountry(): Promise<void> { async connect(ssid: string): Promise<void> {
if (!this.config.isLan) { const loader = this.loader
const alert = await this.alertCtrl.create({ .open('Connecting. This could take a while...')
header: 'Cannot Complete Action', .subscribe()
message:
'You must be connected to your server via LAN to change the country.', try {
buttons: [ await this.api.connectWifi({ ssid })
{ await this.confirmWifi(ssid)
text: 'OK', } catch (e: any) {
role: 'cancel', this.errToast.present(e)
}, } finally {
], loader.unsubscribe()
cssClass: 'enter-click',
})
await alert.present()
return
} }
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) { async forget(ssid: string, wifi: RR.GetWifiRes): Promise<void> {
const { name, spec } = getWifiValueSpec(ssid, needsPW) 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>>> = { const options: Partial<TuiDialogOptions<FormContext<WiFiForm>>> = {
label: name, label: wifiSpec.name,
data: { data: {
spec, spec: wifiSpec.spec,
buttons: [ buttons: [
{ {
text: 'Save for Later', 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', text: 'Save and Connect',
@@ -131,107 +146,36 @@ export class WifiPage {
], ],
}, },
} }
this.formDialog.open(FormPage, options) this.formDialog.open(FormPage, options)
} }
async presentAction(ssid: string) { private getWifi$(): Observable<RR.GetWifiRes> {
const buttons: ActionSheetButton[] = [ return from(this.api.getWifi({}, 10000)).pipe(
{ catchError((e: any) => {
text: 'Forget', this.errToast.present(e)
icon: 'trash', return []
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 async setCountry(country: string): Promise<void> { private async presentToastSuccess(): Promise<void> {
const loader = this.loader.open('Setting country...').subscribe() const toast = await this.toastCtrl.create({
header: 'Connection successful!',
try { position: 'bottom',
await this.api.setWifiCountry({ country }) duration: 4000,
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.',
buttons: [ buttons: [
{ {
text: 'Ok', side: 'start',
role: 'cancel', icon: 'close',
cssClass: 'enter-click', handler: () => {
return true
},
}, },
], ],
cssClass: 'success-toast',
}) })
await alert.present() await toast.present()
} }
private async presentToastFail(): Promise<void> { private async presentToastFail(): Promise<void> {
@@ -255,36 +199,11 @@ export class WifiPage {
await toast.present() await toast.present()
} }
private async connect(ssid: string): Promise<void> { private async save(
const loader = this.loader ssid: string,
.open('Connecting. This could take a while...') password: string,
.subscribe() wifi: RR.GetWifiRes,
): Promise<boolean> {
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> {
const loader = this.loader.open('Saving...').subscribe() const loader = this.loader.open('Saving...').subscribe()
try { try {
@@ -294,7 +213,9 @@ export class WifiPage {
priority: 0, priority: 0,
connect: false, connect: false,
}) })
await this.getWifi() wifi.ssids[ssid] = 0
this.localChanges$.next(wifi)
this.trigger$.next('')
return true return true
} catch (e: any) { } catch (e: any) {
this.errToast.present(e) this.errToast.present(e)
@@ -319,7 +240,7 @@ export class WifiPage {
priority: 0, priority: 0,
connect: true, connect: true,
}) })
await this.confirmWifi(ssid, true) await this.confirmWifi(ssid)
return true return true
} catch (e: any) { } catch (e: any) {
this.errToast.present(e) this.errToast.present(e)
@@ -328,52 +249,52 @@ export class WifiPage {
loader.unsubscribe() loader.unsubscribe()
} }
} }
}
function getWifiValueSpec( private async confirmWifi(ssid: string): Promise<void> {
ssid?: string, const maxAttempts = 5
needsPW: boolean = true, let attempts = 0
): ValueSpecObject {
return { while (true) {
type: 'object', if (attempts > maxAttempts) {
name: 'WiFi Credentials', this.presentToastFail()
description: break
'Enter the network SSID and password. You can connect now or save the network for later.', }
warning: null,
spec: { try {
ssid: { const start = new Date().valueOf()
type: 'text', const newWifi = await this.api.getWifi({}, 10000)
name: 'Network SSID', const end = new Date().valueOf()
description: null, if (newWifi.connected === ssid) {
inputmode: 'text', this.localChanges$.next(newWifi)
placeholder: null, this.presentToastSuccess()
patterns: [], break
minLength: null, } else {
maxLength: null, attempts++
required: true, const diff = end - start
masked: false, // 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
default: ssid || null, await pauseFor(Math.max(1000, 4000 - diff))
warning: null, }
}, } catch (e) {
password: { attempts++
type: 'text', console.warn(e)
name: 'Password', }
description: null, }
inputmode: 'text', }
placeholder: null, }
required: needsPW,
masked: true, @Pipe({
minLength: null, name: 'toWifiIcon',
maxLength: null, })
patterns: [ export class ToWifiIconPipe implements PipeTransform {
{ transform(signal: number): string {
regex: '^.{8,}$', if (signal >= 0 && signal < 5) {
description: 'Must be longer than 8 characters', return 'assets/img/icons/wifi-0.png'
}, } else if (signal >= 5 && signal < 50) {
], return 'assets/img/icons/wifi-1.png'
default: null, } else if (signal >= 50 && signal < 90) {
warning: null, return 'assets/img/icons/wifi-2.png'
}, } else {
}, return 'assets/img/icons/wifi-3.png'
}
} }
} }

View File

@@ -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,
},
},
}

View File

@@ -104,9 +104,6 @@ export module RR {
// wifi // wifi
export type SetWifiCountryReq = { country: string }
export type SetWifiCountryRes = null
export type GetWifiReq = {} export type GetWifiReq = {}
export type GetWifiRes = { export type GetWifiRes = {
ssids: { ssids: {
@@ -127,6 +124,9 @@ export module RR {
} }
export type AddWifiRes = null export type AddWifiRes = null
export type EnableWifiReq = { enable: boolean } // wifi.enable
export type EnableWifiRes = null
export type ConnectWifiReq = { ssid: string } // wifi.connect export type ConnectWifiReq = { ssid: string } // wifi.connect
export type ConnectWifiRes = null export type ConnectWifiRes = null

View File

@@ -147,15 +147,13 @@ export abstract class ApiService {
// wifi // wifi
abstract enableWifi(params: RR.EnableWifiReq): Promise<RR.EnableWifiRes>
abstract getWifi( abstract getWifi(
params: RR.GetWifiReq, params: RR.GetWifiReq,
timeout: number, timeout: number,
): Promise<RR.GetWifiRes> ): Promise<RR.GetWifiRes>
abstract setWifiCountry(
params: RR.SetWifiCountryReq,
): Promise<RR.SetWifiCountryRes>
abstract addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> abstract addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes>
abstract connectWifi(params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> abstract connectWifi(params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes>

View File

@@ -266,6 +266,10 @@ export class LiveApiService extends ApiService {
// wifi // wifi
async enableWifi(params: RR.EnableWifiReq): Promise<RR.EnableWifiRes> {
return this.rpcRequest({ method: 'wifi.enable', params })
}
async getWifi( async getWifi(
params: RR.GetWifiReq, params: RR.GetWifiReq,
timeout?: number, timeout?: number,
@@ -273,12 +277,6 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'wifi.get', params, timeout }) 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> { async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
return this.rpcRequest({ method: 'wifi.add', params }) return this.rpcRequest({ method: 'wifi.add', params })
} }

View File

@@ -418,18 +418,23 @@ export class MockApiService extends ApiService {
// wifi // 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> { async getWifi(params: RR.GetWifiReq): Promise<RR.GetWifiRes> {
await pauseFor(2000) await pauseFor(2000)
return Mock.Wifi return Mock.Wifi
} }
async setWifiCountry(
params: RR.SetWifiCountryReq,
): Promise<RR.SetWifiCountryRes> {
await pauseFor(2000)
return null
}
async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> { async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
await pauseFor(2000) await pauseFor(2000)
return null return null

View File

@@ -41,7 +41,8 @@ export const mockPatchData: DataModel = {
}, },
'server-info': { 'server-info': {
id: 'abcdefgh', id: 'abcdefgh',
version: '0.3.4.3', version: '0.3.4',
country: 'us',
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(), 'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
'lan-address': 'https://adjective-noun.local', 'lan-address': 'https://adjective-noun.local',
'tor-address': 'http://myveryownspecialtoraddress.onion', 'tor-address': 'http://myveryownspecialtoraddress.onion',
@@ -56,6 +57,7 @@ export const mockPatchData: DataModel = {
}, },
}, },
'last-wifi-region': null, 'last-wifi-region': null,
'wifi-enabled': false,
'unread-notification-count': 4, 'unread-notification-count': 4,
// password is asdfasdf // password is asdfasdf
'password-hash': 'password-hash':

View File

@@ -64,11 +64,13 @@ export interface DevProjectData {
export interface ServerInfo { export interface ServerInfo {
id: string id: string
version: string version: string
country: string
'last-backup': string | null 'last-backup': string | null
'lan-address': Url 'lan-address': Url
'tor-address': Url 'tor-address': Url
'ip-info': IpInfo 'ip-info': IpInfo
'last-wifi-region': string | null 'last-wifi-region': string | null
'wifi-enabled': boolean
'unread-notification-count': number 'unread-notification-count': number
'status-info': ServerStatusInfo 'status-info': ServerStatusInfo
'eos-version-compat': string 'eos-version-compat': string