Drew cleanup (#380)

* accordion works

* cleanup

* styling

* more styling

* App show change (#387)

* show page change

* no marketplace

* app show changes

* update marketplace list

* icon

* top left icon

* toolbar

* right size

* out of toolbar

* no service details

* fix skeleton text and server metrics page

* stuck

* add session management

* complete sessions feature

* app show page

* remove unnecessary icons

* add cli to list of possible sessions

* Modal global (#383)

* modal checkpoint

* global modal

* black looks good now

* black looks good now

* not smaller

Co-authored-by: Drew Ansbacher <drew.ansbacher@spiredigital.com>

Co-authored-by: Drew Ansbacher <drew.ansbacher@spiredigital.com>
Co-authored-by: Drew Ansbacher <drew.ansbacher@gmail.com>

Co-authored-by: Drew Ansbacher <drew.ansbacher@spiredigital.com>
Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
This commit is contained in:
Drew Ansbacher
2021-07-29 16:59:03 -04:00
committed by Aiden McClelland
parent 4c294566d7
commit a43ff976a2
71 changed files with 808 additions and 694 deletions

View File

@@ -1,34 +1,48 @@
<ion-app>
<ion-split-pane [disabled]="!showMenu" (ionSplitPaneVisible)="splitPaneVisible($event)" contentId="main-content">
<ion-menu contentId="main-content" type="overlay">
<ion-content scrollY="false" class="menu-style">
<ion-list style="padding: 0px">
<ion-content color="light" scrollY="false">
<div style="text-align: center;" class="ion-padding">
<img style="width: 45%;" src="assets/img/logo.png">
</div>
<div class="divider"></div>
<ion-item-group style="padding: 30px 0px;">
<ion-menu-toggle auto-hide="false" *ngFor="let page of appPages; let i = index">
<ion-item
style="padding-left: 10px;"
color="transparent"
button
(click)="selectedIndex = i"
routerDirection="root"
[routerLink]="[page.url]"
lines="none"
detail="false"
[class.selected]="selectedIndex === i"
>
<ion-icon slot="start" [name]="page.icon"></ion-icon>
<ion-label style="font-family: 'Montserrat';">{{ page.title }}</ion-label>
<ion-icon slot="start" [name]="page.icon" [class]="selectedIndex === i ? 'bold' : 'dim'"></ion-icon>
<ion-label
style="font-family: 'Montserrat';"
[class]="selectedIndex === i ? 'bold' : 'dim'"
>
{{ page.title }}
</ion-label>
<ion-badge *ngIf="page.url === '/notifications' && unreadCount" color="danger" style="margin-right: 3%;" [class.selected-badge]="selectedIndex == i">{{ unreadCount }}</ion-badge>
</ion-item>
</ion-menu-toggle>
</ion-list>
</ion-item-group>
<div style="text-align: center; height: 75px; position:absolute; bottom:0; right:0; width: 100%;">
<div class="divider" style="margin-bottom: 10px;"></div>
<ion-menu-toggle auto-hide="false">
<ion-item button lines="none" style="--background: transparent; margin-bottom: 86px; text-align: center;" fill="clear" (click)="presentAlertLogout()">
<ion-label><ion-text
style="font-family: 'Montserrat';"
color="dark"
>
Log Out
</ion-text></ion-label>
</ion-item>
</ion-menu-toggle>
</div>
</ion-content>
<ion-footer style="padding-bottom: 16px; text-align: center;" class="menu-style">
<ion-menu-toggle auto-hide="false">
<ion-item button style="--background:var(--ion-background-color); margin-bottom: 16px;" fill="clear" (click)="presentAlertLogout()">
<ion-icon size="small" slot="start" color="dark" name="log-out-outline"></ion-icon>
<ion-label><ion-text color="danger">Logout</ion-text></ion-label>
</ion-item>
</ion-menu-toggle>
<img style="width: 25%;" src="assets/img/logo.png">
</ion-footer>
</ion-menu>
<ion-router-outlet id="main-content"></ion-router-outlet>
</ion-split-pane>
@@ -80,6 +94,8 @@
<ion-icon name="newspaper-outline"></ion-icon>
<ion-icon name="notifications-outline"></ion-icon>
<ion-icon name="rocket-outline"></ion-icon>
<ion-icon name="phone-portrait-outline"></ion-icon>
<ion-icon name="play-circle-outline"></ion-icon>
<ion-icon name="power"></ion-icon>
<ion-icon name="pulse"></ion-icon>
<ion-icon name="qr-code-outline"></ion-icon>
@@ -89,10 +105,8 @@
<ion-icon name="remove-outline"></ion-icon>
<ion-icon name="save-outline"></ion-icon>
<ion-icon name="shield-checkmark-outline"></ion-icon>
<ion-icon name="sync-circle-outline"></ion-icon>
<ion-icon name="storefront-outline"></ion-icon>
<ion-icon name="terminal-outline"></ion-icon>
<ion-icon name="timer-outline"></ion-icon>
<ion-icon name="trash-outline"></ion-icon>
<ion-icon name="warning-outline"></ion-icon>
<ion-icon name="wifi"></ion-icon>
@@ -100,7 +114,6 @@
<!-- Ionic components -->
<ion-action-sheet></ion-action-sheet>
<ion-alert></ion-alert>
<ion-avatar></ion-avatar>
<ion-badge></ion-badge>
<ion-button></ion-button>
<ion-buttons></ion-buttons>
@@ -137,11 +150,9 @@
<ion-select></ion-select>
<ion-select-option></ion-select-option>
<ion-slides></ion-slides>
<ion-spinner name="dots"></ion-spinner>
<ion-spinner name="lines"></ion-spinner>
<ion-text></ion-text>
<ion-text style="font-weight: bold">load bold</ion-text>
<ion-textarea></ion-textarea>
<ion-title></ion-title>
<ion-toast></ion-toast>
<ion-toggle></ion-toggle>

View File

@@ -1,18 +1,9 @@
.selected {
--background: linear-gradient(120deg, #1e1e1e -1%, var(--ion-color-danger) 100%);
.bold {
font-weight: bold;
}
.menu-style {
border-style: solid;
border-width: 0px 1px 0px 0px;
border-color: var(--ion-color-danger);
ion-item::part(native) {
height: 56px;
}
}
.selected-badge {
background-color: #1e1e1e;
.dim {
color: var(--ion-color-dark-shade);
}
ion-split-pane {

View File

@@ -71,9 +71,7 @@ export class AppComponent {
private readonly config: ConfigService,
readonly splitPane: SplitPaneTracker,
) {
// set dark theme
document.body.classList.toggle('dark', true)
this.init()
this.init()
}
async init () {
@@ -266,7 +264,6 @@ export class AppComponent {
},
{
text: 'Logout',
cssClass: 'alert-danger',
handler: () => {
this.logout()
},

View File

@@ -26,12 +26,12 @@
<ion-text color="warning">Will Stop</ion-text>
</div>
<ion-item
style="--ion-item-background: rgb(0,0,0,0); margin-top: 5px"
style="--ion-item-background: margin-top: 5px"
*ngFor="let dep of dependentBreakages | keyvalue"
>
<ion-avatar style="position: relative; height: 4vh; width: 4vh" slot="start">
<ion-thumbnail style="position: relative; height: 4vh; width: 4vh" slot="start">
<img [src]="dep.value.iconURL" />
</ion-avatar>
</ion-thumbnail>
<ion-label>
<h5>{{ dep.value.title }}</h5>
</ion-label>

View File

@@ -2,7 +2,7 @@
<ion-toolbar>
<ion-label class="toolbar-label text-ellipses">
<h1 class="toolbar-title">{{ params.toolbar.title }}</h1>
<h3 style="font-size: large; font-style: italic">{{ params.toolbar.action }} <ion-text style="font-size: medium;" color="medium">{{ params.toolbar.version | displayEmver }}</ion-text></h3>
<h3 style="font-size: large; font-style: italic">{{ params.toolbar.action }} <ion-text style="font-size: medium;">{{ params.toolbar.version | displayEmver }}</ion-text></h3>
</ion-label>
</ion-toolbar>
</ion-header>
@@ -37,11 +37,11 @@
<ng-container *ngIf="!initializing && !error">
<!-- cancel button if loading/not loading -->
<ion-button slot="start" *ngIf="(currentSlide.loading$ | async) && currentBottomBar.cancel.whileLoading as cancel" (click)="transitions.cancel()" class="toolbar-button" fill="outline" color="medium">
<ion-button slot="start" *ngIf="(currentSlide.loading$ | async) && currentBottomBar.cancel.whileLoading as cancel" (click)="transitions.cancel()" class="toolbar-button" fill="outline">
<ion-text *ngIf="cancel.text" [class.smaller-text]="cancel.text > 16">{{ cancel.text }}</ion-text>
<ion-icon *ngIf="!cancel.text" name="close-outline"></ion-icon>
</ion-button>
<ion-button slot="start" *ngIf="!(currentSlide.loading$ | async) && currentBottomBar.cancel.afterLoading as cancel" (click)="transitions.cancel()" class="toolbar-button" fill="outline" color="medium">
<ion-button slot="start" *ngIf="!(currentSlide.loading$ | async) && currentBottomBar.cancel.afterLoading as cancel" (click)="transitions.cancel()" class="toolbar-button" fill="outline">
<ion-text *ngIf="cancel.text" [class.smaller-text]="cancel.text > 16">{{ cancel.text }}</ion-text>
<ion-icon *ngIf="!cancel.text" name="close-outline"></ion-icon>
</ion-button>

View File

@@ -6,7 +6,7 @@
<div class="organizer">
<ion-label class="ion-text-wrap">
<ion-text color="medium">{{ spec.name }}</ion-text>
{{ spec.name }}
<ion-text class="new-tag" *ngIf="anno | annotationStatus: 'Added'">(new)</ion-text>
</ion-label>
</div>

View File

@@ -1,3 +1,3 @@
ion-note {
width: 50%;
}
}

View File

@@ -8,7 +8,7 @@ import { PkgStatusRendering } from 'src/app/services/pkg-status-rendering.servic
})
export class StatusComponent {
@Input() rendering: PkgStatusRendering
@Input() size?: 'small' | 'medium' | 'large' = 'large'
@Input() size?: 'small' | 'medium' | 'large' | 'x-large' = 'large'
@Input() style?: string = 'regular'
@Input() weight?: string = 'normal'
}

View File

@@ -1,8 +1,8 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon name="close-outline"></ion-icon>
<ion-button (click)="dismiss()" color="light">
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>{{ action.name }}</ion-title>

View File

@@ -55,7 +55,7 @@
<ion-label>{{ valueString[i] }}</ion-label>
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(i, $event)">
<ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
</ion-button>
</ion-item>
</div>

View File

@@ -118,7 +118,6 @@ export class AppConfigListPage extends ModalPresentable {
},
{
text: 'Delete',
cssClass: 'alert-danger',
handler: () => {
if (typeof key === 'number') {
(this.value as any[]).splice(key, 1)

View File

@@ -41,7 +41,6 @@ export class AppConfigObjectPage {
},
{
text: 'Delete',
cssClass: 'alert-danger',
handler: () => {
this.dismiss(true)
},

View File

@@ -33,7 +33,7 @@
<ion-button *ngIf="spec.masked" fill="clear" [color]="unmasked ? 'danger' : 'primary'" (click)="toggleMask()">
<ion-icon slot="icon-only" [name]="unmasked ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
</ion-button>
<ion-button *ngIf="value && spec.nullable" fill="clear" color="medium" (click)="clear()">
<ion-button *ngIf="value && spec.nullable" fill="clear" (click)="clear()">
<ion-icon slot="icon-only" name="close" size="small"></ion-icon>
</ion-button>
</div>
@@ -41,8 +41,8 @@
<!-- number -->
<ion-item *ngIf="spec.type === 'number'">
<ion-input type="tel" placeholder="Enter value" [(ngModel)]="value" (ngModelChange)="handleInput()"></ion-input>
<span slot="end" *ngIf="spec.units"><ion-text color="medium">{{ spec.units }}</ion-text></span>
<ion-button *ngIf="value && spec.nullable" slot="end" fill="clear" color="medium" (click)="clear()">
<span slot="end" *ngIf="spec.units"><ion-text>{{ spec.units }}</ion-text></span>
<ion-button *ngIf="value && spec.nullable" slot="end" fill="clear" (click)="clear()">
<ion-icon slot="icon-only" name="close" size="small"></ion-icon>
</ion-button>
</ion-item>
@@ -63,20 +63,18 @@
<!-- metadata -->
<div class="ion-padding-start">
<p *ngIf="spec.type === 'string' && spec.patternDescription">
<ion-text color="medium">{{ spec.patternDescription }}</ion-text>
{{ spec.patternDescription }}
</p>
<p *ngIf="spec.type === 'number' && spec.integral">
<ion-text color="medium">{{ integralDescription }}</ion-text>
{{ integralDescription }}
</p>
<p *ngIf="rangeDescription">
<ion-text color="medium">{{ rangeDescription }}</ion-text>
</p>
<p *ngIf="spec.default !== undefined">
<ion-text color="medium">
<p>Default: {{ defaultDescription }} <ion-icon style="padding-left: 8px;" name="refresh-outline" color="dark" style="cursor: pointer;" (click)="refreshDefault()"></ion-icon></p>
<p *ngIf="spec.type === 'number' && spec.units">Units: {{ spec.units }}</p>
</ion-text>
{{ rangeDescription }}
</p>
<ng-container *ngIf="spec.default !== undefined">
<p>Default: {{ defaultDescription }} <ion-icon style="padding-left: 8px;" name="refresh-outline" color="dark" style="cursor: pointer;" (click)="refreshDefault()"></ion-icon></p>
<p *ngIf="spec.type === 'number' && spec.units">Units: {{ spec.units }}</p>
</ng-container>
</div>
</ion-item-group>

View File

@@ -176,7 +176,6 @@ export class AppConfigValuePage {
},
{
text: `Leave`,
cssClass: 'alert-danger',
handler: () => {
this.modalCtrl.dismiss()
},

View File

@@ -1,22 +1,21 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Restore From Backup</ion-title>
<ion-buttons slot="end">
<ion-button (click)="refresh()">
<ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
<ion-button (click)="dismiss()">
<ion-icon name="arrow-back"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>
Restore From Backup
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<text-spinner *ngIf="loading; else loaded" text="Loading Drives"></text-spinner>
<text-spinner *ngIf="loading" text="Loading Drives"></text-spinner>
<text-spinner *ngIf="submitting" text="Initiating Backup"></text-spinner>
<ng-template #loaded>
<ion-content *ngIf="!loading && !submitting">
<ion-item class="ion-margin-bottom">
<ion-label class="ion-text-wrap">
<p class="ion-padding-bottom"><ion-text color="warning">Warning</ion-text></p>
@@ -56,5 +55,5 @@
</ion-item-group>
</ion-card-content>
</ion-card>
</ng-template>
</ion-content>
</ion-content>

View File

@@ -0,0 +1,25 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { AppRestoreComponent } from './app-restore.component'
import { PwaBackComponentModule } from '../../components/pwa-back-button/pwa-back.component.module'
import { BackupConfirmationComponentModule } from '../backup-confirmation/backup-confirmation.component.module'
import { SharingModule } from '../../modules/sharing.module'
import { TextSpinnerComponentModule } from '../../components/text-spinner/text-spinner.component.module'
@NgModule({
imports: [
CommonModule,
IonicModule,
SharingModule,
BackupConfirmationComponentModule,
PwaBackComponentModule,
TextSpinnerComponentModule,
],
declarations: [
AppRestoreComponent,
],
exports: [AppRestoreComponent],
})
export class AppRestoreComponentModule { }

View File

@@ -1,31 +1,28 @@
import { Component, ViewChild } from '@angular/core'
import { IonContent, LoadingController, ModalController } from '@ionic/angular'
import { Component, Input } from '@angular/core'
import { LoadingController, ModalController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/backup-confirmation.component'
import { DiskInfo } from 'src/app/services/api/api.types'
import { ActivatedRoute } from '@angular/router'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { Subscription } from 'rxjs'
import { take } from 'rxjs/operators'
import { ErrorToastService } from 'src/app/services/error-toast.service'
@Component({
selector: 'app-restore',
templateUrl: './app-restore.page.html',
styleUrls: ['./app-restore.page.scss'],
templateUrl: './app-restore.component.html',
styleUrls: ['./app-restore.component.scss'],
})
export class AppRestorePage {
export class AppRestoreComponent {
@Input() pkgId: string
disks: DiskInfo
pkgId: string
title: string
loading = true
submitting = false
allPartitionsMounted: boolean
@ViewChild(IonContent) content: IonContent
subs: Subscription[] = []
constructor (
private readonly route: ActivatedRoute,
private readonly modalCtrl: ModalController,
private readonly embassyApi: ApiService,
private readonly loadingCtrl: LoadingController,
@@ -34,13 +31,13 @@ export class AppRestorePage {
) { }
ngOnInit () {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
console.log('initing')
this.getExternalDisks()
}
ngAfterViewInit () {
this.content.scrollToPoint(undefined, 1)
}
// ngAfterViewInit () {
// this.content.scrollToPoint(undefined, 1)
// }
async refresh () {
this.loading = true
@@ -54,6 +51,7 @@ export class AppRestorePage {
} catch (e) {
this.errToast.present(e)
} finally {
console.log('loading false')
this.loading = false
}
}
@@ -77,11 +75,14 @@ export class AppRestorePage {
await m.present()
}
dismiss () {
this.modalCtrl.dismiss({ })
}
private async restore (logicalname: string, password: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
})
await loader.present()
console.log('here here here')
this.submitting = true
// await loader.present()
try {
await this.embassyApi.restorePackage({
@@ -90,9 +91,9 @@ export class AppRestorePage {
password,
})
} catch (e) {
this.errToast.present(e)
this.modalCtrl.dismiss({ error: e })
} finally {
loader.dismiss()
this.modalCtrl.dismiss({ })
}
}
}

View File

@@ -2,11 +2,11 @@
<div style="height: 85%; margin: 24px; display: flex; flex-direction: column; justify-content: space-between;">
<div *ngIf="type === 'backup'">
<h4><ion-text color="dark">Encrypt Backup</ion-text></h4>
<p><ion-text color="medium">Enter your master password to create an encrypted backup.</ion-text></p>
<p><ion-text>Enter your master password to create an encrypted backup.</ion-text></p>
</div>
<div *ngIf="type === 'restore'">
<h4><ion-text color="dark">Decrypt Backup</ion-text></h4>
<p><ion-text color="medium">Enter the password that was originally used to encrypt this backup.</ion-text></p>
<p><ion-text>Enter the password that was originally used to encrypt this backup.</ion-text></p>
</div>
<div>

View File

@@ -7,6 +7,7 @@ import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-b
import { QRComponentModule } from 'src/app/components/qr/qr.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { AppActionInputPageModule } from 'src/app/modals/app-action-input/app-action-input.module'
import { AppRestoreComponentModule } from 'src/app/modals/app-restore/app-restore.component.module'
const routes: Routes = [
{
@@ -24,6 +25,7 @@ const routes: Routes = [
QRComponentModule,
SharingModule,
AppActionInputPageModule,
AppRestoreComponentModule,
],
declarations: [AppActionsPage],
})

View File

@@ -7,9 +7,57 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ng-container *ngIf="patch.data['package-data'][pkgId] as pkg">
<ion-item-group>
<ion-grid class="ion-text-center" style="margin: 0 6px;">
<ion-row>
<ion-col *ngFor="let action of pkg.manifest.actions | keyvalue: asIsOrder" size="6">
<ion-card button style="cursor: pointer !important; height: 88%;" color="light" (click)="handleAction(pkg, action)">
<ion-card-header>
<ion-card-subtitle>
<ion-icon size="large" *ngIf="!(action.value['allowed-statuses'] | includes: pkg.installed.status.main.status); else goodIcon" color="danger" name="close-outline"></ion-icon>
<ion-icon size="large" #goAhead name="play-circle-outline"></ion-icon>
</ion-card-subtitle>
<ion-card-title>{{ action.value.name }}</ion-card-title>
</ion-card-header>
<ion-card-content>
{{ action.value.description }}
</ion-card-content>
</ion-card>
</ion-col>
<ion-col size="6">
<ion-card button style="cursor: pointer !important; height: 88%;" color="light" (click)="restore()">
<ion-card-header>
<ion-card-subtitle>
<ion-icon size="large" name="color-wand-outline"></ion-icon>
</ion-card-subtitle>
<ion-card-title>Restore From Backup</ion-card-title>
</ion-card-header>
<ion-card-content>
All changes since backup will be lost.
</ion-card-content>
</ion-card>
</ion-col>
<ion-col size="6">
<ion-card button style="cursor: pointer !important; height: 88%;" color="light" (click)="uninstall(pkg.manifest)">
<ion-card-header>
<ion-card-subtitle>
<ion-icon size="large" name="trash-outline"></ion-icon>
</ion-card-subtitle>
<ion-card-title>Uninstall</ion-card-title>
</ion-card-header>
<ion-card-content>
This will uninstall the service from your Embassy and delete all data permanently.
</ion-card-content>
</ion-card>
</ion-col>
</ion-row>
</ion-grid>
<!-- <ion-item-group>
<ion-item button *ngFor="let action of pkg.manifest.actions | keyvalue: asIsOrder" (click)="handleAction(pkg, action)" >
<ion-label class="ion-text-wrap">
<h2><ion-text color="primary">{{ action.value.name }}</ion-text><ion-icon *ngIf="!(action.value['allowed-statuses'] | includes: pkg.installed.status.main.status)" color="danger" name="close-outline"></ion-icon></h2>
@@ -22,6 +70,6 @@
<p><ion-text color="dark">This will uninstall the service from your Embassy and delete all data permanently.</ion-text></p>
</ion-label>
</ion-item>
</ion-item-group>
</ion-item-group> -->
</ng-container>
</ion-content>

View File

@@ -10,6 +10,7 @@ import { Subscription } from 'rxjs'
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
import { AppActionInputPage } from 'src/app/modals/app-action-input/app-action-input.page'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { AppRestoreComponent } from 'src/app/modals/app-restore/app-restore.component'
@Component({
selector: 'app-actions',
@@ -79,7 +80,7 @@ export class AppActionsPage {
await alert.present()
}
} else {
const statuses = [...action.value['allowedStatuses']]
const statuses = [...action.value['allowed-statuses']]
const last = statuses.pop()
let statusesStr = statuses.join(', ')
let error = null
@@ -103,6 +104,23 @@ export class AppActionsPage {
}
}
async restore (): Promise<void> {
const m = await this.modalCtrl.create({
componentProps: {
pkgId: this.pkgId,
},
component: AppRestoreComponent,
backdropDismiss: false,
})
m.onWillDismiss().then(res => {
const data = res.data
if (data.error) this.errToast.present(data.error)
})
return await m.present()
}
async uninstall (manifest: Manifest) {
const { id, title, version, alerts } = manifest
const data = await wizardModal(
@@ -134,7 +152,6 @@ export class AppActionsPage {
header: 'Execution Complete',
message: res.message.split('\n').join('</br ></br />'),
buttons: ['OK'],
cssClass: 'alert-success-message',
})
await successAlert.present()
} catch (e) {

View File

@@ -52,9 +52,9 @@
<ion-label class="ion-text-wrap">
<h2 style="display: flex; align-items: center;">
<ion-icon size="small" style="margin: 4px" slot="start" color="primary" slot="start" name="ellipse"></ion-icon>
<ion-avatar style="width: 3vh; height: 3vh; margin: 0px 2px 0px 5px;" slot="start">
<ion-thumbnail style="width: 3vh; height: 3vh; margin: 0px 2px 0px 5px;" slot="start">
<img [src]="rec.dependentIcon" [alt]="rec.dependentTitle"/>
</ion-avatar>
</ion-thumbnail>
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: smaller;">{{ rec.dependentTitle }}</ion-text>
</h2>
<div style="margin: 7px 5px;">

View File

@@ -206,7 +206,6 @@ export class AppConfigPage {
},
{
text: `Leave`,
cssClass: 'alert-danger',
handler: () => {
this.navCtrl.back()
},

View File

@@ -7,43 +7,37 @@
</ion-toolbar>
</ion-header>
<ion-content>
<ion-grid *ngIf="patch.data['package-data'][pkgId] as pkg">
<ion-row>
<ion-col *ngFor="let interface of pkg.manifest.interfaces | keyvalue: asIsOrder" sizeXs="12" sizeSm="12" sizeMd="6">
<ion-card>
<ion-card-header>
<ion-card-title>{{ interface.value.name }}</ion-card-title>
<ion-card-subtitle>{{ interface.value.description }}</ion-card-subtitle>
<ion-button style="margin-top: 12px;" *ngIf="interface.value.ui" [disabled]="!(pkg | isLaunchable)" fill="outline" color="dark" expand="block" (click)="launch(pkg)">
Launch
<ion-icon slot="end" name="rocket-outline"></ion-icon>
</ion-button>
</ion-card-header>
<ion-card-content>
<ng-container *ngIf="pkg.installed['interface-info'].addresses[interface.key] as int">
<ion-item>
<ion-label class="ion-text-wrap">
<h2>Tor Address</h2>
<p>{{ 'http://' + int['tor-address'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="copy('http://' + int['tor-address'])">
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-item>
<ion-item>
<ion-label class="ion-text-wrap">
<h2>LAN Address</h2>
<p>{{ 'https://' + int['lan-address'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="copy('https://' + int['lan-address'])">
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-item>
</ng-container>
</ion-card-content>
</ion-card>
</ion-col>
</ion-row>
</ion-grid>
<ion-content *ngIf="patch.data['package-data'][pkgId] as pkg">
<ion-card *ngFor="let interface of pkg.manifest.interfaces | keyvalue: asIsOrder">
<ion-card-header>
<ion-card-title>{{ interface.value.name }}</ion-card-title>
<ion-card-subtitle>{{ interface.value.description }}</ion-card-subtitle>
<ion-button style="margin-top: 12px;" *ngIf="interface.value.ui" [disabled]="!(pkg | isLaunchable)" fill="outline" color="dark" expand="block" (click)="launch(pkg)">
Launch
<ion-icon slot="end" name="rocket-outline"></ion-icon>
</ion-button>
</ion-card-header>
<ion-card-content>
<ng-container *ngIf="pkg.installed['interface-info'].addresses[interface.key] as int">
<ion-item>
<ion-label class="ion-text-wrap">
<h2>Tor Address</h2>
<p>{{ 'http://' + int['tor-address'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="copy('http://' + int['tor-address'])">
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-item>
<ion-item>
<ion-label class="ion-text-wrap">
<h2>LAN Address</h2>
<p>{{ 'https://' + int['lan-address'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="copy('https://' + int['lan-address'])">
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-item>
</ng-container>
</ion-card-content>
</ion-card>
</ion-content>

View File

@@ -10,10 +10,10 @@
<ion-content style="position: relative">
<div *ngIf="pkgs | empty; else list" class="ion-text-center ion-padding">
<div style="display: flex; flex-direction: column; justify-content: center; height: 40vh">
<h2>Welcome to your <span style="font-style: italic; color: var(--ion-color-danger)">Embassy</span></h2>
<h2>Welcome to <ion-text color="danger" style="font-family: 'Montserrat';">Embassy</ion-text></h2>
<p class="ion-text-wrap">Get started by installing your first service.</p>
</div>
<ion-button [routerLink]="['/marketplace']" style="width: 50%;" fill="outline">
<ion-button color="dark" [routerLink]="['/marketplace']" style="width: 50%;">
<ion-icon slot="start" name="storefront-outline"></ion-icon>
Marketplace
</ion-button>

View File

@@ -25,7 +25,6 @@
.main-img {
width: 50%;
margin: 12px;
border-radius: var(--icon-border-radius);
}
.bulb-on {
@@ -70,7 +69,7 @@
border-color: transparent;
}
ion-icon {
color: var(--ion-color-medium);
color: var(--ion-color-dark-shade);
}
}

View File

@@ -12,7 +12,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding" color="light">
<ion-content class="ion-padding">
<text-spinner *ngIf="!logs" text="Loading Logs"></text-spinner>

View File

@@ -3,62 +3,19 @@
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Health</ion-title>
<ion-title>Monitor</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content class="ion-padding">
<ion-card *ngIf="mainStatus">
<ion-card-header>
<ion-card-title>
<ion-icon style="vertical-align: middle; padding-right: 12px;" name="medkit-outline"></ion-icon>
<span style="vertical-align: middle;">Health Checks</span>
</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-item *ngIf="mainStatus.health | empty; else health">
<ion-label>
No health checks
</ion-label>
</ion-item>
<ng-template #health>
<ion-item *ngIf="!(mainStatus.health | empty); else noHealth" color="light" style="margin: 10px;">
<ion-label>
<div *ngFor="let health of mainStatus.health | keyvalue : asIsOrder" class="align" style="margin-left: 12px;">
<ion-icon *ngIf="health.value.result === 'success'" name="checkmark-outline" color="success"></ion-icon>
<ion-icon *ngIf="health.value.result === 'starting'" name="timer-outline" color="warning"></ion-icon>
<ion-icon *ngIf="health.value.result === 'loading'" name="sync-circle-outline" color="warning"></ion-icon>
<ion-icon *ngIf="health.value.result === 'failure'" name="close-outline" color="danger"></ion-icon>
<ion-icon *ngIf="health.value.result === 'disabled'" name="remove-outline" color="medium"></ion-icon>
<h2>{{ health.key }}</h2>
<p style="margin-left: 24px;" *ngIf="health.value.result === 'failure'"><ion-text color="danger">{{ health.value.error }}</ion-text></p>
</div>
</ion-label>
</ion-item>
</ng-template>
</ion-card-content>
</ion-card>
<ion-card>
<ion-card-header>
<ion-card-title>
<ion-icon style="vertical-align: middle; padding-right: 12px;" name="pulse-outline"></ion-icon>
<span style="vertical-align: middle;">Metrics</span>
</ion-card-title>
</ion-card-header>
<ion-card-content>
<skeleton-list *ngIf="loading" rows="3"></skeleton-list>
<ion-item-group *ngIf="!loading">
<ion-item *ngFor="let metric of metrics | keyvalue : asIsOrder">
<ion-label>
<ion-text color="medium">{{ metric.key }}</ion-text>
</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-note>
</ion-item>
</ion-item-group>
</ion-card-content>
</ion-card>
<skeleton-list *ngIf="loading" rows="3"></skeleton-list>
<ion-item-group *ngIf="!loading">
<ion-item *ngFor="let metric of metrics | 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-note>
</ion-item>
</ion-item-group>
</ion-content>

View File

@@ -6,7 +6,6 @@ import { AppShowPage } from './app-show.page'
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module'
const routes: Routes = [
@@ -24,7 +23,6 @@ const routes: Routes = [
IonicModule,
RouterModule.forChild(routes),
PwaBackComponentModule,
BadgeMenuComponentModule,
InstallWizardComponentModule,
],
declarations: [AppShowPage],

View File

@@ -3,53 +3,118 @@
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Service Details</ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>
<ion-item lines="none">
<ion-avatar slot="start">
<img [src]="pkg['static-files'].icon" />
</ion-avatar>
<ion-label style="text-overflow: ellipsis;">
<h1 style="font-family: 'Montserrat';" [class.less-large]="pkg.manifest.title.length > 20">
{{ pkg.manifest.title }}
</h1>
<h2>{{ pkg.manifest.version | displayEmver }}</h2>
</ion-label>
</ion-item>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content>
<ng-container *ngIf="pkg">
<!-- top plate -->
<div class="top-plate">
<ion-item lines="none">
<ion-thumbnail slot="start">
<img [src]="pkg['static-files'].icon" />
</ion-thumbnail>
<ion-label class="ion-text-wrap">
<h1 style="font-family: 'Montserrat';" [class.less-large]="pkg.manifest.title.length > 20">
{{ pkg.manifest.title }}
</h1>
<h5>{{ pkg.manifest.version | displayEmver }}</h5>
<ion-item-group>
<!-- ** always ** -->
<ion-item-divider>Status</ion-item-divider>
<ion-item>
<ion-label style="overflow: visible;">
<status size="x-large" weight="500" [rendering]="rendering"></status>
</ion-label>
</ion-item>
<div class="status-readout">
<status size="large" weight="500" [rendering]="rendering"></status>
<ion-button *ngIf="rendering.feStatus === FeStatus.NeedsConfig" expand="block" [routerLink]="['config']">
Configure
</ion-button>
<ion-button *ngIf="[FeStatus.Running, FeStatus.StartingUp, FeStatus.NeedsAttention] | includes : rendering.feStatus" expand="block" color="danger" (click)="stop()">
Stop
</ion-button>
<ion-button *ngIf="rendering.feStatus === FeStatus.DependencyIssue" expand="block" (click)="scrollToRequirements()">
Fix
</ion-button>
<ion-button *ngIf="rendering.feStatus === FeStatus.Stopped" expand="block" color="success" (click)="tryStart()">
Start
</ion-button>
</div>
<ng-container *ngIf="pkg.state === PackageState.Installed">
<ion-button size="small" *ngIf="(pkg | hasUi)" [disabled]="!(pkg | isLaunchable)" class="launch-button" expand="block" (click)="launchUiTab()">
Launch Web Interface
<ion-button slot="end" class="action-button" *ngIf="pkg.state === PackageState.Installed && (pkg | hasUi)" [disabled]="!(pkg | isLaunchable)" (click)="launchUiTab()">
Web
<ion-icon slot="end" name="rocket-outline"></ion-icon>
</ion-button>
</ng-container>
</div>
<ion-button slot="end" class="action-button" *ngIf="rendering.feStatus === FeStatus.NeedsConfig" [routerLink]="['config']">
Configure
</ion-button>
<ion-button slot="end" class="action-button" *ngIf="[FeStatus.Running, FeStatus.StartingUp, FeStatus.NeedsAttention] | includes : rendering.feStatus" color="danger" (click)="stop()">
Stop
</ion-button>
<ion-button slot="end" class="action-button" *ngIf="rendering.feStatus === FeStatus.DependencyIssue" (click)="scrollToRequirements()">
Fix
</ion-button>
<ion-button slot="end" class="action-button" *ngIf="rendering.feStatus === FeStatus.Stopped" color="success" (click)="tryStart()">
Start
</ion-button>
</ion-item>
<!-- ** iff installed ** -->
<ng-container *ngIf="pkg.state === PackageState.Installed">
<!-- ** iff health checks ** -->
<ng-container *ngIf="!(mainStatus.health | empty)">
<ion-item-divider>Health Checks</ion-item-divider>
<ion-item *ngFor="let health of mainStatus.health | keyvalue : asIsOrder">
<ion-spinner class="icon-spinner" color="warning" slot="start" *ngIf="['starting', 'loading'] | includes : health.value.result"></ion-spinner>
<ion-icon slot="start" *ngIf="health.value.result === 'success'" name="checkmark-outline" color="success"></ion-icon>
<ion-icon slot="start" *ngIf="health.value.result === 'failure'" name="close-outline" color="danger"></ion-icon>
<ion-icon slot="start" *ngIf="health.value.result === 'disabled'" name="remove-outline" color="dark"></ion-icon>
<ion-label>
<p>{{ health.key }}</p>
<h2>{{ health.value.result }}</h2>
<p *ngIf="health.value.result === 'failure'"><ion-text color="danger">{{ health.value.error }}</ion-text></p>
</ion-label>
</ion-item>
</ng-container>
<!-- ** always ** -->
<ion-item-divider>Menu</ion-item-divider>
<ion-item button detail *ngFor="let button of buttons" (click)="button.action()">
<ion-icon slot="start" [name]="button.icon"></ion-icon>
<ion-label>{{ button.title }}</ion-label>
</ion-item>
<!-- ** iff dependencies ** -->
<ng-container *ngIf="!(pkg.installed['current-dependencies'] | empty)">
<ion-item-divider id="dependencies">Dependencies</ion-item-divider>
<!-- A current-dependency is a subset of the pkg.manifest.dependencies that is currently required as determined by the service config. -->
<ion-item *ngFor="let dep of pkg.installed['current-dependencies'] | keyvalue">
<ion-thumbnail slot="start">
<img [src]="patch.data['package-data'][dep.key] ? patch.data['package-data'][dep.key]['static-files'].icon : pkg.installed.status['dependency-errors'][dep.key]?.icon" />
</ion-thumbnail>
<ion-label class="ion-text-wrap">
<h2 style="font-family: 'Montserrat'">{{ patch.data['package-data'][dep.key] ? patch.data['package-data'][dep.key].manifest.title : pkg.installed.status['dependency-errors'][dep.key]?.title }}</h2>
<p>{{ pkg.manifest.dependencies[dep.key].version | displayEmver }}</p>
<p><ion-text [color]="pkg.installed.status['dependency-errors'][dep.key] ? 'warning' : 'success'">{{ pkg.installed.status['dependency-errors'][dep.key] ? pkg.installed.status['dependency-errors'][dep.key].type : 'satisfied' }}</ion-text></p>
</ion-label>
<ion-button *ngIf="!pkg.installed.status['dependency-errors'][dep.key] || (pkg.installed.status['dependency-errors'][dep.key] && [DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed] | includes : pkg.installed.status['dependency-errors'][dep.key].type)" slot="end" size="small" [routerLink]="['/services', dep.key]">
View
</ion-button>
<ng-container *ngIf="pkg.installed.status['dependency-errors'][dep.key]">
<ion-button *ngIf="!patch.data['package-data'][dep.key]" slot="end" size="small" (click)="fixDep('install', dep.key)">
Install
</ion-button>
<ng-container *ngIf="patch.data['package-data'][dep.key] && patch.data['package-data'][dep.key].state === PackageState.Installed">
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.NotRunning" slot="end" size="small" [routerLink]="['/services', dep.key]">
Start
</ion-button>
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.IncorrectVersion" slot="end" size="small" (click)="fixDep('update', dep.key)">
Update
</ion-button>
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.ConfigUnsatisfied" slot="end" size="small" (click)="fixDep('configure', dep.key)">
Configure
</ion-button>
</ng-container>
<div *ngIf="patch.data['package-data'][dep.key] && patch.data['package-data'][dep.key].state !== PackageState.Installed" slot="end">
<ion-spinner [color]="patch.data['package-data'][dep.key].state === PackageState.Removing ? 'danger' : 'primary'" style="height: 3vh; width: 3vh" name="dots"></ion-spinner>
</div>
</ng-container>
</ion-item>
</ng-container>
</ng-container>
</ion-item-group>
<!-- ** installed or updating ** -->
<div *ngIf="[PackageState.Installing, PackageState.Updating] | includes : pkg.state" style="padding: 16px;">
<p>Downloading: {{ (pkg['install-progress'] | installState).downloadProgress }}%</p>
<ion-progress-bar
@@ -69,72 +134,5 @@
[value]="(pkg['install-progress'] | installState).unpackProgress / 100"
></ion-progress-bar>
</div>
<ng-container *ngIf="!([FeStatus.Installing, FeStatus.Updating, FeStatus.Removing] | includes : rendering.feStatus)">
<ion-grid class="ion-text-center" style="margin: 0 6px;">
<ion-row>
<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 : rendering.feStatus" (click)="button.action()">
<div>
<ion-icon size="large" [name]="button.icon"></ion-icon>
<br/><br/>
{{ button.title }}
</div>
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
<ion-item-group class="ion-padding-bottom">
<!-- dependencies -->
<ng-container *ngIf="!(pkg.installed['current-dependencies'] | empty)">
<ion-item-divider id="dependencies">Dependencies</ion-item-divider>
<!-- A current-dependency is a subset of the pkg.manifest.dependencies that is currently required as determined by the service config. -->
<ion-grid>
<ion-row>
<ion-col *ngFor="let dep of pkg.installed['current-dependencies'] | keyvalue" sizeXs="12" sizeMd="6">
<ion-item>
<ion-thumbnail slot="start">
<img [src]="patch.data['package-data'][dep.key] ? patch.data['package-data'][dep.key]['static-files'].icon : pkg.installed.status['dependency-errors'][dep.key]?.icon" />
</ion-thumbnail>
<ion-label class="ion-text-wrap">
<h2 style="font-family: 'Montserrat'">{{ patch.data['package-data'][dep.key] ? patch.data['package-data'][dep.key].manifest.title : pkg.installed.status['dependency-errors'][dep.key]?.title }}</h2>
<p>{{ pkg.manifest.dependencies[dep.key].version | displayEmver }}</p>
<p><ion-text [color]="pkg.installed.status['dependency-errors'][dep.key] ? 'warning' : 'success'">{{ pkg.installed.status['dependency-errors'][dep.key] ? pkg.installed.status['dependency-errors'][dep.key].type : 'satisfied' }}</ion-text></p>
</ion-label>
<ion-button *ngIf="!pkg.installed.status['dependency-errors'][dep.key] || (pkg.installed.status['dependency-errors'][dep.key] && [DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed] | includes : pkg.installed.status['dependency-errors'][dep.key].type)" slot="end" size="small" [routerLink]="['/services', dep.key]">
View
</ion-button>
<ng-container *ngIf="pkg.installed.status['dependency-errors'][dep.key]">
<ion-button *ngIf="!patch.data['package-data'][dep.key]" slot="end" size="small" (click)="fixDep('install', dep.key)">
Install
</ion-button>
<ng-container *ngIf="patch.data['package-data'][dep.key] && patch.data['package-data'][dep.key].state === PackageState.Installed">
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.NotRunning" slot="end" size="small" [routerLink]="['/services', dep.key]">
Start
</ion-button>
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.IncorrectVersion" slot="end" size="small" (click)="fixDep('update', dep.key)">
Update
</ion-button>
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.ConfigUnsatisfied" slot="end" size="small" (click)="fixDep('configure', dep.key)">
Configure
</ion-button>
</ng-container>
<div *ngIf="patch.data['package-data'][dep.key] && patch.data['package-data'][dep.key].state !== PackageState.Installed" slot="end" class="spinner">
<ion-spinner [color]="patch.data['package-data'][dep.key].state === PackageState.Removing ? 'danger' : 'primary'" style="height: 3vh; width: 3vh" name="dots"></ion-spinner>
</div>
</ng-container>
</ion-item>
</ion-col>
</ion-row>
</ion-grid>
</ng-container>
</ion-item-group>
</ng-container>
</ng-container>
</ion-content>

View File

@@ -1,45 +1,14 @@
.less-large {
font-size: 20px !important;
font-size: 18px !important;
}
.top-plate {
background: var(--ion-item-background);
margin: 0 16px;
padding: 12px;
border-radius: 10px;
border-style: solid;
border-color: #373737;
ion-item {
border-radius: 10px;
}
}
.status-readout {
--background: transparent;
display: flex;
justify-content: space-between;
padding: 8px 16px;
border-radius: 10px;
align-items: center;
background: var(--ion-background-color);
.action-button {
margin: 10px;
border-style: solid;
border-width: 1px;
border-color: #404040;
min-height: 36px;
min-width: 72px;
}
.launch-button {
--background: rgb(70 193 255 / 75%);
--background-hover: rgb(70 193 255);
--background-hover-opacity: 100%;
--border-style: none;
--color: white;
--border-radius: 10px;
margin: 12px 10px;
}
.dep-card {
border-radius: 10px;
border-style: solid;
border-color: #404040;
}
.icon-spinner {
height: 20px;
width: 20px;
}

View File

@@ -8,7 +8,7 @@ import { wizardModal } from 'src/app/components/install-wizard/install-wizard.co
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { ConfigService } from 'src/app/services/config.service'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, MainStatus, PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
import { FEStatus, PkgStatusRendering, renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
import { ConnectionService } from 'src/app/services/connection.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
@@ -29,6 +29,8 @@ export class AppShowPage {
DependencyErrorType = DependencyErrorType
rendering: PkgStatusRendering
Math = Math
mainStatus: MainStatus
@ViewChild(IonContent) content: IonContent
subs: Subscription[] = []
@@ -59,6 +61,11 @@ export class AppShowPage {
this.connected = connected
this.rendering = renderPkgStatus(pkg.state, pkg.installed.status)
}),
this.patch.watch$('package-data', this.pkgId, 'installed', 'status', 'main')
.subscribe(main => {
this.mainStatus = main
console.log(this.mainStatus)
}),
]
this.setButtons()
}
@@ -236,17 +243,9 @@ export class AppShowPage {
color: 'danger',
disabled: [],
},
{
action: () => this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }),
title: 'Monitor',
icon: 'medkit-outline',
color: 'danger',
// @TODO make the disabled check better. Don't want to list every status here. Monitor should be disabled except is pkg is running.
disabled: [FEStatus.Installing, FEStatus.Updating, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
},
{
action: () => this.navCtrl.navigateForward(['config'], { relativeTo: this.route }),
title: 'Configure',
title: 'Settings',
icon: 'construct-outline',
color: 'danger',
disabled: [FEStatus.Installing, FEStatus.Updating, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
@@ -272,6 +271,14 @@ export class AppShowPage {
color: 'danger',
disabled: [],
},
{
action: () => this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }),
title: 'Monitor',
icon: 'pulse-outline',
color: 'danger',
// @TODO make the disabled check better. Don't want to list every status here. Monitor should be disabled except is pkg is running.
disabled: [FEStatus.Installing, FEStatus.Updating, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
},
{
action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
title: 'Logs',
@@ -279,13 +286,6 @@ export class AppShowPage {
color: 'danger',
disabled: [],
},
{
action: () => this.navCtrl.navigateForward(['restore'], { relativeTo: this.route }),
title: 'Restore From Backup',
icon: 'color-wand-outline',
color: 'danger',
disabled: [FEStatus.Connecting, FEStatus.Installing, FEStatus.Updating, FEStatus.Stopping, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
},
{
action: () => this.navCtrl.navigateForward(['manifest'], { relativeTo: this.route }),
title: 'Package Details',
@@ -295,18 +295,11 @@ export class AppShowPage {
},
{
action: () => this.donate(),
title: 'Support Project',
title: 'Donate',
icon: 'logo-bitcoin',
color: 'danger',
disabled: [],
},
{
action: () => this.navCtrl.navigateForward(['/marketplace', this.pkgId], { relativeTo: this.route }),
title: 'Marketplace Listing',
icon: 'storefront-outline',
color: 'danger',
disabled: [],
},
]
}
}

View File

@@ -51,10 +51,6 @@ const routes: Routes = [
path: ':pkgId/properties',
loadChildren: () => import('./app-properties/app-properties.module').then(m => m.AppPropertiesPageModule),
},
{
path: ':pkgId/restore',
loadChildren: () => import('./app-restore/app-restore.module').then(m => m.AppRestorePageModule),
},
]
@NgModule({

View File

@@ -1,31 +1,35 @@
<ion-content class="ion-padding">
<ion-grid style="height: 100%; max-width: 500px;">
<ion-content>
<ion-grid style="height: 100%; max-width: 540px;">
<ion-row class="ion-align-items-center" style="height: 100%;">
<ion-col>
<ion-card>
<div style="padding: 20px;">
<ion-card-header class="ion-text-center">
<img src="assets/img/logo.png" style="max-width: 120px;" />
</ion-card-header>
<ion-card-content style="padding-top: 30px;">
<form (submit)="submit()">
<ion-item-group>
<ion-item color="light">
<ion-input [type]="unmasked ? 'text' : 'password'" name="password" placeholder="Enter Password" [(ngModel)]="password" (ionChange)="error = ''"></ion-input>
<ion-button fill="clear" color="dark" (click)="toggleMask()">
<ion-icon slot="icon-only" [name]="unmasked ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
</ion-button>
</ion-item>
<ion-item *ngIf="error" lines="none">
<ion-label class="ion-text-wrap" color="danger">{{ error }}</ion-label>
</ion-item>
</ion-item-group>
<ion-button color="dark" class="sharp-button" type="submit" [disabled]="!password" style="margin-top: 60px" expand="block" fill="outline">
Next
</ion-button>
</form>
</ion-card-content>
</div>
<ion-col class="ion-text-center">
<div style="padding-bottom: 32px;">
<img src="assets/img/logo.png" style="max-width: 240px;" />
</div>
<ion-card color="dark">
<ion-card-header class="ion-text-center" style="padding-bottom: 8px;">
<ion-card-title>Log in to Embassy</ion-card-title>
</ion-card-header>
<ion-card-content class="ion-margin">
<form (submit)="submit()" style="margin-bottom: 12px;">
<ion-item-group>
<p class="input-label">Password</p>
<ion-item color="dark">
<ion-icon slot="start" name="key-outline" style="margin-right: 16px;"></ion-icon>
<ion-input [type]="unmasked ? 'text' : 'password'" name="password" [(ngModel)]="password" (ionChange)="error = ''"></ion-input>
<ion-button fill="clear" color="light" (click)="toggleMask()">
<ion-icon slot="icon-only" [name]="unmasked ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
</ion-button>
</ion-item>
<p *ngIf="error" style="text-align: left; padding-top: 4px"><ion-text color="danger">{{ error }}</ion-text></p>
</ion-item-group>
<ion-button class="login-button" type="submit" expand="block">
<span style="font-size: larger; font-weight: bold;">Log In</span>
</ion-button>
</form>
</ion-card-content>
</ion-card>
</ion-col>
</ion-row>

View File

@@ -1,3 +1,28 @@
.sharp-button {
--border-radius: 1px;
ion-card-title {
margin: 24px 0;
font-family: 'Montserrat';
font-size: x-large;
--color: var(--ion-color-light);
}
ion-item {
--border-radius: 4px;
--border-style: solid;
--border-width: 1px;
--border-color: var(--ion-color-light);
}
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
font-weight: bold;
}
.login-button {
margin-inline-start: 0;
margin-inline-end: 0;
margin-top: 24px;
height: 48px;
--background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-dark) 150%);
}

View File

@@ -33,7 +33,7 @@ export class LoginPage {
this.error = ''
this.loader = await this.loadingCtrl.create({
message: 'Authenticating',
message: 'Logging in',
spinner: 'lines',
})
await this.loader.present()

View File

@@ -11,11 +11,23 @@
<text-spinner *ngIf="!marketplaceService.releaseNotes[pkgId]; else loaded" text="Loading Release Notes"></text-spinner>
<ng-template #loaded>
<div *ngFor="let note of marketplaceService.releaseNotes[pkgId] | keyvalue : asIsOrder">
<ion-button (click)="setSelected(note.key)" expand="full" color="light" style="height: 50px;" >
<div style="margin: 0px;" *ngFor="let note of marketplaceService.releaseNotes[pkgId] | keyvalue : asIsOrder">
<ion-button
(click)="setSelected(note.key)"
expand="full" color="light"
style="height: 50px; margin: 1px;"
[class]="selected === note.key ? 'ion-activated' : ''"
>
<p style="position: absolute; left: 10px;">{{ note.key | displayEmver }}</p>
</ion-button>
<ion-card *ngIf="selected === note.key" class="acc-text" color="light" >
<ion-card
[id]="note.key"
[ngStyle]="{
'max-height': selected === note.key ? getDocSize(note.key) : '0px',
'transition': 'max-height 0.2s ease-out'
}"
class="panel"
color="light" >
<ion-text id='release-notes' [innerHTML]="note.value | markdown"></ion-text>
</ion-card>
</div>

View File

@@ -1,8 +1,8 @@
.metric-note {
font-size: 16px;
.panel {
margin: 0px;
padding: 0px 24px;
}
.acc-text {
margin: 0px 0px 10px 0px;
padding: 15px;
.active {
border: 5px solid #4d4d4d;
}

View File

@@ -37,6 +37,11 @@ export class AppReleaseNotes {
}
}
getDocSize (selected: string) {
const element = document.getElementById(selected)
return `${element.scrollHeight}px`
}
asIsOrder (a: any, b: any) {
return 0
}

View File

@@ -1,59 +1,59 @@
<ion-header>
<ion-toolbar>
<ion-title>Service Marketplace</ion-title>
<ion-toolbar *ngIf="!pageLoading">
<ion-searchbar color="dark" (ionChange)="search($event)" debounce="400"></ion-searchbar>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>
</ion-toolbar>
<ion-toolbar *ngIf="!pageLoading">
<ion-searchbar (ionChange)="search($event)" debounce="400"></ion-searchbar>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content class="ion-padding">
<text-spinner *ngIf="pageLoading; else pageLoaded" text="Loading Marketplace"></text-spinner>
<ng-template #pageLoaded>
<ion-card *ngIf="eos" class="eos-card" (click)="updateEos()">
<ion-card-header>
<ion-card-subtitle>Now Available...</ion-card-subtitle>
<ion-card-title>EmbassyOS Version {{ eos.version }}</ion-card-title>
</ion-card-header>
<ion-card-content>
{{ eos.headline }}
</ion-card-content>
</ion-card>
<h1 style="font-family: 'Montserrat'; font-weight: 100px; margin: 0 0 32px 0;" class="ion-text-center">Embassy Marketplace</h1>
<h2 class="ion-margin-start">Categories</h2>
<div class="scrollable">
<div class="scrollable ion-text-center">
<ion-button
*ngFor="let cat of data.categories"
fill="clear"
[color]="cat === category ? 'primary' : 'dark'"
[class.cat-selected]="cat === category"
[class]="cat === category ? 'selected' : 'dim'"
(click)="switchCategory(cat)"
>
{{ cat }}
</ion-button>
</div>
<div class="divider" style="margin: 24px;"></div>
<div *ngIf="pkgsLoading; else loaded" style="margin-top: 64px;" class="ion-text-center">
<text-spinner [text]="'Loading ' + (category | titlecase)"></text-spinner>
<text-spinner text="Loading Packages"></text-spinner>
</div>
<ng-template #loaded>
<ion-grid>
<ion-row>
<ion-col *ngIf="eos && category === 'featured'" sizeXs="12" sizeSm="12" sizeMd="6">
<ion-item button class="eos-item" (click)="updateEos()">
<ion-thumbnail slot="start">
<img src="assets/img/icon.png" />
</ion-thumbnail>
<ion-label>
<h3>Now Available...</h3>
<h2>Embassy OS {{ eos.version }}</h2>
<p>{{ eos.headline }}</p>
</ion-label>
</ion-item>
</ion-col>
<ion-col *ngFor="let pkg of pkgs" sizeXs="12" sizeSm="12" sizeMd="6">
<ion-item [routerLink]="['/marketplace', pkg.manifest.id]">
<ion-thumbnail slot="start">
<img [src]="pkg.icon" />
</ion-thumbnail>
<ion-label>
<h2 style="font-family: 'Montserrat';">{{ pkg.manifest.title }}</h2>
<p>{{ pkg.manifest.description.short }}</p>
<ng-container *ngIf="localPkgs[pkg.manifest.id] as localPkg">
<h2 style="font-family: 'Montserrat'; font-weight: bold;">{{ pkg.manifest.title }}</h2>
<h3>{{ pkg.manifest.description.short }}</h3>
<ng-container *ngIf="localPkgs[pkg.manifest.id] as localPkg; else none">
<p *ngIf="localPkg.state === PackageState.Installed">
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 0" color="success">Installed</ion-text>
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</ion-text>
@@ -66,6 +66,9 @@
<ion-spinner name="dots" color="warning"></ion-spinner>
</p>
</ng-container>
<ng-template #none>
<p>Not Installed</p>
</ng-template>
</ion-label>
</ion-item>
</ion-col>

View File

@@ -2,7 +2,7 @@
overflow: auto;
white-space: nowrap;
// background-color: var(--ion-color-light);
height: 80px;
height: 60px;
/* Hide scrollbar for Chrome, Safari and Opera */
::-webkit-scrollbar {
@@ -14,14 +14,16 @@
scrollbar-width: none; /* Firefox */
}
.eos-card {
--background: linear-gradient(45deg, #101010 16%, var(--ion-color-danger) 150%);
margin: 0 10px 16px 10px;
cursor: pointer;
.eos-item {
--border-style: none;
--background: linear-gradient(45deg, var(--ion-color-dark) -380%, var(--ion-color-medium) 100%)
}
.cat-selected {
border-width: 0 0 1px 0;
border-style: solid;
border-color: var(--ion-color-primary);
.selected {
font-weight: bold;
}
.dim {
font-weight: 300;
color: var(--ion-color-dark-shade);
}

View File

@@ -5,7 +5,6 @@ import { IonicModule } from '@ionic/angular'
import { MarketplaceShowPage } from './marketplace-show.page'
import { SharingModule } from 'src/app/modules/sharing.module'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module'
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
@@ -26,7 +25,6 @@ const routes: Routes = [
RouterModule.forChild(routes),
SharingModule,
PwaBackComponentModule,
BadgeMenuComponentModule,
InstallWizardComponentModule,
],
declarations: [MarketplaceShowPage],

View File

@@ -4,9 +4,6 @@
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Listing</ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
@@ -27,13 +24,14 @@
<div class="header-status">
<!-- no localPkg -->
<p *ngIf="!localPkg; else local">
<ion-text color="medium">Not Installed</ion-text>
Not Installed
</p>
<!-- localPkg -->
<ng-template #local>
<!-- installed -->
<p *ngIf="localPkg.state === PackageState.Installed">
<ion-text color="medium">Installed at {{ localPkg.manifest.version | displayEmver }}</ion-text>
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 0" color="success">Installed</ion-text>
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</ion-text>
</p>
<!-- installing, updating -->
<p *ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state">
@@ -51,17 +49,17 @@
</ion-col>
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
<!-- no localPkg -->
<ion-button *ngIf="!localPkg; else localPkg2" class="main-action-button" expand="block" (click)="install()">
<ion-button *ngIf="!localPkg; else localPkg2" expand="block" (click)="install()">
Install
</ion-button>
<!-- localPkg -->
<ng-template #localPkg2>
<!-- not installing, updating, or removing -->
<ng-container *ngIf="localPkg.state === PackageState.Installed">
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === -1" class="main-action-button" expand="block" (click)="update('update')">
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === -1" expand="block" (click)="update('update')">
Update
</ion-button>
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === 1" class="main-action-button" expand="block" color="warning" (click)="update('downgrade')">
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === 1" expand="block" color="warning" (click)="update('downgrade')">
Downgrade
</ion-button>
</ng-container>
@@ -74,9 +72,9 @@
<ion-item *ngIf="rec && showRec" class="rec-item">
<ion-label class="ion-text-wrap">
<h2 style="display: flex; align-items: center;">
<ion-avatar style="height: 3vh; width: 3vh; margin: 5px" slot="start">
<ion-thumbnail style="height: 3vh; width: 3vh; margin: 5px" slot="start">
<img [src]="rec.dependentIcon" [alt]="rec.dependentTitle"/>
</ion-avatar>
</ion-thumbnail>
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: smaller;">{{ rec.dependentTitle }}</ion-text>
</h2>
<div style="margin: 7px 5px;">

View File

@@ -6,6 +6,7 @@ import { NotificationsPage } from './notifications.page'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
const routes: Routes = [
{
@@ -22,6 +23,7 @@ const routes: Routes = [
PwaBackComponentModule,
BadgeMenuComponentModule,
SharingModule,
TextSpinnerComponentModule,
],
declarations: [NotificationsPage],
})

View File

@@ -15,14 +15,12 @@
<ion-refresher-content pullingIcon="lines" refreshingSpinner="lines"></ion-refresher-content>
</ion-refresher>
<ion-spinner *ngIf="loading" class="center" name="lines" color="warning"></ion-spinner>
<text-spinner *ngIf="loading" text="Loading Notifications"></text-spinner>
<ion-item-group *ngIf="!notifications.length && !loading">
<ion-item>
<ion-label class="ion-text-wrap">
<h2>
<ion-text color="medium">Notifications about Embassy and services will appear here.</ion-text>
</h2>
Notifications about Embassy and services will appear here.
</ion-label>
</ion-item>
</ion-item-group>
@@ -47,7 +45,7 @@
</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="remove(not.id, i)">
<ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
</ion-button>
</ion-item>
</ion-item-group>

View File

@@ -38,6 +38,9 @@
<ion-item detail="true" button [routerLink]="['ssh-keys']">
<ion-label>SSH Keys</ion-label>
</ion-item>
<ion-item detail="true" button [routerLink]="['sessions']">
<ion-label>Active Sessions</ion-label>
</ion-item>
</ion-item-group>
</ion-content>

View File

@@ -6,6 +6,10 @@ const routes: Routes = [
path: '',
loadChildren: () => import('./security-options/security-options.module').then(m => m.SecurityOptionsPageModule),
},
{
path: 'sessions',
loadChildren: () => import('./sessions/sessions.module').then(m => m.SessionsPageModule),
},
{
path: 'ssh-keys',
loadChildren: () => import('./ssh-keys/ssh-keys.module').then(m => m.SSHKeysPageModule),

View File

@@ -1,17 +1,16 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { AppRestorePage } from './app-restore.page'
import { RouterModule, Routes } from '@angular/router'
import { SessionsPage } from './sessions.page'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
import { BackupConfirmationComponentModule } from 'src/app/modals/backup-confirmation/backup-confirmation.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
const routes: Routes = [
{
path: '',
component: AppRestorePage,
component: SessionsPage,
},
]
@@ -19,14 +18,11 @@ const routes: Routes = [
imports: [
CommonModule,
IonicModule,
SharingModule,
RouterModule.forChild(routes),
BackupConfirmationComponentModule,
PwaBackComponentModule,
SharingModule,
TextSpinnerComponentModule,
],
declarations: [
AppRestorePage,
],
declarations: [SessionsPage],
})
export class AppRestorePageModule { }
export class SessionsPageModule { }

View File

@@ -0,0 +1,41 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Active Sessions</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<text-spinner *ngIf="loading" text="Loading Sessions"></text-spinner>
<ion-item-group *ngIf="!loading">
<ion-item-divider>Current Session</ion-item-divider>
<ion-item *ngIf="sessionInfo.sessions[sessionInfo.current] as current">
<ion-icon slot="start" [name]="getPlatformIcon(current.metadata.platforms)"></ion-icon>
<ion-label class="ion-text-wrap">
<h1>{{ getPlatformName(current.metadata.platforms) }}</h1>
<h2>Last Active: {{ current['last-active'] | date : 'medium' }}</h2>
<p>{{ current['user-agent'] }}</p>
</ion-label>
</ion-item>
<ion-item-divider>Other Sessions</ion-item-divider>
<div *ngFor="let session of sessionInfo.sessions | keyvalue : asIsOrder">
<ion-item *ngIf="session.key !== sessionInfo.current">
<ion-icon slot="start" [name]="getPlatformIcon(session.value.metadata.platforms)"></ion-icon>
<ion-label class="ion-text-wrap">
<h1>{{ getPlatformName(session.value.metadata.platforms) }}</h1>
<h2>Last Active: {{ session.value['last-active'] | date : 'medium' }}</h2>
<p>{{ session.value['user-agent'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="presentAlertKill(session.key)">
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
</ion-button>
</ion-item>
</div>
</ion-item-group>
</ion-content>

View File

@@ -0,0 +1,99 @@
import { Component } from '@angular/core'
import { AlertController, getPlatforms, LoadingController } from '@ionic/angular'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
import { PlatformType, RR, SessionMetadata } from 'src/app/services/api/api.types'
@Component({
selector: 'sessions',
templateUrl: 'sessions.page.html',
styleUrls: ['sessions.page.scss'],
})
export class SessionsPage {
loading = true
sessionInfo: RR.GetSessionsRes
constructor (
private readonly loadingCtrl: LoadingController,
private readonly errToast: ErrorToastService,
private readonly alertCtrl: AlertController,
private readonly embassyApi: ApiService,
) { }
async ngOnInit () {
getPlatforms()
this.sessionInfo = await this.embassyApi.getSessions({ })
this.loading = false
}
async presentAlertKill (hash: string) {
const alert = await this.alertCtrl.create({
backdropDismiss: false,
header: 'Caution',
message: `Are you sure you want to kill this session?`,
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Kill',
handler: () => {
this.kill(hash)
},
},
],
})
await alert.present()
}
async kill (hash: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Killing session...',
cssClass: 'loader',
})
await loader.present()
try {
await this.embassyApi.killSessions({ hashes: [hash] })
delete this.sessionInfo.sessions[hash]
} catch (e) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
}
getPlatformIcon (platforms: PlatformType[]): string {
if (platforms.includes('cli')) {
return 'terminal-outline'
} else if (platforms.includes('desktop')) {
return 'desktop-outline'
} else {
return 'phone-portrait-outline'
}
}
getPlatformName (platforms: PlatformType[]): string {
if (platforms.includes('cli')) {
return 'CLI'
} else if (platforms.includes('desktop')) {
return 'Desktop/Laptop'
} else if (platforms.includes('android')) {
return 'Android Device'
} else if (platforms.includes('iphone')) {
return 'iPhone'
} else if (platforms.includes('ipad')) {
return 'iPad'
} else if (platforms.includes('ios')) {
return 'iOS Device'
} else {
return 'Unknown Device'
}
}
asIsOrder (a: any, b: any) {
return 0
}
}

View File

@@ -22,7 +22,7 @@
{{ ssh.value.alg }} {{ ssh.key }} {{ ssh.value.hostname }}
</ion-label>
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(ssh.key)">
<ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
</ion-button>
</ion-item>
</ion-item-group>

View File

@@ -57,7 +57,6 @@ export class SSHKeysPage {
},
{
text: 'Delete',
cssClass: 'alert-danger',
handler: () => {
this.delete(hash)
},

View File

@@ -12,7 +12,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding" color="light">
<ion-content class="ion-padding">
<text-spinner *ngIf="loading; else loaded" text="Loading Logs"></text-spinner>
<ng-template #loaded>

View File

@@ -7,16 +7,14 @@
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content class="ion-padding">
<skeleton-list *ngIf="loading; else loaded" groups="3"></skeleton-list>
<ng-template #loaded>
<ion-item-group *ngFor="let metricGroup of metrics | keyvalue : asIsOrder">
<ion-item-divider class="divider">{{ metricGroup.key }}</ion-item-divider>
<ion-item-divider>{{ metricGroup.key }}</ion-item-divider>
<ion-item *ngFor="let metric of metricGroup.value | keyvalue : asIsOrder">
<ion-label>
<ion-text color="medium">{{ metric.key }}</ion-text>
</ion-label>
<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-note>

View File

@@ -1,3 +1,3 @@
.metric-note {
font-size: 16px;
}
}

View File

@@ -7,12 +7,11 @@
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content class="ion-padding">
<ion-item-group>
<div *ngFor="let cat of settings | keyvalue : asIsOrder">
<ion-item-divider>{{ cat.key }}</ion-item-divider>
<ion-item style="cursor: pointer;" button *ngFor="let button of cat.value" (click)="button.action()">
<ion-item-divider><ion-text color="dark">{{ cat.key }}</ion-text></ion-item-divider>
<ion-item [detail]="button.detail" button *ngFor="let button of cat.value" (click)="button.action()">
<ion-icon slot="start" [name]="button.icon"></ion-icon>
<ion-label>{{ button.title }}</ion-label>
</ion-item>

View File

@@ -37,7 +37,6 @@ export class ServerShowPage {
},
{
text: 'Restart',
cssClass: 'alert-danger',
handler: () => {
this.restart()
},
@@ -59,7 +58,6 @@ export class ServerShowPage {
},
{
text: 'Shutdown',
cssClass: 'alert-danger',
handler: () => {
this.shutdown()
},
@@ -110,16 +108,19 @@ export class ServerShowPage {
title: 'Privacy and Security',
icon: 'shield-checkmark-outline',
action: () => this.navCtrl.navigateForward(['security'], { relativeTo: this.route }),
detail: true,
},
{
title: 'LAN',
icon: 'home-outline',
action: () => this.navCtrl.navigateForward(['lan'], { relativeTo: this.route }),
detail: true,
},
{
title: 'WiFi',
icon: 'wifi',
action: () => this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }),
detail: true,
},
],
'Insights': [
@@ -127,16 +128,19 @@ export class ServerShowPage {
title: 'About',
icon: 'information-circle-outline',
action: () => this.navCtrl.navigateForward(['specs'], { relativeTo: this.route }),
detail: true,
},
{
title: 'Monitor',
icon: 'pulse',
action: () => this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }),
detail: true,
},
{
title: 'Logs',
icon: 'newspaper-outline',
action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
detail: true,
},
],
'Backups': [
@@ -144,6 +148,7 @@ export class ServerShowPage {
title: 'Create Backup',
icon: 'save-outline',
action: () => this.navCtrl.navigateForward(['backup'], { relativeTo: this.route }),
detail: true,
},
],
'Power': [
@@ -151,11 +156,13 @@ export class ServerShowPage {
title: 'Restart',
icon: 'reload-outline',
action: () => this.presentAlertRestart(),
detail: false,
},
{
title: 'Shutdown',
icon: 'power',
action: () => this.presentAlertShutdown(),
detail: false,
},
],
}
@@ -171,5 +178,6 @@ interface ServerSettings {
title: string
icon: string
action: Function
detail: boolean
}[]
}

View File

@@ -14,7 +14,7 @@
<ion-item-group *ngIf="patch.data['server-info'] as server">
<ion-item>
<ion-label>
<h2>Version</h2>
<h2>EmbassyOS Version</h2>
<p>{{ server.version | displayEmver }}</p>
</ion-label>
</ion-item>

View File

@@ -29,7 +29,6 @@ export class WifiListPage {
const buttons: ActionSheetButton[] = [
{
text: 'Forget',
cssClass: 'alert-danger',
handler: () => {
this.delete(ssid)
},

View File

@@ -739,6 +739,26 @@ export module Mock {
},
]
export const Sessions: RR.GetSessionsRes = {
current: 'b7b1a9cef4284f00af9e9dda6e676177',
sessions: {
'9513226517c54ddd8107d6d7b9d8aed7': {
'last-active': '2021-07-14T20:49:17.774Z',
'user-agent': 'AppleWebKit/{WebKit Rev} (KHTML, like Gecko)',
metadata: {
platforms: ['iphone', 'mobileweb', 'mobile', 'ios'],
},
},
'b7b1a9cef4284f00af9e9dda6e676177': {
'last-active': '2021-06-14T20:49:17.774Z',
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
metadata: {
platforms: ['desktop'],
},
},
},
}
export const SshKeys: RR.GetSSHKeysRes = {
'28:d2:7e:78:61:b4:bf:g2:de:24:15:96:4e:d4:15:53': {
alg: 'ed25519',

View File

@@ -16,7 +16,7 @@ export module RR {
// auth
export type LoginReq = { password: string } // auth.login - unauthed
export type LoginReq = { password: string, metadata: SessionMetadata } // auth.login - unauthed
export type loginRes = null
export type LogoutReq = { } // auth.logout
@@ -47,6 +47,17 @@ export module RR {
export type RefreshLanReq = { } // network.lan.refresh
export type RefreshLanRes = null
// sessions
export type GetSessionsReq = { } // sessions.list
export type GetSessionsRes = {
current: string,
sessions: { [hash: string]: Session }
}
export type KillSessionsReq = WithExpire<{ hashes: string[] }> // sessions.kill
export type KillSessionsRes = WithRevision<null>
// marketplace URLs
export type SetEosMarketplaceReq = WithExpire<{ url: string }> // marketplace.eos.set
@@ -253,6 +264,18 @@ export interface Metric {
}
}
export interface Session {
'last-active': string
'user-agent': string
metadata: SessionMetadata
}
export interface SessionMetadata {
platforms: PlatformType[]
}
export type PlatformType = 'cli' | 'ios' | 'ipad' | 'iphone' | 'android' | 'phablet' | 'tablet' | 'cordova' | 'capacitor' | 'electron' | 'pwa' | 'mobile' | 'mobileweb' | 'desktop' | 'hybrid'
export interface DiskInfo {
[id: string]: DiskInfoEntry
}

View File

@@ -32,6 +32,10 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
abstract logout (params: RR.LogoutReq): Promise<RR.LogoutRes>
abstract getSessions (params: RR.GetSessionsReq): Promise<RR.GetSessionsRes>
abstract killSessions (params: RR.KillSessionsReq): Promise<RR.KillSessionsRes>
// server
protected abstract setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes>

View File

@@ -41,6 +41,14 @@ export class LiveApiService extends ApiService {
return this.http.rpcRequest({ method: 'auth.logout', params })
}
async getSessions (params: RR.GetSessionsReq): Promise<RR.GetSessionsRes> {
return this.http.rpcRequest({ method: 'auth.session.list', params })
}
async killSessions (params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
return this.http.rpcRequest({ method: 'auth.session.kill', params })
}
// server
async setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes> {

View File

@@ -50,6 +50,16 @@ export class MockApiService extends ApiService {
return null
}
async getSessions (params: RR.GetSessionsReq): Promise<RR.GetSessionsRes> {
await pauseFor(2000)
return Mock.Sessions
}
async killSessions (params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
await pauseFor(2000)
return null
}
// server
async setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes> {

View File

@@ -3,6 +3,7 @@ import { BehaviorSubject, Observable } from 'rxjs'
import { distinctUntilChanged } from 'rxjs/operators'
import { ApiService } from './api/embassy/embassy-api.service'
import { Storage } from '@ionic/storage'
import { getPlatforms, isPlatform } from '@ionic/angular'
export enum AuthState {
UNVERIFIED,
@@ -31,7 +32,10 @@ export class AuthService {
}
async login (password: string): Promise<void> {
await this.embassyApi.login({ password })
await this.embassyApi.login({
password,
metadata: { platforms: getPlatforms() },
})
await this.storage.set(this.LOGGED_IN_KEY, true)
this.authState$.next(AuthState.VERIFIED)
}

View File

@@ -67,6 +67,7 @@ export class ConfigService {
}
launchableURL (pkg: PackageDataEntry): string {
console.log('PKGPKGPKG', pkg)
return this.isTor() ? `http://${torUiAddress(pkg)}` : `https://${lanUiAddress(pkg)}`
}
}
@@ -85,7 +86,7 @@ export function torUiAddress (pkg: PackageDataEntry): string {
const val = interfaces[key]
return val.ui && val['tor-config']
})
return pkg['interface-info'].addresses[id]['tor-address']
return pkg.installed['interface-info'].addresses[id]['tor-address']
}
export function lanUiAddress (pkg: PackageDataEntry): string {
@@ -94,7 +95,7 @@ export function lanUiAddress (pkg: PackageDataEntry): string {
const val = interfaces[key]
return val.ui && val['lan-config']
})
return pkg['interface-info'].addresses[id]['lan-address']
return pkg.installed['interface-info'].addresses[id]['lan-address']
}
export function hasUi (interfaces: { [id: string]: InterfaceDef }): boolean {

BIN
ui/src/assets/img/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -25,10 +25,6 @@
@import "~@ionic/angular/css/text-transformation.css";
@import "~@ionic/angular/css/flex-utils.css";
.ios ion-title {
padding-inline-start: 60px;
padding-inline-end: 60px;
}
.select-change-warning .alert-sub-title {
color: var(--ion-color-warning)
@@ -100,8 +96,11 @@ ion-toast {
white-space: normal !important;
}
img {
border-radius: 100%;
}
ion-card-title {
color: var(--ion-color-dark);
font-family: 'Montserrat';
}
@@ -109,11 +108,6 @@ ion-title {
font-family: 'Montserrat';
}
ion-textarea {
margin-top: 0;
padding-left: 12px;
}
ion-note {
max-width: 140px;
overflow: hidden;
@@ -125,23 +119,16 @@ ion-badge {
font-weight: bold;
}
ion-item-divider {
--background: transparent;
text-transform: uppercase;
margin-top: 12px;
font-weight: 400;
ion-toolbar {
--min-height: 72px;
--ion-background-color: var(--ion-color-light);
}
ion-infinite-scroll ion-infinite-scroll-content {
--color: var(--ion-color-warning) !important;
}
ion-action-sheet {
--backdrop-opacity: 0.75 !important;
}
ion-alert {
--backdrop-opacity: 0.75 !important;
.alert-button {
color: var(--ion-color-dark) !important;
}
@@ -151,10 +138,6 @@ ion-button {
--color: var(--ion-color-dark) !important;
}
ion-loading {
--backdrop-opacity: 0.75 !important;
}
* {
-webkit-user-select: text;
-moz-user-select: text;
@@ -162,14 +145,6 @@ ion-loading {
user-select: text;
}
ion-popover {
--background: var(--ion-color-warning) !important;
ion-backdrop {
--backdrop-opacity: 0.45 !important;
}
}
.text-ellipses {
overflow: hidden;
text-overflow: ellipsis;
@@ -185,13 +160,19 @@ ion-popover {
display: block;
}
.modal-wrapper.sc-ion-modal-md {
border-radius: 6px;
border: 2px solid rgba(255,255,255,.03);
box-shadow: 0 0 70px 70px black;
}
@media (min-width:1000px) {
.modal-wrapper {
position: absolute;
height: 60% !important;
top: 20%;
width: 50% !important;
left: 25%;
height: 70% !important;
top: 15%;
width: 60% !important;
left: 20%;
display: block;
}
}
@@ -227,6 +208,10 @@ ion-popover {
}
}
.custom-modal {
border-radius: 8px;
}
ion-slides {
.slider-wrapper {
height: 100%;
@@ -239,12 +224,21 @@ ion-slides {
--background: transparent !important;
}
ion-loading {
z-index: 100 !important;
ion-item-divider {
text-transform: uppercase;
margin-top: 24px;
font-weight: 600;
--color: var(--ion-color-dark);
border: none;
font-size: medium;
}
ion-modal {
--backdrop-opacity: 0.75 !important;
ion-item {
border-radius: 4px;
}
ion-loading {
z-index: 100 !important;
}
.swiper-pagination {
@@ -253,10 +247,6 @@ ion-modal {
padding-bottom: 3px;
}
ion-avatar {
--border-radius: var(--icon-border-radius);
}
.notifier-item {
margin: 12px;
margin-top: 0px;
@@ -277,10 +267,9 @@ ion-avatar {
--padding-start: 10px;
}
ion-item-divider {
color: var(--ion-color-medium);
font-size: medium;
padding-left: 10px;
.divider {
background: linear-gradient(90deg,var(--ion-color-light) 0,var(--ion-color-dark) 50%,var(--ion-color-light) 100%);
height: 1px;
}
.dots {

View File

@@ -4,83 +4,13 @@
/** Ionic CSS Variables **/
:root {
--ion-font-family: 'Open Sans';
/** primary **/
--ion-color-primary: #3880ff;
--ion-color-primary-rgb: 56, 128, 255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #3171e0;
--ion-color-primary-tint: #4c8dff;
--ion-background-color: var(--ion-color-medium);
--ion-background-color-rgb: var(--ion-color-medium-rgb);
--ion-text-color: var(--ion-color-dark);
--ion-text-color-rgb: var(--ion-color-dark-rgb);
// --ion-backdrop-opacity: .75;
/** secondary **/
--ion-color-secondary: #3dc2ff;
--ion-color-secondary-rgb: 61, 194, 255;
--ion-color-secondary-contrast: #ffffff;
--ion-color-secondary-contrast-rgb: 255, 255, 255;
--ion-color-secondary-shade: #36abe0;
--ion-color-secondary-tint: #50c8ff;
/** tertiary **/
--ion-color-tertiary: #5260ff;
--ion-color-tertiary-rgb: 82, 96, 255;
--ion-color-tertiary-contrast: #ffffff;
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
--ion-color-tertiary-shade: #4854e0;
--ion-color-tertiary-tint: #6370ff;
/** success **/
--ion-color-success: #2dd36f;
--ion-color-success-rgb: 45, 211, 111;
--ion-color-success-contrast: #ffffff;
--ion-color-success-contrast-rgb: 255, 255, 255;
--ion-color-success-shade: #28ba62;
--ion-color-success-tint: #42d77d;
/** warning **/
--ion-color-warning: #ffc409;
--ion-color-warning-rgb: 255, 196, 9;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0ac08;
--ion-color-warning-tint: #ffca22;
/** danger **/
--ion-color-danger: #eb445a;
--ion-color-danger-rgb: 235, 68, 90;
--ion-color-danger-contrast: #ffffff;
--ion-color-danger-contrast-rgb: 255, 255, 255;
--ion-color-danger-shade: #cf3c4f;
--ion-color-danger-tint: #ed576b;
/** dark **/
--ion-color-dark: #222428;
--ion-color-dark-rgb: 34, 36, 40;
--ion-color-dark-contrast: #ffffff;
--ion-color-dark-contrast-rgb: 255, 255, 255;
--ion-color-dark-shade: #1e2023;
--ion-color-dark-tint: #383a3e;
/** medium **/
--ion-color-medium: #92949c;
--ion-color-medium-rgb: 146, 148, 156;
--ion-color-medium-contrast: #ffffff;
--ion-color-medium-contrast-rgb: 255, 255, 255;
--ion-color-medium-shade: #808289;
--ion-color-medium-tint: #9d9fa6;
/** light **/
--ion-color-light: #f4f5f8;
--ion-color-light-rgb: 244, 245, 248;
--ion-color-light-contrast: #000000;
--ion-color-light-contrast-rgb: 0, 0, 0;
--ion-color-light-shade: #d7d8da;
--ion-color-light-tint: #f5f6f9;
--icon-border-radius: 10px
}
body.dark {
--ion-color-primary: #428cff;
--ion-color-primary: #0075e1;
--ion-color-primary-rgb: 66,140,255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255,255,255;
@@ -122,79 +52,26 @@ body.dark {
--ion-color-danger-shade: #e04055;
--ion-color-danger-tint: #ff5b71;
--ion-color-dark: #f4f5f8;
--ion-color-dark-rgb: 244,245,248;
--ion-color-light: #181818;
--ion-color-light-rgb: 24,24,24;
--ion-color-light-contrast: #ffffff;
--ion-color-light-contrast-rgb: 0,0,0;
--ion-color-light-shade: #000000;
--ion-color-light-tint: #000000;
--ion-color-medium: #222428;
--ion-color-medium-rgb: 34,36,40;
--ion-color-medium-contrast: #ffffff;
--ion-color-medium-contrast-rgb: 255,255,255;
--ion-color-medium-shade: #1e2023;
--ion-color-medium-tint: #383a3e;
--ion-color-dark: #e0e0e0;
--ion-color-dark-rgb: 224,224,224;
--ion-color-dark-contrast: #000000;
--ion-color-dark-contrast-rgb: 0,0,0;
--ion-color-dark-shade: #d7d8da;
--ion-color-dark-tint: #f5f6f9;
--ion-color-medium: #989aa2;
--ion-color-medium-rgb: 152,154,162;
--ion-color-medium-contrast: #000000;
--ion-color-medium-contrast-rgb: 0,0,0;
--ion-color-medium-shade: #86888f;
--ion-color-medium-tint: #a2a4ab;
--ion-color-light: #222428;
--ion-color-light-rgb: 34,36,40;
--ion-color-light-contrast: #ffffff;
--ion-color-light-contrast-rgb: 255,255,255;
--ion-color-light-shade: #1e2023;
--ion-color-light-tint: #383a3e;
}
/*
* iOS Dark Theme
* -------------------------------------------
*/
.ios body.dark {
--ion-background-color: #000000;
--ion-background-color-rgb: 0,0,0;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255,255,255;
--ion-color-step-50: #0d0d0d;
--ion-color-step-100: #1a1a1a;
--ion-color-step-150: #262626;
--ion-color-step-200: #333333;
--ion-color-step-250: #404040;
--ion-color-step-300: #4d4d4d;
--ion-color-step-350: #595959;
--ion-color-step-400: #666666;
--ion-color-step-450: #737373;
--ion-color-step-500: #808080;
--ion-color-step-550: #8c8c8c;
--ion-color-step-600: #999999;
--ion-color-step-650: #a6a6a6;
--ion-color-step-700: #b3b3b3;
--ion-color-step-750: #bfbfbf;
--ion-color-step-800: #cccccc;
--ion-color-step-850: #d9d9d9;
--ion-color-step-900: #e6e6e6;
--ion-color-step-950: #f2f2f2;
--ion-item-background: #1e1e1e;
--ion-toolbar-background: #1f1f1f;
}
/*
* Material Design Dark Theme
* -------------------------------------------
*/
.md body.dark {
--ion-background-color: #121212;
--ion-background-color-rgb: 18,18,18;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255,255,255;
--ion-border-color: #222222;
--ion-color-dark-shade: #bfbfbf;
--ion-color-dark-tint: #d8d8d8;
--ion-color-step-50: #1e1e1e;
--ion-color-step-100: #2a2a2a;
@@ -215,10 +92,6 @@ body.dark {
--ion-color-step-850: #dbdbdb;
--ion-color-step-900: #e7e7e7;
--ion-color-step-950: #f3f3f3;
--ion-item-background: #1e1e1e;
--ion-toolbar-background: #1f1f1f;
}
@font-face {