chore: enable strict mode (#1569)

* chore: enable strict mode

* refactor: remove sync data access from PatchDbService

* launchable even when no LAN url

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
Alex Inkin
2022-07-22 18:51:08 +03:00
committed by GitHub
parent 9a01a0df8e
commit 7b8a0eadf3
130 changed files with 1130 additions and 1045 deletions

View File

@@ -4,6 +4,11 @@
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>Kernel Logs</ion-title>
<ion-buttons slot="end">
<ion-button (click)="copy()">
<ion-icon name="copy-outline"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>

View File

@@ -1,5 +1,7 @@
import { Component } from '@angular/core'
import { ToastController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { copyToClipboard, strip } from 'src/app/util/web.util'
@Component({
selector: 'kernel-logs',
@@ -7,12 +9,10 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
styleUrls: ['./kernel-logs.page.scss'],
})
export class KernelLogsPage {
pkgId: string
loading = true
needInfinite = true
before: string
constructor(private readonly embassyApi: ApiService) {}
constructor(
private readonly embassyApi: ApiService,
private readonly toastCtrl: ToastController,
) {}
fetchFetchLogs() {
return async (params: {
@@ -27,4 +27,22 @@ export class KernelLogsPage {
})
}
}
async copy(): Promise<void> {
const logs = document
.getElementById('template')
?.cloneNode(true) as HTMLElement
const formatted = '```' + strip(logs.innerHTML) + '```'
const success = await copyToClipboard(formatted)
const message = success
? 'Copied to clipboard!'
: 'Failed to copy to clipboard.'
const toast = await this.toastCtrl.create({
header: message,
position: 'bottom',
duration: 1000,
})
await toast.present()
}
}

View File

@@ -21,9 +21,11 @@ import {
first,
takeUntil,
} from 'rxjs/operators'
import { getServerInfo } from '../../../util/get-server-info'
import { getMarketplace } from '../../../util/get-marketplace'
type Marketplaces = {
id: string | undefined
id: string | null
name: string
url: string
}[]
@@ -35,7 +37,7 @@ type Marketplaces = {
providers: [DestroyService],
})
export class MarketplacesPage {
selectedId: string | undefined
selectedId: string | null = null
marketplaces: Marketplaces = []
constructor(
@@ -47,7 +49,7 @@ export class MarketplacesPage {
@Inject(AbstractMarketplaceService)
private readonly marketplaceService: MarketplaceService,
private readonly config: ConfigService,
public readonly patch: PatchDbService,
private readonly patch: PatchDbService,
private readonly destroy$: DestroyService,
) {}
@@ -58,13 +60,13 @@ export class MarketplacesPage {
.subscribe((mp: UIMarketplaceData | undefined) => {
let marketplaces: Marketplaces = [
{
id: undefined,
id: null,
name: this.config.marketplace.name,
url: this.config.marketplace.url,
},
]
if (mp) {
this.selectedId = mp['selected-id'] || undefined
this.selectedId = mp['selected-id']
const alts = Object.entries(mp['known-hosts']).map(([k, v]) => {
return {
id: k,
@@ -107,34 +109,33 @@ export class MarketplacesPage {
await modal.present()
}
async presentAction(id: string = '') {
async presentAction(id: string | null) {
// no need to view actions if is selected marketplace
if (id === this.patch.getData().ui.marketplace?.['selected-id']) return
const marketplace = await getMarketplace(this.patch)
if (id === marketplace['selected-id']) return
const buttons: ActionSheetButton[] = [
{
text: 'Forget',
icon: 'trash',
role: 'destructive',
handler: () => {
this.delete(id)
},
},
{
text: 'Connect to marketplace',
text: 'Connect',
handler: () => {
this.connect(id)
},
},
]
if (!id) {
buttons.shift()
if (id) {
buttons.unshift({
text: 'Delete',
role: 'destructive',
handler: () => {
this.delete(id)
},
})
}
const action = await this.actionCtrl.create({
header: id,
subHeader: 'Manage marketplaces',
header: this.marketplaces.find(mp => mp.id === id)?.name,
mode: 'ios',
buttons,
})
@@ -142,10 +143,8 @@ export class MarketplacesPage {
await action.present()
}
private async connect(id: string): Promise<void> {
const marketplace: UIMarketplaceData = JSON.parse(
JSON.stringify(this.patch.getData().ui.marketplace),
)
private async connect(id: string | null): Promise<void> {
const marketplace = await getMarketplace(this.patch)
const url = id
? marketplace['known-hosts'][id].url
@@ -157,10 +156,8 @@ export class MarketplacesPage {
await loader.present()
try {
await this.marketplaceService.getMarketplaceData(
{ 'server-id': this.patch.getData()['server-info'].id },
url,
)
const { id } = await getServerInfo(this.patch)
await this.marketplaceService.getMarketplaceData({ 'server-id': id }, url)
} catch (e: any) {
this.errToast.present(e)
loader.dismiss()
@@ -169,9 +166,13 @@ export class MarketplacesPage {
loader.message = 'Changing Marketplace...'
const value: UIMarketplaceData = {
...marketplace,
'selected-id': id,
}
try {
marketplace['selected-id'] = id
await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace })
await this.api.setDbValue({ pointer: `/marketplace`, value })
} catch (e: any) {
this.errToast.present(e)
loader.dismiss()
@@ -189,10 +190,8 @@ export class MarketplacesPage {
}
private async delete(id: string): Promise<void> {
if (!id) return
const marketplace: UIMarketplaceData = JSON.parse(
JSON.stringify(this.patch.getData().ui.marketplace),
)
const data = await getMarketplace(this.patch)
const marketplace: UIMarketplaceData = JSON.parse(JSON.stringify(data))
const loader = await this.loadingCtrl.create({
message: 'Deleting...',
@@ -210,13 +209,12 @@ export class MarketplacesPage {
}
private async save(url: string): Promise<void> {
const marketplace = this.patch.getData().ui.marketplace
? (JSON.parse(
JSON.stringify(this.patch.getData().ui.marketplace),
) as UIMarketplaceData)
const data = await getMarketplace(this.patch)
const marketplace: UIMarketplaceData = data
? JSON.parse(JSON.stringify(data))
: {
'selected-id': undefined,
'known-hosts': {} as Record<string, unknown>,
'selected-id': null,
'known-hosts': {},
}
// no-op on duplicates
@@ -231,8 +229,9 @@ export class MarketplacesPage {
try {
const id = v4()
const { id: serverId } = await getServerInfo(this.patch)
const { name } = await this.marketplaceService.getMarketplaceData(
{ 'server-id': this.patch.getData()['server-info'].id },
{ 'server-id': serverId },
url,
)
marketplace['known-hosts'][id] = { name, url }
@@ -254,13 +253,12 @@ export class MarketplacesPage {
}
private async saveAndConnect(url: string): Promise<void> {
const marketplace = this.patch.getData().ui.marketplace
? (JSON.parse(
JSON.stringify(this.patch.getData().ui.marketplace),
) as UIMarketplaceData)
const data = await getMarketplace(this.patch)
const marketplace: UIMarketplaceData = data
? JSON.parse(JSON.stringify(data))
: {
'selected-id': undefined,
'known-hosts': {} as Record<string, unknown>,
'selected-id': null,
'known-hosts': {},
}
// no-op on duplicates
@@ -274,8 +272,9 @@ export class MarketplacesPage {
try {
const id = v4()
const { id: serverId } = await getServerInfo(this.patch)
const { name } = await this.marketplaceService.getMarketplaceData(
{ 'server-id': this.patch.getData()['server-info'].id },
{ 'server-id': serverId },
url,
)
marketplace['known-hosts'][id] = { name, url }

View File

@@ -11,7 +11,10 @@
<ng-container *ngIf="ui$ | async as ui">
<ion-item-group *ngIf="server$ | async as server">
<ion-item-divider>General</ion-item-divider>
<ion-item button (click)="presentModalName('Embassy-' + server.id)">
<ion-item
button
(click)="presentModalName('Embassy-' + server.id, ui.name)"
>
<ion-label>Device Name</ion-label>
<ion-note slot="end">{{ ui.name || 'Embassy-' + server.id }}</ion-note>
</ion-item>

View File

@@ -1,4 +1,4 @@
import { Component, ViewChild } from '@angular/core'
import { Component } from '@angular/core'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import {
LoadingController,
@@ -34,7 +34,10 @@ export class PreferencesPage {
readonly serverConfig: ServerConfigService,
) {}
async presentModalName(placeholder: string): Promise<void> {
async presentModalName(
placeholder: string,
initialValue: string,
): Promise<void> {
const options: GenericInputOptions = {
title: 'Edit Device Name',
message: 'This is for your reference only.',
@@ -42,7 +45,7 @@ export class PreferencesPage {
useMask: false,
placeholder,
nullable: true,
initialValue: this.patch.getData().ui.name,
initialValue,
buttonText: 'Save',
submitFn: (value: string) =>
this.setDbValue('name', value || placeholder),

View File

@@ -7,7 +7,6 @@ import {
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { take } from 'rxjs/operators'
import { PackageMainStatus } from 'src/app/services/patch-db/data-model'
import { EOSService } from 'src/app/services/eos.service'
import { Observable } from 'rxjs'
@Component({
@@ -25,10 +24,7 @@ export class BackingUpComponent {
PackageMainStatus = PackageMainStatus
constructor(
public readonly eosService: EOSService,
public readonly patch: PatchDbService,
) {}
constructor(private readonly patch: PatchDbService) {}
}
@Pipe({

View File

@@ -20,6 +20,7 @@ import {
import { BackupSelectPage } from 'src/app/modals/backup-select/backup-select.page'
import { EOSService } from 'src/app/services/eos.service'
import { DestroyService } from '@start9labs/shared'
import { getServerInfo } from 'src/app/util/get-server-info'
@Component({
selector: 'server-backup',
@@ -28,7 +29,6 @@ import { DestroyService } from '@start9labs/shared'
providers: [DestroyService],
})
export class ServerBackupPage {
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>
serviceIds: string[] = []
readonly backingUp$ = this.eosService.backingUp$
@@ -56,8 +56,6 @@ export class ServerBackupPage {
async presentModalSelect(
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
) {
this.target = target
const modal = await this.modalCtrl.create({
presentingElement: await this.modalCtrl.getTop(),
component: BackupSelectPage,
@@ -66,14 +64,16 @@ export class ServerBackupPage {
modal.onWillDismiss().then(res => {
if (res.data) {
this.serviceIds = res.data
this.presentModalPassword()
this.presentModalPassword(target)
}
})
await modal.present()
}
async presentModalPassword(): Promise<void> {
private async presentModalPassword(
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
): Promise<void> {
const options: GenericInputOptions = {
title: 'Master Password Needed',
message: 'Enter your master password to encrypt this backup.',
@@ -83,25 +83,29 @@ export class ServerBackupPage {
buttonText: 'Create Backup',
submitFn: async (password: string) => {
// confirm password matches current master password
const passwordHash =
this.patch.getData()['server-info']['password-hash']
const { 'password-hash': passwordHash } = await getServerInfo(
this.patch,
)
argon2.verify(passwordHash, password)
// first time backup
if (!this.target.hasValidBackup) {
await this.createBackup(password)
if (!target.hasValidBackup) {
await this.createBackup(target, password)
// existing backup
} else {
try {
const passwordHash =
this.target.entry['embassy-os']?.['password-hash'] || ''
target.entry['embassy-os']?.['password-hash'] || ''
argon2.verify(passwordHash, password)
} catch {
setTimeout(() => this.presentModalOldPassword(password), 500)
setTimeout(
() => this.presentModalOldPassword(target, password),
500,
)
return
}
await this.createBackup(password)
await this.createBackup(target, password)
}
},
}
@@ -115,7 +119,10 @@ export class ServerBackupPage {
await m.present()
}
private async presentModalOldPassword(password: string): Promise<void> {
private async presentModalOldPassword(
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
password: string,
): Promise<void> {
const options: GenericInputOptions = {
title: 'Original Password Needed',
message:
@@ -125,11 +132,10 @@ export class ServerBackupPage {
useMask: true,
buttonText: 'Create Backup',
submitFn: async (oldPassword: string) => {
const passwordHash =
this.target.entry['embassy-os']?.['password-hash'] || ''
const passwordHash = target.entry['embassy-os']?.['password-hash'] || ''
argon2.verify(passwordHash, oldPassword)
await this.createBackup(password, oldPassword)
await this.createBackup(target, password, oldPassword)
},
}
@@ -143,6 +149,7 @@ export class ServerBackupPage {
}
private async createBackup(
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
password: string,
oldPassword?: string,
): Promise<void> {
@@ -153,7 +160,7 @@ export class ServerBackupPage {
try {
await this.embassyApi.createBackup({
'target-id': this.target.id,
'target-id': target.id,
'package-ids': this.serviceIds,
'old-password': oldPassword || null,
password,

View File

@@ -4,9 +4,11 @@
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>OS Logs</ion-title>
<ion-button slot="end" fill="clear" size="small" (click)="copy()">
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
<ion-buttons slot="end">
<ion-button (click)="copy()">
<ion-icon name="copy-outline"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>

View File

@@ -9,11 +9,6 @@ import { copyToClipboard, strip } from 'src/app/util/web.util'
styleUrls: ['./server-logs.page.scss'],
})
export class ServerLogsPage {
pkgId: string
loading = true
needInfinite = true
before: string
constructor(
private readonly embassyApi: ApiService,
private readonly toastCtrl: ToastController,

View File

@@ -4,25 +4,32 @@
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>Monitor</ion-title>
<ion-title slot="end"><ion-spinner name="dots" class="fader"></ion-spinner></ion-title>
<ion-title slot="end"
><ion-spinner name="dots" class="fader"></ion-spinner
></ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<skeleton-list *ngIf="loading" groups="2"></skeleton-list>
<skeleton-list *ngIf="loading" [groups]="2"></skeleton-list>
<div id="metricSection">
<ng-container *ngIf="!loading">
<ion-item-group *ngFor="let metricGroup of metrics | keyvalue : asIsOrder">
<ion-item-group
*ngFor="let metricGroup of metrics | keyvalue : asIsOrder"
>
<ion-item-divider>{{ metricGroup.key }}</ion-item-divider>
<ion-item *ngFor="let metric of metricGroup.value | keyvalue : asIsOrder">
<ion-item
*ngFor="let metric of metricGroup.value | keyvalue : asIsOrder"
>
<ion-label>{{ metric.key }}</ion-label>
<ion-note *ngIf="metric.value" slot="end" class="metric-note">
<ion-text style="color: white;">{{ metric.value.value }} {{ metric.value.unit }}</ion-text>
<ion-text style="color: white"
>{{ metric.value.value }} {{ metric.value.unit }}</ion-text
>
</ion-note>
</ion-item>
</ion-item-group>
</ng-container>
</div>
</ion-content>
</ion-content>

View File

@@ -37,7 +37,7 @@
<ng-container *ngFor="let button of cat.value">
<ion-item
button
[style.display]="(button.title === 'Repair Disk' && !(localStorageService.showDiskRepair$ | async)) ? 'none' : 'block'"
[style.display]="(button.title === 'Repair Disk' && !(showDiskRepair$ | async)) ? 'none' : 'block'"
[detail]="button.detail"
[disabled]="button.disabled | async"
(click)="button.action()"
@@ -55,7 +55,7 @@
*ngIf="!statusInfo['backup-progress'] && !statusInfo['update-progress']"
>
Last Backup: {{ server['last-backup'] ?
(server['last-backup'] | date: 'short') : 'never' }}
(server['last-backup'] | date: 'medium') : 'never' }}
</ion-text>
<span *ngIf="!!statusInfo['backup-progress']" class="inline">
<ion-spinner
@@ -76,9 +76,7 @@
Update Complete. Restart to apply changes
</ion-text>
<ng-template #notUpdated>
<ng-container
*ngIf="eosService.showUpdate$ | async; else check"
>
<ng-container *ngIf="showUpdate$ | async; else check">
<ion-text class="inline" color="success">
<ion-icon name="rocket-outline"></ion-icon>
Update Available

View File

@@ -15,6 +15,7 @@ import { EOSService } from 'src/app/services/eos.service'
import { LocalStorageService } from 'src/app/services/local-storage.service'
import { RecoveredPackageDataEntry } from 'src/app/services/patch-db/data-model'
import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page'
import { getAllPackages } from '../../../util/get-package-data'
@Component({
selector: 'server-show',
@@ -22,12 +23,14 @@ import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page'
styleUrls: ['server-show.page.scss'],
})
export class ServerShowPage {
hasRecoveredPackage: boolean
hasRecoveredPackage = false
clicks = 0
readonly server$ = this.patch.watch$('server-info')
readonly ui$ = this.patch.watch$('ui')
readonly connected$ = this.patch.connected$
readonly showUpdate$ = this.eosService.showUpdate$
readonly showDiskRepair$ = this.localStorageService.showDiskRepair$
constructor(
private readonly alertCtrl: AlertController,
@@ -38,8 +41,8 @@ export class ServerShowPage {
private readonly navCtrl: NavController,
private readonly route: ActivatedRoute,
private readonly patch: PatchDbService,
public readonly eosService: EOSService,
public readonly localStorageService: LocalStorageService,
private readonly eosService: EOSService,
private readonly localStorageService: LocalStorageService,
) {}
ngOnInit() {
@@ -63,7 +66,7 @@ export class ServerShowPage {
} else {
const modal = await this.modalCtrl.create({
componentProps: {
releaseNotes: this.eosService.eos['release-notes'],
releaseNotes: this.eosService.eos?.['release-notes'],
},
component: OSUpdatePage,
})
@@ -117,7 +120,8 @@ export class ServerShowPage {
}
async presentAlertSystemRebuild() {
const minutes = Object.keys(this.patch.getData()['package-data']).length * 2
const localPkgs = await getAllPackages(this.patch)
const minutes = Object.keys(localPkgs).length * 2
const alert = await this.alertCtrl.create({
header: 'Warning',
message: `This action will tear down all service containers and rebuild them from scratch. No data will be deleted. This action is useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues. It may take up to ${minutes} minutes to complete. During this time, you will lose all connectivity to your Embassy.`,

View File

@@ -21,7 +21,7 @@
<ion-item>
<ion-label>
<h2>Git Hash</h2>
<p>{{ config.gitHash }}</p>
<p>{{ gitHash }}</p>
</ion-label>
</ion-item>

View File

@@ -1,5 +1,5 @@
import { Component, ViewChild } from '@angular/core'
import { IonContent, ToastController } from '@ionic/angular'
import { Component } from '@angular/core'
import { ToastController } from '@ionic/angular'
import { copyToClipboard } from 'src/app/util/web.util'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { ConfigService } from 'src/app/services/config.service'
@@ -10,18 +10,16 @@ import { ConfigService } from 'src/app/services/config.service'
styleUrls: ['./server-specs.page.scss'],
})
export class ServerSpecsPage {
@ViewChild(IonContent) content: IonContent
readonly server$ = this.patch.watch$('server-info')
constructor(
private readonly toastCtrl: ToastController,
private readonly patch: PatchDbService,
public readonly config: ConfigService,
private readonly config: ConfigService,
) {}
ngAfterViewInit() {
this.content.scrollToPoint(undefined, 1)
get gitHash(): string {
return this.config.gitHash
}
async copy(address: string) {

View File

@@ -9,7 +9,7 @@
<ion-content class="ion-padding-top">
<!-- loading -->
<ion-item-group *ngIf="loading">
<ion-item-group *ngIf="loading; else notLoading">
<div *ngFor="let entry of ['This Session', 'Other Sessions']">
<ion-item-divider>{{ entry }}</ion-item-divider>
<ion-item style="padding-bottom: 6px">
@@ -41,60 +41,64 @@
</ion-item-group>
<!-- not loading -->
<ion-item-group *ngIf="!loading">
<ion-item-divider>Current Session</ion-item-divider>
<ion-item>
<ion-icon
slot="start"
size="large"
[name]="getPlatformIcon(currentSession.metadata.platforms)"
></ion-icon>
<ion-label>
<h1>{{ getPlatformName(currentSession.metadata.platforms) }}</h1>
<h2>
Last Active: {{ currentSession['last-active'] | date : 'medium' }}
</h2>
<p>{{ currentSession['user-agent'] }}</p>
</ion-label>
</ion-item>
<ion-item-divider>
Other Sessions
<ion-button
*ngIf="otherSessions.length"
slot="end"
fill="clear"
strong
(click)="presentAlertKillAll()"
>
Terminate all
</ion-button>
</ion-item-divider>
<div *ngFor="let session of otherSessions">
<ng-template #notLoading>
<ion-item-group *ngIf="currentSession">
<ion-item-divider>Current Session</ion-item-divider>
<ion-item>
<ion-icon
slot="start"
size="large"
[name]="getPlatformIcon(session.metadata.platforms)"
[name]="getPlatformIcon(currentSession.metadata.platforms)"
></ion-icon>
<ion-label>
<h1>{{ getPlatformName(session.metadata.platforms) }}</h1>
<h2>Last Active: {{ session['last-active'] | date : 'medium' }}</h2>
<p>{{ session['user-agent'] }}</p>
<h1>{{ getPlatformName(currentSession.metadata.platforms) }}</h1>
<h2>
Last Active: {{ currentSession['last-active'] | date : 'medium' }}
</h2>
<p>{{ currentSession['user-agent'] }}</p>
</ion-label>
</ion-item>
<ion-item-divider>
Other Sessions
<ion-button
*ngIf="otherSessions.length"
slot="end"
fill="clear"
(click)="presentAlertKill(session.id)"
strong
(click)="presentAlertKillAll()"
>
<ion-icon slot="icon-only" name="log-out-outline"></ion-icon>
Terminate all
</ion-button>
</ion-item-divider>
<div *ngFor="let session of otherSessions">
<ion-item>
<ion-icon
slot="start"
size="large"
[name]="getPlatformIcon(session.metadata.platforms)"
></ion-icon>
<ion-label>
<h1>{{ getPlatformName(session.metadata.platforms) }}</h1>
<h2>Last Active: {{ session['last-active'] | date : 'medium' }}</h2>
<p>{{ session['user-agent'] }}</p>
</ion-label>
<ion-button
slot="end"
fill="clear"
color="danger"
(click)="kill([session.id])"
>
Logout
<ion-icon slot="start" name="log-out-outline"></ion-icon>
</ion-button>
</ion-item>
</div>
<ion-item *ngIf="!otherSessions.length">
<ion-label>
<p>You are not logged in anywhere else</p>
</ion-label>
</ion-item>
</div>
<ion-item *ngIf="!otherSessions.length">
<ion-label>
<p>You are not logged in anywhere else</p>
</ion-label>
</ion-item>
</ion-item-group>
</ion-item-group>
</ng-template>
</ion-content>

View File

@@ -11,7 +11,7 @@ import { PlatformType, Session } from 'src/app/services/api/api.types'
})
export class SessionsPage {
loading = true
currentSession: Session
currentSession?: Session
otherSessions: SessionWithId[] = []
constructor(
@@ -67,27 +67,6 @@ export class SessionsPage {
await alert.present()
}
async presentAlertKill(id: string) {
const alert = await this.alertCtrl.create({
header: 'Confirm',
message: 'Terminate other web session?',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Terminate',
handler: () => {
this.kill([id])
},
cssClass: 'enter-click',
},
],
})
await alert.present()
}
async kill(ids: string[]): Promise<void> {
const loader = await this.loadingCtrl.create({
message: `Terminating session${ids.length > 1 ? 's' : ''}...`,

View File

@@ -21,14 +21,13 @@
color="dark"
style="font-size: 42px"
></ion-icon>
<h4>Manually upload a service package</h4>
<h4>Upload .s9pk package file</h4>
<p *ngIf="onTor">
<ion-text color="success"
>Tip: switch to LAN for faster uploads.</ion-text
>
</p>
<br />
<ion-button color="primary" type="file">
<ion-button color="primary" type="file" class="ion-margin-top">
<label for="upload-photo">Browse</label>
<input
type="file"

View File

@@ -58,7 +58,6 @@
min-width: 200px;
max-width: 200px;
height: auto;
padding: 0;
display: flex;
flex-direction: column;
justify-content: center;
@@ -89,4 +88,4 @@
p {
text-align: center;
}
}
}

View File

@@ -30,7 +30,7 @@ export class SideloadPage {
file: null,
}
onTor = this.config.isTor()
uploadState: {
uploadState?: {
invalid: boolean
message: string
}
@@ -52,6 +52,7 @@ export class SideloadPage {
const files = e.target.files
this.setFile(files)
}
async setFile(files?: File[]) {
if (!files || !files.length) return
const file = files[0]

View File

@@ -66,7 +66,7 @@
<ion-icon slot="start" name="key-outline" size="large"></ion-icon>
<ion-label>
<h1>{{ ssh.hostname }}</h1>
<h2>{{ ssh['created-at'] | date: 'short' }}</h2>
<h2>{{ ssh['created-at'] | date: 'medium' }}</h2>
<p>{{ ssh.alg }} {{ ssh.fingerprint }}</p>
</ion-label>
<ion-button