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:
Aaron Greenspan
2021-02-12 11:36:53 -07:00
committed by Aiden McClelland
parent a20970fa17
commit 5cf7d1ff88
14 changed files with 194 additions and 57 deletions

View File

@@ -20,6 +20,7 @@ export abstract class ApiService {
this.$unauthorizedApiResponse$.next()
}
abstract testConnection (url: string): Promise<true>
abstract getCheckAuth (): Promise<Unit> // Throws an error on failed auth.
abstract postLogin (password: string): Promise<Unit> // Throws an error on failed auth.
abstract postLogout (): Promise<Unit> // Throws an error on failed auth.
@@ -28,6 +29,7 @@ export abstract class ApiService {
abstract getServerMetrics (): Promise<ReqRes.GetServerMetricsRes>
abstract getNotifications (page: number, perPage: number): Promise<S9Notification[]>
abstract deleteNotification (id: string): Promise<Unit>
abstract toggleAppLAN (appId: string, toggle: 'enable' | 'disable'): Promise<Unit>
abstract updateAgent (version: any): Promise<Unit>
abstract acknowledgeOSWelcome (version: string): Promise<Unit>
abstract getAvailableApps (): Promise<AppAvailablePreview[]>

View File

@@ -5,11 +5,13 @@ import { AppAvailablePreview, AppAvailableFull, AppInstalledFull, AppInstalledPr
import { S9Notification, SSHFingerprint, ServerModel, DiskInfo } from '../../models/server-model'
import { ApiService, ReqRes } from './api.service'
import { ApiServer, Unit } from './api-types'
import { HttpErrorResponse } from '@angular/common/http'
import { HttpClient, HttpErrorResponse } from '@angular/common/http'
import { isUnauthorized } from 'src/app/util/web.util'
import { Replace } from 'src/app/util/types.util'
import { AppMetrics, parseMetricsPermissive } from 'src/app/util/metrics.util'
import { modulateTime } from 'src/app/util/misc.util'
import { Observable, of, throwError } from 'rxjs'
import { catchError, mapTo } from 'rxjs/operators'
@Injectable()
export class LiveApiService extends ApiService {
@@ -20,6 +22,10 @@ export class LiveApiService extends ApiService {
private readonly serverModel: ServerModel,
) { super() }
testConnection (url: string): Promise<true> {
return this.http.raw.get(url).pipe(mapTo(true as true), catchError(e => catchHttpStatusError(e))).toPromise()
}
// Used to check whether password or key is valid. If so, it will be used implicitly by all other calls.
async getCheckAuth (): Promise<Unit> {
return this.http.serverRequest<Unit>({ method: Method.GET, url: '/authenticate' }, { version: '' })
@@ -214,6 +220,10 @@ export class LiveApiService extends ApiService {
})
}
async toggleAppLAN (appId: string, toggle: 'enable' | 'disable'): Promise<Unit> {
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/lan/${toggle}` })
}
async addSSHKey (sshKey: string): Promise<Unit> {
const data: ReqRes.PostAddSSHKeyReq = {
sshKey,
@@ -275,3 +285,11 @@ const dryRunParam = (dryRun: boolean, first: boolean) => {
return first ? `?dryrun` : `&dryrun`
}
function catchHttpStatusError (error: HttpErrorResponse): Observable<true> {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
return throwError('Not Connected')
} else {
return of(true)
}
}

View File

@@ -41,6 +41,18 @@ export class MockApiService extends ApiService {
}
}
testCounter = 0
async testConnection (): Promise<true> {
console.log('testing connection')
this.testCounter ++
await pauseFor(1000)
if (this.testCounter > 5) {
return true
} else {
throw new Error('Not Connected')
}
}
async ejectExternalDisk (): Promise<Unit> {
await pauseFor(2000)
return { }
@@ -144,6 +156,10 @@ export class MockApiService extends ApiService {
return mockAppDependentBreakages
}
async toggleAppLAN (appId: string, toggle: 'enable' | 'disable'): Promise<Unit> {
return { }
}
async restartApp (appId: string): Promise<Unit> {
return { }
}

View File

@@ -24,6 +24,8 @@ export function toInstalledPreview (f: AppInstalledFull): AppInstalledPreview {
iconURL: f.iconURL,
torAddress: f.torAddress,
ui: f.ui,
lanAddress: f.lanAddress,
lanEnabled: f.lanEnabled,
}
}
@@ -47,8 +49,10 @@ export function toServiceBreakage (f: BaseApp): DependentBreakage {
export const bitcoinI: AppInstalledFull = {
id: 'bitcoind',
versionInstalled: '0.18.1',
lanAddress: 'bitcoinLan.local',
lanEnabled: true,
title: 'Bitcoin Core',
torAddress: 'sample-bitcoin-tor-address-and-some-more-tor-address.onion',
torAddress: '4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion',
status: AppStatus.STOPPED,
iconURL: 'assets/img/service-icons/bitcoind.png',
instructions: 'some instructions',
@@ -61,10 +65,12 @@ export const bitcoinI: AppInstalledFull = {
export const lightningI: AppInstalledFull = {
id: 'c-lightning',
lanAddress: 'lightningLan.local',
lanEnabled: true,
status: AppStatus.RUNNING,
title: 'C Lightning',
versionInstalled: '1.0.0',
torAddress: 'sample-bitcoin-tor-address-and-some-more-tor-address.onion',
torAddress: '4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion',
iconURL: 'assets/img/service-icons/bitwarden.png',
instructions: 'some instructions',
lastBackup: new Date().toISOString(),
@@ -84,6 +90,8 @@ export const lightningI: AppInstalledFull = {
export const cupsI: AppInstalledFull = {
id: 'cups',
lanAddress: 'cupsLan.local',
lanEnabled: false,
versionInstalled: '2.1.0',
title: 'Cups Messenger',
torAddress: 'sample-cups-tor-address.onion',

View File

@@ -1,5 +1,12 @@
import { Injectable } from '@angular/core'
const { useMocks, mockOver, skipStartupAlerts } = require('../../../use-mocks.json') as UseMocks
type UseMocks = {
useMocks: boolean
mockOver: 'tor' | 'lan'
skipStartupAlerts: boolean
}
@Injectable({
providedIn: 'root',
})
@@ -8,17 +15,18 @@ export class ConfigService {
version = require('../../../package.json').version
api = {
useMocks: require('../../../use-mocks.json').useMocks,
useMocks,
url: '/api',
version: '/v0',
root: '', // empty will default to same origin
}
skipStartupAlerts = skipStartupAlerts
isConsulateIos = window['platform'] === 'ios'
isConsulateAndroid = window['platform'] === 'android'
isTor () : boolean {
return this.api.useMocks || this.origin.endsWith('.onion')
return (this.api.useMocks && mockOver === 'tor') || this.origin.endsWith('.onion')
}
}

View File

@@ -13,6 +13,10 @@ export class HttpService {
private readonly config: ConfigService,
) { }
get raw () : HttpClient {
return this.http
}
async serverRequest<T> (options: HttpOptions, overrides: Partial<{ version: string }> = { }): Promise<T> {
options.url = leadingSlash(`${this.config.api.url}${exists(overrides.version) ? overrides.version : this.config.api.version}${options.url}`)
if ( this.config.api.root && this.config.api.root !== '' ) {

View File

@@ -21,7 +21,30 @@ export class StartupAlertsNotifier {
private readonly emver: Emver,
private readonly osUpdateService: OsUpdateService,
private readonly wizardBaker: WizardBaker,
) { }
) {
const welcome: Check<S9Server> = {
name: 'welcome',
shouldRun: s => this.shouldRunOsWelcome(s),
check: async s => s,
display: s => this.displayOsWelcome(s),
hasRun: this.config.skipStartupAlerts,
}
const osUpdate: Check<ReqRes.GetVersionLatestRes | undefined> = {
name: 'osUpdate',
shouldRun: s => this.shouldRunOsUpdateCheck(s),
check: s => this.osUpdateCheck(s),
display: vl => this.displayOsUpdateCheck(vl),
hasRun: this.config.skipStartupAlerts,
}
const apps: Check<boolean> = {
name: 'apps',
shouldRun: s => this.shouldRunAppsCheck(s),
check: () => this.appsCheck(),
display: () => this.displayAppsCheck(),
hasRun: this.config.skipStartupAlerts,
}
this.checks = [welcome, osUpdate, apps]
}
// This takes our three checks and filters down to those that should run.
// Then, the reduce fires, quickly iterating through yielding a promise (previousDisplay) to the next element
@@ -48,29 +71,7 @@ export class StartupAlertsNotifier {
}, Promise.resolve(true))
}
welcome: Check<S9Server> = {
name: 'welcome',
shouldRun: s => this.shouldRunOsWelcome(s),
check: async s => s,
display: s => this.displayOsWelcome(s),
hasRun: false,
}
osUpdate: Check<ReqRes.GetVersionLatestRes | undefined> = {
name: 'osUpdate',
shouldRun: s => this.shouldRunOsUpdateCheck(s),
check: s => this.osUpdateCheck(s),
display: vl => this.displayOsUpdateCheck(vl),
hasRun: false,
}
apps: Check<boolean> = {
name: 'apps',
shouldRun: s => this.shouldRunAppsCheck(s),
check: () => this.appsCheck(),
display: () => this.displayAppsCheck(),
hasRun: false,
}
checks: Check<any>[] = [this.welcome, this.osUpdate, this.apps]
checks: Check<any>[]
private shouldRunOsWelcome (s: S9Server): boolean {
return !s.welcomeAck && s.versionInstalled === this.config.version