mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
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:
committed by
Aiden McClelland
parent
4c294566d7
commit
a43ff976a2
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -206,7 +206,6 @@ export class AppConfigPage {
|
||||
},
|
||||
{
|
||||
text: `Leave`,
|
||||
cssClass: 'alert-danger',
|
||||
handler: () => {
|
||||
this.navCtrl.back()
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<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>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding-top">
|
||||
|
||||
<text-spinner *ngIf="loading; else loaded" text="Loading Drives"></text-spinner>
|
||||
|
||||
<ng-template #loaded>
|
||||
<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>
|
||||
<h2>
|
||||
Restoring from backup will overwrite all current data for {{ patch.data['package-data'][pkgId].manifest.title }} .
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider>Select Backup Drive</ion-item-divider>
|
||||
|
||||
<ion-item *ngIf="allPartitionsMounted">
|
||||
<ion-text class="ion-text-wrap" color="warning">No partitions available. Insert the storage device containing the backup you intend to restore.</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<ion-card *ngFor="let disk of disks | keyvalue">
|
||||
<ion-card-header>
|
||||
<ion-card-title>
|
||||
{{ disk.value.size }}
|
||||
</ion-card-title>
|
||||
<ion-card-subtitle>
|
||||
{{ disk.key }}
|
||||
</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-item-group>
|
||||
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key)">
|
||||
<ion-icon slot="start" name="save-outline"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ partition.value.label || partition.key }} ({{ partition.value.size || 'unknown size' }})</h2>
|
||||
<p *ngIf="!partition.value['is-mounted']; else unavailable"><ion-text color="success">Available</ion-text></p>
|
||||
<ng-template #unavailable>
|
||||
<p><ion-text color="danger">Unavailable</ion-text></p>
|
||||
</ng-template>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ng-template>
|
||||
</ion-content>
|
||||
@@ -1,98 +0,0 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { IonContent, 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'],
|
||||
})
|
||||
export class AppRestorePage {
|
||||
disks: DiskInfo
|
||||
pkgId: string
|
||||
title: string
|
||||
loading = true
|
||||
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,
|
||||
private readonly errToast: ErrorToastService,
|
||||
public readonly patch: PatchDbService,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||
this.getExternalDisks()
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
async refresh () {
|
||||
this.loading = true
|
||||
await this.getExternalDisks()
|
||||
}
|
||||
|
||||
async getExternalDisks (): Promise<void> {
|
||||
try {
|
||||
this.disks = await this.embassyApi.getDisks({ })
|
||||
this.allPartitionsMounted = Object.values(this.disks).every(d => Object.values(d.partitions).every(p => p['is-mounted']))
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async presentModal (logicalname: string): Promise<void> {
|
||||
const m = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
type: 'restore',
|
||||
},
|
||||
cssClass: 'alertlike-modal',
|
||||
component: BackupConfirmationComponent,
|
||||
backdropDismiss: false,
|
||||
})
|
||||
|
||||
m.onWillDismiss().then(res => {
|
||||
const data = res.data
|
||||
if (data.cancel) return
|
||||
this.restore(logicalname, data.password)
|
||||
})
|
||||
|
||||
await m.present()
|
||||
}
|
||||
|
||||
private async restore (logicalname: string, password: string): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.restorePackage({
|
||||
id: this.pkgId,
|
||||
logicalname,
|
||||
password,
|
||||
})
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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: [],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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%);
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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),
|
||||
|
||||
@@ -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 { }
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -57,7 +57,6 @@ export class SSHKeysPage {
|
||||
},
|
||||
{
|
||||
text: 'Delete',
|
||||
cssClass: 'alert-danger',
|
||||
handler: () => {
|
||||
this.delete(hash)
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.metric-note {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}[]
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -29,7 +29,6 @@ export class WifiListPage {
|
||||
const buttons: ActionSheetButton[] = [
|
||||
{
|
||||
text: 'Forget',
|
||||
cssClass: 'alert-danger',
|
||||
handler: () => {
|
||||
this.delete(ssid)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user