react to enter key for alerts and modals. Styling and logic

This commit is contained in:
Matt Hill
2021-08-24 16:55:53 -06:00
committed by Matt Hill
parent 89229cdd1f
commit fc82fc2ec4
24 changed files with 195 additions and 229 deletions

View File

@@ -101,7 +101,7 @@
<ion-icon name="pulse"></ion-icon> <ion-icon name="pulse"></ion-icon>
<ion-icon name="qr-code-outline"></ion-icon> <ion-icon name="qr-code-outline"></ion-icon>
<ion-icon name="receipt-outline"></ion-icon> <ion-icon name="receipt-outline"></ion-icon>
<ion-icon name="refresh-outline"></ion-icon> <ion-icon name="refresh"></ion-icon>
<ion-icon name="reload-outline"></ion-icon> <ion-icon name="reload-outline"></ion-icon>
<ion-icon name="remove-outline"></ion-icon> <ion-icon name="remove-outline"></ion-icon>
<ion-icon name="save-outline"></ion-icon> <ion-icon name="save-outline"></ion-icon>

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core' import { Component, HostListener } from '@angular/core'
import { Storage } from '@ionic/storage-angular' import { Storage } from '@ionic/storage-angular'
import { AuthService, AuthState } from './services/auth.service' import { AuthService, AuthState } from './services/auth.service'
import { ApiService } from './services/api/embassy-api.service' import { ApiService } from './services/api/embassy-api.service'
@@ -24,6 +24,15 @@ import { Subscription } from 'rxjs'
styleUrls: ['app.component.scss'], styleUrls: ['app.component.scss'],
}) })
export class AppComponent { export class AppComponent {
@HostListener('document:keypress', ['$event'])
handleKeyboardEvent (event: KeyboardEvent) {
if (event.key === 'Enter') {
const elems = document.getElementsByClassName('enter-click')
const elem = elems[elems.length - 1] as HTMLButtonElement
if (elem) elem.click()
}
}
ServerStatus = ServerStatus ServerStatus = ServerStatus
showMenu = false showMenu = false
selectedIndex = 0 selectedIndex = 0
@@ -143,6 +152,47 @@ export class AppComponent {
window.open(url, '_blank') window.open(url, '_blank')
} }
async presentAlertLogout () {
// @TODO warn user no way to recover Embassy if logout and forget password. Maybe require password to logout?
const alert = await this.alertCtrl.create({
header: 'Caution',
message: 'Are you sure you want to logout?',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Logout',
cssClass: 'enter-click',
handler: () => {
this.logout()
},
},
],
})
await alert.present()
}
private async logout () {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Logging out...',
cssClass: 'loader',
})
await loader.present()
try {
await this.embassyApi.logout({ })
this.authService.setUnverified()
} catch (e) {
await this.errToast.present(e)
} finally {
loader.dismiss()
}
}
private watchConnection (): Subscription { private watchConnection (): Subscription {
return this.connectionService.watchFailure$() return this.connectionService.watchFailure$()
.pipe( .pipe(
@@ -234,7 +284,7 @@ export class AppComponent {
}) })
} }
async presentAlertRefreshNeeded () { private async presentAlertRefreshNeeded () {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: false, backdropDismiss: false,
header: 'Refresh Needed', header: 'Refresh Needed',
@@ -242,6 +292,7 @@ export class AppComponent {
buttons: [ buttons: [
{ {
text: 'Refresh Page', text: 'Refresh Page',
cssClass: 'enter-click',
handler: () => { handler: () => {
location.reload() location.reload()
}, },
@@ -251,46 +302,6 @@ export class AppComponent {
await alert.present() await alert.present()
} }
async presentAlertLogout () {
// @TODO warn user no way to recover Embassy if logout and forget password. Maybe require password to logout?
const alert = await this.alertCtrl.create({
backdropDismiss: false,
header: 'Caution',
message: 'Are you sure you want to logout?',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Logout',
handler: () => {
this.logout()
},
},
],
})
await alert.present()
}
private async logout () {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Logging out...',
cssClass: 'loader',
})
await loader.present()
try {
await this.embassyApi.logout({ })
this.authService.setUnverified()
} catch (e) {
await this.errToast.present(e)
} finally {
loader.dismiss()
}
}
private async presentToastNotifications () { private async presentToastNotifications () {
const toast = await this.toastCtrl.create({ const toast = await this.toastCtrl.create({
header: 'Embassy', header: 'Embassy',

View File

@@ -1,15 +1,16 @@
<ion-item-group [formGroup]="formGroup"> <ion-item-group [formGroup]="formGroup">
<div *ngFor="let entry of formGroup.controls | keyvalue : asIsOrder"> <div *ngFor="let entry of formGroup.controls | keyvalue : asIsOrder">
<!-- union enum -->
<ng-container *ngIf="unionSpec && entry.key === unionSpec.tag.id"> <ng-container *ngIf="unionSpec && entry.key === unionSpec.tag.id">
<p class="input-label">{{ unionSpec.tag.name }}</p> <p class="input-label">{{ unionSpec.tag.name }}</p>
<ion-item> <ion-item>
<ion-label>{{ unionSpec.tag.name }}</ion-label> <ion-label>{{ unionSpec.tag.name }}</ion-label>
<ion-select slot="end" placeholder="Select" [formControlName]="unionSpec.tag.id" [selectedText]="unionSpec.tag['variant-names'][entry.value.value]" (ionChange)="presentAlertChangeWarning(entry.key, unionSpec); updateUnion($event)"> <ion-select [interfaceOptions]="{ message: getWarningText(unionSpec.warning) }" slot="end" placeholder="Select" [formControlName]="unionSpec.tag.id" [selectedText]="unionSpec.tag['variant-names'][entry.value.value]" (ionChange)="updateUnion($event)">
<ion-select-option *ngFor="let option of Object.keys(unionSpec.variants)" [value]="option"> <ion-select-option *ngFor="let option of Object.keys(unionSpec.variants)" [value]="option">
{{ unionSpec.tag['variant-names'][option] }} {{ unionSpec.tag['variant-names'][option] }}
</ion-select-option> </ion-select-option>
</ion-select> </ion-select>
</ion-item> </ion-item>
</ng-container> </ng-container>
<ng-container *ngIf="objectSpec[entry.key] as spec"> <ng-container *ngIf="objectSpec[entry.key] as spec">
<!-- primitive --> <!-- primitive -->

View File

@@ -1,7 +1,7 @@
import { Component, Input, Output, SimpleChange, EventEmitter } from '@angular/core' import { Component, Input, Output, SimpleChange, EventEmitter } from '@angular/core'
import { AbstractFormGroupDirective, FormArray, FormGroup } from '@angular/forms' import { AbstractFormGroupDirective, FormArray, FormGroup } from '@angular/forms'
import { AlertController, IonicSafeString, ModalController } from '@ionic/angular' import { AlertButton, AlertController, IonicSafeString, ModalController } from '@ionic/angular'
import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecBoolean, ValueSpecList, ValueSpecListOf, ValueSpecNumber, ValueSpecString, ValueSpecUnion } from 'src/app/pkg-config/config-types' import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecBoolean, ValueSpecList, ValueSpecListOf, ValueSpecUnion } from 'src/app/pkg-config/config-types'
import { FormService } from 'src/app/services/form.service' import { FormService } from 'src/app/services/form.service'
import { Range } from 'src/app/pkg-config/config-utilities' import { Range } from 'src/app/pkg-config/config-utilities'
import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page' import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page'
@@ -34,17 +34,6 @@ export class FormObjectComponent {
) { } ) { }
ngOnChanges (changes: { [propName: string]: SimpleChange }) { ngOnChanges (changes: { [propName: string]: SimpleChange }) {
// @TODO figure out why changes are being triggered so often. If too heavy, switch to ngOnInit and figure out another way to manually reset defaults is executed. Needed because otherwise ObjectListInfo won't be accurate.
// if ( changes['current'] && changes['current'].previousValue != changes['current'].currentValue ) {
// console.log('CURRENT')
// }
// if ( changes['formGroup'] && changes['formGroup'].previousValue != changes['formGroup'].currentValue ) {
// console.log('FORM GROUP')
// }
// if ( changes['objectSpec'] && changes['objectSpec'].previousValue != changes['objectSpec'].currentValue ) {
// console.log('OBJECT SPEC')
// }
// Lists are automatically expanded, but their members are not // Lists are automatically expanded, but their members are not
Object.keys(this.objectSpec).forEach(key => { Object.keys(this.objectSpec).forEach(key => {
const spec = this.objectSpec[key] const spec = this.objectSpec[key]
@@ -157,12 +146,13 @@ export class FormObjectComponent {
if (!spec.warning || this.warningAck[key]) return okFn ? okFn() : null if (!spec.warning || this.warningAck[key]) return okFn ? okFn() : null
this.warningAck[key] = true this.warningAck[key] = true
const buttons = [ const buttons: AlertButton[] = [
{ {
text: 'Ok', text: 'Ok',
handler: () => { handler: () => {
if (okFn) okFn() if (okFn) okFn()
}, },
cssClass: 'enter-click',
}, },
] ]
@@ -186,7 +176,6 @@ export class FormObjectComponent {
async presentAlertDelete (key: string, index: number) { async presentAlertDelete (key: string, index: number) {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: false,
header: 'Confirm', header: 'Confirm',
message: 'Are you sure you want to delete this entry?', message: 'Are you sure you want to delete this entry?',
buttons: [ buttons: [
@@ -199,6 +188,7 @@ export class FormObjectComponent {
handler: () => { handler: () => {
this.deleteListItem(key, index) this.deleteListItem(key, index)
}, },
cssClass: 'enter-click',
}, },
], ],
}) })
@@ -260,7 +250,6 @@ export class FormLabelComponent {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: name, header: name,
message: description, message: description,
buttons: ['Ok'],
}) })
await alert.present() await alert.present()
} }

View File

@@ -49,12 +49,12 @@
<!-- next/finish buttons --> <!-- next/finish buttons -->
<ng-container *ngIf="!(currentSlide.loading$ | async)"> <ng-container *ngIf="!(currentSlide.loading$ | async)">
<!-- next --> <!-- next -->
<ion-button slot="end" *ngIf="currentBottomBar.next as next" (click)="transitions.next()" fill="outline" class="toolbar-button" color="primary"> <ion-button slot="end" *ngIf="currentBottomBar.next as next" (click)="transitions.next()" fill="outline" class="toolbar-button enter-click">
<ion-text [class.smaller-text]="next.length > 16">{{ next }}</ion-text> <ion-text [class.smaller-text]="next.length > 16">{{ next }}</ion-text>
</ion-button> </ion-button>
<!-- finish --> <!-- finish -->
<ion-button slot="end" *ngIf="currentBottomBar.finish as finish" (click)="transitions.final()" fill="outline" class="toolbar-button" color="primary"> <ion-button slot="end" *ngIf="currentBottomBar.finish as finish" (click)="transitions.final()" fill="outline" class="toolbar-button enter-click">
<ion-text [class.smaller-text]="finish.length > 16">{{ finish }}</ion-text> <ion-text [class.smaller-text]="finish.length > 16">{{ finish }}</ion-text>
</ion-button> </ion-button>
</ng-container> </ng-container>

View File

@@ -1,9 +1,14 @@
<ion-header style="min-height: unset;"> <ion-header>
<ion-toolbar *ngIf="patch.data['package-data'][pkgId] as pkg"> <ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>Config</ion-title> <ion-title>Config</ion-title>
<ion-buttons slot="end" class="ion-padding-end"> <ion-buttons slot="end" class="ion-padding-end">
<ion-button fill="clear" [disabled]="loadingText" (click)="resetDefaults()"> <ion-button fill="clear" [disabled]="loadingText" (click)="resetDefaults()">
<ion-icon slot="start" name="refresh-outline"></ion-icon> <ion-icon slot="start" name="refresh"></ion-icon>
Reset Defaults Reset Defaults
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>
@@ -19,17 +24,11 @@
<ng-template #loaded> <ng-template #loaded>
<ng-container *ngIf="patch.data['package-data'][pkgId] as pkg"> <ng-container *ngIf="patch.data['package-data'][pkgId] as pkg">
<ng-container *ngIf="pkg.manifest.config && !pkg.installed.status.configured && !edited"> <ion-item>
<ion-item class="notifier-item"> <ion-label>
<ion-label> <ion-text color="success">To use the default config for {{ pkg.manifest.title }}, click "Save" below.</ion-text>
<h2 style="display: flex; align-items: center; margin-bottom: 3px;"> </ion-label>
<ion-icon size="small" style="margin-right: 5px" slot="start" color="dark" slot="start" name="alert-circle-outline"></ion-icon> </ion-item>
<ion-text style="font-size: smaller;">Initial Config</ion-text>
</h2>
<p style="font-size: small">To use the default config for {{ pkg.manifest.title }}, click "Save" above.</p>
</ion-label>
</ion-item>
</ng-container>
<ng-container *ngIf="rec && showRec"> <ng-container *ngIf="rec && showRec">
<ion-item class="rec-item"> <ion-item class="rec-item">
@@ -81,13 +80,8 @@
<ion-footer> <ion-footer>
<ion-toolbar *ngIf="patch.data['package-data'][pkgId] as pkg"> <ion-toolbar *ngIf="patch.data['package-data'][pkgId] as pkg">
<ion-buttons slot="start" class="ion-padding-start">
<ion-button fill="outline" (click)="dismiss()">
Cancel
</ion-button>
</ion-buttons>
<ion-buttons slot="end" class="ion-padding-end"> <ion-buttons slot="end" class="ion-padding-end">
<ion-button fill="outline" color="primary" [disabled]="loadingText" (click)="save(pkg)"> <ion-button fill="outline" [disabled]="loadingText" (click)="save(pkg)" class="enter-click">
Save Save
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>

View File

@@ -172,6 +172,7 @@ export class AppConfigPage {
handler: () => { handler: () => {
this.modalCtrl.dismiss() this.modalCtrl.dismiss()
}, },
cssClass: 'enter-click',
}, },
], ],
}) })

View File

@@ -1,10 +1,15 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title> <ion-title>
{{ spec.name }} {{ spec.name }}
</ion-title> </ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">
<ion-button slot="end" fill="clear" color="primary" (click)="toggleSelectAll()"> <ion-button slot="end" fill="clear" (click)="toggleSelectAll()">
{{ selectAll ? 'All' : 'None' }} {{ selectAll ? 'All' : 'None' }}
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>
@@ -22,13 +27,8 @@
<ion-footer> <ion-footer>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start" class="ion-padding-start">
<ion-button fill="outline" (click)="dismiss()">
Cancel
</ion-button>
</ion-buttons>
<ion-buttons slot="end" class="ion-padding-end"> <ion-buttons slot="end" class="ion-padding-end">
<ion-button fill="outline" color="primary" (click)="save()"> <ion-button fill="outline" (click)="save()" class="enter-click">
Done Done
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>

View File

@@ -1,5 +1,10 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>{{ title }}</ion-title> <ion-title>{{ title }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -15,13 +20,8 @@
<ion-footer> <ion-footer>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start" class="ion-padding-start">
<ion-button fill="clear" (click)="dismiss()">
Cancel
</ion-button>
</ion-buttons>
<ion-buttons slot="end" class="ion-padding-end"> <ion-buttons slot="end" class="ion-padding-end">
<ion-button *ngFor="let button of buttons" fill="clear" (click)="handleClick(button)"> <ion-button *ngFor="let button of buttons" fill="outline" (click)="handleClick(button)">
{{ button.text }} {{ button.text }}
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>

View File

@@ -8,5 +8,5 @@
.main-content { .main-content {
height: 100%; height: 100%;
color: var(--ion-color-medium); color: var(--ion-color-dark);
} }

View File

@@ -80,6 +80,7 @@ export class AppActionsPage {
handler: () => { handler: () => {
this.executeAction(pkg.manifest.id, action.key) this.executeAction(pkg.manifest.id, action.key)
}, },
cssClass: 'enter-click',
}, },
], ],
}) })
@@ -104,7 +105,7 @@ export class AppActionsPage {
header: 'Forbidden', header: 'Forbidden',
message: error || `Action "${action.value.name}" can only be executed when service is ${statusesStr}`, message: error || `Action "${action.value.name}" can only be executed when service is ${statusesStr}`,
buttons: ['OK'], buttons: ['OK'],
cssClass: 'alert-error-message', cssClass: 'alert-error-message enter-click',
}) })
await alert.present() await alert.present()
} }
@@ -159,7 +160,13 @@ export class AppActionsPage {
const successAlert = await this.alertCtrl.create({ const successAlert = await this.alertCtrl.create({
header: 'Execution Complete', header: 'Execution Complete',
message: res.message.split('\n').join('</br ></br />'), message: res.message.split('\n').join('</br ></br />'),
buttons: ['OK'], buttons: [
{
text: 'Ok',
role: 'cancel',
cssClass: 'enter-click',
},
],
}) })
setTimeout(() => successAlert.present(), 400) setTimeout(() => successAlert.present(), 400)

View File

@@ -6,7 +6,7 @@
<ion-title>Properties</ion-title> <ion-title>Properties</ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">
<ion-button (click)="refresh()"> <ion-button (click)="refresh()">
<ion-icon slot="start" name="refresh-outline"></ion-icon> <ion-icon slot="start" name="refresh"></ion-icon>
Refresh Refresh
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>

View File

@@ -130,7 +130,6 @@ export class AppShowPage {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: 'Not Accepting Donations', header: 'Not Accepting Donations',
message: `The developers of ${this.pkg.manifest.title} have not provided a donation URL. Please contact them directly if you insist on giving them money.`, message: `The developers of ${this.pkg.manifest.title} have not provided a donation URL. Please contact them directly if you insist on giving them money.`,
buttons: ['OK'],
}) })
await alert.present() await alert.present()
} }
@@ -215,6 +214,7 @@ export class AppShowPage {
handler: () => { handler: () => {
this.start() this.start()
}, },
cssClass: 'enter-click',
}, },
], ],
}) })

View File

@@ -140,18 +140,6 @@
<ion-row> <ion-row>
<ion-col sizeSm="12" sizeMd="6"> <ion-col sizeSm="12" sizeMd="6">
<ion-item-group> <ion-item-group>
<ion-item detail="false">
<ion-label>
<h2>Service ID</h2>
<p>{{ pkg.manifest.id }}</p>
</ion-label>
</ion-item>
<ion-item detail="false">
<ion-label>
<h2>Categories</h2>
<p>{{ pkg.categories.join(', ') }}</p>
</ion-label>
</ion-item>
<ion-item button detail="false" (click)="presentAlertVersions()"> <ion-item button detail="false" (click)="presentAlertVersions()">
<ion-label> <ion-label>
<h2>Other Versions</h2> <h2>Other Versions</h2>
@@ -198,20 +186,6 @@
</ion-label> </ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon> <ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item> </ion-item>
<ion-item [href]="pkg.manifest['marketing-site']" target="_blank" detail="false">
<ion-label>
<h2>Marketing Site</h2>
<p>{{ pkg.manifest['marketing-site'] }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item *ngIf="pkg.manifest['donation-url'] as donationUrl" [href]="donationUrl" target="_blank" detail="false">
<ion-label>
<h2>Donation Site</h2>
<p>{{ donationUrl }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
</ion-item-group> </ion-item-group>
</ion-col> </ion-col>
</ion-row> </ion-row>

View File

@@ -81,7 +81,6 @@ export class MarketplaceShowPage {
async presentAlertVersions () { async presentAlertVersions () {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: 'Versions', header: 'Versions',
backdropDismiss: false,
inputs: this.marketplaceService.pkgs[this.pkgId].versions.sort((a, b) => -1 * this.emver.compare(a, b)).map(v => { inputs: this.marketplaceService.pkgs[this.pkgId].versions.sort((a, b) => -1 * this.emver.compare(a, b)).map(v => {
return { return {
name: v, // for CSS name: v, // for CSS
@@ -100,6 +99,7 @@ export class MarketplaceShowPage {
handler: (version: string) => { handler: (version: string) => {
this.getPkg(version) this.getPkg(version)
}, },
cssClass: 'enter-click',
}, },
], ],
}) })

View File

@@ -32,7 +32,6 @@ export class SessionsPage {
async presentAlertKill (id: string) { async presentAlertKill (id: string) {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: false,
header: 'Caution', header: 'Caution',
message: `Are you sure you want to kill this session?`, message: `Are you sure you want to kill this session?`,
buttons: [ buttons: [
@@ -45,6 +44,7 @@ export class SessionsPage {
handler: () => { handler: () => {
this.kill(id) this.kill(id)
}, },
cssClass: 'enter-click',
}, },
], ],
}) })

View File

@@ -22,7 +22,7 @@
<ion-item-divider>Saved Keys</ion-item-divider> <ion-item-divider>Saved Keys</ion-item-divider>
<ion-item button detail="false" (click)="serverConfig.presentModalInput('ssh')"> <ion-item button detail="false" (click)="presentModalAdd()">
<ion-icon slot="start" name="add" size="large"></ion-icon> <ion-icon slot="start" name="add" size="large"></ion-icon>
<ion-label>Add new key</ion-label> <ion-label>Add new key</ion-label>
</ion-item> </ion-item>

View File

@@ -1,10 +1,9 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { ServerConfigService } from 'src/app/services/server-config.service' import { AlertController, LoadingController, ModalController } from '@ionic/angular'
import { AlertController, LoadingController } from '@ionic/angular'
import { SSHService } from './ssh.service'
import { Subscription } from 'rxjs'
import { SSHKeys } from 'src/app/services/api/api.types' import { SSHKeys } from 'src/app/services/api/api.types'
import { ErrorToastService } from 'src/app/services/error-toast.service' import { ErrorToastService } from 'src/app/services/error-toast.service'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/backup-confirmation.component'
@Component({ @Component({
selector: 'ssh-keys', selector: 'ssh-keys',
@@ -14,37 +13,66 @@ import { ErrorToastService } from 'src/app/services/error-toast.service'
export class SSHKeysPage { export class SSHKeysPage {
loading = true loading = true
sshKeys: SSHKeys sshKeys: SSHKeys
subs: Subscription[] = []
readonly docsUrl = 'https://docs.start9.com/user-manual/general/developer-options/ssh-setup.html' readonly docsUrl = 'https://docs.start9.com/user-manual/general/developer-options/ssh-setup.html'
constructor ( constructor (
private readonly loadingCtrl: LoadingController, private readonly loadingCtrl: LoadingController,
private readonly modalCtrl: ModalController,
private readonly errToast: ErrorToastService, private readonly errToast: ErrorToastService,
private readonly alertCtrl: AlertController, private readonly alertCtrl: AlertController,
private readonly sshService: SSHService, private readonly embassyApi: ApiService,
public readonly serverConfig: ServerConfigService,
) { } ) { }
async ngOnInit () { async ngOnInit () {
this.subs = [ await this.getKeys()
this.sshService.watch$()
.subscribe(keys => {
this.sshKeys = keys
}),
]
await this.sshService.getKeys()
this.loading = false
} }
ngOnDestroy () { async getKeys (): Promise<void> {
this.subs.forEach(sub => sub.unsubscribe()) try {
this.sshKeys = await this.embassyApi.getSshKeys({ })
} catch (e) {
this.errToast.present(e)
} finally {
this.loading = false
}
}
async presentModalAdd () {
const { name, description } = sshSpec
const modal = await this.modalCtrl.create({
component: BackupConfirmationComponent,
componentProps: {
title: name,
message: description,
label: name,
submitFn: this.add,
},
cssClass: 'alertlike-modal',
})
await modal.present()
}
async add (pubkey: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
cssClass: 'loader',
})
await loader.present()
try {
const key = await this.embassyApi.addSshKey({ pubkey })
this.sshKeys = { ...this.sshKeys, ...key }
} catch (e) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
} }
async presentAlertDelete (hash: string) { async presentAlertDelete (hash: string) {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: false,
header: 'Caution', header: 'Caution',
message: `Are you sure you want to delete this key?`, message: `Are you sure you want to delete this key?`,
buttons: [ buttons: [
@@ -57,6 +85,7 @@ export class SSHKeysPage {
handler: () => { handler: () => {
this.delete(hash) this.delete(hash)
}, },
cssClass: 'enter-click',
}, },
], ],
}) })
@@ -72,7 +101,8 @@ export class SSHKeysPage {
await loader.present() await loader.present()
try { try {
await this.sshService.delete(hash) await this.embassyApi.deleteSshKey({ hash })
delete this.sshKeys[hash]
} catch (e) { } catch (e) {
this.errToast.present(e) this.errToast.present(e)
} finally { } finally {
@@ -84,3 +114,15 @@ export class SSHKeysPage {
return 0 return 0
} }
} }
const sshSpec = {
type: 'string',
name: 'SSH Key',
description: 'Enter the SSH public key of you would like to authorize for root access to your Embassy.',
nullable: false,
// @TODO regex for SSH Key
// pattern: '',
'pattern-description': 'Must be a valid SSH key',
masked: false,
copyable: false,
}

View File

@@ -17,27 +17,5 @@ export class SSHService {
return this.keys$.asObservable() return this.keys$.asObservable()
} }
async getKeys (): Promise<void> {
const keys = await this.embassyApi.getSshKeys({ })
this.keys$.next(keys)
}
async add (pubkey: string): Promise<void> {
const key = await this.embassyApi.addSshKey({ pubkey })
const keys = this.keys$.getValue()
this.keys$.next({ ...keys, ...key })
}
async delete (hash: string): Promise<void> {
await this.embassyApi.deleteSshKey({ hash })
const keys = this.keys$.getValue()
const filtered = Object.keys(keys)
.filter(h => h !== hash)
.reduce((res, h) => {
res[h] = keys[h]
return res
}, { })
this.keys$.next(filtered)
}
} }

View File

@@ -27,7 +27,6 @@ export class ServerShowPage {
async presentAlertRestart () { async presentAlertRestart () {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: false,
header: 'Confirm', header: 'Confirm',
message: `Are you sure you want to restart your Embassy?`, message: `Are you sure you want to restart your Embassy?`,
buttons: [ buttons: [
@@ -40,15 +39,15 @@ export class ServerShowPage {
handler: () => { handler: () => {
this.restart() this.restart()
}, },
cssClass: 'enter-click',
}, },
]}, ],
) })
await alert.present() await alert.present()
} }
async presentAlertShutdown () { async presentAlertShutdown () {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: false,
header: 'Confirm', header: 'Confirm',
message: `Are you sure you want to shut down your Embassy? To turn it back on, you will need to physically unplug the device and plug it back in.`, message: `Are you sure you want to shut down your Embassy? To turn it back on, you will need to physically unplug the device and plug it back in.`,
buttons: [ buttons: [
@@ -61,6 +60,7 @@ export class ServerShowPage {
handler: () => { handler: () => {
this.shutdown() this.shutdown()
}, },
cssClass: 'enter-click',
}, },
], ],
}) })

View File

@@ -33,7 +33,7 @@ export class WifiPage {
try { try {
await this.getWifi() await this.getWifi()
} catch (e) { } catch (e) {
this.errToast.present(e.message) this.errToast.present(e)
} finally { } finally {
this.loading = false this.loading = false
} }
@@ -72,7 +72,7 @@ export class WifiPage {
}, },
}, },
], ],
cssClass: 'wide-alert', cssClass: 'wide-alert enter-click',
}) })
await alert.present() await alert.present()
} }
@@ -206,7 +206,13 @@ export class WifiPage {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: `Connected to "${ssid}"`, header: `Connected to "${ssid}"`,
message: 'Note. It may take several minutes to an hour for your Embassy to reconnect over Tor.', message: 'Note. It may take several minutes to an hour for your Embassy to reconnect over Tor.',
buttons: ['OK'], buttons: [
{
text: 'Ok',
role: 'cancel',
cssClass: 'enter-click',
},
],
}) })
await alert.present() await alert.present()

View File

@@ -4,7 +4,6 @@ import { PatchConnection, PatchDbService } from './patch-db/patch-db.service'
import { HttpService, Method } from './http.service' import { HttpService, Method } from './http.service'
import { distinctUntilChanged } from 'rxjs/operators' import { distinctUntilChanged } from 'rxjs/operators'
import { ConfigService } from './config.service' import { ConfigService } from './config.service'
import { pauseFor } from '../util/misc.util'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',

View File

@@ -1,12 +1,9 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { AlertInput, AlertButton } from '@ionic/core' import { AlertInput, AlertButton } from '@ionic/core'
import { ApiService } from './api/embassy-api.service' import { ApiService } from './api/embassy-api.service'
import { ConfigSpec, ValueSpecString } from '../pkg-config/config-types' import { ConfigSpec } from '../pkg-config/config-types'
import { SSHService } from '../pages/server-routes/security-routes/ssh-keys/ssh.service'
import { AlertController, LoadingController } from '@ionic/angular' import { AlertController, LoadingController } from '@ionic/angular'
import { ErrorToastService } from './error-toast.service' import { ErrorToastService } from './error-toast.service'
import { ModalController } from '@ionic/angular'
import { BackupConfirmationComponent } from '../modals/backup-confirmation/backup-confirmation.component'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -14,12 +11,10 @@ import { BackupConfirmationComponent } from '../modals/backup-confirmation/backu
export class ServerConfigService { export class ServerConfigService {
constructor ( constructor (
private readonly modalCtrl: ModalController,
private readonly loadingCtrl: LoadingController, private readonly loadingCtrl: LoadingController,
private readonly errToast: ErrorToastService, private readonly errToast: ErrorToastService,
private readonly alertCtrl: AlertController, private readonly alertCtrl: AlertController,
private readonly embassyApi: ApiService, private readonly embassyApi: ApiService,
private readonly sshService: SSHService,
) { } ) { }
async presentAlert (key: string, current?: any): Promise<void> { async presentAlert (key: string, current?: any): Promise<void> {
@@ -49,6 +44,7 @@ export class ServerConfigService {
loader.dismiss() loader.dismiss()
} }
}, },
cssClass: 'enter-click',
}, },
] ]
@@ -84,24 +80,6 @@ export class ServerConfigService {
await alert.present() await alert.present()
} }
async presentModalInput (key: string, current?: string) {
const { name, description, masked } = serverConfig[key] as ValueSpecString
const modal = await this.modalCtrl.create({
component: BackupConfirmationComponent,
componentProps: {
title: name,
message: description,
label: name,
useMask: masked,
value: current,
submitFn: this.saveFns[key],
},
cssClass: 'alertlike-modal',
})
await modal.present()
}
// async presentModalForm (key: string) { // async presentModalForm (key: string) {
// const modal = await this.modalCtrl.create({ // const modal = await this.modalCtrl.create({
// component: AppActionInputPage, // component: AppActionInputPage,
@@ -123,9 +101,6 @@ export class ServerConfigService {
'auto-check-updates': async (enabled: boolean) => { 'auto-check-updates': async (enabled: boolean) => {
return this.embassyApi.setDbValue({ pointer: '/auto-check-updates', value: enabled }) return this.embassyApi.setDbValue({ pointer: '/auto-check-updates', value: enabled })
}, },
ssh: async (pubkey: string) => {
return this.sshService.add(pubkey)
},
// 'eos-marketplace': async () => { // 'eos-marketplace': async () => {
// return this.embassyApi.setEosMarketplace() // return this.embassyApi.setEosMarketplace()
// }, // },
@@ -148,17 +123,6 @@ export const serverConfig: ConfigSpec = {
description: 'On launch, EmbassyOS will automatically check for updates of itself and your installed services. Updating still requires user approval and action. No updates will ever be performed automatically.', description: 'On launch, EmbassyOS will automatically check for updates of itself and your installed services. Updating still requires user approval and action. No updates will ever be performed automatically.',
default: true, default: true,
}, },
ssh: {
type: 'string',
name: 'SSH Key',
description: 'Enter the SSH public key of you would like to authorize for root access to your Embassy.',
nullable: false,
// @TODO regex for SSH Key
// pattern: '',
'pattern-description': 'Must be a valid SSH key',
masked: false,
copyable: false,
},
// 'eos-marketplace': { // 'eos-marketplace': {
// type: 'boolean', // type: 'boolean',
// name: 'Tor Only Marketplace', // name: 'Tor Only Marketplace',

View File

@@ -155,7 +155,6 @@ export class StartupAlertsService {
private async displayAppsCheck (): Promise<boolean> { private async displayAppsCheck (): Promise<boolean> {
return new Promise(async resolve => { return new Promise(async resolve => {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: true,
header: 'Updates Available!', header: 'Updates Available!',
message: new IonicSafeString( message: new IonicSafeString(
`<div style="display: flex; flex-direction: column; justify-content: space-around; min-height: 100px"> `<div style="display: flex; flex-direction: column; justify-content: space-around; min-height: 100px">
@@ -173,8 +172,9 @@ export class StartupAlertsService {
{ {
text: 'View in Marketplace', text: 'View in Marketplace',
handler: () => { handler: () => {
return this.navCtrl.navigateForward('/marketplace').then(() => resolve(false)) this.navCtrl.navigateForward('/marketplace').then(() => resolve(false))
}, },
cssClass: 'enter-click',
}, },
], ],
}) })
@@ -186,7 +186,6 @@ export class StartupAlertsService {
private async presentAlertNewOS (versionLatest: string): Promise<{ cancel?: true, update?: true }> { private async presentAlertNewOS (versionLatest: string): Promise<{ cancel?: true, update?: true }> {
return new Promise(async resolve => { return new Promise(async resolve => {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: true,
header: 'New EmbassyOS Version!', header: 'New EmbassyOS Version!',
message: new IonicSafeString( message: new IonicSafeString(
`<div style="display: flex; flex-direction: column; justify-content: space-around; min-height: 100px"> `<div style="display: flex; flex-direction: column; justify-content: space-around; min-height: 100px">
@@ -204,6 +203,7 @@ export class StartupAlertsService {
{ {
text: 'Update', text: 'Update',
handler: () => resolve({ update: true }), handler: () => resolve({ update: true }),
cssClass: 'enter-click',
}, },
], ],
}) })