mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Next (#2170)
* 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:
committed by
Aiden McClelland
parent
aeb6da111b
commit
e867f31c31
@@ -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",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"useMocks": true,
|
||||
"enableWidgets": false,
|
||||
"packageArch": "aarch64",
|
||||
"osArch": "raspberrypi",
|
||||
"ui": {
|
||||
|
||||
1179
frontend/package-lock.json
generated
1179
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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": {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
tui-root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
tui-root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -2,3 +2,7 @@
|
||||
display: block;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.column {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<ion-row>
|
||||
<ion-col
|
||||
*ngFor="let pkg of ['', '', '', '']"
|
||||
responsiveCol
|
||||
sizeXs="12"
|
||||
sizeSm="12"
|
||||
sizeMd="6"
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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']"
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<ion-row>
|
||||
<ion-col
|
||||
*ngFor="let dep of pkg.manifest.dependencies | keyvalue"
|
||||
responsiveCol
|
||||
sizeSm="12"
|
||||
sizeMd="6"
|
||||
>
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
tui-root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<div (elasticContainer)="height = $event">
|
||||
<div class="wrapper" (elasticContainer)="height = $event">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
||||
@@ -3,3 +3,8 @@
|
||||
overflow: hidden;
|
||||
transition: height 0.25s;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding-top: 1px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
|
||||
import { ElementDirective } from './element.directive'
|
||||
|
||||
@NgModule({
|
||||
declarations: [ElementDirective],
|
||||
exports: [ElementDirective],
|
||||
})
|
||||
export class ElementModule {}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
@@ -80,7 +80,6 @@ const routes: Routes = [
|
||||
scrollPositionRestoration: 'enabled',
|
||||
preloadingStrategy: PreloadAllModules,
|
||||
initialNavigation: 'disabled',
|
||||
useHash: true,
|
||||
}),
|
||||
],
|
||||
exports: [RouterModule],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']"
|
||||
|
||||
@@ -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,
|
||||
) {}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>,
|
||||
) {}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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']">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,9 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tui-island {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1 @@
|
||||
<ion-button class="add">Add to quick launch</ion-button>
|
||||
@@ -0,0 +1,3 @@
|
||||
.add {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,13 @@
|
||||
:host {
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-radius: inherit;
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { NetworkComponent } from './network.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
declarations: [NetworkComponent],
|
||||
exports: [NetworkComponent],
|
||||
})
|
||||
export class NetworkModule {}
|
||||
@@ -0,0 +1 @@
|
||||
System time and uptime
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user