mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +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 { 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 {}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// 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
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user