mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
react to enter key for alerts and modals. Styling and logic
This commit is contained in:
@@ -101,7 +101,7 @@
|
||||
<ion-icon name="pulse"></ion-icon>
|
||||
<ion-icon name="qr-code-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="remove-outline"></ion-icon>
|
||||
<ion-icon name="save-outline"></ion-icon>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { Component, HostListener } from '@angular/core'
|
||||
import { Storage } from '@ionic/storage-angular'
|
||||
import { AuthService, AuthState } from './services/auth.service'
|
||||
import { ApiService } from './services/api/embassy-api.service'
|
||||
@@ -24,6 +24,15 @@ import { Subscription } from 'rxjs'
|
||||
styleUrls: ['app.component.scss'],
|
||||
})
|
||||
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
|
||||
showMenu = false
|
||||
selectedIndex = 0
|
||||
@@ -143,6 +152,47 @@ export class AppComponent {
|
||||
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 {
|
||||
return this.connectionService.watchFailure$()
|
||||
.pipe(
|
||||
@@ -234,7 +284,7 @@ export class AppComponent {
|
||||
})
|
||||
}
|
||||
|
||||
async presentAlertRefreshNeeded () {
|
||||
private async presentAlertRefreshNeeded () {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
header: 'Refresh Needed',
|
||||
@@ -242,6 +292,7 @@ export class AppComponent {
|
||||
buttons: [
|
||||
{
|
||||
text: 'Refresh Page',
|
||||
cssClass: 'enter-click',
|
||||
handler: () => {
|
||||
location.reload()
|
||||
},
|
||||
@@ -251,46 +302,6 @@ export class AppComponent {
|
||||
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 () {
|
||||
const toast = await this.toastCtrl.create({
|
||||
header: 'Embassy',
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<ion-item-group [formGroup]="formGroup">
|
||||
<div *ngFor="let entry of formGroup.controls | keyvalue : asIsOrder">
|
||||
<!-- union enum -->
|
||||
<ng-container *ngIf="unionSpec && entry.key === unionSpec.tag.id">
|
||||
<p class="input-label">{{ unionSpec.tag.name }}</p>
|
||||
<ion-item>
|
||||
<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-option *ngFor="let option of Object.keys(unionSpec.variants)" [value]="option">
|
||||
{{ unionSpec.tag['variant-names'][option] }}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<p class="input-label">{{ unionSpec.tag.name }}</p>
|
||||
<ion-item>
|
||||
<ion-label>{{ unionSpec.tag.name }}</ion-label>
|
||||
<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">
|
||||
{{ unionSpec.tag['variant-names'][option] }}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="objectSpec[entry.key] as spec">
|
||||
<!-- primitive -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, Input, Output, SimpleChange, EventEmitter } from '@angular/core'
|
||||
import { AbstractFormGroupDirective, FormArray, FormGroup } from '@angular/forms'
|
||||
import { AlertController, IonicSafeString, ModalController } from '@ionic/angular'
|
||||
import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecBoolean, ValueSpecList, ValueSpecListOf, ValueSpecNumber, ValueSpecString, ValueSpecUnion } from 'src/app/pkg-config/config-types'
|
||||
import { AlertButton, AlertController, IonicSafeString, ModalController } from '@ionic/angular'
|
||||
import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecBoolean, ValueSpecList, ValueSpecListOf, ValueSpecUnion } from 'src/app/pkg-config/config-types'
|
||||
import { FormService } from 'src/app/services/form.service'
|
||||
import { Range } from 'src/app/pkg-config/config-utilities'
|
||||
import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page'
|
||||
@@ -34,17 +34,6 @@ export class FormObjectComponent {
|
||||
) { }
|
||||
|
||||
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
|
||||
Object.keys(this.objectSpec).forEach(key => {
|
||||
const spec = this.objectSpec[key]
|
||||
@@ -157,12 +146,13 @@ export class FormObjectComponent {
|
||||
if (!spec.warning || this.warningAck[key]) return okFn ? okFn() : null
|
||||
this.warningAck[key] = true
|
||||
|
||||
const buttons = [
|
||||
const buttons: AlertButton[] = [
|
||||
{
|
||||
text: 'Ok',
|
||||
handler: () => {
|
||||
if (okFn) okFn()
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -186,7 +176,6 @@ export class FormObjectComponent {
|
||||
|
||||
async presentAlertDelete (key: string, index: number) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
header: 'Confirm',
|
||||
message: 'Are you sure you want to delete this entry?',
|
||||
buttons: [
|
||||
@@ -199,6 +188,7 @@ export class FormObjectComponent {
|
||||
handler: () => {
|
||||
this.deleteListItem(key, index)
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -260,7 +250,6 @@ export class FormLabelComponent {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: name,
|
||||
message: description,
|
||||
buttons: ['Ok'],
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
@@ -49,12 +49,12 @@
|
||||
<!-- next/finish buttons -->
|
||||
<ng-container *ngIf="!(currentSlide.loading$ | async)">
|
||||
<!-- 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-button>
|
||||
|
||||
<!-- 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-button>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<ion-header style="min-height: unset;">
|
||||
<ion-toolbar *ngIf="patch.data['package-data'][pkgId] as pkg">
|
||||
<ion-header>
|
||||
<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-buttons slot="end" class="ion-padding-end">
|
||||
<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
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
@@ -19,17 +24,11 @@
|
||||
<ng-template #loaded>
|
||||
|
||||
<ng-container *ngIf="patch.data['package-data'][pkgId] as pkg">
|
||||
<ng-container *ngIf="pkg.manifest.config && !pkg.installed.status.configured && !edited">
|
||||
<ion-item class="notifier-item">
|
||||
<ion-label>
|
||||
<h2 style="display: flex; align-items: center; margin-bottom: 3px;">
|
||||
<ion-icon size="small" style="margin-right: 5px" slot="start" color="dark" slot="start" name="alert-circle-outline"></ion-icon>
|
||||
<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>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-text color="success">To use the default config for {{ pkg.manifest.title }}, click "Save" below.</ion-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ng-container *ngIf="rec && showRec">
|
||||
<ion-item class="rec-item">
|
||||
@@ -81,13 +80,8 @@
|
||||
|
||||
<ion-footer>
|
||||
<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-button fill="outline" color="primary" [disabled]="loadingText" (click)="save(pkg)">
|
||||
<ion-button fill="outline" [disabled]="loadingText" (click)="save(pkg)" class="enter-click">
|
||||
Save
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
|
||||
@@ -172,6 +172,7 @@ export class AppConfigPage {
|
||||
handler: () => {
|
||||
this.modalCtrl.dismiss()
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<ion-header>
|
||||
<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>
|
||||
{{ spec.name }}
|
||||
</ion-title>
|
||||
<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' }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
@@ -22,13 +27,8 @@
|
||||
|
||||
<ion-footer>
|
||||
<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-button fill="outline" color="primary" (click)="save()">
|
||||
<ion-button fill="outline" (click)="save()" class="enter-click">
|
||||
Done
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<ion-header>
|
||||
<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-toolbar>
|
||||
</ion-header>
|
||||
@@ -15,13 +20,8 @@
|
||||
|
||||
<ion-footer>
|
||||
<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-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 }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
|
||||
.main-content {
|
||||
height: 100%;
|
||||
color: var(--ion-color-medium);
|
||||
color: var(--ion-color-dark);
|
||||
}
|
||||
@@ -80,6 +80,7 @@ export class AppActionsPage {
|
||||
handler: () => {
|
||||
this.executeAction(pkg.manifest.id, action.key)
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -104,7 +105,7 @@ export class AppActionsPage {
|
||||
header: 'Forbidden',
|
||||
message: error || `Action "${action.value.name}" can only be executed when service is ${statusesStr}`,
|
||||
buttons: ['OK'],
|
||||
cssClass: 'alert-error-message',
|
||||
cssClass: 'alert-error-message enter-click',
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
@@ -159,7 +160,13 @@ export class AppActionsPage {
|
||||
const successAlert = await this.alertCtrl.create({
|
||||
header: 'Execution Complete',
|
||||
message: res.message.split('\n').join('</br ></br />'),
|
||||
buttons: ['OK'],
|
||||
buttons: [
|
||||
{
|
||||
text: 'Ok',
|
||||
role: 'cancel',
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
setTimeout(() => successAlert.present(), 400)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<ion-title>Properties</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="refresh()">
|
||||
<ion-icon slot="start" name="refresh-outline"></ion-icon>
|
||||
<ion-icon slot="start" name="refresh"></ion-icon>
|
||||
Refresh
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
|
||||
@@ -130,7 +130,6 @@ export class AppShowPage {
|
||||
const alert = await this.alertCtrl.create({
|
||||
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.`,
|
||||
buttons: ['OK'],
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
@@ -215,6 +214,7 @@ export class AppShowPage {
|
||||
handler: () => {
|
||||
this.start()
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -140,18 +140,6 @@
|
||||
<ion-row>
|
||||
<ion-col sizeSm="12" sizeMd="6">
|
||||
<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-label>
|
||||
<h2>Other Versions</h2>
|
||||
@@ -198,20 +186,6 @@
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</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-col>
|
||||
</ion-row>
|
||||
|
||||
@@ -81,7 +81,6 @@ export class MarketplaceShowPage {
|
||||
async presentAlertVersions () {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Versions',
|
||||
backdropDismiss: false,
|
||||
inputs: this.marketplaceService.pkgs[this.pkgId].versions.sort((a, b) => -1 * this.emver.compare(a, b)).map(v => {
|
||||
return {
|
||||
name: v, // for CSS
|
||||
@@ -100,6 +99,7 @@ export class MarketplaceShowPage {
|
||||
handler: (version: string) => {
|
||||
this.getPkg(version)
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -32,7 +32,6 @@ export class SessionsPage {
|
||||
|
||||
async presentAlertKill (id: string) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
header: 'Caution',
|
||||
message: `Are you sure you want to kill this session?`,
|
||||
buttons: [
|
||||
@@ -45,6 +44,7 @@ export class SessionsPage {
|
||||
handler: () => {
|
||||
this.kill(id)
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<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-label>Add new key</ion-label>
|
||||
</ion-item>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||
import { AlertController, LoadingController } from '@ionic/angular'
|
||||
import { SSHService } from './ssh.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { AlertController, LoadingController, ModalController } from '@ionic/angular'
|
||||
import { SSHKeys } from 'src/app/services/api/api.types'
|
||||
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({
|
||||
selector: 'ssh-keys',
|
||||
@@ -14,37 +13,66 @@ import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
export class SSHKeysPage {
|
||||
loading = true
|
||||
sshKeys: SSHKeys
|
||||
subs: Subscription[] = []
|
||||
readonly docsUrl = 'https://docs.start9.com/user-manual/general/developer-options/ssh-setup.html'
|
||||
|
||||
constructor (
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly sshService: SSHService,
|
||||
public readonly serverConfig: ServerConfigService,
|
||||
private readonly embassyApi: ApiService,
|
||||
) { }
|
||||
|
||||
async ngOnInit () {
|
||||
this.subs = [
|
||||
this.sshService.watch$()
|
||||
.subscribe(keys => {
|
||||
this.sshKeys = keys
|
||||
}),
|
||||
]
|
||||
|
||||
await this.sshService.getKeys()
|
||||
|
||||
this.loading = false
|
||||
await this.getKeys()
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
async getKeys (): Promise<void> {
|
||||
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) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
header: 'Caution',
|
||||
message: `Are you sure you want to delete this key?`,
|
||||
buttons: [
|
||||
@@ -57,6 +85,7 @@ export class SSHKeysPage {
|
||||
handler: () => {
|
||||
this.delete(hash)
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -72,7 +101,8 @@ export class SSHKeysPage {
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.sshService.delete(hash)
|
||||
await this.embassyApi.deleteSshKey({ hash })
|
||||
delete this.sshKeys[hash]
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
@@ -84,3 +114,15 @@ export class SSHKeysPage {
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -17,27 +17,5 @@ export class SSHService {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ export class ServerShowPage {
|
||||
|
||||
async presentAlertRestart () {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
header: 'Confirm',
|
||||
message: `Are you sure you want to restart your Embassy?`,
|
||||
buttons: [
|
||||
@@ -40,15 +39,15 @@ export class ServerShowPage {
|
||||
handler: () => {
|
||||
this.restart()
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
]},
|
||||
)
|
||||
],
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
async presentAlertShutdown () {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
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.`,
|
||||
buttons: [
|
||||
@@ -61,6 +60,7 @@ export class ServerShowPage {
|
||||
handler: () => {
|
||||
this.shutdown()
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -33,7 +33,7 @@ export class WifiPage {
|
||||
try {
|
||||
await this.getWifi()
|
||||
} catch (e) {
|
||||
this.errToast.present(e.message)
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
@@ -72,7 +72,7 @@ export class WifiPage {
|
||||
},
|
||||
},
|
||||
],
|
||||
cssClass: 'wide-alert',
|
||||
cssClass: 'wide-alert enter-click',
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
@@ -206,7 +206,13 @@ export class WifiPage {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: `Connected to "${ssid}"`,
|
||||
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()
|
||||
|
||||
@@ -4,7 +4,6 @@ import { PatchConnection, PatchDbService } from './patch-db/patch-db.service'
|
||||
import { HttpService, Method } from './http.service'
|
||||
import { distinctUntilChanged } from 'rxjs/operators'
|
||||
import { ConfigService } from './config.service'
|
||||
import { pauseFor } from '../util/misc.util'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { AlertInput, AlertButton } from '@ionic/core'
|
||||
import { ApiService } from './api/embassy-api.service'
|
||||
import { ConfigSpec, ValueSpecString } from '../pkg-config/config-types'
|
||||
import { SSHService } from '../pages/server-routes/security-routes/ssh-keys/ssh.service'
|
||||
import { ConfigSpec } from '../pkg-config/config-types'
|
||||
import { AlertController, LoadingController } from '@ionic/angular'
|
||||
import { ErrorToastService } from './error-toast.service'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { BackupConfirmationComponent } from '../modals/backup-confirmation/backup-confirmation.component'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -14,12 +11,10 @@ import { BackupConfirmationComponent } from '../modals/backup-confirmation/backu
|
||||
export class ServerConfigService {
|
||||
|
||||
constructor (
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly sshService: SSHService,
|
||||
) { }
|
||||
|
||||
async presentAlert (key: string, current?: any): Promise<void> {
|
||||
@@ -49,6 +44,7 @@ export class ServerConfigService {
|
||||
loader.dismiss()
|
||||
}
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -84,24 +80,6 @@ export class ServerConfigService {
|
||||
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) {
|
||||
// const modal = await this.modalCtrl.create({
|
||||
// component: AppActionInputPage,
|
||||
@@ -123,9 +101,6 @@ export class ServerConfigService {
|
||||
'auto-check-updates': async (enabled: boolean) => {
|
||||
return this.embassyApi.setDbValue({ pointer: '/auto-check-updates', value: enabled })
|
||||
},
|
||||
ssh: async (pubkey: string) => {
|
||||
return this.sshService.add(pubkey)
|
||||
},
|
||||
// 'eos-marketplace': async () => {
|
||||
// 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.',
|
||||
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': {
|
||||
// type: 'boolean',
|
||||
// name: 'Tor Only Marketplace',
|
||||
|
||||
@@ -155,7 +155,6 @@ export class StartupAlertsService {
|
||||
private async displayAppsCheck (): Promise<boolean> {
|
||||
return new Promise(async resolve => {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: true,
|
||||
header: 'Updates Available!',
|
||||
message: new IonicSafeString(
|
||||
`<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',
|
||||
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 }> {
|
||||
return new Promise(async resolve => {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: true,
|
||||
header: 'New EmbassyOS Version!',
|
||||
message: new IonicSafeString(
|
||||
`<div style="display: flex; flex-direction: column; justify-content: space-around; min-height: 100px">
|
||||
@@ -204,6 +203,7 @@ export class StartupAlertsService {
|
||||
{
|
||||
text: 'Update',
|
||||
handler: () => resolve({ update: true }),
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user