mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
UI/feature/enable disable lan (#192)
* ui: skip startup notifications in mocks * ui: enable-disable lan toggle in ui * ui: remove this.lanAddress for this.app.lanAddress
This commit is contained in:
committed by
Aiden McClelland
parent
a20970fa17
commit
5cf7d1ff88
@@ -20,10 +20,13 @@
|
||||
hasFetchedFull: app.hasFetchedFull | async,
|
||||
iconURL: app.iconURL | async,
|
||||
title: app.title | async,
|
||||
ui: app.ui | async
|
||||
ui: app.ui | async,
|
||||
lanAddress: app.lanAddress | async,
|
||||
lanEnabled: app.lanEnabled | async,
|
||||
launchDisabled: (app.status | async) !== 'RUNNING' || (!isTor && !($lanConnected$ | async)),
|
||||
testingLanConnection: $testingLanConnection$ | async
|
||||
} as vars" class="ion-padding-bottom">
|
||||
<ion-spinner *ngIf="$loading$ | async" class="center" name="lines" color="warning"></ion-spinner>
|
||||
|
||||
<ng-container *ngIf="!($loading$ | async)">
|
||||
<ion-refresher *ngIf="app && app.id" slot="fixed" (ionRefresh)="doRefresh($event)">
|
||||
<ion-refresher-content pullingIcon="lines" refreshingSpinner="lines"></ion-refresher-content>
|
||||
@@ -83,7 +86,7 @@
|
||||
<ion-icon color="medium" name="information-circle-outline">
|
||||
</ion-icon>
|
||||
</ion-button>
|
||||
<ion-button [disabled]="vars.status !== 'RUNNING'" class="launch-button" [class.launch-button-off]="vars.status !== 'RUNNING'" (click)="launchUiTab()">
|
||||
<ion-button [disabled]="vars.launchDisabled" class="launch-button" [class.launch-button-off]="vars.launchDisabled" (click)="launchUiTab()">
|
||||
<ion-icon style="position: absolute; z-index: 1; left: 0;" name="globe-outline"></ion-icon>
|
||||
<ion-text>LAUNCH</ion-text>
|
||||
</ion-button>
|
||||
@@ -97,20 +100,16 @@
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>Tor Address</h2>
|
||||
<p>{{ vars.torAddress }}</p>
|
||||
<p><a (click)="copyTor()">{{ vars.torAddress }} <ion-icon name="copy-outline" class="tiny-icon"></ion-icon></a></p>
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" (click)="copyTor()">
|
||||
<ion-icon slot="icon-only" name="copy-outline" color="primary"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="vars.ui" lines="none">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>LAN Address</h2>
|
||||
<p>{{ lanAddress }}</p>
|
||||
<p><a (click)="copyLan()">{{ vars.lanAddress }} <ion-icon name="copy-outline" class="tiny-icon"></ion-icon></a></p>
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" (click)="copyLan()">
|
||||
<ion-icon slot="icon-only" name="copy-outline" color="primary"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-toggle *ngIf="!vars.testingLanConnection" (ionChange)="$lanToggled$.next($event)" [checked]="vars.lanEnabled" slot="end" fill="clear" class="lan-toggle" [disabled]="vars.status !== 'RUNNING'"></ion-toggle>
|
||||
<ion-spinner slot="end" *ngIf="vars.testingLanConnection" name="lines" color="medium"></ion-spinner>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider>Backups</ion-item-divider>
|
||||
|
||||
@@ -59,3 +59,11 @@
|
||||
right: -2px;
|
||||
--border-radius: 100px;
|
||||
}
|
||||
|
||||
.lan-toggle {
|
||||
width: 2em;
|
||||
height: 1em;
|
||||
--handle-width: 0.9em;
|
||||
--handle-height: 0.9em;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@@ -6,21 +6,20 @@ import { copyToClipboard } from 'src/app/util/web.util'
|
||||
import { AppModel, AppStatus } from 'src/app/models/app-model'
|
||||
import { AppInstalledFull } from 'src/app/models/app-types'
|
||||
import { ModelPreload } from 'src/app/models/model-preload'
|
||||
import { chill, pauseFor } from 'src/app/util/misc.util'
|
||||
import { chill, modulateTime, pauseFor } from 'src/app/util/misc.util'
|
||||
import { PropertySubject, peekProperties } from 'src/app/util/property-subject.util'
|
||||
import { AppBackupPage } from 'src/app/modals/app-backup/app-backup.page'
|
||||
import { LoaderService, markAsLoadingDuring$, markAsLoadingDuringP } from 'src/app/services/loader.service'
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs'
|
||||
import { BehaviorSubject, combineLatest, from, merge, Observable, of, Subject } from 'rxjs'
|
||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { catchError, concatMap, filter, switchMap, tap } from 'rxjs/operators'
|
||||
import { catchError, concatMap, delay, filter, map, retryWhen, switchMap, take, tap } from 'rxjs/operators'
|
||||
import { Cleanup } from 'src/app/util/cleanup'
|
||||
import { InformationPopoverComponent } from 'src/app/components/information-popover/information-popover.component'
|
||||
import { Emver } from 'src/app/services/emver.service'
|
||||
import { displayEmver } from 'src/app/pipes/emver.pipe'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { ServerModel } from 'src/app/models/server-model'
|
||||
|
||||
import { squash } from 'src/app/util/rxjs.util'
|
||||
@Component({
|
||||
selector: 'app-installed-show',
|
||||
templateUrl: './app-installed-show.page.html',
|
||||
@@ -32,17 +31,21 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
|
||||
$error$ = new BehaviorSubject<string>('')
|
||||
app: PropertySubject<AppInstalledFull> = { } as any
|
||||
lanAddress = ''
|
||||
appId: string
|
||||
AppStatus = AppStatus
|
||||
showInstructions = false
|
||||
isConsulate: boolean
|
||||
isTor: boolean
|
||||
|
||||
// true iff service lan address has been tested and is accessible
|
||||
$lanConnected$: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||
// true during service lan address testing
|
||||
$testingLanConnection$: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||
|
||||
dependencyDefintion = () => `<span style="font-style: italic">Dependencies</span> are other services which must be installed, configured appropriately, and started in order to start ${this.app.title.getValue()}`
|
||||
launchDefinition = `<span style="font-style: italic">Launch A Service</span> <p>This button appears only for services that can be accessed inside the browser. If a service does not have this button, you must access it using another interface, such as a mobile app, desktop app, or another service on the Embassy. Please view the instructions for a service for details on how to use it.</p>`
|
||||
launchOffDefinition = `<span style="font-style: italic">Launch A Service</span> <p>This button appears only for services that can be accessed inside the browser. Get your service running in order to launch!</p>`
|
||||
launchLocalDefinition = `<span style="font-style: italic">Launch A Service</span> <p>This button appears only for services that can be accessed inside the browser. Visit your Embassy at its Tor address to launch this service!</p>`
|
||||
launchLocalDefinition = `<span style="font-style: italic">Launch A Service</span> <p>This button appears only for services that can be accessed inside the browser. To launch this service over LAN, enable the toggle below by your service's LAN Address.</p>`
|
||||
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
|
||||
@@ -59,7 +62,6 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
private readonly appModel: AppModel,
|
||||
private readonly popoverController: PopoverController,
|
||||
private readonly emver: Emver,
|
||||
private readonly serverModel: ServerModel,
|
||||
config: ConfigService,
|
||||
) {
|
||||
super()
|
||||
@@ -69,21 +71,72 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
|
||||
async ngOnInit () {
|
||||
this.appId = this.route.snapshot.paramMap.get('appId') as string
|
||||
const server = this.serverModel.peek()
|
||||
this.lanAddress = `https://${this.appId}.${server.serverId}.local`
|
||||
|
||||
this.cleanup(
|
||||
markAsLoadingDuring$(this.$loading$, this.preload.appFull(this.appId))
|
||||
.pipe(
|
||||
tap(app => this.app = app),
|
||||
concatMap(() => this.syncWhenDependencyInstalls()), //must be final in stack
|
||||
catchError(e => of(this.setError(e))),
|
||||
concatMap(app =>
|
||||
merge(
|
||||
this.syncWhenDependencyInstalls(),
|
||||
combineLatest([app.lanEnabled, this.$lanConnected$, app.status, this.$testingLanConnection$]).pipe(
|
||||
filter(([_, __, s, alreadyConnecting]) => s === AppStatus.RUNNING && !alreadyConnecting),
|
||||
concatMap(([enabled, connected]) => {
|
||||
if (enabled && !connected) return markAsLoadingDuring$(this.$testingLanConnection$, this.testLanConnection())
|
||||
if (!enabled && connected) return of(this.$lanConnected$.next(false))
|
||||
return of()
|
||||
}),
|
||||
),
|
||||
),
|
||||
), //must be final in stack
|
||||
catchError(e => this.setError(e)),
|
||||
).subscribe(),
|
||||
)
|
||||
}
|
||||
|
||||
testLanConnection () : Observable<void> {
|
||||
if (!this.app.lanAddress) return of()
|
||||
|
||||
return this.app.lanAddress.pipe(
|
||||
switchMap(la => this.apiService.testConnection(la)),
|
||||
retryWhen(errors => errors.pipe(delay(2500), take(20))),
|
||||
catchError(() => of(false)),
|
||||
take(1),
|
||||
map(connected => this.$lanConnected$.next(connected)),
|
||||
)
|
||||
}
|
||||
|
||||
enableLan (): Observable<void> {
|
||||
return from(this.apiService.toggleAppLAN(this.appId, 'enable')).pipe(squash)
|
||||
}
|
||||
|
||||
disableLan (): Observable<void> {
|
||||
return from(this.apiService.toggleAppLAN(this.appId, 'disable')).pipe(
|
||||
map(() => this.appModel.update({ id: this.appId, lanEnabled: false }), modulateTime(new Date(), 10, 'seconds')),
|
||||
map(() => this.$lanConnected$.next(false)),
|
||||
squash,
|
||||
)
|
||||
}
|
||||
|
||||
$lanToggled$ = new Subject()
|
||||
ionViewDidEnter () {
|
||||
markAsLoadingDuringP(this.$loadingDependencies$, this.getApp())
|
||||
this.cleanup(
|
||||
combineLatest([this.$lanToggled$, this.app.lanEnabled, this.$testingLanConnection$]).pipe(
|
||||
filter(([_, __, alreadyLoading]) => !alreadyLoading),
|
||||
map(([e, _]) => [(e as any).detail.checked, _]),
|
||||
// if the app is already in the desired state, we bail
|
||||
// this can happen because ionChange triggers when the [checked] value changes
|
||||
filter(([uiEnabled, appEnabled]) => (uiEnabled && !appEnabled) || (!uiEnabled && appEnabled)),
|
||||
map(([enabled]) => enabled
|
||||
? this.enableLan().pipe(concatMap(() => this.testLanConnection()))
|
||||
: this.disableLan(),
|
||||
),
|
||||
concatMap(o => markAsLoadingDuring$(this.$testingLanConnection$, o).pipe(
|
||||
catchError(e => this.setError(e)),
|
||||
)),
|
||||
).subscribe({ error: e => console.error(e) }),
|
||||
)
|
||||
}
|
||||
|
||||
async doRefresh (event: any) {
|
||||
@@ -113,7 +166,8 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
const torAddress = this.app.torAddress.getValue()
|
||||
uiAddress = torAddress.startsWith('http') ? torAddress : `http://${torAddress}`
|
||||
} else {
|
||||
uiAddress = this.lanAddress
|
||||
const lanAddress = this.app.lanAddress.getValue()
|
||||
uiAddress = lanAddress.startsWith('http') ? lanAddress : `http://${lanAddress}`
|
||||
}
|
||||
return window.open(uiAddress, '_blank')
|
||||
}
|
||||
@@ -183,8 +237,9 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
}
|
||||
|
||||
async copyLan () {
|
||||
const app = peekProperties(this.app)
|
||||
let message = ''
|
||||
await copyToClipboard(this.lanAddress).then(success => { message = success ? 'copied to clipboard!' : 'failed to copy' })
|
||||
await copyToClipboard(app.lanAddress).then(success => { message = success ? 'copied to clipboard!' : 'failed to copy' })
|
||||
|
||||
const toast = await this.toastCtrl.create({
|
||||
header: message,
|
||||
@@ -324,8 +379,9 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
return await popover.present()
|
||||
}
|
||||
|
||||
private setError (e: Error) {
|
||||
private setError (e: Error): Observable<void> {
|
||||
this.$error$.next(e.message)
|
||||
return of()
|
||||
}
|
||||
|
||||
private clearError () {
|
||||
|
||||
Reference in New Issue
Block a user