mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
better network monitoring
This commit is contained in:
committed by
Aiden McClelland
parent
80db9b71b9
commit
d8ef531721
@@ -12,7 +12,7 @@ import { LoadingOptions } from '@ionic/core'
|
|||||||
import { PatchDbModel } from './models/patch-db/patch-db-model'
|
import { PatchDbModel } from './models/patch-db/patch-db-model'
|
||||||
import { HttpService } from './services/http.service'
|
import { HttpService } from './services/http.service'
|
||||||
import { ServerStatus } from './models/patch-db/data-model'
|
import { ServerStatus } from './models/patch-db/data-model'
|
||||||
import { ConnectionService } from './services/connection.service'
|
import { ConnectionFailure, ConnectionService } from './services/connection.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -83,6 +83,7 @@ export class AppComponent {
|
|||||||
this.http.authReqEnabled = true
|
this.http.authReqEnabled = true
|
||||||
this.showMenu = true
|
this.showMenu = true
|
||||||
this.patch.start()
|
this.patch.start()
|
||||||
|
this.connectionService.start()
|
||||||
// watch network
|
// watch network
|
||||||
this.watchConnection(auth)
|
this.watchConnection(auth)
|
||||||
// watch router to highlight selected menu item
|
// watch router to highlight selected menu item
|
||||||
@@ -95,6 +96,7 @@ export class AppComponent {
|
|||||||
} else if (auth === AuthState.UNVERIFIED) {
|
} else if (auth === AuthState.UNVERIFIED) {
|
||||||
this.http.authReqEnabled = false
|
this.http.authReqEnabled = false
|
||||||
this.showMenu = false
|
this.showMenu = false
|
||||||
|
this.connectionService.stop()
|
||||||
this.patch.stop()
|
this.patch.stop()
|
||||||
this.storage.clear()
|
this.storage.clear()
|
||||||
this.router.navigate(['/login'], { replaceUrl: true })
|
this.router.navigate(['/login'], { replaceUrl: true })
|
||||||
@@ -107,13 +109,13 @@ export class AppComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private watchConnection (auth: AuthState): void {
|
private watchConnection (auth: AuthState): void {
|
||||||
this.connectionService.monitor$()
|
this.connectionService.watch$()
|
||||||
.pipe(
|
.pipe(
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
takeWhile(() => auth === AuthState.VERIFIED),
|
takeWhile(() => auth === AuthState.VERIFIED),
|
||||||
)
|
)
|
||||||
.subscribe(internet => {
|
.subscribe(connectionFailure => {
|
||||||
if (!internet) {
|
if (connectionFailure !== ConnectionFailure.None) {
|
||||||
this.presentToastOffline()
|
this.presentToastOffline()
|
||||||
} else {
|
} else {
|
||||||
if (this.offlineToast) {
|
if (this.offlineToast) {
|
||||||
@@ -121,7 +123,6 @@ export class AppComponent {
|
|||||||
this.offlineToast = undefined
|
this.offlineToast = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('INTERNET CONNECTION', internet)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
||||||
import { ConnectionState } from 'src/app/services/connection.service'
|
|
||||||
import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
|
import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||||
|
import { ConnectionStatus } from 'patch-db-client'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'status',
|
selector: 'status',
|
||||||
@@ -10,7 +10,7 @@ import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
|
|||||||
})
|
})
|
||||||
export class StatusComponent {
|
export class StatusComponent {
|
||||||
@Input() pkg: PackageDataEntry
|
@Input() pkg: PackageDataEntry
|
||||||
@Input() connection: ConnectionState
|
@Input() connected: boolean
|
||||||
@Input() size?: 'small' | 'medium' | 'large' = 'large'
|
@Input() size?: 'small' | 'medium' | 'large' = 'large'
|
||||||
@Input() style?: string = 'regular'
|
@Input() style?: string = 'regular'
|
||||||
@Input() weight?: string = 'normal'
|
@Input() weight?: string = 'normal'
|
||||||
@@ -19,7 +19,7 @@ export class StatusComponent {
|
|||||||
showDots = false
|
showDots = false
|
||||||
|
|
||||||
ngOnChanges () {
|
ngOnChanges () {
|
||||||
const { display, color, showDots } = renderPkgStatus(this.pkg, this.connection)
|
const { display, color, showDots } = renderPkgStatus(this.pkg)
|
||||||
this.display = display
|
this.display = display
|
||||||
this.color = color
|
this.color = color
|
||||||
this.showDots = showDots
|
this.showDots = showDots
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export interface ServerInfo {
|
|||||||
'lan-address': URL
|
'lan-address': URL
|
||||||
'tor-address': URL
|
'tor-address': URL
|
||||||
status: ServerStatus
|
status: ServerStatus
|
||||||
registry: URL
|
'package-registry': URL
|
||||||
|
'system-registry': URL
|
||||||
wifi: WiFiInfo
|
wifi: WiFiInfo
|
||||||
'unread-notification-count': number
|
'unread-notification-count': number
|
||||||
specs: {
|
specs: {
|
||||||
@@ -24,6 +25,10 @@ export interface ServerInfo {
|
|||||||
Disk: string
|
Disk: string
|
||||||
Memory: string
|
Memory: string
|
||||||
}
|
}
|
||||||
|
'connection-addresses': {
|
||||||
|
tor: string[]
|
||||||
|
clearnet: string[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ServerStatus {
|
export enum ServerStatus {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Inject, Injectable, InjectionToken } from '@angular/core'
|
import { Inject, Injectable, InjectionToken } from '@angular/core'
|
||||||
import { Bootstrapper, PatchDB, Source, Store } from 'patch-db-client'
|
import { Bootstrapper, ConnectionStatus, PatchDB, Source, Store } from 'patch-db-client'
|
||||||
import { Observable, of, Subscription } from 'rxjs'
|
import { Observable, of, Subscription } from 'rxjs'
|
||||||
import { catchError, debounceTime } from 'rxjs/operators'
|
import { catchError, debounceTime, map } from 'rxjs/operators'
|
||||||
import { DataModel } from './data-model'
|
import { DataModel } from './data-model'
|
||||||
|
|
||||||
export const BOOTSTRAPPER = new InjectionToken<Bootstrapper<DataModel>>('app.config')
|
export const BOOTSTRAPPER = new InjectionToken<Bootstrapper<DataModel>>('app.config')
|
||||||
@@ -37,6 +37,7 @@ export class PatchDbModel {
|
|||||||
},
|
},
|
||||||
error: e => {
|
error: e => {
|
||||||
console.error('patch-db-sync sub ERROR', e)
|
console.error('patch-db-sync sub ERROR', e)
|
||||||
|
this.start()
|
||||||
},
|
},
|
||||||
complete: () => {
|
complete: () => {
|
||||||
console.error('patch-db-sync sub COMPLETE')
|
console.error('patch-db-sync sub COMPLETE')
|
||||||
@@ -54,7 +55,18 @@ export class PatchDbModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch$: Store < DataModel > ['watch$'] = (...args: (string | number)[]): Observable<DataModel> => {
|
connected$ (): Observable<boolean> {
|
||||||
|
return this.patchDb.connectionStatus$
|
||||||
|
.pipe(
|
||||||
|
map(status => status === ConnectionStatus.Connected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionStatus$ (): Observable<ConnectionStatus> {
|
||||||
|
return this.patchDb.connectionStatus$.asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch$: Store<DataModel> ['watch$'] = (...args: (string | number)[]): Observable<DataModel> => {
|
||||||
// console.log('WATCHING')
|
// console.log('WATCHING')
|
||||||
return this.patchDb.store.watch$(...(args as [])).pipe(
|
return this.patchDb.store.watch$(...(args as [])).pipe(
|
||||||
catchError(e => {
|
catchError(e => {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content style="position: relative">
|
<ion-content style="position: relative">
|
||||||
<ng-container *ngIf="patch.watch$('package-data') | ngrxPush as pkgs">
|
|
||||||
|
|
||||||
<div *ngIf="pkgs | empty; else list" class="ion-text-center ion-padding">
|
<div *ngIf="pkgs | empty; else list" class="ion-text-center ion-padding">
|
||||||
<div style="display: flex; flex-direction: column; justify-content: center; height: 40vh">
|
<div style="display: flex; flex-direction: column; justify-content: center; height: 40vh">
|
||||||
@@ -23,7 +22,7 @@
|
|||||||
|
|
||||||
<ng-template #list>
|
<ng-template #list>
|
||||||
<ion-grid>
|
<ion-grid>
|
||||||
<ion-row *ngIf="connectionService.monitor$() | ngrxPush as connection">
|
<ion-row>
|
||||||
<ion-col *ngFor="let pkg of pkgs | keyvalue : asIsOrder" sizeXs="4" sizeSm="3" sizeLg="3" sizeXl="2">
|
<ion-col *ngFor="let pkg of pkgs | keyvalue : asIsOrder" sizeXs="4" sizeSm="3" sizeLg="3" sizeXl="2">
|
||||||
<ion-card class="installed-card" style="position:relative" [routerLink]="['/services', (pkg.value | manifest).id]">
|
<ion-card class="installed-card" style="position:relative" [routerLink]="['/services', (pkg.value | manifest).id]">
|
||||||
<div class="launch-container" *ngIf="pkg.value | hasUi">
|
<div class="launch-container" *ngIf="pkg.value | hasUi">
|
||||||
@@ -34,13 +33,13 @@
|
|||||||
|
|
||||||
<img style="position: absolute" class="main-img" [src]="pkg.value['static-files'].icon" alt="icon" />
|
<img style="position: absolute" class="main-img" [src]="pkg.value['static-files'].icon" alt="icon" />
|
||||||
<img class="main-img" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">
|
<img class="main-img" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">
|
||||||
<img class="bulb-on" *ngIf="pkg.value | displayBulb: 'green' : connection" src="assets/img/running-bulb.png"/>
|
<img class="bulb-on" *ngIf="pkg.value | displayBulb: 'green' : connected" src="assets/img/running-bulb.png"/>
|
||||||
<img class="bulb-on" *ngIf="pkg.value | displayBulb: 'red' : connection" src="assets/img/issue-bulb.png"/>
|
<img class="bulb-on" *ngIf="pkg.value | displayBulb: 'red' : connected" src="assets/img/issue-bulb.png"/>
|
||||||
<img class="bulb-on" *ngIf="pkg.value | displayBulb: 'yellow' : connection" src="assets/img/warning-bulb.png"/>
|
<img class="bulb-on" *ngIf="pkg.value | displayBulb: 'yellow' : connected" src="assets/img/warning-bulb.png"/>
|
||||||
<img class="bulb-off" *ngIf="pkg.value | displayBulb: 'off' : connection" src="assets/img/off-bulb.png"/>
|
<img class="bulb-off" *ngIf="pkg.value | displayBulb: 'off' : connected" src="assets/img/off-bulb.png"/>
|
||||||
|
|
||||||
<ion-card-header>
|
<ion-card-header>
|
||||||
<status [pkg]="pkg.value" [connection]="connection" size="calc(4px + .7vw)" weight="bold"></status>
|
<status *ngIf="connected" [pkg]="pkg.value" size="calc(4px + .7vw)" weight="bold"></status>
|
||||||
<ion-card-title>{{ (pkg.value | manifest).title }}</ion-card-title>
|
<ion-card-title>{{ (pkg.value | manifest).title }}</ion-card-title>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
@@ -48,5 +47,4 @@
|
|||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-container>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
|||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
|
||||||
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-list',
|
selector: 'app-list',
|
||||||
@@ -10,7 +11,9 @@ import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
|
|||||||
styleUrls: ['./app-list.page.scss'],
|
styleUrls: ['./app-list.page.scss'],
|
||||||
})
|
})
|
||||||
export class AppListPage {
|
export class AppListPage {
|
||||||
pkgs: PackageDataEntry[] = []
|
pkgs: { [id: string]: PackageDataEntry } = { }
|
||||||
|
connected: boolean
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
@@ -18,6 +21,19 @@ export class AppListPage {
|
|||||||
public readonly patch: PatchDbModel,
|
public readonly patch: PatchDbModel,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('package-data').subscribe(pkgs => {
|
||||||
|
this.pkgs = pkgs
|
||||||
|
}),
|
||||||
|
this.patch.connected$().subscribe(c => this.connected = c),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
launchUi (pkg: PackageDataEntry, event: Event): void {
|
launchUi (pkg: PackageDataEntry, event: Event): void {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|||||||
@@ -16,8 +16,7 @@
|
|||||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container *ngrxLet="connectionService.monitor$() as connection">
|
<ng-container *ngIf="pkg">
|
||||||
<ng-container *ngIf="pkg | status : connection as status">
|
|
||||||
<!-- top plate -->
|
<!-- top plate -->
|
||||||
<div class="top-plate">
|
<div class="top-plate">
|
||||||
<ion-item class="no-cushion-item" lines="none">
|
<ion-item class="no-cushion-item" lines="none">
|
||||||
@@ -42,17 +41,17 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<div class="status-readout">
|
<div class="status-readout">
|
||||||
<status size="large" weight="bold" [pkg]="pkg" [connection]="connection"></status>
|
<status *ngIf="connected" size="large" weight="bold" [pkg]="pkg"></status>
|
||||||
<ion-button *ngIf="status === FeStatus.NeedsConfig" expand="block" fill="outline" [routerLink]="['config']">
|
<ion-button *ngIf="pkg.status === FeStatus.NeedsConfig" expand="block" fill="outline" [routerLink]="['config']">
|
||||||
Configure
|
Configure
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="[FeStatus.Running, FeStatus.StartingUp, FeStatus.NeedsAttention] | includes : status" expand="block" fill="outline" color="danger" (click)="stop()">
|
<ion-button *ngIf="[FeStatus.Running, FeStatus.StartingUp, FeStatus.NeedsAttention] | includes : pkg.status" expand="block" fill="outline" color="danger" (click)="stop()">
|
||||||
Stop
|
Stop
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="status === FeStatus.DependencyIssue" expand="block" fill="outline" (click)="scrollToRequirements()">
|
<ion-button *ngIf="pkg.status === FeStatus.DependencyIssue" expand="block" fill="outline" (click)="scrollToRequirements()">
|
||||||
Fix
|
Fix
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="status === FeStatus.Stopped" expand="block" fill="outline" color="success" (click)="tryStart()">
|
<ion-button *ngIf="pkg.status === FeStatus.Stopped" expand="block" fill="outline" color="success" (click)="tryStart()">
|
||||||
Start
|
Start
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,11 +62,11 @@
|
|||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="!([FeStatus.Installing, FeStatus.Updating, FeStatus.Removing] | includes : status)">
|
<ng-container *ngIf="!([FeStatus.Installing, FeStatus.Updating, FeStatus.Removing] | includes : pkg.status)">
|
||||||
<ion-grid class="ion-text-center" style="margin: 0 6px;">
|
<ion-grid class="ion-text-center" style="margin: 0 6px;">
|
||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col *ngFor="let button of buttons" sizeMd="4" sizeSm="6" sizeXs="6">
|
<ion-col *ngFor="let button of buttons" sizeMd="4" sizeSm="6" sizeXs="6">
|
||||||
<ion-button style="width: 100%; min-height: 120px;" color="light" [disabled]="button.disabled | includes : status" (click)="button.action()">
|
<ion-button style="width: 100%; min-height: 120px;" color="light" [disabled]="button.disabled | includes : pkg.status" (click)="button.action()">
|
||||||
<div>
|
<div>
|
||||||
<ion-icon size="large" [name]="button.icon"></ion-icon>
|
<ion-icon size="large" [name]="button.icon"></ion-icon>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
@@ -133,5 +132,4 @@
|
|||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ export class AppShowPage {
|
|||||||
error: string
|
error: string
|
||||||
pkgId: string
|
pkgId: string
|
||||||
pkg: PackageDataEntry
|
pkg: PackageDataEntry
|
||||||
pkgSub: Subscription
|
|
||||||
hideLAN: boolean
|
hideLAN: boolean
|
||||||
buttons: Button[] = []
|
buttons: Button[] = []
|
||||||
manifest: Manifest = { } as Manifest
|
manifest: Manifest = { } as Manifest
|
||||||
|
connected: boolean
|
||||||
|
|
||||||
|
subs: Subscription[] = []
|
||||||
|
|
||||||
FeStatus = FEStatus
|
FeStatus = FEStatus
|
||||||
PackageState = PackageState
|
PackageState = PackageState
|
||||||
@@ -49,10 +51,13 @@ export class AppShowPage {
|
|||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||||
this.pkgSub = this.patch.watch$('package-data', this.pkgId).subscribe(pkg => {
|
this.subs = [
|
||||||
|
this.patch.watch$('package-data', this.pkgId).subscribe(pkg => {
|
||||||
this.pkg = pkg
|
this.pkg = pkg
|
||||||
this.manifest = getManifest(this.pkg)
|
this.manifest = getManifest(this.pkg)
|
||||||
})
|
}),
|
||||||
|
this.patch.connected$().subscribe(c => this.connected = c),
|
||||||
|
]
|
||||||
this.setButtons()
|
this.setButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +66,7 @@ export class AppShowPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnDestroy () {
|
async ngOnDestroy () {
|
||||||
this.pkgSub.unsubscribe()
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
}
|
}
|
||||||
|
|
||||||
launchUiTab (): void {
|
launchUiTab (): void {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import { PackageDataEntry } from '../models/patch-db/data-model'
|
import { PackageDataEntry } from '../models/patch-db/data-model'
|
||||||
import { ConnectionState } from '../services/connection.service'
|
|
||||||
import { renderPkgStatus } from '../services/pkg-status-rendering.service'
|
import { renderPkgStatus } from '../services/pkg-status-rendering.service'
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
@@ -8,8 +7,9 @@ import { renderPkgStatus } from '../services/pkg-status-rendering.service'
|
|||||||
})
|
})
|
||||||
export class DisplayBulbPipe implements PipeTransform {
|
export class DisplayBulbPipe implements PipeTransform {
|
||||||
|
|
||||||
transform (pkg: PackageDataEntry, bulb: DisplayBulb, connection: ConnectionState): boolean {
|
transform (pkg: PackageDataEntry, bulb: DisplayBulb, connected: boolean): boolean {
|
||||||
const { color } = renderPkgStatus(pkg, connection)
|
if (!connected) return bulb === 'off'
|
||||||
|
const { color } = renderPkgStatus(pkg)
|
||||||
switch (color) {
|
switch (color) {
|
||||||
case 'danger': return bulb === 'red'
|
case 'danger': return bulb === 'red'
|
||||||
case 'success': return bulb === 'green'
|
case 'success': return bulb === 'green'
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import { PackageDataEntry } from '../models/patch-db/data-model'
|
import { PackageDataEntry } from '../models/patch-db/data-model'
|
||||||
import { ConnectionState } from '../services/connection.service'
|
|
||||||
import { FEStatus, renderPkgStatus } from '../services/pkg-status-rendering.service'
|
import { FEStatus, renderPkgStatus } from '../services/pkg-status-rendering.service'
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'status',
|
name: 'status',
|
||||||
})
|
})
|
||||||
export class StatusPipe implements PipeTransform {
|
export class StatusPipe implements PipeTransform {
|
||||||
transform (pkg: PackageDataEntry, connection: ConnectionState): FEStatus {
|
transform (pkg: PackageDataEntry): FEStatus {
|
||||||
return renderPkgStatus(pkg, connection).feStatus
|
return renderPkgStatus(pkg).feStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
import { Http, Source, Update, Operation, Revision } from 'patch-db-client'
|
import { Http, Update, Operation, Revision } from 'patch-db-client'
|
||||||
import { RR } from './api-types'
|
import { RR } from './api-types'
|
||||||
import { DataModel } from 'src/app/models/patch-db/data-model'
|
import { DataModel } from 'src/app/models/patch-db/data-model'
|
||||||
import { filter } from 'rxjs/operators'
|
import { filter } from 'rxjs/operators'
|
||||||
import * as uuid from 'uuid'
|
|
||||||
|
|
||||||
export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
export abstract class ApiService implements Http<DataModel> {
|
||||||
protected readonly sync = new Subject<Update<DataModel>>()
|
protected readonly sync = new Subject<Update<DataModel>>()
|
||||||
private syncing = true
|
private syncing = true
|
||||||
|
|
||||||
|
|||||||
@@ -719,13 +719,18 @@ export module Mock {
|
|||||||
selected: 'Goosers5G',
|
selected: 'Goosers5G',
|
||||||
connected: 'Goosers5G',
|
connected: 'Goosers5G',
|
||||||
},
|
},
|
||||||
registry: 'https://registry.start9.com',
|
'package-registry': 'https://registry.start9.com',
|
||||||
|
'system-registry': 'https://registry.start9.com',
|
||||||
'unread-notification-count': 4,
|
'unread-notification-count': 4,
|
||||||
specs: {
|
specs: {
|
||||||
CPU: 'Cortex-A72: 4 Cores @1500MHz',
|
CPU: 'Cortex-A72: 4 Cores @1500MHz',
|
||||||
Disk: '1TB SSD',
|
Disk: '1TB SSD',
|
||||||
Memory: '8GB',
|
Memory: '8GB',
|
||||||
},
|
},
|
||||||
|
'connection-addresses': {
|
||||||
|
tor: [],
|
||||||
|
clearnet: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'package-data': {
|
'package-data': {
|
||||||
'bitcoind': bitcoind,
|
'bitcoind': bitcoind,
|
||||||
|
|||||||
@@ -1,80 +1,98 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { BehaviorSubject, fromEvent, merge, Observable, Subscription, timer } from 'rxjs'
|
import { BehaviorSubject, combineLatest, fromEvent, merge, Observable, Subscription } from 'rxjs'
|
||||||
import { delay, retryWhen, switchMap, tap } from 'rxjs/operators'
|
import { ConnectionStatus } from '../../../../../patch-db/client/dist'
|
||||||
import { ApiService } from './api/api.service'
|
import { DataModel } from '../models/patch-db/data-model'
|
||||||
|
import { PatchDbModel } from '../models/patch-db/patch-db-model'
|
||||||
|
import { HttpService, Method } from './http.service'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ConnectionService {
|
export class ConnectionService {
|
||||||
private httpSubscription$: Subscription
|
private addrs: DataModel['server-info']['connection-addresses']
|
||||||
private readonly networkState$ = new BehaviorSubject<boolean>(navigator.onLine)
|
private readonly networkState$ = new BehaviorSubject<boolean>(true)
|
||||||
private readonly internetState$ = new BehaviorSubject<boolean | null>(null)
|
private readonly connectionFailure$ = new BehaviorSubject<ConnectionFailure>(ConnectionFailure.None)
|
||||||
|
private subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly apiService: ApiService,
|
private readonly httpService: HttpService,
|
||||||
) {
|
private readonly patch: PatchDbModel,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
watch$ () {
|
||||||
|
return this.connectionFailure$.asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
start () {
|
||||||
|
this.subs = [
|
||||||
|
this.patch.watch$('server-info')
|
||||||
|
.subscribe(data => {
|
||||||
|
if (!data) return
|
||||||
|
this.addrs = data['connection-addresses'] || {
|
||||||
|
tor: [],
|
||||||
|
clearnet: [],
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
merge(fromEvent(window, 'online'), fromEvent(window, 'offline'))
|
merge(fromEvent(window, 'online'), fromEvent(window, 'offline'))
|
||||||
.subscribe(event => {
|
.subscribe(event => {
|
||||||
this.networkState$.next(event.type === 'online')
|
this.networkState$.next(event.type === 'online')
|
||||||
})
|
|
||||||
|
|
||||||
this.networkState$
|
|
||||||
.subscribe(online => {
|
|
||||||
if (online) {
|
|
||||||
this.testInternet()
|
|
||||||
} else {
|
|
||||||
this.killHttp()
|
|
||||||
this.internetState$.next(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor$ (): Observable<boolean> {
|
|
||||||
return this.internetState$.asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
private testInternet (): void {
|
|
||||||
this.killHttp()
|
|
||||||
|
|
||||||
// ping server every 10 seconds
|
|
||||||
this.httpSubscription$ = timer(0, 10000)
|
|
||||||
.pipe(
|
|
||||||
switchMap(() => this.apiService.echo()),
|
|
||||||
retryWhen(errors =>
|
|
||||||
errors.pipe(
|
|
||||||
tap(val => {
|
|
||||||
console.error('Echo error: ', val)
|
|
||||||
this.internetState$.next(false)
|
|
||||||
}),
|
}),
|
||||||
// restart after 2 seconds
|
|
||||||
delay(2000),
|
combineLatest([this.networkState$, this.patch.connectionStatus$()])
|
||||||
),
|
.subscribe(async ([network, connectionStatus]) => {
|
||||||
),
|
if (connectionStatus !== ConnectionStatus.Disconnected) {
|
||||||
)
|
this.connectionFailure$.next(ConnectionFailure.None)
|
||||||
.subscribe(() => {
|
} else if (!network) {
|
||||||
this.internetState$.next(true)
|
this.connectionFailure$.next(ConnectionFailure.Network)
|
||||||
|
} else {
|
||||||
|
this.connectionFailure$.next(ConnectionFailure.Diagnosing)
|
||||||
|
const torSuccess = await this.testAddrs(this.addrs.tor)
|
||||||
|
if (torSuccess) {
|
||||||
|
this.connectionFailure$.next(ConnectionFailure.Embassy)
|
||||||
|
} else {
|
||||||
|
const clearnetSuccess = await this.testAddrs(this.addrs.clearnet)
|
||||||
|
if (clearnetSuccess) {
|
||||||
|
this.connectionFailure$.next(ConnectionFailure.Tor)
|
||||||
|
} else {
|
||||||
|
this.connectionFailure$.next(ConnectionFailure.Internet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
stop () {
|
||||||
|
this.subs.forEach(sub => {
|
||||||
|
sub.unsubscribe()
|
||||||
})
|
})
|
||||||
|
this.subs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
private killHttp () {
|
private async testAddrs (addrs: string[]): Promise<boolean> {
|
||||||
if (this.httpSubscription$) {
|
if (!addrs.length) return true
|
||||||
this.httpSubscription$.unsubscribe()
|
|
||||||
this.httpSubscription$ = undefined
|
const results = await Promise.all(addrs.map(async addr => {
|
||||||
|
try {
|
||||||
|
await this.httpService.httpRequest({
|
||||||
|
method: Method.GET,
|
||||||
|
url: addr,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
}))
|
||||||
|
return results.includes(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export enum ConnectionFailure {
|
||||||
* Instance of this interface is used to report current connection status.
|
None = 'none',
|
||||||
*/
|
Diagnosing = 'diagnosing',
|
||||||
export interface ConnectionState {
|
Network = 'network',
|
||||||
/**
|
Embassy = 'embassy',
|
||||||
* "True" if browser has network connection. Determined by Window objects "online" / "offline" events.
|
Tor = 'tor',
|
||||||
*/
|
Internet = 'internet',
|
||||||
network: boolean
|
|
||||||
/**
|
|
||||||
* "True" if browser has Internet access. Determined by heartbeat system which periodically makes request to heartbeat Url.
|
|
||||||
*/
|
|
||||||
internet: boolean | null
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
import { HealthCheckResultLoading, MainStatusRunning, PackageDataEntry, PackageMainStatus, PackageState, Status } from '../models/patch-db/data-model'
|
import { HealthCheckResultLoading, MainStatusRunning, PackageDataEntry, PackageMainStatus, PackageState, Status } from '../models/patch-db/data-model'
|
||||||
import { ConnectionState } from './connection.service'
|
|
||||||
|
|
||||||
export function renderPkgStatus (pkg: PackageDataEntry, connection: ConnectionState): PkgStatusRendering {
|
|
||||||
if (!connection.network || !connection.internet) {
|
|
||||||
return { display: 'Connecting', color: 'medium', showDots: true, feStatus: FEStatus.Connecting }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export function renderPkgStatus (pkg: PackageDataEntry): PkgStatusRendering {
|
||||||
switch (pkg.state) {
|
switch (pkg.state) {
|
||||||
case PackageState.Installing: return { display: 'Installing', color: 'primary', showDots: true, feStatus: FEStatus.Installing }
|
case PackageState.Installing: return { display: 'Installing', color: 'primary', showDots: true, feStatus: FEStatus.Installing }
|
||||||
case PackageState.Updating: return { display: 'Updating', color: 'primary', showDots: true, feStatus: FEStatus.Updating }
|
case PackageState.Updating: return { display: 'Updating', color: 'primary', showDots: true, feStatus: FEStatus.Updating }
|
||||||
|
|||||||
Reference in New Issue
Block a user