* feat: add widgets (#2034)

* feat: add Taiga UI library (#1992)

* feat: add widgets

* update patchdb

* right resizable sidebar with widgets

* feat: add resizing directive

* chore: remove unused code

* chore: remove unnecessary dep

* feat: `ResponsiveCol` add directive for responsive grid

* feat: add widgets edit mode and dialogs

* feat: add widgets model and modal

* chore: fix import

* chore: hide mobile widgets behind flag

* chore: add dummy widgets

* chore: start working on heath widget and implement other comments

* feat: health widget

* feat: add saving widgets and sidebar params to patch

* feat: preemptive UI update for widgets

* update health widget with more accurate states and styling (#2127)

* feat: `ResponsiveCol` add directive for responsive grid

* chore: some changes after merge

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>

* fix(shared): `ElasticContainer` fix collapsing margin (#2150)

* fix(shared): `ElasticContainer` fix collapsing margin

* fix toolbar height so titles not chopped

---------

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>

* feat: make widgets sidebar width togglable (#2146)

* feat: make widgets sidebar width togglable

* feat: move widgets under header

* chore: fix wide layout

* fix(shared): `ResponsiveCol` fix missing grid steps (#2153)

* fix widget flag and refactor for non-persistence

* default widget flag to false

* fix(shared): fix responsive column size (#2159)

* fix(shared): fix responsive column size

* fix: add responsiveness to all pages

* fix responsiveness on more pages

* fix: comments

* revert some padding changes

---------

Co-authored-by: Lucy Cifferello <12953208+elvece@users.noreply.github.com>
Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>

* chore: add analyzer (#2165)

* fix list styling to previous default (#2173)

* fix list styling to previous default

* dont need important flag

---------

Co-authored-by: Alex Inkin <alexander@inkin.ru>
Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>
This commit is contained in:
Matt Hill
2023-03-02 15:21:03 -07:00
committed by Aiden McClelland
parent aeb6da111b
commit e867f31c31
113 changed files with 2452 additions and 295 deletions

View File

@@ -35,9 +35,17 @@
"glob": "**/*",
"input": "node_modules/monaco-editor",
"output": "assets/monaco-editor/"
},
{
"glob": "**/*",
"input": "node_modules/@taiga-ui/icons/src",
"output": "assets/taiga-ui/icons"
}
],
"styles": [
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less",
"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less",
"node_modules/@taiga-ui/styles/taiga-ui-global.less",
"projects/shared/styles/variables.scss",
"projects/shared/styles/global.scss",
"projects/shared/styles/shared.scss",
@@ -155,9 +163,16 @@
"glob": "**/*.svg",
"input": "node_modules/ionicons/dist/ionicons/svg",
"output": "./svg"
},
{
"glob": "**/*",
"input": "node_modules/@taiga-ui/icons/src",
"output": "assets/taiga-ui/icons"
}
],
"styles": [
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less",
"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less",
"projects/shared/styles/variables.scss",
"projects/shared/styles/global.scss",
"projects/shared/styles/shared.scss",
@@ -285,9 +300,16 @@
"glob": "**/*.svg",
"input": "node_modules/ionicons/dist/ionicons/svg",
"output": "./svg"
},
{
"glob": "**/*",
"input": "node_modules/@taiga-ui/icons/src",
"output": "assets/taiga-ui/icons"
}
],
"styles": [
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less",
"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less",
"projects/shared/styles/variables.scss",
"projects/shared/styles/global.scss",
"projects/shared/styles/shared.scss",
@@ -405,9 +427,16 @@
"glob": "**/*.svg",
"input": "node_modules/ionicons/dist/ionicons/svg",
"output": "./svg"
},
{
"glob": "**/*",
"input": "node_modules/@taiga-ui/icons/src",
"output": "assets/taiga-ui/icons"
}
],
"styles": [
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less",
"node_modules/@taiga-ui/core/styles/taiga-ui-fonts.less",
"projects/shared/styles/variables.scss",
"projects/shared/styles/global.scss",
"projects/shared/styles/shared.scss",

View File

@@ -1,5 +1,6 @@
{
"useMocks": true,
"enableWidgets": false,
"packageArch": "aarch64",
"osArch": "raspberrypi",
"ui": {

File diff suppressed because it is too large Load Diff

View File

@@ -17,9 +17,11 @@
"build:install-wiz": "ng run install-wizard:build",
"build:setup": "ng run setup-wizard:build",
"build:ui": "ng run ui:build",
"build:ui:stats": "ng run ui:build --stats-json",
"build:all": "npm run build:deps && npm run build:dui && npm run build:setup && npm run build:ui && npm run build:install-wiz",
"build:shared": "ng build shared",
"build:marketplace": "npm run build:shared && ng build marketplace",
"analyze:ui": "webpack-bundle-analyzer dist/ui/stats.json",
"publish:shared": "npm run build:shared && npm publish ./dist/shared --access public",
"publish:marketplace": "npm run build:marketplace && npm publish ./dist/marketplace --access public",
"start:dui": "npm run-script build-config && ionic serve --project diagnostic-ui --host 0.0.0.0",
@@ -39,12 +41,18 @@
"@angular/platform-browser-dynamic": "^14.1.0",
"@angular/router": "^14.1.0",
"@ionic/angular": "^6.1.15",
"@materia-ui/ngx-monaco-editor": "^6.0.0",
"@ng-web-apis/common": "^2.0.0",
"@ng-web-apis/mutation-observer": "^2.0.0",
"@ng-web-apis/resize-observer": "^2.0.0",
"@materia-ui/ngx-monaco-editor": "^6.0.0",
"@start9labs/argon2": "^0.1.0",
"@start9labs/emver": "^0.1.5",
"@taiga-ui/addon-charts": "^3.14.0",
"@taiga-ui/cdk": "^3.14.0",
"@taiga-ui/core": "^3.14.0",
"@taiga-ui/icons": "^3.14.0",
"@taiga-ui/kit": "^3.14.0",
"@taiga-ui/styles": "^3.14.0",
"angular-svg-round-progressbar": "^9.0.0",
"ansi-to-html": "^0.7.2",
"base64-js": "^1.5.1",
@@ -93,7 +101,8 @@
"raw-loader": "^4.0.2",
"ts-node": "^10.7.0",
"tslint": "^6.1.3",
"typescript": "^4.6.3"
"typescript": "^4.6.3",
"webpack-bundle-analyzer": "^4.8.0"
},
"husky": {
"hooks": {

View File

@@ -1,3 +1,5 @@
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>
<tui-root>
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>
</tui-root>

View File

@@ -0,0 +1,8 @@
:host {
display: block;
height: 100%;
}
tui-root {
height: 100%;
}

View File

@@ -1,7 +1,8 @@
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { RouteReuseStrategy } from '@angular/router'
import { IonicModule, IonicRouteStrategy } from '@ionic/angular'
import { TuiRootModule } from '@taiga-ui/core'
import { AppComponent } from './app.component'
import { AppRoutingModule } from './app-routing.module'
import { HttpClientModule } from '@angular/common/http'
@@ -19,11 +20,12 @@ const {
declarations: [AppComponent],
imports: [
HttpClientModule,
BrowserModule,
BrowserAnimationsModule,
IonicModule.forRoot({
mode: 'md',
}),
AppRoutingModule,
TuiRootModule,
],
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },

View File

@@ -1,3 +1,5 @@
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>
<tui-root>
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>
</tui-root>

View File

@@ -0,0 +1,8 @@
:host {
display: block;
height: 100%;
}
tui-root {
height: 100%;
}

View File

@@ -1,7 +1,8 @@
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { RouteReuseStrategy } from '@angular/router'
import { IonicModule, IonicRouteStrategy } from '@ionic/angular'
import { TuiRootModule } from '@taiga-ui/core'
import { AppComponent } from './app.component'
import { AppRoutingModule } from './app-routing.module'
import { HttpClientModule } from '@angular/common/http'
@@ -19,11 +20,12 @@ const {
declarations: [AppComponent],
imports: [
HttpClientModule,
BrowserModule,
BrowserAnimationsModule,
IonicModule.forRoot({
mode: 'md',
}),
AppRoutingModule,
TuiRootModule,
],
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },

View File

@@ -6,6 +6,7 @@
"@angular/core": ">=13.2.0",
"@ionic/angular": ">=6.0.0",
"@start9labs/shared": ">=0.3.0",
"@taiga-ui/cdk": ">=3.0.0",
"fuse.js": "^6.4.6"
},
"dependencies": {

View File

@@ -1,6 +1,6 @@
<ion-grid>
<ion-row>
<ion-col sizeSm="8" offset-sm="2" sizeLg="6" offset-lg="3">
<ion-col responsiveCol class="column" sizeSm="8" sizeLg="6">
<ion-toolbar color="transparent" class="ion-text-left">
<ion-searchbar
color="dark"

View File

@@ -2,3 +2,7 @@
display: block;
padding-bottom: 32px;
}
.column {
margin: 0 auto;
}

View File

@@ -1,11 +1,12 @@
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular'
import { ResponsiveColModule } from '@start9labs/shared'
import { SearchComponent } from './search.component'
@NgModule({
imports: [IonicModule, FormsModule],
imports: [IonicModule, FormsModule, ResponsiveColModule],
declarations: [SearchComponent],
exports: [SearchComponent],
})

View File

@@ -13,6 +13,7 @@
<ion-row>
<ion-col
*ngFor="let pkg of ['', '', '', '']"
responsiveCol
sizeXs="12"
sizeSm="12"
sizeMd="6"

View File

@@ -1,11 +1,12 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { ResponsiveColModule } from '@start9labs/shared'
import { SkeletonComponent } from './skeleton.component'
@NgModule({
imports: [CommonModule, IonicModule],
imports: [CommonModule, IonicModule, ResponsiveColModule],
declarations: [SkeletonComponent],
exports: [SkeletonComponent],
})

View File

@@ -1,4 +1,4 @@
<ion-content>
<ion-content class="with-widgets">
<ng-container *ngIf="notes$ | async as notes; else loading">
<div *ngFor="let note of notes | keyvalue: asIsOrder">
<ion-button
@@ -11,7 +11,7 @@
<p class="version">{{ note.key | displayEmver }}</p>
</ion-button>
<ion-card
elementRef
tuiElement
#element="elementRef"
class="panel"
color="light"

View File

@@ -5,8 +5,8 @@ import {
EmverPipesModule,
MarkdownPipeModule,
TextSpinnerComponentModule,
ElementModule,
} from '@start9labs/shared'
import { TuiElementModule } from '@taiga-ui/cdk'
import { ReleaseNotesComponent } from './release-notes.component'
@@ -17,7 +17,7 @@ import { ReleaseNotesComponent } from './release-notes.component'
TextSpinnerComponentModule,
EmverPipesModule,
MarkdownPipeModule,
ElementModule,
TuiElementModule,
],
declarations: [ReleaseNotesComponent],
exports: [ReleaseNotesComponent],

View File

@@ -22,7 +22,7 @@
<ion-item-divider>Additional Info</ion-item-divider>
<ion-grid *ngIf="pkg.manifest as manifest">
<ion-row>
<ion-col sizeXs="12" sizeMd="6">
<ion-col responsiveCol sizeXs="12" sizeMd="6">
<ion-item-group>
<ion-item
*ngIf="manifest['git-hash'] as gitHash; else noHash"
@@ -71,7 +71,7 @@
</ion-item>
</ion-item-group>
</ion-col>
<ion-col sizeXs="12" sizeMd="6">
<ion-col responsiveCol sizeXs="12" sizeMd="6">
<ion-item-group>
<ion-item
[href]="manifest['upstream-repo']"

View File

@@ -1,12 +1,12 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { MarkdownModule } from '@start9labs/shared'
import { MarkdownModule, ResponsiveColModule } from '@start9labs/shared'
import { AdditionalComponent } from './additional.component'
@NgModule({
imports: [CommonModule, IonicModule, MarkdownModule],
imports: [CommonModule, IonicModule, MarkdownModule, ResponsiveColModule],
declarations: [AdditionalComponent],
exports: [AdditionalComponent],
})

View File

@@ -3,6 +3,7 @@
<ion-row>
<ion-col
*ngFor="let dep of pkg.manifest.dependencies | keyvalue"
responsiveCol
sizeSm="12"
sizeMd="6"
>

View File

@@ -2,7 +2,11 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { EmverPipesModule, SharedPipesModule } from '@start9labs/shared'
import {
EmverPipesModule,
ResponsiveColModule,
SharedPipesModule,
} from '@start9labs/shared'
import { DependenciesComponent } from './dependencies.component'
@@ -13,6 +17,7 @@ import { DependenciesComponent } from './dependencies.component'
IonicModule,
SharedPipesModule,
EmverPipesModule,
ResponsiveColModule,
],
declarations: [DependenciesComponent],
exports: [DependenciesComponent],

View File

@@ -1,3 +1,5 @@
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>
<tui-root>
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>
</tui-root>

View File

@@ -0,0 +1,8 @@
:host {
display: block;
height: 100%;
}
tui-root {
height: 100%;
}

View File

@@ -1,7 +1,8 @@
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { RouteReuseStrategy } from '@angular/router'
import { HttpClientModule } from '@angular/common/http'
import { TuiRootModule } from '@taiga-ui/core'
import { ApiService } from './services/api/api.service'
import { MockApiService } from './services/api/mock-api.service'
import { LiveApiService } from './services/api/live-api.service'
@@ -27,7 +28,7 @@ const {
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
IonicModule.forRoot({
mode: 'md',
navAnimation: iosTransitionAnimation,
@@ -39,6 +40,7 @@ const {
LoadingPageModule,
RecoverPageModule,
TransferPageModule,
TuiRootModule,
],
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },

View File

@@ -2,6 +2,8 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { ResponsiveColModule } from '@start9labs/shared'
import { SuccessPage } from './success.page'
import { PasswordPageModule } from '../../modals/password/password.module'
import { SuccessPageRoutingModule } from './success-routing.module'
@@ -14,6 +16,7 @@ import { DownloadDocComponent } from './download-doc/download-doc.component'
IonicModule,
PasswordPageModule,
SuccessPageRoutingModule,
ResponsiveColModule,
],
declarations: [SuccessPage, DownloadDocComponent],
exports: [SuccessPage],

View File

@@ -7,7 +7,7 @@
<ng-container *ngIf="isKiosk; else notKiosk">
<ion-card>
<ion-row class="ion-align-items-center">
<ion-col size-xs="12" class="ion-text-center">
<ion-col responsiveCol sizeXs="12" class="ion-text-center">
<div class="inline" style="margin-bottom: 3rem">
<ion-icon
name="checkmark-circle-outline"
@@ -33,7 +33,7 @@
<ng-template #notKiosk>
<ion-card>
<ion-row class="ion-align-items-center">
<ion-col size-xs="12" class="ion-text-center">
<ion-col responsiveCol sizeXs="12" class="ion-text-center">
<div style="margin-bottom: 4rem">
<div class="inline">
<ion-icon

View File

@@ -8,7 +8,8 @@
"@ionic/angular": ">=6.0.0",
"@ng-web-apis/mutation-observer": ">=2.0.0",
"@ng-web-apis/resize-observer": ">=2.0.0",
"@start9labs/emver": "^0.1.5"
"@start9labs/emver": "^0.1.5",
"@taiga-ui/cdk": ">=3.0.0"
},
"exports": {
"./assets/": "./assets/"

View File

@@ -1,3 +1,3 @@
<div (elasticContainer)="height = $event">
<div class="wrapper" (elasticContainer)="height = $event">
<ng-content></ng-content>
</div>

View File

@@ -3,3 +3,8 @@
overflow: hidden;
transition: height 0.25s;
}
.wrapper {
padding-top: 1px;
margin-top: -1px;
}

View File

@@ -29,7 +29,7 @@ export class ElasticContainerDirective {
inject(ResizeObserverService),
inject(MutationObserverService),
).pipe(
map(() => this.elementRef.nativeElement.clientHeight),
map(() => this.elementRef.nativeElement.clientHeight - 1), // Compensate for padding
distinctUntilChanged(),
)
}

View File

@@ -1,11 +0,0 @@
import { Directive, ElementRef, Inject } from '@angular/core'
@Directive({
selector: '[elementRef]',
exportAs: 'elementRef',
})
export class ElementDirective<T extends Element> extends ElementRef<T> {
constructor(@Inject(ElementRef) { nativeElement }: ElementRef<T>) {
super(nativeElement)
}
}

View File

@@ -1,9 +0,0 @@
import { NgModule } from '@angular/core'
import { ElementDirective } from './element.directive'
@NgModule({
declarations: [ElementDirective],
exports: [ElementDirective],
})
export class ElementModule {}

View File

@@ -0,0 +1,68 @@
import {
Directive,
ElementRef,
Inject,
InjectionToken,
Input,
NgZone,
} from '@angular/core'
import { ResizeObserverService } from '@ng-web-apis/resize-observer'
import { distinctUntilChanged, Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { tuiZonefree } from '@taiga-ui/cdk'
export type Step = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
/**
* Not exported:
* https://github.com/ionic-team/ionic-framework/blob/main/core/src/utils/media.ts
*
* export const SIZE_TO_MEDIA: any = {
* xs: '(min-width: 0px)',
* sm: '(min-width: 576px)',
* md: '(min-width: 768px)',
* lg: '(min-width: 992px)',
* xl: '(min-width: 1200px)',
* };
*/
export const BREAKPOINTS = new InjectionToken<readonly [number, Step][]>(
'BREAKPOINTS',
{
factory: () => [
[1200, 'xl'],
[992, 'lg'],
[768, 'md'],
[576, 'sm'],
[0, 'xs'],
],
},
)
@Directive({
selector: '[responsiveColViewport]',
exportAs: 'viewport',
providers: [ResizeObserverService],
})
export class ResponsiveColViewportDirective extends Observable<Step> {
@Input()
responsiveColViewport: Observable<Step> | '' = ''
private readonly stream$ = this.resize$.pipe(
map(() => this.elementRef.nativeElement.clientWidth),
map(width => this.breakpoints.find(([step]) => width >= step)?.[1] || 'xs'),
distinctUntilChanged(),
tuiZonefree(this.zone),
)
constructor(
@Inject(BREAKPOINTS)
private readonly breakpoints: readonly [number, Step][],
private readonly resize$: ResizeObserverService,
private readonly elementRef: ElementRef<HTMLElement>,
private readonly zone: NgZone,
) {
super(subscriber =>
(this.responsiveColViewport || this.stream$).subscribe(subscriber),
)
}
}

View File

@@ -0,0 +1,56 @@
import { Directive, OnInit, Optional } from '@angular/core'
import { TuiDestroyService } from '@taiga-ui/cdk'
import {
ResponsiveColViewportDirective,
Step,
} from './responsive-col-viewport.directive'
import { IonCol } from '@ionic/angular'
import { takeUntil } from 'rxjs'
const SIZE: readonly Step[] = ['xl', 'lg', 'md', 'sm', 'xs']
@Directive({
selector: 'ion-col[responsiveCol]',
providers: [TuiDestroyService],
})
export class ResponsiveColDirective implements OnInit {
readonly size: Record<Step, string | undefined> = {
xs: '12',
sm: '6',
md: '4',
lg: '3',
xl: '2',
}
constructor(
@Optional()
viewport$: ResponsiveColViewportDirective | null,
destroy$: TuiDestroyService,
private readonly col: IonCol,
) {
viewport$?.pipe(takeUntil(destroy$)).subscribe(size => {
const max = this.size[size] || this.findMax(size)
this.col.sizeLg = max
this.col.sizeMd = max
this.col.sizeSm = max
this.col.sizeXl = max
this.col.sizeXs = max
})
}
ngOnInit() {
this.size.lg = this.col.sizeLg
this.size.md = this.col.sizeMd
this.size.sm = this.col.sizeSm
this.size.xl = this.col.sizeXl
this.size.xs = this.col.sizeXs
}
private findMax(current: Step): string | undefined {
const start = SIZE.indexOf(current) - 1
const max = SIZE.find((size, i) => i > start && this.size[size]) || current
return this.size[max]
}
}

View File

@@ -0,0 +1,10 @@
import { NgModule } from '@angular/core'
import { ResponsiveColViewportDirective } from './responsive-col-viewport.directive'
import { ResponsiveColDirective } from './responsive-col.directive'
@NgModule({
declarations: [ResponsiveColDirective, ResponsiveColViewportDirective],
exports: [ResponsiveColDirective, ResponsiveColViewportDirective],
})
export class ResponsiveColModule {}

View File

@@ -22,8 +22,9 @@ export * from './components/toast/toast.component'
export * from './components/toast/toast.module'
export * from './components/toast/toast-button.directive'
export * from './directives/element/element.directive'
export * from './directives/element/element.module'
export * from './directives/responsive-col/responsive-col.directive'
export * from './directives/responsive-col/responsive-col.module'
export * from './directives/responsive-col/responsive-col-viewport.directive'
export * from './directives/safe-links/safe-links.directive'
export * from './directives/safe-links/safe-links.module'
@@ -40,7 +41,6 @@ export * from './pipes/shared/trust.pipe'
export * from './pipes/unit-conversion/unit-conversion.module'
export * from './pipes/unit-conversion/unit-conversion.pipe'
export * from './services/destroy.service'
export * from './services/download-html.service'
export * from './services/emver.service'
export * from './services/error-toast.service'

View File

@@ -1,13 +0,0 @@
import { Injectable, OnDestroy } from '@angular/core'
import { ReplaySubject } from 'rxjs'
/**
* Observable abstraction over ngOnDestroy to use with takeUntil
*/
@Injectable()
export class DestroyService extends ReplaySubject<void> implements OnDestroy {
ngOnDestroy() {
this.next()
this.complete()
}
}

View File

@@ -3,6 +3,7 @@ export type WorkspaceConfig = {
osArch: 'aarch64' | 'x86_64' | 'raspberrypi'
gitHash: string
useMocks: boolean
enableWidgets: boolean
// each key corresponds to a project and values adjust settings for that project, eg: ui, install-wizard, setup-wizard, diagnostic-ui
ui: {
api: {

View File

@@ -80,7 +80,6 @@ const routes: Routes = [
scrollPositionRestoration: 'enabled',
preloadingStrategy: PreloadAllModules,
initialNavigation: 'disabled',
useHash: true,
}),
],
exports: [RouterModule],

View File

@@ -1,33 +1,77 @@
<ion-app appEnter>
<ion-content>
<ion-split-pane
contentId="main-content"
[disabled]="!(authService.isVerified$ | async)"
(ionSplitPaneVisible)="splitPaneVisible($event)"
>
<ion-menu contentId="main-content" type="overlay">
<ion-content color="light" scrollY="false">
<app-menu *ngIf="authService.isVerified$ | async"></app-menu>
</ion-content>
</ion-menu>
<ion-router-outlet
id="main-content"
class="container"
[class.container_offline]="
(authService.isVerified$ | async) && !(connection.connected$ | async)
"
></ion-router-outlet>
</ion-split-pane>
<tui-root
*ngIf="widgetDrawer$ | async as drawer"
tuiMode="onDark"
[style.--widgets-width.px]="drawer.open ? drawer.width : 0"
>
<ion-app appEnter>
<ion-content>
<ion-split-pane
contentId="main-content"
[disabled]="!(authService.isVerified$ | async)"
(ionSplitPaneVisible)="splitPaneVisible($event)"
>
<ion-menu
contentId="main-content"
type="overlay"
side="start"
class="left-menu"
>
<ion-content color="light" scrollY="false">
<app-menu *ngIf="authService.isVerified$ | async"></app-menu>
</ion-content>
</ion-menu>
<section appPreloader></section>
</ion-content>
<ion-footer>
<footer appFooter></footer>
</ion-footer>
<ion-footer
*ngIf="(authService.isVerified$ | async) && !(sidebarOpen$ | async)"
>
<connection-bar></connection-bar>
</ion-footer>
<toast-container></toast-container>
</ion-app>
<ion-menu
contentId="main-content"
type="overlay"
side="end"
class="right-menu container"
[class.container_offline]="
(authService.isVerified$ | async) &&
!(connection.connected$ | async)
"
[class.right-menu_hidden]="!drawer.open"
[style.--side-width.px]="drawer.width"
>
<div class="divider">
<button
class="widgets-button"
[class.widgets-button_collapse]="drawer.width === 600"
(click)="onResize(drawer)"
></button>
</div>
<widgets *ngIf="drawer.open" [wide]="drawer.width === 600"></widgets>
</ion-menu>
<ion-router-outlet
[responsiveColViewport]="viewport"
id="main-content"
class="container"
[class.container_offline]="
(authService.isVerified$ | async) &&
!(connection.connected$ | async)
"
>
<ion-content
#viewport="viewport"
responsiveColViewport
class="ion-padding with-widgets"
style="pointer-events: none; opacity: 0"
></ion-content>
</ion-router-outlet>
</ion-split-pane>
<section appPreloader></section>
</ion-content>
<ion-footer>
<footer appFooter></footer>
</ion-footer>
<ion-footer
*ngIf="(authService.isVerified$ | async) && !(sidebarOpen$ | async)"
>
<connection-bar></connection-bar>
</ion-footer>
<toast-container></toast-container>
</ion-app>
</tui-root>
<tui-theme-night></tui-theme-night>

View File

@@ -1,8 +1,13 @@
:host {
display: block;
height: 100%;
}
ion-split-pane {
tui-root {
height: 100%;
}
.left-menu {
--side-max-width: 280px;
}
@@ -12,4 +17,103 @@ ion-split-pane {
&_offline {
filter: saturate(0.75) contrast(0.85);
}
}
@media screen and (max-width: 991.499px) {
--widgets-width: 0px;
}
}
.right-menu {
--side-max-width: 600px;
position: fixed;
z-index: 1000;
right: 0;
left: auto;
top: 74px;
// For some reason *ngIf is broken upon first login
&_hidden {
display: none;
}
}
.divider {
height: 100%;
width: 10px;
pointer-events: none;
position: absolute;
left: 0;
top: 0;
bottom: 0;
background: #e2e2e2;
z-index: 10;
opacity: 0.2;
transition: opacity 0.3s;
&:before,
&:after {
content: '';
position: absolute;
top: 50%;
margin-top: -78px;
left: 10px;
width: 60px;
height: 50px;
border-bottom-left-radius: 14px;
box-shadow: -14px 0 0 -1px #e2e2e2;
}
&:after {
margin-top: 28px;
border-radius: 0;
border-top-left-radius: 14px;
}
&:hover {
opacity: 0.4;
}
}
.widgets-button {
position: absolute;
top: 50%;
font-size: 0;
left: 100%;
width: 16px;
height: 60px;
margin-top: -30px;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
background: inherit;
pointer-events: auto;
&:before,
&:after {
content: '';
position: absolute;
top: 50%;
left: 3px;
width: 2px;
height: 8px;
background: black;
transform: rotate(-45deg);
border-radius: 2px;
}
&:before {
margin-top: -5px;
transform: rotate(45deg);
}
&_collapse:before {
transform: rotate(-45deg);
}
&_collapse:after {
transform: rotate(45deg);
}
}

View File

@@ -7,6 +7,10 @@ import { PatchMonitorService } from './services/patch-monitor.service'
import { ConnectionService } from './services/connection.service'
import { Title } from '@angular/platform-browser'
import { ServerNameService } from './services/server-name.service'
import {
ClientStorageService,
WidgetDrawer,
} from './services/client-storage.service'
@Component({
selector: 'app-root',
@@ -16,6 +20,7 @@ import { ServerNameService } from './services/server-name.service'
export class AppComponent implements OnDestroy {
readonly subscription = merge(this.patchData, this.patchMonitor).subscribe()
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
constructor(
private readonly titleService: Title,
@@ -25,6 +30,7 @@ export class AppComponent implements OnDestroy {
private readonly serverNameService: ServerNameService,
readonly authService: AuthService,
readonly connection: ConnectionService,
readonly clientStorageService: ClientStorageService,
) {}
ngOnInit() {
@@ -37,6 +43,13 @@ export class AppComponent implements OnDestroy {
this.splitPane.sidebarOpen$.next(detail.visible)
}
onResize(drawer: WidgetDrawer) {
this.clientStorageService.updateWidgetDrawer({
...drawer,
width: drawer.width === 400 ? 600 : 400,
})
}
ngOnDestroy() {
this.subscription.unsubscribe()
}

View File

@@ -1,9 +1,19 @@
import {
TuiDialogModule,
TuiModeModule,
TuiRootModule,
TuiThemeNightModule,
} from '@taiga-ui/core'
import { HttpClientModule } from '@angular/common/http'
import { NgModule } from '@angular/core'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { IonicModule } from '@ionic/angular'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
import { MarkdownModule, SharedPipesModule } from '@start9labs/shared'
import {
MarkdownModule,
ResponsiveColModule,
SharedPipesModule,
} from '@start9labs/shared'
import { AppComponent } from './app.component'
import { AppRoutingModule } from './app-routing.module'
@@ -18,6 +28,7 @@ import { APP_PROVIDERS } from './app.providers'
import { PatchDbModule } from './services/patch-db/patch-db.module'
import { ToastContainerModule } from './components/toast-container/toast-container.module'
import { ConnectionBarComponentModule } from './components/connection-bar/connection-bar.component.module'
import { WidgetsPageModule } from './pages/widgets/widgets.module'
@NgModule({
declarations: [AppComponent],
@@ -41,6 +52,12 @@ import { ConnectionBarComponentModule } from './components/connection-bar/connec
PatchDbModule,
ToastContainerModule,
ConnectionBarComponentModule,
TuiRootModule,
TuiDialogModule,
TuiModeModule,
TuiThemeNightModule,
WidgetsPageModule,
ResponsiveColModule,
],
providers: APP_PROVIDERS,
bootstrap: [AppComponent],

View File

@@ -1,6 +1,6 @@
<backup-drives-header [type]="type"></backup-drives-header>
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<!-- loading -->
<text-spinner
*ngIf="loading; else loaded"

View File

@@ -1,3 +1,16 @@
<ng-container *ngIf="enableWidgets">
<ion-button class="widgets" color="dark" (click)="onWidgets()">
<ion-icon name="grid-outline"></ion-icon>
</ion-button>
<ion-button
*ngIf="widgetDrawer$ | async as drawer"
class="sidebar"
color="dark"
(click)="onSidebar(drawer)"
>
<ion-icon name="grid-outline"></ion-icon>
</ion-button>
</ng-container>
<div class="wrapper">
<ion-badge
*ngIf="!(sidebarOpen$ | async) && (unreadCount$ | async) as unreadCount"

View File

@@ -2,10 +2,11 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { BadgeMenuComponent } from './badge-menu.component'
import { TuiLetModule } from '@taiga-ui/cdk'
@NgModule({
imports: [CommonModule, IonicModule, TuiLetModule],
declarations: [BadgeMenuComponent],
imports: [CommonModule, IonicModule],
exports: [BadgeMenuComponent],
})
export class BadgeMenuComponentModule {}

View File

@@ -1,6 +1,25 @@
:host {
display: flex;
align-items: center;
padding-right: 8px;
}
.sidebar {
display: none;
}
@media screen and (min-width: 992px) {
.widgets {
display: none;
}
.sidebar {
display: inline-block;
}
}
.wrapper {
position: relative;
margin-right: 1vh;
}
.md-badge {

View File

@@ -2,6 +2,16 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
import { PatchDB } from 'patch-db-client'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { TuiDialogService } from '@taiga-ui/core'
import { WIDGETS_COMPONENT } from '../../pages/widgets/widgets.page'
import { WorkspaceConfig } from '@start9labs/shared'
import {
ClientStorageService,
WidgetDrawer,
} from 'src/app/services/client-storage.service'
const { enableWidgets } =
require('../../../../../../config.json') as WorkspaceConfig
@Component({
selector: 'badge-menu-button',
@@ -10,11 +20,30 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BadgeMenuComponent {
unreadCount$ = this.patch.watch$('server-info', 'unread-notification-count')
sidebarOpen$ = this.splitPane.sidebarOpen$
readonly unreadCount$ = this.patch.watch$(
'server-info',
'unread-notification-count',
)
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
readonly enableWidgets = enableWidgets
constructor(
private readonly splitPane: SplitPaneTracker,
private readonly patch: PatchDB<DataModel>,
private readonly dialog: TuiDialogService,
private readonly clientStorageService: ClientStorageService,
) {}
onSidebar(drawer: WidgetDrawer) {
this.clientStorageService.updateWidgetDrawer({
...drawer,
open: !drawer.open,
})
}
onWidgets() {
this.dialog.open(WIDGETS_COMPONENT, { label: 'Widgets' }).subscribe()
}
}

View File

@@ -11,7 +11,7 @@
[scrollEvents]="true"
(ionScroll)="handleScroll($event)"
(ionScrollEnd)="handleScrollEnd()"
class="ion-padding"
class="ion-padding with-widgets"
>
<ion-infinite-scroll
id="scroller"
@@ -73,7 +73,7 @@
</ng-container>
</ion-content>
<ion-footer>
<ion-footer class="with-widgets">
<ion-toolbar>
<div class="inline ion-padding-start">
<ion-checkbox [(ngModel)]="autoScroll" color="dark"></ion-checkbox>

View File

@@ -15,12 +15,12 @@ import { WebSocketSubjectConfig } from 'rxjs/webSocket'
import {
LogsRes,
ServerLogsReq,
DestroyService,
ErrorToastService,
toLocalIsoString,
Log,
DownloadHTMLService,
} from '@start9labs/shared'
import { TuiDestroyService } from '@taiga-ui/cdk'
import { RR } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConnectionService } from 'src/app/services/connection.service'
@@ -39,7 +39,7 @@ var convert = new Convert({
selector: 'logs',
templateUrl: './logs.component.html',
styleUrls: ['./logs.component.scss'],
providers: [DestroyService, DownloadHTMLService],
providers: [TuiDestroyService, DownloadHTMLService],
})
export class LogsComponent {
@ViewChild(IonContent)
@@ -68,7 +68,7 @@ export class LogsComponent {
constructor(
private readonly errToast: ErrorToastService,
private readonly destroy$: DestroyService,
private readonly destroy$: TuiDestroyService,
private readonly api: ApiService,
private readonly loadingCtrl: LoadingController,
private readonly downloadHtml: DownloadHTMLService,

View File

@@ -3,6 +3,7 @@
<ion-row class="ion-justify-content-center ion-align-items-center">
<ion-col
*ngFor="let card of cards"
responsiveCol
sizeLg="4"
sizeSm="6"
sizeXs="12"

View File

@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { ResponsiveColModule } from '@start9labs/shared'
import { WidgetListComponent } from './widget-list.component'
import { AnyLinkModule } from 'src/app/components/any-link/any-link.component.module'
import { WidgetCardComponentModule } from '../widget-card/widget-card.component.module'
@@ -14,6 +15,7 @@ import { WidgetCardComponentModule } from '../widget-card/widget-card.component.
RouterModule.forChild([]),
AnyLinkModule,
WidgetCardComponentModule,
ResponsiveColModule,
],
exports: [WidgetListComponent],
})

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content class="ion-padding-top with-widgets">
<ion-item-group *ngIf="pkg$ | async as pkg">
<!-- ** standard actions ** -->
<ion-item-divider>Standard Actions</ion-item-divider>

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content class="ion-padding-top with-widgets">
<ion-item-group>
<!-- iff ui -->
<ng-container *ngIf="ui">
@@ -18,9 +18,9 @@
<!-- other interface -->
<ng-container *ngIf="other.length">
<ion-item-divider>Machine Interfaces</ion-item-divider>
<div *ngFor="let interface of other" style="margin-bottom: 30px;">
<div *ngFor="let interface of other" style="margin-bottom: 30px">
<app-interfaces-item [interface]="interface"></app-interfaces-item>
</div>
</ng-container>
</ion-item-group>
</ion-content>
</ion-content>

View File

@@ -5,6 +5,7 @@ import { IonicModule } from '@ionic/angular'
import { AppListPage } from './app-list.page'
import {
EmverPipesModule,
ResponsiveColModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
@@ -35,6 +36,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
BadgeMenuComponentModule,
WidgetListComponentModule,
ResponsiveColModule,
],
declarations: [
AppListPage,

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<!-- loaded -->
<ng-container *ngIf="pkgs$ | async as pkgs; else loading">
<ng-container *ngIf="!pkgs.length; else list">
@@ -20,7 +20,12 @@
<ng-template #list>
<ion-grid>
<ion-row>
<ion-col *ngFor="let pkg of pkgs" sizeSm="12" sizeLg="6">
<ion-col
*ngFor="let pkg of pkgs"
responsiveCol
sizeSm="12"
sizeMd="6"
>
<app-list-pkg
*ngIf="pkg | packageInfo | async as info"
[pkg]="info"

View File

@@ -10,7 +10,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<skeleton-list *ngIf="loading"></skeleton-list>
<ion-item-group *ngIf="!loading">
<ion-item *ngFor="let metric of metrics | keyvalue : asIsOrder">

View File

@@ -13,7 +13,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content class="ion-padding-top with-widgets">
<text-spinner
*ngIf="loading; else loaded"
text="Loading Properties"

View File

@@ -16,11 +16,11 @@ import {
PackageMainStatus,
} from 'src/app/services/patch-db/data-model'
import {
DestroyService,
ErrorToastService,
getPkgId,
copyToClipboard,
} from '@start9labs/shared'
import { TuiDestroyService } from '@taiga-ui/cdk'
import { getValueByPointer } from 'fast-json-patch'
import { map, takeUntil } from 'rxjs/operators'
@@ -28,7 +28,7 @@ import { map, takeUntil } from 'rxjs/operators'
selector: 'app-properties',
templateUrl: './app-properties.page.html',
styleUrls: ['./app-properties.page.scss'],
providers: [DestroyService],
providers: [TuiDestroyService],
})
export class AppPropertiesPage {
loading = true
@@ -56,7 +56,7 @@ export class AppPropertiesPage {
private readonly modalCtrl: ModalController,
private readonly navCtrl: NavController,
private readonly patch: PatchDB<DataModel>,
private readonly destroy$: DestroyService,
private readonly destroy$: TuiDestroyService,
) {}
ionViewDidEnter() {

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { AppShowPage } from './app-show.page'
import { EmverPipesModule } from '@start9labs/shared'
import { EmverPipesModule, ResponsiveColModule } from '@start9labs/shared'
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module'
import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module'
@@ -55,6 +55,7 @@ const routes: Routes = [
EmverPipesModule,
LaunchablePipeModule,
UiPipeModule,
ResponsiveColModule,
],
})
export class AppShowPageModule {}

View File

@@ -3,7 +3,7 @@
<app-show-header [pkg]="pkg"></app-show-header>
<!-- content -->
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<!-- ** installing, updating, restoring ** -->
<ng-container *ngIf="showProgress(pkg); else installed">
<app-show-progress

View File

@@ -1,7 +1,7 @@
<ion-item-divider>Additional Info</ion-item-divider>
<ion-grid *ngIf="pkg.manifest as manifest">
<ion-row>
<ion-col sizeXs="12" sizeMd="6">
<ion-col responsiveCol sizeXs="12" sizeMd="6">
<ion-item-group>
<ion-item>
<ion-label>
@@ -51,7 +51,7 @@
</ion-item>
</ion-item-group>
</ion-col>
<ion-col sizeXs="12" sizeMd="6">
<ion-col responsiveCol sizeXs="12" sizeMd="6">
<ion-item-group>
<ion-item
[href]="manifest['upstream-repo']"

View File

@@ -16,14 +16,15 @@ import { ConfigSpec } from 'src/app/pkg-config/config-types'
import * as yaml from 'js-yaml'
import { v4 } from 'uuid'
import { DataModel, DevData } from 'src/app/services/patch-db/data-model'
import { DestroyService, ErrorToastService } from '@start9labs/shared'
import { ErrorToastService } from '@start9labs/shared'
import { TuiDestroyService } from '@taiga-ui/cdk'
import { takeUntil } from 'rxjs/operators'
@Component({
selector: 'developer-list',
templateUrl: 'developer-list.page.html',
styleUrls: ['developer-list.page.scss'],
providers: [DestroyService],
providers: [TuiDestroyService],
})
export class DeveloperListPage {
devData: DevData = {}
@@ -34,7 +35,7 @@ export class DeveloperListPage {
private readonly loadingCtrl: LoadingController,
private readonly errToast: ErrorToastService,
private readonly alertCtrl: AlertController,
private readonly destroy$: DestroyService,
private readonly destroy$: TuiDestroyService,
private readonly patch: PatchDB<DataModel>,
private readonly actionCtrl: ActionSheetController,
) {}

View File

@@ -3,7 +3,11 @@ import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'
import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { SharedPipesModule, EmverPipesModule } from '@start9labs/shared'
import {
SharedPipesModule,
EmverPipesModule,
ResponsiveColModule,
} from '@start9labs/shared'
import {
FilterPackagesPipeModule,
CategoriesModule,
@@ -41,6 +45,7 @@ const routes: Routes = [
SkeletonModule,
MarketplaceSettingsPageModule,
StoreIconComponentModule,
ResponsiveColModule,
],
declarations: [MarketplaceListPage],
exports: [MarketplaceListPage],

View File

@@ -10,7 +10,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<ng-container *ngIf="details$ | async as details">
<ion-item [color]="details.color">
<ion-icon slot="start" name="information-circle-outline"></ion-icon>
@@ -21,7 +21,7 @@
<ion-grid>
<ion-row>
<ion-col size="12">
<ion-col>
<div class="heading">
<store-icon
class="icon"
@@ -38,7 +38,7 @@
</ion-col>
</ion-row>
<ion-row class="ion-align-items-center">
<ion-col size="12">
<ion-col>
<ng-container *ngIf="store$ | async as store; else loading">
<marketplace-categories
[categories]="store.categories"
@@ -55,6 +55,7 @@
<ion-row *ngIf="localPkgs$ | async as localPkgs">
<ion-col
*ngFor="let pkg of filtered"
responsiveCol
sizeXs="12"
sizeSm="12"
sizeMd="6"

View File

@@ -1,6 +1,6 @@
<marketplace-show-header></marketplace-show-header>
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<ng-container *ngIf="pkg$ | async as pkg else loading">
<ng-container *ngIf="pkg | empty; else show">
<div

View File

@@ -10,7 +10,7 @@
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content class="with-widgets">
<!-- loading -->
<ion-item-group *ngIf="loading; else loaded">
<ion-item-divider>

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content class="ion-padding-top with-widgets">
<ion-item-group>
<!-- about -->
<ion-item class="ion-padding-bottom">

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<ion-grid *ngIf="pkgs$ | async as pkgs">
<ion-row *ngIf="backupProgress$ | async as backupProgress">
<ion-col>

View File

@@ -13,13 +13,13 @@ import { PatchDB } from 'patch-db-client'
import { skip, takeUntil } from 'rxjs/operators'
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
import * as argon2 from '@start9labs/argon2'
import { TuiDestroyService } from '@taiga-ui/cdk'
import {
CifsBackupTarget,
DiskBackupTarget,
} from 'src/app/services/api/api.types'
import { BackupSelectPage } from 'src/app/modals/backup-select/backup-select.page'
import { EOSService } from 'src/app/services/eos.service'
import { DestroyService } from '@start9labs/shared'
import { getServerInfo } from 'src/app/util/get-server-info'
import { DataModel } from 'src/app/services/patch-db/data-model'
@@ -27,7 +27,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
selector: 'server-backup',
templateUrl: './server-backup.page.html',
styleUrls: ['./server-backup.page.scss'],
providers: [DestroyService],
providers: [TuiDestroyService],
})
export class ServerBackupPage {
serviceIds: string[] = []
@@ -39,7 +39,7 @@ export class ServerBackupPage {
private readonly modalCtrl: ModalController,
private readonly embassyApi: ApiService,
private readonly navCtrl: NavController,
private readonly destroy$: DestroyService,
private readonly destroy$: TuiDestroyService,
private readonly eosService: EOSService,
private readonly patch: PatchDB<DataModel>,
) {}

View File

@@ -10,7 +10,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<skeleton-list *ngIf="loading" [groups]="2"></skeleton-list>
<div id="metricSection">

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<!-- loading -->
<ng-template #loading>
<text-spinner text="Connecting to Embassy"></text-spinner>

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content class="with-widgets">
<ion-item-group *ngIf="server$ | async as server">
<ion-item-divider>embassyOS Info</ion-item-divider>
<ion-item>

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content class="ion-padding-top with-widgets">
<!-- loading -->
<ion-item-group *ngIf="loading; else notLoading">
<div *ngFor="let entry of ['This Session', 'Other Sessions']">

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-text-center">
<ion-content class="ion-text-center with-widgets">
<!-- file upload -->
<div
*ngIf="!toUpload.file; else fileUploaded"

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content class="ion-padding-top with-widgets">
<ion-item-group>
<!-- always -->
<ion-item>

View File

@@ -13,7 +13,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content class="ion-padding-top with-widgets">
<ion-item-group>
<!-- always -->
<ion-item>

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-content class="ion-padding with-widgets">
<ion-item-group *ngIf="data$ | async as data">
<ng-container *ngFor="let host of data.hosts">
<ion-item-divider class="header">

View File

@@ -0,0 +1,10 @@
<div *ngIf="installed$ | async as installed" class="wrapper">
<button
*ngFor="let widget of widgets | tuiFilter: filter:installed; empty: empty"
class="tui-island tui-island_size_l"
(click)="context.completeWith(widget)"
>
<span class="tui-island__title">{{ widget.meta.name }}</span>
</button>
<ng-template #empty>No additional widgets found</ng-template>
</div>

View File

@@ -0,0 +1,9 @@
.wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
.tui-island {
text-align: left;
}

View File

@@ -0,0 +1,31 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { PatchDB } from 'patch-db-client'
import { Widget } from '../../../../services/patch-db/data-model'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@tinkoff/ng-polymorpheus'
import { TuiDialogContext } from '@taiga-ui/core'
import { BUILT_IN_WIDGETS } from '../widgets'
@Component({
selector: 'add-widget',
templateUrl: './add.component.html',
styleUrls: ['./add.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddWidgetComponent {
readonly context = inject<TuiDialogContext<Widget>>(POLYMORPHEUS_CONTEXT)
readonly installed$ = inject(PatchDB).watch$('ui', 'widgets')
readonly widgets = BUILT_IN_WIDGETS
readonly filter = (widget: Widget, installed: readonly Widget[]) =>
!installed.find(({ id }) => id === widget.id)
}
export const ADD_WIDGET = new PolymorpheusComponent<
AddWidgetComponent,
TuiDialogContext<Widget>
>(AddWidgetComponent)

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { TuiFilterPipeModule, TuiForModule } from '@taiga-ui/cdk'
import { AddWidgetComponent } from './add.component'
@NgModule({
imports: [CommonModule, TuiFilterPipeModule, TuiForModule],
declarations: [AddWidgetComponent],
exports: [AddWidgetComponent],
})
export class AddWidgetModule {}

View File

@@ -0,0 +1 @@
<ion-button class="add">Add to quick launch</ion-button>

View File

@@ -0,0 +1,3 @@
.add {
font-size: 13px;
}

View File

@@ -0,0 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
@Component({
selector: 'widget-favorites',
templateUrl: './favorites.component.html',
styleUrls: ['./favorites.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FavoritesComponent {}

View File

@@ -0,0 +1,10 @@
import { NgModule } from '@angular/core'
import { FavoritesComponent } from './favorites.component'
import { IonicModule } from '@ionic/angular'
@NgModule({
imports: [IonicModule],
declarations: [FavoritesComponent],
exports: [FavoritesComponent],
})
export class FavoritesModule {}

View File

@@ -0,0 +1,11 @@
<h2 class="widget-title">Service health overview</h2>
<tui-ring-chart
*ngIf="data$ | async as data"
class="ring-chart"
[tuiHintContent]="hint"
[value]="data"
>
<ng-template #hint let-index>
{{ labels[index] }}: {{ data[index] }}
</ng-template>
</tui-ring-chart>

View File

@@ -0,0 +1,19 @@
:host {
/* index order must match labels array */
--tui-chart-0: var(--ion-color-danger-tint); // error
--tui-chart-1: var(--ion-color-success-tint); // healthy
--tui-chart-2: var(--ion-color-warning-tint); // needs attention
--tui-chart-3: var(--ion-color-step-600); // stopped
--tui-chart-4: var(--ion-color-primary-tint); // transitioning
}
.widget-title {
margin: 0;
font-size: 18px;
font-weight: bold;
}
.ring-chart {
transform: scale(0.85);
margin: 0.6rem auto;
}

View File

@@ -0,0 +1,64 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { PatchDB } from 'patch-db-client'
import { map } from 'rxjs/operators'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { PrimaryStatus } from 'src/app/services/pkg-status-rendering.service'
import { getPackageInfo, PkgInfo } from '../../../../util/get-package-info'
@Component({
selector: 'widget-health',
templateUrl: './health.component.html',
styleUrls: ['./health.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HealthComponent {
readonly labels = [
'Error',
'Healthy',
'Needs Attention',
'Stopped',
'Transitioning',
] as const
readonly data$ = inject(PatchDB)
.watch$('package-data')
.pipe(
map(data => {
const pkgs = Object.values<PackageDataEntry>(data).map(getPackageInfo)
const result = this.labels.reduce<Record<string, number>>(
(acc, label) => ({
...acc,
[label]: this.getCount(label, pkgs),
}),
{},
)
result['Healthy'] =
pkgs.length -
result['Error'] -
result['Needs Attention'] -
result['Stopped'] -
result['Transitioning']
return this.labels.map(label => result[label])
}),
)
private getCount(label: string, pkgs: PkgInfo[]): number {
switch (label) {
case 'Error':
return pkgs.filter(
a => a.primaryStatus !== PrimaryStatus.Stopped && a.error,
).length
case 'Needs Attention':
return pkgs.filter(a => a.warning).length
case 'Stopped':
return pkgs.filter(a => a.primaryStatus === PrimaryStatus.Stopped)
.length
case 'Transitioning':
return pkgs.filter(a => a.transitioning).length
default:
return 0
}
}
}

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core'
import { HealthComponent } from './health.component'
import { TuiRingChartModule } from '@taiga-ui/addon-charts'
import { TuiHintModule } from '@taiga-ui/core'
import { CommonModule } from '@angular/common'
@NgModule({
imports: [CommonModule, TuiRingChartModule, TuiHintModule],
declarations: [HealthComponent],
exports: [HealthComponent],
})
export class HealthModule {}

View File

@@ -0,0 +1,30 @@
<div class="stats">
<div class="stat">
<ion-icon class="stat-icon" name="server-outline"></ion-icon>
<div>
30%
<div class="description">Storage</div>
</div>
</div>
<div class="stat">
<ion-icon class="stat-icon" name="hardware-chip-outline"></ion-icon>
<div>
10%
<div class="description">CPU</div>
</div>
</div>
<div class="stat">
<ion-icon class="stat-icon" name="stats-chart-outline"></ion-icon>
<div>
10%
<div class="description">Memory</div>
</div>
</div>
<div class="stat">
<ion-icon class="stat-icon" name="thermometer-outline"></ion-icon>
<div>
50.6⁰C
<div class="description">Temp</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,34 @@
.stats {
display: flex;
align-items: center;
justify-content: space-around;
flex-wrap: wrap;
height: 100%;
text-align: center;
&_mobile .stat {
width: 50%;
}
}
.stat {
display: flex;
align-items: center;
justify-content: center;
:host-context(.wrapper_mobile) & {
width: 50%;
}
}
.stat-icon {
font-size: 32px;
margin: 12px;
}
.description {
color: #3a7be0;
text-transform: uppercase;
font-weight: bold;
font-size: 12px;
}

View File

@@ -0,0 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
@Component({
selector: 'widget-metrics',
templateUrl: './metrics.component.html',
styleUrls: ['./metrics.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MetricsComponent {}

View File

@@ -0,0 +1,10 @@
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { MetricsComponent } from './metrics.component'
@NgModule({
imports: [IonicModule],
declarations: [MetricsComponent],
exports: [MetricsComponent],
})
export class MetricsModule {}

View File

@@ -0,0 +1,7 @@
<iframe
class="iframe"
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>

View File

@@ -0,0 +1,13 @@
:host {
border-radius: inherit;
}
.iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
border-radius: inherit;
}

View File

@@ -0,0 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
@Component({
selector: 'widget-network',
templateUrl: './network.component.html',
styleUrls: ['./network.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NetworkComponent {}

View File

@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core'
import { NetworkComponent } from './network.component'
@NgModule({
imports: [],
declarations: [NetworkComponent],
exports: [NetworkComponent],
})
export class NetworkModule {}

View File

@@ -0,0 +1 @@
System time and uptime

Some files were not shown because too many files have changed in this diff Show More