mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +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
@@ -1,34 +1,48 @@
|
|||||||
<ion-app>
|
<ion-app>
|
||||||
<ion-split-pane [disabled]="!showMenu" (ionSplitPaneVisible)="splitPaneVisible($event)" contentId="main-content">
|
<ion-split-pane [disabled]="!showMenu" (ionSplitPaneVisible)="splitPaneVisible($event)" contentId="main-content">
|
||||||
<ion-menu contentId="main-content" type="overlay">
|
<ion-menu contentId="main-content" type="overlay">
|
||||||
<ion-content scrollY="false" class="menu-style">
|
<ion-content color="light" scrollY="false">
|
||||||
<ion-list style="padding: 0px">
|
<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-menu-toggle auto-hide="false" *ngFor="let page of appPages; let i = index">
|
||||||
<ion-item
|
<ion-item
|
||||||
|
style="padding-left: 10px;"
|
||||||
|
color="transparent"
|
||||||
button
|
button
|
||||||
(click)="selectedIndex = i"
|
(click)="selectedIndex = i"
|
||||||
routerDirection="root"
|
routerDirection="root"
|
||||||
[routerLink]="[page.url]"
|
[routerLink]="[page.url]"
|
||||||
lines="none"
|
lines="none"
|
||||||
detail="false"
|
detail="false"
|
||||||
[class.selected]="selectedIndex === i"
|
|
||||||
>
|
>
|
||||||
<ion-icon slot="start" [name]="page.icon"></ion-icon>
|
<ion-icon slot="start" [name]="page.icon" [class]="selectedIndex === i ? 'bold' : 'dim'"></ion-icon>
|
||||||
<ion-label style="font-family: 'Montserrat';">{{ page.title }}</ion-label>
|
<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-badge *ngIf="page.url === '/notifications' && unreadCount" color="danger" style="margin-right: 3%;" [class.selected-badge]="selectedIndex == i">{{ unreadCount }}</ion-badge>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-menu-toggle>
|
</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-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-menu>
|
||||||
<ion-router-outlet id="main-content"></ion-router-outlet>
|
<ion-router-outlet id="main-content"></ion-router-outlet>
|
||||||
</ion-split-pane>
|
</ion-split-pane>
|
||||||
@@ -80,6 +94,8 @@
|
|||||||
<ion-icon name="newspaper-outline"></ion-icon>
|
<ion-icon name="newspaper-outline"></ion-icon>
|
||||||
<ion-icon name="notifications-outline"></ion-icon>
|
<ion-icon name="notifications-outline"></ion-icon>
|
||||||
<ion-icon name="rocket-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="power"></ion-icon>
|
||||||
<ion-icon name="pulse"></ion-icon>
|
<ion-icon name="pulse"></ion-icon>
|
||||||
<ion-icon name="qr-code-outline"></ion-icon>
|
<ion-icon name="qr-code-outline"></ion-icon>
|
||||||
@@ -89,10 +105,8 @@
|
|||||||
<ion-icon name="remove-outline"></ion-icon>
|
<ion-icon name="remove-outline"></ion-icon>
|
||||||
<ion-icon name="save-outline"></ion-icon>
|
<ion-icon name="save-outline"></ion-icon>
|
||||||
<ion-icon name="shield-checkmark-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="storefront-outline"></ion-icon>
|
||||||
<ion-icon name="terminal-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="trash-outline"></ion-icon>
|
||||||
<ion-icon name="warning-outline"></ion-icon>
|
<ion-icon name="warning-outline"></ion-icon>
|
||||||
<ion-icon name="wifi"></ion-icon>
|
<ion-icon name="wifi"></ion-icon>
|
||||||
@@ -100,7 +114,6 @@
|
|||||||
<!-- Ionic components -->
|
<!-- Ionic components -->
|
||||||
<ion-action-sheet></ion-action-sheet>
|
<ion-action-sheet></ion-action-sheet>
|
||||||
<ion-alert></ion-alert>
|
<ion-alert></ion-alert>
|
||||||
<ion-avatar></ion-avatar>
|
|
||||||
<ion-badge></ion-badge>
|
<ion-badge></ion-badge>
|
||||||
<ion-button></ion-button>
|
<ion-button></ion-button>
|
||||||
<ion-buttons></ion-buttons>
|
<ion-buttons></ion-buttons>
|
||||||
@@ -137,11 +150,9 @@
|
|||||||
<ion-select></ion-select>
|
<ion-select></ion-select>
|
||||||
<ion-select-option></ion-select-option>
|
<ion-select-option></ion-select-option>
|
||||||
<ion-slides></ion-slides>
|
<ion-slides></ion-slides>
|
||||||
<ion-spinner name="dots"></ion-spinner>
|
|
||||||
<ion-spinner name="lines"></ion-spinner>
|
<ion-spinner name="lines"></ion-spinner>
|
||||||
<ion-text></ion-text>
|
<ion-text></ion-text>
|
||||||
<ion-text style="font-weight: bold">load bold</ion-text>
|
<ion-text style="font-weight: bold">load bold</ion-text>
|
||||||
<ion-textarea></ion-textarea>
|
|
||||||
<ion-title></ion-title>
|
<ion-title></ion-title>
|
||||||
<ion-toast></ion-toast>
|
<ion-toast></ion-toast>
|
||||||
<ion-toggle></ion-toggle>
|
<ion-toggle></ion-toggle>
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
.selected {
|
.bold {
|
||||||
--background: linear-gradient(120deg, #1e1e1e -1%, var(--ion-color-danger) 100%);
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-style {
|
.dim {
|
||||||
border-style: solid;
|
color: var(--ion-color-dark-shade);
|
||||||
border-width: 0px 1px 0px 0px;
|
|
||||||
border-color: var(--ion-color-danger);
|
|
||||||
ion-item::part(native) {
|
|
||||||
height: 56px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-badge {
|
|
||||||
background-color: #1e1e1e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-split-pane {
|
ion-split-pane {
|
||||||
|
|||||||
@@ -71,9 +71,7 @@ export class AppComponent {
|
|||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
readonly splitPane: SplitPaneTracker,
|
readonly splitPane: SplitPaneTracker,
|
||||||
) {
|
) {
|
||||||
// set dark theme
|
this.init()
|
||||||
document.body.classList.toggle('dark', true)
|
|
||||||
this.init()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init () {
|
async init () {
|
||||||
@@ -266,7 +264,6 @@ export class AppComponent {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Logout',
|
text: 'Logout',
|
||||||
cssClass: 'alert-danger',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.logout()
|
this.logout()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,12 +26,12 @@
|
|||||||
<ion-text color="warning">Will Stop</ion-text>
|
<ion-text color="warning">Will Stop</ion-text>
|
||||||
</div>
|
</div>
|
||||||
<ion-item
|
<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"
|
*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" />
|
<img [src]="dep.value.iconURL" />
|
||||||
</ion-avatar>
|
</ion-thumbnail>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h5>{{ dep.value.title }}</h5>
|
<h5>{{ dep.value.title }}</h5>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-label class="toolbar-label text-ellipses">
|
<ion-label class="toolbar-label text-ellipses">
|
||||||
<h1 class="toolbar-title">{{ params.toolbar.title }}</h1>
|
<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-label>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
@@ -37,11 +37,11 @@
|
|||||||
<ng-container *ngIf="!initializing && !error">
|
<ng-container *ngIf="!initializing && !error">
|
||||||
|
|
||||||
<!-- cancel button if loading/not loading -->
|
<!-- 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-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-icon *ngIf="!cancel.text" name="close-outline"></ion-icon>
|
||||||
</ion-button>
|
</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-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-icon *ngIf="!cancel.text" name="close-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<div class="organizer">
|
<div class="organizer">
|
||||||
<ion-label class="ion-text-wrap">
|
<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-text class="new-tag" *ngIf="anno | annotationStatus: 'Added'">(new)</ion-text>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
ion-note {
|
ion-note {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { PkgStatusRendering } from 'src/app/services/pkg-status-rendering.servic
|
|||||||
})
|
})
|
||||||
export class StatusComponent {
|
export class StatusComponent {
|
||||||
@Input() rendering: PkgStatusRendering
|
@Input() rendering: PkgStatusRendering
|
||||||
@Input() size?: 'small' | 'medium' | 'large' = 'large'
|
@Input() size?: 'small' | 'medium' | 'large' | 'x-large' = 'large'
|
||||||
@Input() style?: string = 'regular'
|
@Input() style?: string = 'regular'
|
||||||
@Input() weight?: string = 'normal'
|
@Input() weight?: string = 'normal'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-button (click)="dismiss()">
|
<ion-button (click)="dismiss()" color="light">
|
||||||
<ion-icon name="close-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>{{ action.name }}</ion-title>
|
<ion-title>{{ action.name }}</ion-title>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
<ion-label>{{ valueString[i] }}</ion-label>
|
<ion-label>{{ valueString[i] }}</ion-label>
|
||||||
|
|
||||||
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(i, $event)">
|
<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-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ export class AppConfigListPage extends ModalPresentable {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Delete',
|
text: 'Delete',
|
||||||
cssClass: 'alert-danger',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
if (typeof key === 'number') {
|
if (typeof key === 'number') {
|
||||||
(this.value as any[]).splice(key, 1)
|
(this.value as any[]).splice(key, 1)
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export class AppConfigObjectPage {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Delete',
|
text: 'Delete',
|
||||||
cssClass: 'alert-danger',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.dismiss(true)
|
this.dismiss(true)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<ion-button *ngIf="spec.masked" fill="clear" [color]="unmasked ? 'danger' : 'primary'" (click)="toggleMask()">
|
<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-icon slot="icon-only" [name]="unmasked ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
|
||||||
</ion-button>
|
</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-icon slot="icon-only" name="close" size="small"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
<!-- number -->
|
<!-- number -->
|
||||||
<ion-item *ngIf="spec.type === 'number'">
|
<ion-item *ngIf="spec.type === 'number'">
|
||||||
<ion-input type="tel" placeholder="Enter value" [(ngModel)]="value" (ngModelChange)="handleInput()"></ion-input>
|
<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>
|
<span slot="end" *ngIf="spec.units"><ion-text>{{ spec.units }}</ion-text></span>
|
||||||
<ion-button *ngIf="value && spec.nullable" slot="end" fill="clear" color="medium" (click)="clear()">
|
<ion-button *ngIf="value && spec.nullable" slot="end" fill="clear" (click)="clear()">
|
||||||
<ion-icon slot="icon-only" name="close" size="small"></ion-icon>
|
<ion-icon slot="icon-only" name="close" size="small"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
@@ -63,20 +63,18 @@
|
|||||||
<!-- metadata -->
|
<!-- metadata -->
|
||||||
<div class="ion-padding-start">
|
<div class="ion-padding-start">
|
||||||
<p *ngIf="spec.type === 'string' && spec.patternDescription">
|
<p *ngIf="spec.type === 'string' && spec.patternDescription">
|
||||||
<ion-text color="medium">{{ spec.patternDescription }}</ion-text>
|
{{ spec.patternDescription }}
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="spec.type === 'number' && spec.integral">
|
<p *ngIf="spec.type === 'number' && spec.integral">
|
||||||
<ion-text color="medium">{{ integralDescription }}</ion-text>
|
{{ integralDescription }}
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="rangeDescription">
|
<p *ngIf="rangeDescription">
|
||||||
<ion-text color="medium">{{ rangeDescription }}</ion-text>
|
{{ rangeDescription }}
|
||||||
</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>
|
|
||||||
</p>
|
</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>
|
</div>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,6 @@ export class AppConfigValuePage {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: `Leave`,
|
text: `Leave`,
|
||||||
cssClass: 'alert-danger',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.modalCtrl.dismiss()
|
this.modalCtrl.dismiss()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<pwa-back-button></pwa-back-button>
|
<ion-button (click)="dismiss()">
|
||||||
</ion-buttons>
|
<ion-icon name="arrow-back"></ion-icon>
|
||||||
<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-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
Restore From Backup
|
||||||
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<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-item class="ion-margin-bottom">
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<p class="ion-padding-bottom"><ion-text color="warning">Warning</ion-text></p>
|
<p class="ion-padding-bottom"><ion-text color="warning">Warning</ion-text></p>
|
||||||
@@ -56,5 +55,5 @@
|
|||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</ion-card-content>
|
</ion-card-content>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ng-template>
|
</ion-content>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -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 { }
|
||||||
@@ -1,31 +1,28 @@
|
|||||||
import { Component, ViewChild } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { IonContent, LoadingController, ModalController } from '@ionic/angular'
|
import { LoadingController, ModalController } from '@ionic/angular'
|
||||||
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
|
||||||
import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/backup-confirmation.component'
|
import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/backup-confirmation.component'
|
||||||
import { DiskInfo } from 'src/app/services/api/api.types'
|
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 { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { take } from 'rxjs/operators'
|
|
||||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-restore',
|
selector: 'app-restore',
|
||||||
templateUrl: './app-restore.page.html',
|
templateUrl: './app-restore.component.html',
|
||||||
styleUrls: ['./app-restore.page.scss'],
|
styleUrls: ['./app-restore.component.scss'],
|
||||||
})
|
})
|
||||||
export class AppRestorePage {
|
export class AppRestoreComponent {
|
||||||
|
@Input() pkgId: string
|
||||||
disks: DiskInfo
|
disks: DiskInfo
|
||||||
pkgId: string
|
|
||||||
title: string
|
title: string
|
||||||
loading = true
|
loading = true
|
||||||
|
submitting = false
|
||||||
allPartitionsMounted: boolean
|
allPartitionsMounted: boolean
|
||||||
|
|
||||||
@ViewChild(IonContent) content: IonContent
|
|
||||||
subs: Subscription[] = []
|
subs: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly route: ActivatedRoute,
|
|
||||||
private readonly modalCtrl: ModalController,
|
private readonly modalCtrl: ModalController,
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
private readonly loadingCtrl: LoadingController,
|
private readonly loadingCtrl: LoadingController,
|
||||||
@@ -34,13 +31,13 @@ export class AppRestorePage {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
console.log('initing')
|
||||||
this.getExternalDisks()
|
this.getExternalDisks()
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit () {
|
// ngAfterViewInit () {
|
||||||
this.content.scrollToPoint(undefined, 1)
|
// this.content.scrollToPoint(undefined, 1)
|
||||||
}
|
// }
|
||||||
|
|
||||||
async refresh () {
|
async refresh () {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
@@ -54,6 +51,7 @@ export class AppRestorePage {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
} finally {
|
} finally {
|
||||||
|
console.log('loading false')
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,11 +75,14 @@ export class AppRestorePage {
|
|||||||
await m.present()
|
await m.present()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dismiss () {
|
||||||
|
this.modalCtrl.dismiss({ })
|
||||||
|
}
|
||||||
|
|
||||||
private async restore (logicalname: string, password: string): Promise<void> {
|
private async restore (logicalname: string, password: string): Promise<void> {
|
||||||
const loader = await this.loadingCtrl.create({
|
console.log('here here here')
|
||||||
spinner: 'lines',
|
this.submitting = true
|
||||||
})
|
// await loader.present()
|
||||||
await loader.present()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.embassyApi.restorePackage({
|
await this.embassyApi.restorePackage({
|
||||||
@@ -90,9 +91,9 @@ export class AppRestorePage {
|
|||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errToast.present(e)
|
this.modalCtrl.dismiss({ error: e })
|
||||||
} finally {
|
} finally {
|
||||||
loader.dismiss()
|
this.modalCtrl.dismiss({ })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
<div style="height: 85%; margin: 24px; display: flex; flex-direction: column; justify-content: space-between;">
|
<div style="height: 85%; margin: 24px; display: flex; flex-direction: column; justify-content: space-between;">
|
||||||
<div *ngIf="type === 'backup'">
|
<div *ngIf="type === 'backup'">
|
||||||
<h4><ion-text color="dark">Encrypt Backup</ion-text></h4>
|
<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>
|
||||||
<div *ngIf="type === 'restore'">
|
<div *ngIf="type === 'restore'">
|
||||||
<h4><ion-text color="dark">Decrypt Backup</ion-text></h4>
|
<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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -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 { QRComponentModule } from 'src/app/components/qr/qr.component.module'
|
||||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
import { AppActionInputPageModule } from 'src/app/modals/app-action-input/app-action-input.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 = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,7 @@ const routes: Routes = [
|
|||||||
QRComponentModule,
|
QRComponentModule,
|
||||||
SharingModule,
|
SharingModule,
|
||||||
AppActionInputPageModule,
|
AppActionInputPageModule,
|
||||||
|
AppRestoreComponentModule,
|
||||||
],
|
],
|
||||||
declarations: [AppActionsPage],
|
declarations: [AppActionsPage],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,9 +7,57 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding-top">
|
||||||
<ng-container *ngIf="patch.data['package-data'][pkgId] as pkg">
|
<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-item button *ngFor="let action of pkg.manifest.actions | keyvalue: asIsOrder" (click)="handleAction(pkg, action)" >
|
||||||
<ion-label class="ion-text-wrap">
|
<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>
|
<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>
|
<p><ion-text color="dark">This will uninstall the service from your Embassy and delete all data permanently.</ion-text></p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group> -->
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -10,6 +10,7 @@ import { Subscription } from 'rxjs'
|
|||||||
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
||||||
import { AppActionInputPage } from 'src/app/modals/app-action-input/app-action-input.page'
|
import { AppActionInputPage } from 'src/app/modals/app-action-input/app-action-input.page'
|
||||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||||
|
import { AppRestoreComponent } from 'src/app/modals/app-restore/app-restore.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-actions',
|
selector: 'app-actions',
|
||||||
@@ -79,7 +80,7 @@ export class AppActionsPage {
|
|||||||
await alert.present()
|
await alert.present()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const statuses = [...action.value['allowedStatuses']]
|
const statuses = [...action.value['allowed-statuses']]
|
||||||
const last = statuses.pop()
|
const last = statuses.pop()
|
||||||
let statusesStr = statuses.join(', ')
|
let statusesStr = statuses.join(', ')
|
||||||
let error = null
|
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) {
|
async uninstall (manifest: Manifest) {
|
||||||
const { id, title, version, alerts } = manifest
|
const { id, title, version, alerts } = manifest
|
||||||
const data = await wizardModal(
|
const data = await wizardModal(
|
||||||
@@ -134,7 +152,6 @@ export class AppActionsPage {
|
|||||||
header: 'Execution Complete',
|
header: 'Execution Complete',
|
||||||
message: res.message.split('\n').join('</br ></br />'),
|
message: res.message.split('\n').join('</br ></br />'),
|
||||||
buttons: ['OK'],
|
buttons: ['OK'],
|
||||||
cssClass: 'alert-success-message',
|
|
||||||
})
|
})
|
||||||
await successAlert.present()
|
await successAlert.present()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -52,9 +52,9 @@
|
|||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<h2 style="display: flex; align-items: center;">
|
<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-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"/>
|
<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>
|
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: smaller;">{{ rec.dependentTitle }}</ion-text>
|
||||||
</h2>
|
</h2>
|
||||||
<div style="margin: 7px 5px;">
|
<div style="margin: 7px 5px;">
|
||||||
|
|||||||
@@ -206,7 +206,6 @@ export class AppConfigPage {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: `Leave`,
|
text: `Leave`,
|
||||||
cssClass: 'alert-danger',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.navCtrl.back()
|
this.navCtrl.back()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,43 +7,37 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content *ngIf="patch.data['package-data'][pkgId] as pkg">
|
||||||
<ion-grid *ngIf="patch.data['package-data'][pkgId] as pkg">
|
<ion-card *ngFor="let interface of pkg.manifest.interfaces | keyvalue: asIsOrder">
|
||||||
<ion-row>
|
<ion-card-header>
|
||||||
<ion-col *ngFor="let interface of pkg.manifest.interfaces | keyvalue: asIsOrder" sizeXs="12" sizeSm="12" sizeMd="6">
|
<ion-card-title>{{ interface.value.name }}</ion-card-title>
|
||||||
<ion-card>
|
<ion-card-subtitle>{{ interface.value.description }}</ion-card-subtitle>
|
||||||
<ion-card-header>
|
<ion-button style="margin-top: 12px;" *ngIf="interface.value.ui" [disabled]="!(pkg | isLaunchable)" fill="outline" color="dark" expand="block" (click)="launch(pkg)">
|
||||||
<ion-card-title>{{ interface.value.name }}</ion-card-title>
|
Launch
|
||||||
<ion-card-subtitle>{{ interface.value.description }}</ion-card-subtitle>
|
<ion-icon slot="end" name="rocket-outline"></ion-icon>
|
||||||
<ion-button style="margin-top: 12px;" *ngIf="interface.value.ui" [disabled]="!(pkg | isLaunchable)" fill="outline" color="dark" expand="block" (click)="launch(pkg)">
|
</ion-button>
|
||||||
Launch
|
</ion-card-header>
|
||||||
<ion-icon slot="end" name="rocket-outline"></ion-icon>
|
<ion-card-content>
|
||||||
</ion-button>
|
<ng-container *ngIf="pkg.installed['interface-info'].addresses[interface.key] as int">
|
||||||
</ion-card-header>
|
<ion-item>
|
||||||
<ion-card-content>
|
<ion-label class="ion-text-wrap">
|
||||||
<ng-container *ngIf="pkg.installed['interface-info'].addresses[interface.key] as int">
|
<h2>Tor Address</h2>
|
||||||
<ion-item>
|
<p>{{ 'http://' + int['tor-address'] }}</p>
|
||||||
<ion-label class="ion-text-wrap">
|
</ion-label>
|
||||||
<h2>Tor Address</h2>
|
<ion-button slot="end" fill="clear" (click)="copy('http://' + int['tor-address'])">
|
||||||
<p>{{ 'http://' + int['tor-address'] }}</p>
|
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
</ion-label>
|
</ion-button>
|
||||||
<ion-button slot="end" fill="clear" (click)="copy('http://' + int['tor-address'])">
|
</ion-item>
|
||||||
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
|
<ion-item>
|
||||||
</ion-button>
|
<ion-label class="ion-text-wrap">
|
||||||
</ion-item>
|
<h2>LAN Address</h2>
|
||||||
<ion-item>
|
<p>{{ 'https://' + int['lan-address'] }}</p>
|
||||||
<ion-label class="ion-text-wrap">
|
</ion-label>
|
||||||
<h2>LAN Address</h2>
|
<ion-button slot="end" fill="clear" (click)="copy('https://' + int['lan-address'])">
|
||||||
<p>{{ 'https://' + int['lan-address'] }}</p>
|
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
</ion-label>
|
</ion-button>
|
||||||
<ion-button slot="end" fill="clear" (click)="copy('https://' + int['lan-address'])">
|
</ion-item>
|
||||||
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
|
</ng-container>
|
||||||
</ion-button>
|
</ion-card-content>
|
||||||
</ion-item>
|
</ion-card>
|
||||||
</ng-container>
|
|
||||||
</ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-grid>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -10,10 +10,10 @@
|
|||||||
<ion-content style="position: relative">
|
<ion-content style="position: relative">
|
||||||
<div *ngIf="pkgs | empty; else list" class="ion-text-center ion-padding">
|
<div *ngIf="pkgs | empty; else list" class="ion-text-center ion-padding">
|
||||||
<div style="display: flex; flex-direction: column; justify-content: center; height: 40vh">
|
<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>
|
<p class="ion-text-wrap">Get started by installing your first service.</p>
|
||||||
</div>
|
</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>
|
<ion-icon slot="start" name="storefront-outline"></ion-icon>
|
||||||
Marketplace
|
Marketplace
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
.main-img {
|
.main-img {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
border-radius: var(--icon-border-radius);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bulb-on {
|
.bulb-on {
|
||||||
@@ -70,7 +69,7 @@
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
ion-icon {
|
ion-icon {
|
||||||
color: var(--ion-color-medium);
|
color: var(--ion-color-dark-shade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding" color="light">
|
<ion-content class="ion-padding">
|
||||||
|
|
||||||
<text-spinner *ngIf="!logs" text="Loading Logs"></text-spinner>
|
<text-spinner *ngIf="!logs" text="Loading Logs"></text-spinner>
|
||||||
|
|
||||||
|
|||||||
@@ -3,62 +3,19 @@
|
|||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Health</ion-title>
|
<ion-title>Monitor</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content class="ion-padding">
|
||||||
|
|
||||||
<ion-card *ngIf="mainStatus">
|
<skeleton-list *ngIf="loading" rows="3"></skeleton-list>
|
||||||
<ion-card-header>
|
<ion-item-group *ngIf="!loading">
|
||||||
<ion-card-title>
|
<ion-item *ngFor="let metric of metrics | keyvalue : asIsOrder">
|
||||||
<ion-icon style="vertical-align: middle; padding-right: 12px;" name="medkit-outline"></ion-icon>
|
<ion-label>{{ metric.key }}</ion-label>
|
||||||
<span style="vertical-align: middle;">Health Checks</span>
|
<ion-note *ngIf="metric.value" slot="end" class="metric-note">
|
||||||
</ion-card-title>
|
<ion-text style="color: white;">{{ metric.value.value }} {{ metric.value.unit }}</ion-text>
|
||||||
</ion-card-header>
|
</ion-note>
|
||||||
<ion-card-content>
|
</ion-item>
|
||||||
<ion-item *ngIf="mainStatus.health | empty; else health">
|
</ion-item-group>
|
||||||
<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>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { AppShowPage } from './app-show.page'
|
|||||||
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
||||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.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'
|
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -24,7 +23,6 @@ const routes: Routes = [
|
|||||||
IonicModule,
|
IonicModule,
|
||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
PwaBackComponentModule,
|
PwaBackComponentModule,
|
||||||
BadgeMenuComponentModule,
|
|
||||||
InstallWizardComponentModule,
|
InstallWizardComponentModule,
|
||||||
],
|
],
|
||||||
declarations: [AppShowPage],
|
declarations: [AppShowPage],
|
||||||
|
|||||||
@@ -3,53 +3,118 @@
|
|||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Service Details</ion-title>
|
<ion-item lines="none">
|
||||||
<ion-buttons slot="end">
|
<ion-avatar slot="start">
|
||||||
<badge-menu-button></badge-menu-button>
|
<img [src]="pkg['static-files'].icon" />
|
||||||
</ion-buttons>
|
</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-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content>
|
||||||
<ng-container *ngIf="pkg">
|
<ng-container *ngIf="pkg">
|
||||||
<!-- top plate -->
|
<ion-item-group>
|
||||||
<div class="top-plate">
|
<!-- ** always ** -->
|
||||||
<ion-item lines="none">
|
<ion-item-divider>Status</ion-item-divider>
|
||||||
<ion-thumbnail slot="start">
|
<ion-item>
|
||||||
<img [src]="pkg['static-files'].icon" />
|
<ion-label style="overflow: visible;">
|
||||||
</ion-thumbnail>
|
<status size="x-large" weight="500" [rendering]="rendering"></status>
|
||||||
<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-label>
|
</ion-label>
|
||||||
</ion-item>
|
<ion-button slot="end" class="action-button" *ngIf="pkg.state === PackageState.Installed && (pkg | hasUi)" [disabled]="!(pkg | isLaunchable)" (click)="launchUiTab()">
|
||||||
|
Web
|
||||||
<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-icon slot="end" name="rocket-outline"></ion-icon>
|
<ion-icon slot="end" name="rocket-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ng-container>
|
<ion-button slot="end" class="action-button" *ngIf="rendering.feStatus === FeStatus.NeedsConfig" [routerLink]="['config']">
|
||||||
</div>
|
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;">
|
<div *ngIf="[PackageState.Installing, PackageState.Updating] | includes : pkg.state" style="padding: 16px;">
|
||||||
<p>Downloading: {{ (pkg['install-progress'] | installState).downloadProgress }}%</p>
|
<p>Downloading: {{ (pkg['install-progress'] | installState).downloadProgress }}%</p>
|
||||||
<ion-progress-bar
|
<ion-progress-bar
|
||||||
@@ -69,72 +134,5 @@
|
|||||||
[value]="(pkg['install-progress'] | installState).unpackProgress / 100"
|
[value]="(pkg['install-progress'] | installState).unpackProgress / 100"
|
||||||
></ion-progress-bar>
|
></ion-progress-bar>
|
||||||
</div>
|
</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>
|
</ng-container>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -1,45 +1,14 @@
|
|||||||
.less-large {
|
.less-large {
|
||||||
font-size: 20px !important;
|
font-size: 18px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-plate {
|
.action-button {
|
||||||
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);
|
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-style: solid;
|
min-height: 36px;
|
||||||
border-width: 1px;
|
min-width: 72px;
|
||||||
border-color: #404040;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.launch-button {
|
.icon-spinner {
|
||||||
--background: rgb(70 193 255 / 75%);
|
height: 20px;
|
||||||
--background-hover: rgb(70 193 255);
|
width: 20px;
|
||||||
--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;
|
|
||||||
}
|
|
||||||
@@ -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 { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.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 { FEStatus, PkgStatusRendering, renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||||
@@ -29,6 +29,8 @@ export class AppShowPage {
|
|||||||
DependencyErrorType = DependencyErrorType
|
DependencyErrorType = DependencyErrorType
|
||||||
rendering: PkgStatusRendering
|
rendering: PkgStatusRendering
|
||||||
Math = Math
|
Math = Math
|
||||||
|
mainStatus: MainStatus
|
||||||
|
|
||||||
|
|
||||||
@ViewChild(IonContent) content: IonContent
|
@ViewChild(IonContent) content: IonContent
|
||||||
subs: Subscription[] = []
|
subs: Subscription[] = []
|
||||||
@@ -59,6 +61,11 @@ export class AppShowPage {
|
|||||||
this.connected = connected
|
this.connected = connected
|
||||||
this.rendering = renderPkgStatus(pkg.state, pkg.installed.status)
|
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()
|
this.setButtons()
|
||||||
}
|
}
|
||||||
@@ -236,17 +243,9 @@ export class AppShowPage {
|
|||||||
color: 'danger',
|
color: 'danger',
|
||||||
disabled: [],
|
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 }),
|
action: () => this.navCtrl.navigateForward(['config'], { relativeTo: this.route }),
|
||||||
title: 'Configure',
|
title: 'Settings',
|
||||||
icon: 'construct-outline',
|
icon: 'construct-outline',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
disabled: [FEStatus.Installing, FEStatus.Updating, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
|
disabled: [FEStatus.Installing, FEStatus.Updating, FEStatus.Removing, FEStatus.BackingUp, FEStatus.Restoring],
|
||||||
@@ -272,6 +271,14 @@ export class AppShowPage {
|
|||||||
color: 'danger',
|
color: 'danger',
|
||||||
disabled: [],
|
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 }),
|
action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
|
||||||
title: 'Logs',
|
title: 'Logs',
|
||||||
@@ -279,13 +286,6 @@ export class AppShowPage {
|
|||||||
color: 'danger',
|
color: 'danger',
|
||||||
disabled: [],
|
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 }),
|
action: () => this.navCtrl.navigateForward(['manifest'], { relativeTo: this.route }),
|
||||||
title: 'Package Details',
|
title: 'Package Details',
|
||||||
@@ -295,18 +295,11 @@ export class AppShowPage {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: () => this.donate(),
|
action: () => this.donate(),
|
||||||
title: 'Support Project',
|
title: 'Donate',
|
||||||
icon: 'logo-bitcoin',
|
icon: 'logo-bitcoin',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
disabled: [],
|
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',
|
path: ':pkgId/properties',
|
||||||
loadChildren: () => import('./app-properties/app-properties.module').then(m => m.AppPropertiesPageModule),
|
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({
|
@NgModule({
|
||||||
|
|||||||
@@ -1,31 +1,35 @@
|
|||||||
<ion-content class="ion-padding">
|
<ion-content>
|
||||||
<ion-grid style="height: 100%; max-width: 500px;">
|
<ion-grid style="height: 100%; max-width: 540px;">
|
||||||
<ion-row class="ion-align-items-center" style="height: 100%;">
|
<ion-row class="ion-align-items-center" style="height: 100%;">
|
||||||
<ion-col>
|
<ion-col class="ion-text-center">
|
||||||
<ion-card>
|
|
||||||
<div style="padding: 20px;">
|
<div style="padding-bottom: 32px;">
|
||||||
<ion-card-header class="ion-text-center">
|
<img src="assets/img/logo.png" style="max-width: 240px;" />
|
||||||
<img src="assets/img/logo.png" style="max-width: 120px;" />
|
</div>
|
||||||
</ion-card-header>
|
|
||||||
<ion-card-content style="padding-top: 30px;">
|
<ion-card color="dark">
|
||||||
<form (submit)="submit()">
|
<ion-card-header class="ion-text-center" style="padding-bottom: 8px;">
|
||||||
<ion-item-group>
|
<ion-card-title>Log in to Embassy</ion-card-title>
|
||||||
<ion-item color="light">
|
</ion-card-header>
|
||||||
<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-card-content class="ion-margin">
|
||||||
<ion-icon slot="icon-only" [name]="unmasked ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
|
<form (submit)="submit()" style="margin-bottom: 12px;">
|
||||||
</ion-button>
|
<ion-item-group>
|
||||||
</ion-item>
|
<p class="input-label">Password</p>
|
||||||
<ion-item *ngIf="error" lines="none">
|
<ion-item color="dark">
|
||||||
<ion-label class="ion-text-wrap" color="danger">{{ error }}</ion-label>
|
<ion-icon slot="start" name="key-outline" style="margin-right: 16px;"></ion-icon>
|
||||||
</ion-item>
|
<ion-input [type]="unmasked ? 'text' : 'password'" name="password" [(ngModel)]="password" (ionChange)="error = ''"></ion-input>
|
||||||
</ion-item-group>
|
<ion-button fill="clear" color="light" (click)="toggleMask()">
|
||||||
<ion-button color="dark" class="sharp-button" type="submit" [disabled]="!password" style="margin-top: 60px" expand="block" fill="outline">
|
<ion-icon slot="icon-only" [name]="unmasked ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
|
||||||
Next
|
</ion-button>
|
||||||
</ion-button>
|
</ion-item>
|
||||||
</form>
|
<p *ngIf="error" style="text-align: left; padding-top: 4px"><ion-text color="danger">{{ error }}</ion-text></p>
|
||||||
</ion-card-content>
|
</ion-item-group>
|
||||||
</div>
|
<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-card>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
|
|||||||
@@ -1,3 +1,28 @@
|
|||||||
.sharp-button {
|
ion-card-title {
|
||||||
--border-radius: 1px;
|
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.error = ''
|
||||||
|
|
||||||
this.loader = await this.loadingCtrl.create({
|
this.loader = await this.loadingCtrl.create({
|
||||||
message: 'Authenticating',
|
message: 'Logging in',
|
||||||
spinner: 'lines',
|
spinner: 'lines',
|
||||||
})
|
})
|
||||||
await this.loader.present()
|
await this.loader.present()
|
||||||
|
|||||||
@@ -11,11 +11,23 @@
|
|||||||
<text-spinner *ngIf="!marketplaceService.releaseNotes[pkgId]; else loaded" text="Loading Release Notes"></text-spinner>
|
<text-spinner *ngIf="!marketplaceService.releaseNotes[pkgId]; else loaded" text="Loading Release Notes"></text-spinner>
|
||||||
|
|
||||||
<ng-template #loaded>
|
<ng-template #loaded>
|
||||||
<div *ngFor="let note of marketplaceService.releaseNotes[pkgId] | keyvalue : asIsOrder">
|
<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;" >
|
<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>
|
<p style="position: absolute; left: 10px;">{{ note.key | displayEmver }}</p>
|
||||||
</ion-button>
|
</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-text id='release-notes' [innerHTML]="note.value | markdown"></ion-text>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
.metric-note {
|
.panel {
|
||||||
font-size: 16px;
|
margin: 0px;
|
||||||
|
padding: 0px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.acc-text {
|
.active {
|
||||||
margin: 0px 0px 10px 0px;
|
border: 5px solid #4d4d4d;
|
||||||
padding: 15px;
|
|
||||||
}
|
}
|
||||||
@@ -37,6 +37,11 @@ export class AppReleaseNotes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDocSize (selected: string) {
|
||||||
|
const element = document.getElementById(selected)
|
||||||
|
return `${element.scrollHeight}px`
|
||||||
|
}
|
||||||
|
|
||||||
asIsOrder (a: any, b: any) {
|
asIsOrder (a: any, b: any) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,59 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar *ngIf="!pageLoading">
|
||||||
<ion-title>Service Marketplace</ion-title>
|
<ion-searchbar color="dark" (ionChange)="search($event)" debounce="400"></ion-searchbar>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
<badge-menu-button></badge-menu-button>
|
<badge-menu-button></badge-menu-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
<ion-toolbar *ngIf="!pageLoading">
|
|
||||||
<ion-searchbar (ionChange)="search($event)" debounce="400"></ion-searchbar>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding-top">
|
<ion-content class="ion-padding">
|
||||||
<text-spinner *ngIf="pageLoading; else pageLoaded" text="Loading Marketplace"></text-spinner>
|
<text-spinner *ngIf="pageLoading; else pageLoaded" text="Loading Marketplace"></text-spinner>
|
||||||
|
|
||||||
<ng-template #pageLoaded>
|
<ng-template #pageLoaded>
|
||||||
<ion-card *ngIf="eos" class="eos-card" (click)="updateEos()">
|
<h1 style="font-family: 'Montserrat'; font-weight: 100px; margin: 0 0 32px 0;" class="ion-text-center">Embassy Marketplace</h1>
|
||||||
<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>
|
|
||||||
|
|
||||||
<h2 class="ion-margin-start">Categories</h2>
|
<div class="scrollable ion-text-center">
|
||||||
|
|
||||||
<div class="scrollable">
|
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngFor="let cat of data.categories"
|
*ngFor="let cat of data.categories"
|
||||||
fill="clear"
|
fill="clear"
|
||||||
[color]="cat === category ? 'primary' : 'dark'"
|
[class]="cat === category ? 'selected' : 'dim'"
|
||||||
[class.cat-selected]="cat === category"
|
|
||||||
(click)="switchCategory(cat)"
|
(click)="switchCategory(cat)"
|
||||||
>
|
>
|
||||||
{{ cat }}
|
{{ cat }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="divider" style="margin: 24px;"></div>
|
||||||
|
|
||||||
<div *ngIf="pkgsLoading; else loaded" style="margin-top: 64px;" class="ion-text-center">
|
<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>
|
</div>
|
||||||
|
|
||||||
<ng-template #loaded>
|
<ng-template #loaded>
|
||||||
<ion-grid>
|
<ion-grid>
|
||||||
<ion-row>
|
<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-col *ngFor="let pkg of pkgs" sizeXs="12" sizeSm="12" sizeMd="6">
|
||||||
<ion-item [routerLink]="['/marketplace', pkg.manifest.id]">
|
<ion-item [routerLink]="['/marketplace', pkg.manifest.id]">
|
||||||
<ion-thumbnail slot="start">
|
<ion-thumbnail slot="start">
|
||||||
<img [src]="pkg.icon" />
|
<img [src]="pkg.icon" />
|
||||||
</ion-thumbnail>
|
</ion-thumbnail>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2 style="font-family: 'Montserrat';">{{ pkg.manifest.title }}</h2>
|
<h2 style="font-family: 'Montserrat'; font-weight: bold;">{{ pkg.manifest.title }}</h2>
|
||||||
<p>{{ pkg.manifest.description.short }}</p>
|
<h3>{{ pkg.manifest.description.short }}</h3>
|
||||||
<ng-container *ngIf="localPkgs[pkg.manifest.id] as localPkg">
|
<ng-container *ngIf="localPkgs[pkg.manifest.id] as localPkg; else none">
|
||||||
<p *ngIf="localPkg.state === PackageState.Installed">
|
<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) === 0" color="success">Installed</ion-text>
|
||||||
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</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>
|
<ion-spinner name="dots" color="warning"></ion-spinner>
|
||||||
</p>
|
</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<ng-template #none>
|
||||||
|
<p>Not Installed</p>
|
||||||
|
</ng-template>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
// background-color: var(--ion-color-light);
|
// background-color: var(--ion-color-light);
|
||||||
height: 80px;
|
height: 60px;
|
||||||
|
|
||||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@@ -14,14 +14,16 @@
|
|||||||
scrollbar-width: none; /* Firefox */
|
scrollbar-width: none; /* Firefox */
|
||||||
}
|
}
|
||||||
|
|
||||||
.eos-card {
|
.eos-item {
|
||||||
--background: linear-gradient(45deg, #101010 16%, var(--ion-color-danger) 150%);
|
--border-style: none;
|
||||||
margin: 0 10px 16px 10px;
|
--background: linear-gradient(45deg, var(--ion-color-dark) -380%, var(--ion-color-medium) 100%)
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cat-selected {
|
.selected {
|
||||||
border-width: 0 0 1px 0;
|
font-weight: bold;
|
||||||
border-style: solid;
|
}
|
||||||
border-color: var(--ion-color-primary);
|
|
||||||
|
.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 { MarketplaceShowPage } from './marketplace-show.page'
|
||||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.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 { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
||||||
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.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'
|
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
|
||||||
@@ -26,7 +25,6 @@ const routes: Routes = [
|
|||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
SharingModule,
|
SharingModule,
|
||||||
PwaBackComponentModule,
|
PwaBackComponentModule,
|
||||||
BadgeMenuComponentModule,
|
|
||||||
InstallWizardComponentModule,
|
InstallWizardComponentModule,
|
||||||
],
|
],
|
||||||
declarations: [MarketplaceShowPage],
|
declarations: [MarketplaceShowPage],
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
<pwa-back-button></pwa-back-button>
|
<pwa-back-button></pwa-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Listing</ion-title>
|
<ion-title>Listing</ion-title>
|
||||||
<ion-buttons slot="end">
|
|
||||||
<badge-menu-button></badge-menu-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
@@ -27,13 +24,14 @@
|
|||||||
<div class="header-status">
|
<div class="header-status">
|
||||||
<!-- no localPkg -->
|
<!-- no localPkg -->
|
||||||
<p *ngIf="!localPkg; else local">
|
<p *ngIf="!localPkg; else local">
|
||||||
<ion-text color="medium">Not Installed</ion-text>
|
Not Installed
|
||||||
</p>
|
</p>
|
||||||
<!-- localPkg -->
|
<!-- localPkg -->
|
||||||
<ng-template #local>
|
<ng-template #local>
|
||||||
<!-- installed -->
|
<!-- installed -->
|
||||||
<p *ngIf="localPkg.state === PackageState.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>
|
</p>
|
||||||
<!-- installing, updating -->
|
<!-- installing, updating -->
|
||||||
<p *ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state">
|
<p *ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state">
|
||||||
@@ -51,17 +49,17 @@
|
|||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
|
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
|
||||||
<!-- no localPkg -->
|
<!-- 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
|
Install
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<!-- localPkg -->
|
<!-- localPkg -->
|
||||||
<ng-template #localPkg2>
|
<ng-template #localPkg2>
|
||||||
<!-- not installing, updating, or removing -->
|
<!-- not installing, updating, or removing -->
|
||||||
<ng-container *ngIf="localPkg.state === PackageState.Installed">
|
<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
|
Update
|
||||||
</ion-button>
|
</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
|
Downgrade
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -74,9 +72,9 @@
|
|||||||
<ion-item *ngIf="rec && showRec" class="rec-item">
|
<ion-item *ngIf="rec && showRec" class="rec-item">
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<h2 style="display: flex; align-items: center;">
|
<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"/>
|
<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>
|
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: smaller;">{{ rec.dependentTitle }}</ion-text>
|
||||||
</h2>
|
</h2>
|
||||||
<div style="margin: 7px 5px;">
|
<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 { 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 { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
|
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -22,6 +23,7 @@ const routes: Routes = [
|
|||||||
PwaBackComponentModule,
|
PwaBackComponentModule,
|
||||||
BadgeMenuComponentModule,
|
BadgeMenuComponentModule,
|
||||||
SharingModule,
|
SharingModule,
|
||||||
|
TextSpinnerComponentModule,
|
||||||
],
|
],
|
||||||
declarations: [NotificationsPage],
|
declarations: [NotificationsPage],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,14 +15,12 @@
|
|||||||
<ion-refresher-content pullingIcon="lines" refreshingSpinner="lines"></ion-refresher-content>
|
<ion-refresher-content pullingIcon="lines" refreshingSpinner="lines"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</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-group *ngIf="!notifications.length && !loading">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<h2>
|
Notifications about Embassy and services will appear here.
|
||||||
<ion-text color="medium">Notifications about Embassy and services will appear here.</ion-text>
|
|
||||||
</h2>
|
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
@@ -47,7 +45,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button slot="end" fill="clear" (click)="remove(not.id, i)">
|
<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-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|||||||
@@ -38,6 +38,9 @@
|
|||||||
<ion-item detail="true" button [routerLink]="['ssh-keys']">
|
<ion-item detail="true" button [routerLink]="['ssh-keys']">
|
||||||
<ion-label>SSH Keys</ion-label>
|
<ion-label>SSH Keys</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<ion-item detail="true" button [routerLink]="['sessions']">
|
||||||
|
<ion-label>Active Sessions</ion-label>
|
||||||
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -6,6 +6,10 @@ const routes: Routes = [
|
|||||||
path: '',
|
path: '',
|
||||||
loadChildren: () => import('./security-options/security-options.module').then(m => m.SecurityOptionsPageModule),
|
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',
|
path: 'ssh-keys',
|
||||||
loadChildren: () => import('./ssh-keys/ssh-keys.module').then(m => m.SSHKeysPageModule),
|
loadChildren: () => import('./ssh-keys/ssh-keys.module').then(m => m.SSHKeysPageModule),
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { IonicModule } from '@ionic/angular'
|
||||||
import { AppRestorePage } from './app-restore.page'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
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 { 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 { SharingModule } from 'src/app/modules/sharing.module'
|
||||||
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
|
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: AppRestorePage,
|
component: SessionsPage,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -19,14 +18,11 @@ const routes: Routes = [
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
IonicModule,
|
IonicModule,
|
||||||
SharingModule,
|
|
||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
BackupConfirmationComponentModule,
|
|
||||||
PwaBackComponentModule,
|
PwaBackComponentModule,
|
||||||
|
SharingModule,
|
||||||
TextSpinnerComponentModule,
|
TextSpinnerComponentModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [SessionsPage],
|
||||||
AppRestorePage,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
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 }}
|
{{ ssh.value.alg }} {{ ssh.key }} {{ ssh.value.hostname }}
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(ssh.key)">
|
<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-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ export class SSHKeysPage {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Delete',
|
text: 'Delete',
|
||||||
cssClass: 'alert-danger',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.delete(hash)
|
this.delete(hash)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</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>
|
<text-spinner *ngIf="loading; else loaded" text="Loading Logs"></text-spinner>
|
||||||
|
|
||||||
<ng-template #loaded>
|
<ng-template #loaded>
|
||||||
|
|||||||
@@ -7,16 +7,14 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content class="ion-padding">
|
||||||
<skeleton-list *ngIf="loading; else loaded" groups="3"></skeleton-list>
|
<skeleton-list *ngIf="loading; else loaded" groups="3"></skeleton-list>
|
||||||
|
|
||||||
<ng-template #loaded>
|
<ng-template #loaded>
|
||||||
<ion-item-group *ngFor="let metricGroup of metrics | keyvalue : asIsOrder">
|
<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-item *ngFor="let metric of metricGroup.value | keyvalue : asIsOrder">
|
||||||
<ion-label>
|
<ion-label>{{ metric.key }}</ion-label>
|
||||||
<ion-text color="medium">{{ metric.key }}</ion-text>
|
|
||||||
</ion-label>
|
|
||||||
<ion-note *ngIf="metric.value" slot="end" class="metric-note">
|
<ion-note *ngIf="metric.value" slot="end" class="metric-note">
|
||||||
<ion-text style="color: white;">{{ metric.value.value }} {{ metric.value.unit }}</ion-text>
|
<ion-text style="color: white;">{{ metric.value.value }} {{ metric.value.unit }}</ion-text>
|
||||||
</ion-note>
|
</ion-note>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
.metric-note {
|
.metric-note {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,11 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content class="ion-padding">
|
||||||
|
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<div *ngFor="let cat of settings | keyvalue : asIsOrder">
|
<div *ngFor="let cat of settings | keyvalue : asIsOrder">
|
||||||
<ion-item-divider>{{ cat.key }}</ion-item-divider>
|
<ion-item-divider><ion-text color="dark">{{ cat.key }}</ion-text></ion-item-divider>
|
||||||
<ion-item style="cursor: pointer;" button *ngFor="let button of cat.value" (click)="button.action()">
|
<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-icon slot="start" [name]="button.icon"></ion-icon>
|
||||||
<ion-label>{{ button.title }}</ion-label>
|
<ion-label>{{ button.title }}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export class ServerShowPage {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Restart',
|
text: 'Restart',
|
||||||
cssClass: 'alert-danger',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.restart()
|
this.restart()
|
||||||
},
|
},
|
||||||
@@ -59,7 +58,6 @@ export class ServerShowPage {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Shutdown',
|
text: 'Shutdown',
|
||||||
cssClass: 'alert-danger',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.shutdown()
|
this.shutdown()
|
||||||
},
|
},
|
||||||
@@ -110,16 +108,19 @@ export class ServerShowPage {
|
|||||||
title: 'Privacy and Security',
|
title: 'Privacy and Security',
|
||||||
icon: 'shield-checkmark-outline',
|
icon: 'shield-checkmark-outline',
|
||||||
action: () => this.navCtrl.navigateForward(['security'], { relativeTo: this.route }),
|
action: () => this.navCtrl.navigateForward(['security'], { relativeTo: this.route }),
|
||||||
|
detail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'LAN',
|
title: 'LAN',
|
||||||
icon: 'home-outline',
|
icon: 'home-outline',
|
||||||
action: () => this.navCtrl.navigateForward(['lan'], { relativeTo: this.route }),
|
action: () => this.navCtrl.navigateForward(['lan'], { relativeTo: this.route }),
|
||||||
|
detail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'WiFi',
|
title: 'WiFi',
|
||||||
icon: 'wifi',
|
icon: 'wifi',
|
||||||
action: () => this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }),
|
action: () => this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }),
|
||||||
|
detail: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'Insights': [
|
'Insights': [
|
||||||
@@ -127,16 +128,19 @@ export class ServerShowPage {
|
|||||||
title: 'About',
|
title: 'About',
|
||||||
icon: 'information-circle-outline',
|
icon: 'information-circle-outline',
|
||||||
action: () => this.navCtrl.navigateForward(['specs'], { relativeTo: this.route }),
|
action: () => this.navCtrl.navigateForward(['specs'], { relativeTo: this.route }),
|
||||||
|
detail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Monitor',
|
title: 'Monitor',
|
||||||
icon: 'pulse',
|
icon: 'pulse',
|
||||||
action: () => this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }),
|
action: () => this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }),
|
||||||
|
detail: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Logs',
|
title: 'Logs',
|
||||||
icon: 'newspaper-outline',
|
icon: 'newspaper-outline',
|
||||||
action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
|
action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
|
||||||
|
detail: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'Backups': [
|
'Backups': [
|
||||||
@@ -144,6 +148,7 @@ export class ServerShowPage {
|
|||||||
title: 'Create Backup',
|
title: 'Create Backup',
|
||||||
icon: 'save-outline',
|
icon: 'save-outline',
|
||||||
action: () => this.navCtrl.navigateForward(['backup'], { relativeTo: this.route }),
|
action: () => this.navCtrl.navigateForward(['backup'], { relativeTo: this.route }),
|
||||||
|
detail: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'Power': [
|
'Power': [
|
||||||
@@ -151,11 +156,13 @@ export class ServerShowPage {
|
|||||||
title: 'Restart',
|
title: 'Restart',
|
||||||
icon: 'reload-outline',
|
icon: 'reload-outline',
|
||||||
action: () => this.presentAlertRestart(),
|
action: () => this.presentAlertRestart(),
|
||||||
|
detail: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Shutdown',
|
title: 'Shutdown',
|
||||||
icon: 'power',
|
icon: 'power',
|
||||||
action: () => this.presentAlertShutdown(),
|
action: () => this.presentAlertShutdown(),
|
||||||
|
detail: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -171,5 +178,6 @@ interface ServerSettings {
|
|||||||
title: string
|
title: string
|
||||||
icon: string
|
icon: string
|
||||||
action: Function
|
action: Function
|
||||||
|
detail: boolean
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<ion-item-group *ngIf="patch.data['server-info'] as server">
|
<ion-item-group *ngIf="patch.data['server-info'] as server">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Version</h2>
|
<h2>EmbassyOS Version</h2>
|
||||||
<p>{{ server.version | displayEmver }}</p>
|
<p>{{ server.version | displayEmver }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export class WifiListPage {
|
|||||||
const buttons: ActionSheetButton[] = [
|
const buttons: ActionSheetButton[] = [
|
||||||
{
|
{
|
||||||
text: 'Forget',
|
text: 'Forget',
|
||||||
cssClass: 'alert-danger',
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.delete(ssid)
|
this.delete(ssid)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 = {
|
export const SshKeys: RR.GetSSHKeysRes = {
|
||||||
'28:d2:7e:78:61:b4:bf:g2:de:24:15:96:4e:d4:15:53': {
|
'28:d2:7e:78:61:b4:bf:g2:de:24:15:96:4e:d4:15:53': {
|
||||||
alg: 'ed25519',
|
alg: 'ed25519',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export module RR {
|
|||||||
|
|
||||||
// auth
|
// 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 loginRes = null
|
||||||
|
|
||||||
export type LogoutReq = { } // auth.logout
|
export type LogoutReq = { } // auth.logout
|
||||||
@@ -47,6 +47,17 @@ export module RR {
|
|||||||
export type RefreshLanReq = { } // network.lan.refresh
|
export type RefreshLanReq = { } // network.lan.refresh
|
||||||
export type RefreshLanRes = null
|
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
|
// marketplace URLs
|
||||||
|
|
||||||
export type SetEosMarketplaceReq = WithExpire<{ url: string }> // marketplace.eos.set
|
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 {
|
export interface DiskInfo {
|
||||||
[id: string]: DiskInfoEntry
|
[id: string]: DiskInfoEntry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
|||||||
|
|
||||||
abstract logout (params: RR.LogoutReq): Promise<RR.LogoutRes>
|
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
|
// server
|
||||||
|
|
||||||
protected abstract setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes>
|
protected abstract setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes>
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ export class LiveApiService extends ApiService {
|
|||||||
return this.http.rpcRequest({ method: 'auth.logout', params })
|
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
|
// server
|
||||||
|
|
||||||
async setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes> {
|
async setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes> {
|
||||||
|
|||||||
@@ -50,6 +50,16 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
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
|
// server
|
||||||
|
|
||||||
async setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes> {
|
async setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes> {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { BehaviorSubject, Observable } from 'rxjs'
|
|||||||
import { distinctUntilChanged } from 'rxjs/operators'
|
import { distinctUntilChanged } from 'rxjs/operators'
|
||||||
import { ApiService } from './api/embassy/embassy-api.service'
|
import { ApiService } from './api/embassy/embassy-api.service'
|
||||||
import { Storage } from '@ionic/storage'
|
import { Storage } from '@ionic/storage'
|
||||||
|
import { getPlatforms, isPlatform } from '@ionic/angular'
|
||||||
|
|
||||||
export enum AuthState {
|
export enum AuthState {
|
||||||
UNVERIFIED,
|
UNVERIFIED,
|
||||||
@@ -31,7 +32,10 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async login (password: string): Promise<void> {
|
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)
|
await this.storage.set(this.LOGGED_IN_KEY, true)
|
||||||
this.authState$.next(AuthState.VERIFIED)
|
this.authState$.next(AuthState.VERIFIED)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
launchableURL (pkg: PackageDataEntry): string {
|
launchableURL (pkg: PackageDataEntry): string {
|
||||||
|
console.log('PKGPKGPKG', pkg)
|
||||||
return this.isTor() ? `http://${torUiAddress(pkg)}` : `https://${lanUiAddress(pkg)}`
|
return this.isTor() ? `http://${torUiAddress(pkg)}` : `https://${lanUiAddress(pkg)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +86,7 @@ export function torUiAddress (pkg: PackageDataEntry): string {
|
|||||||
const val = interfaces[key]
|
const val = interfaces[key]
|
||||||
return val.ui && val['tor-config']
|
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 {
|
export function lanUiAddress (pkg: PackageDataEntry): string {
|
||||||
@@ -94,7 +95,7 @@ export function lanUiAddress (pkg: PackageDataEntry): string {
|
|||||||
const val = interfaces[key]
|
const val = interfaces[key]
|
||||||
return val.ui && val['lan-config']
|
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 {
|
export function hasUi (interfaces: { [id: string]: InterfaceDef }): boolean {
|
||||||
|
|||||||
BIN
ui/src/assets/img/icon.png
Normal file
BIN
ui/src/assets/img/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -25,10 +25,6 @@
|
|||||||
@import "~@ionic/angular/css/text-transformation.css";
|
@import "~@ionic/angular/css/text-transformation.css";
|
||||||
@import "~@ionic/angular/css/flex-utils.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 {
|
.select-change-warning .alert-sub-title {
|
||||||
color: var(--ion-color-warning)
|
color: var(--ion-color-warning)
|
||||||
@@ -100,8 +96,11 @@ ion-toast {
|
|||||||
white-space: normal !important;
|
white-space: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
ion-card-title {
|
ion-card-title {
|
||||||
color: var(--ion-color-dark);
|
|
||||||
font-family: 'Montserrat';
|
font-family: 'Montserrat';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,11 +108,6 @@ ion-title {
|
|||||||
font-family: 'Montserrat';
|
font-family: 'Montserrat';
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-textarea {
|
|
||||||
margin-top: 0;
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-note {
|
ion-note {
|
||||||
max-width: 140px;
|
max-width: 140px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -125,23 +119,16 @@ ion-badge {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-item-divider {
|
ion-toolbar {
|
||||||
--background: transparent;
|
--min-height: 72px;
|
||||||
text-transform: uppercase;
|
--ion-background-color: var(--ion-color-light);
|
||||||
margin-top: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-infinite-scroll ion-infinite-scroll-content {
|
ion-infinite-scroll ion-infinite-scroll-content {
|
||||||
--color: var(--ion-color-warning) !important;
|
--color: var(--ion-color-warning) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-action-sheet {
|
|
||||||
--backdrop-opacity: 0.75 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-alert {
|
ion-alert {
|
||||||
--backdrop-opacity: 0.75 !important;
|
|
||||||
.alert-button {
|
.alert-button {
|
||||||
color: var(--ion-color-dark) !important;
|
color: var(--ion-color-dark) !important;
|
||||||
}
|
}
|
||||||
@@ -151,10 +138,6 @@ ion-button {
|
|||||||
--color: var(--ion-color-dark) !important;
|
--color: var(--ion-color-dark) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-loading {
|
|
||||||
--backdrop-opacity: 0.75 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
-moz-user-select: text;
|
-moz-user-select: text;
|
||||||
@@ -162,14 +145,6 @@ ion-loading {
|
|||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-popover {
|
|
||||||
--background: var(--ion-color-warning) !important;
|
|
||||||
|
|
||||||
ion-backdrop {
|
|
||||||
--backdrop-opacity: 0.45 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-ellipses {
|
.text-ellipses {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -185,13 +160,19 @@ ion-popover {
|
|||||||
display: block;
|
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) {
|
@media (min-width:1000px) {
|
||||||
.modal-wrapper {
|
.modal-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 60% !important;
|
height: 70% !important;
|
||||||
top: 20%;
|
top: 15%;
|
||||||
width: 50% !important;
|
width: 60% !important;
|
||||||
left: 25%;
|
left: 20%;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,6 +208,10 @@ ion-popover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-modal {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
ion-slides {
|
ion-slides {
|
||||||
.slider-wrapper {
|
.slider-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -239,12 +224,21 @@ ion-slides {
|
|||||||
--background: transparent !important;
|
--background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-loading {
|
ion-item-divider {
|
||||||
z-index: 100 !important;
|
text-transform: uppercase;
|
||||||
|
margin-top: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
--color: var(--ion-color-dark);
|
||||||
|
border: none;
|
||||||
|
font-size: medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-modal {
|
ion-item {
|
||||||
--backdrop-opacity: 0.75 !important;
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-loading {
|
||||||
|
z-index: 100 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.swiper-pagination {
|
.swiper-pagination {
|
||||||
@@ -253,10 +247,6 @@ ion-modal {
|
|||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-avatar {
|
|
||||||
--border-radius: var(--icon-border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifier-item {
|
.notifier-item {
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
@@ -277,10 +267,9 @@ ion-avatar {
|
|||||||
--padding-start: 10px;
|
--padding-start: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-item-divider {
|
.divider {
|
||||||
color: var(--ion-color-medium);
|
background: linear-gradient(90deg,var(--ion-color-light) 0,var(--ion-color-dark) 50%,var(--ion-color-light) 100%);
|
||||||
font-size: medium;
|
height: 1px;
|
||||||
padding-left: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dots {
|
.dots {
|
||||||
|
|||||||
@@ -4,83 +4,13 @@
|
|||||||
/** Ionic CSS Variables **/
|
/** Ionic CSS Variables **/
|
||||||
:root {
|
:root {
|
||||||
--ion-font-family: 'Open Sans';
|
--ion-font-family: 'Open Sans';
|
||||||
/** primary **/
|
--ion-background-color: var(--ion-color-medium);
|
||||||
--ion-color-primary: #3880ff;
|
--ion-background-color-rgb: var(--ion-color-medium-rgb);
|
||||||
--ion-color-primary-rgb: 56, 128, 255;
|
--ion-text-color: var(--ion-color-dark);
|
||||||
--ion-color-primary-contrast: #ffffff;
|
--ion-text-color-rgb: var(--ion-color-dark-rgb);
|
||||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
// --ion-backdrop-opacity: .75;
|
||||||
--ion-color-primary-shade: #3171e0;
|
|
||||||
--ion-color-primary-tint: #4c8dff;
|
|
||||||
|
|
||||||
/** secondary **/
|
--ion-color-primary: #0075e1;
|
||||||
--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-rgb: 66,140,255;
|
--ion-color-primary-rgb: 66,140,255;
|
||||||
--ion-color-primary-contrast: #ffffff;
|
--ion-color-primary-contrast: #ffffff;
|
||||||
--ion-color-primary-contrast-rgb: 255,255,255;
|
--ion-color-primary-contrast-rgb: 255,255,255;
|
||||||
@@ -122,79 +52,26 @@ body.dark {
|
|||||||
--ion-color-danger-shade: #e04055;
|
--ion-color-danger-shade: #e04055;
|
||||||
--ion-color-danger-tint: #ff5b71;
|
--ion-color-danger-tint: #ff5b71;
|
||||||
|
|
||||||
--ion-color-dark: #f4f5f8;
|
--ion-color-light: #181818;
|
||||||
--ion-color-dark-rgb: 244,245,248;
|
--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: #000000;
|
||||||
--ion-color-dark-contrast-rgb: 0,0,0;
|
--ion-color-dark-contrast-rgb: 0,0,0;
|
||||||
--ion-color-dark-shade: #d7d8da;
|
--ion-color-dark-shade: #bfbfbf;
|
||||||
--ion-color-dark-tint: #f5f6f9;
|
--ion-color-dark-tint: #d8d8d8;
|
||||||
|
|
||||||
--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-step-50: #1e1e1e;
|
--ion-color-step-50: #1e1e1e;
|
||||||
--ion-color-step-100: #2a2a2a;
|
--ion-color-step-100: #2a2a2a;
|
||||||
@@ -215,10 +92,6 @@ body.dark {
|
|||||||
--ion-color-step-850: #dbdbdb;
|
--ion-color-step-850: #dbdbdb;
|
||||||
--ion-color-step-900: #e7e7e7;
|
--ion-color-step-900: #e7e7e7;
|
||||||
--ion-color-step-950: #f3f3f3;
|
--ion-color-step-950: #f3f3f3;
|
||||||
|
|
||||||
--ion-item-background: #1e1e1e;
|
|
||||||
|
|
||||||
--ion-toolbar-background: #1f1f1f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|||||||
Reference in New Issue
Block a user