mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
only emit when things change (#2428)
* only emit when things change * remove log * remove test call * more efficient, thanks BluJ * point free
This commit is contained in:
54
frontend/package-lock.json
generated
54
frontend/package-lock.json
generated
@@ -37,6 +37,7 @@
|
||||
"cbor-web": "^8.1.0",
|
||||
"core-js": "^3.21.1",
|
||||
"dompurify": "^2.3.6",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-json-patch": "^3.1.1",
|
||||
"fuse.js": "^6.4.6",
|
||||
"jose": "^4.9.0",
|
||||
@@ -7512,6 +7513,20 @@
|
||||
"clone": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz",
|
||||
"integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/define-lazy-prop": {
|
||||
"version": "2.0.0",
|
||||
"devOptional": true,
|
||||
@@ -7521,10 +7536,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/define-properties": {
|
||||
"version": "1.1.4",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
||||
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.0",
|
||||
"object-keys": "^1.1.1"
|
||||
},
|
||||
@@ -8208,7 +8225,8 @@
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"license": "MIT"
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.12",
|
||||
@@ -8560,12 +8578,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.1.3",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -8704,6 +8724,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.10",
|
||||
"devOptional": true,
|
||||
@@ -8758,6 +8790,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"dev": true,
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"cbor-web": "^8.1.0",
|
||||
"core-js": "^3.21.1",
|
||||
"dompurify": "^2.3.6",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-json-patch": "^3.1.1",
|
||||
"fuse.js": "^6.4.6",
|
||||
"jose": "^4.9.0",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
sizeMd="6"
|
||||
>
|
||||
<app-list-pkg
|
||||
*ngIf="pkg | packageInfo | async as info"
|
||||
*ngIf="pkg.manifest.id | packageInfo | async as info"
|
||||
[pkg]="info"
|
||||
></app-list-pkg>
|
||||
</ion-col>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { Observable, combineLatest } from 'rxjs'
|
||||
import { filter, map, startWith } from 'rxjs/operators'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { Observable, combineLatest, firstValueFrom } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { getPackageInfo, PkgInfo } from '../../../util/get-package-info'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DepErrorService } from 'src/app/services/dep-error.service'
|
||||
@@ -18,12 +15,10 @@ export class PackageInfoPipe implements PipeTransform {
|
||||
private readonly depErrorService: DepErrorService,
|
||||
) {}
|
||||
|
||||
transform(pkg: PackageDataEntry): Observable<PkgInfo> {
|
||||
transform(pkgId: string): Observable<PkgInfo> {
|
||||
return combineLatest([
|
||||
this.patch
|
||||
.watch$('package-data', pkg.manifest.id)
|
||||
.pipe(filter(Boolean), startWith(pkg)),
|
||||
this.depErrorService.depErrors$,
|
||||
this.patch.watch$('package-data', pkgId),
|
||||
this.depErrorService.getPkgDepErrors$(pkgId),
|
||||
]).pipe(map(([pkg, depErrors]) => getPackageInfo(pkg, depErrors)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
import {
|
||||
DepErrorService,
|
||||
DependencyErrorType,
|
||||
PackageDependencyErrors,
|
||||
PkgDependencyErrors,
|
||||
} from 'src/app/services/dep-error.service'
|
||||
import { combineLatest } from 'rxjs'
|
||||
|
||||
@@ -50,15 +50,14 @@ export class AppShowPage {
|
||||
private readonly pkgId = getPkgId(this.route)
|
||||
|
||||
readonly pkgPlus$ = combineLatest([
|
||||
this.patch.watch$('package-data'),
|
||||
this.depErrorService.depErrors$,
|
||||
this.patch.watch$('package-data', this.pkgId),
|
||||
this.depErrorService.getPkgDepErrors$(this.pkgId),
|
||||
]).pipe(
|
||||
tap(([pkgs, _]) => {
|
||||
tap(([pkg, _]) => {
|
||||
// if package disappears, navigate to list page
|
||||
if (!pkgs[this.pkgId]) this.navCtrl.navigateRoot('/services')
|
||||
if (!pkg) this.navCtrl.navigateRoot('/services')
|
||||
}),
|
||||
map(([pkgs, depErrors]) => {
|
||||
const pkg = pkgs[this.pkgId]
|
||||
map(([pkg, depErrors]) => {
|
||||
return {
|
||||
pkg,
|
||||
dependencies: this.getDepInfo(pkg, depErrors),
|
||||
@@ -93,7 +92,7 @@ export class AppShowPage {
|
||||
|
||||
private getDepInfo(
|
||||
pkg: PackageDataEntry,
|
||||
depErrors: PackageDependencyErrors,
|
||||
depErrors: PkgDependencyErrors,
|
||||
): DependencyInfo[] {
|
||||
const pkgInstalled = pkg.installed
|
||||
|
||||
@@ -107,7 +106,7 @@ export class AppShowPage {
|
||||
private getDepValues(
|
||||
pkgInstalled: InstalledPackageDataEntry,
|
||||
depId: string,
|
||||
depErrors: PackageDependencyErrors,
|
||||
depErrors: PkgDependencyErrors,
|
||||
): DependencyInfo {
|
||||
const { errorText, fixText, fixAction } = this.getDepErrors(
|
||||
pkgInstalled,
|
||||
@@ -134,10 +133,10 @@ export class AppShowPage {
|
||||
private getDepErrors(
|
||||
pkgInstalled: InstalledPackageDataEntry,
|
||||
depId: string,
|
||||
depErrors: PackageDependencyErrors,
|
||||
depErrors: PkgDependencyErrors,
|
||||
) {
|
||||
const pkgManifest = pkgInstalled.manifest
|
||||
const depError = depErrors[pkgInstalled.manifest.id][depId]
|
||||
const depError = depErrors[depId]
|
||||
|
||||
let errorText: string | null = null
|
||||
let fixText: string | null = null
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
} 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'
|
||||
import { combineLatest } from 'rxjs'
|
||||
import { DepErrorService } from 'src/app/services/dep-error.service'
|
||||
|
||||
@Component({
|
||||
selector: 'widget-health',
|
||||
@@ -23,31 +25,32 @@ export class HealthComponent {
|
||||
'Transitioning',
|
||||
] as const
|
||||
|
||||
readonly data$ = inject(PatchDB<DataModel>)
|
||||
.watch$('package-data')
|
||||
.pipe(
|
||||
map(data => {
|
||||
const pkgs = Object.values<PackageDataEntry>(data).map(
|
||||
pkg => getPackageInfo(pkg, {}), // @TODO hack because not currently using widget
|
||||
)
|
||||
const result = this.labels.reduce<Record<string, number>>(
|
||||
(acc, label) => ({
|
||||
...acc,
|
||||
[label]: this.getCount(label, pkgs),
|
||||
}),
|
||||
{},
|
||||
)
|
||||
readonly data$ = combineLatest([
|
||||
inject(PatchDB<DataModel>).watch$('package-data'),
|
||||
inject(DepErrorService).depErrors$,
|
||||
]).pipe(
|
||||
map(([data, depErrors]) => {
|
||||
const pkgs = Object.values<PackageDataEntry>(data).map(pkg =>
|
||||
getPackageInfo(pkg, depErrors[pkg.manifest.id]),
|
||||
)
|
||||
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']
|
||||
result['Healthy'] =
|
||||
pkgs.length -
|
||||
result['Error'] -
|
||||
result['Needs Attention'] -
|
||||
result['Stopped'] -
|
||||
result['Transitioning']
|
||||
|
||||
return this.labels.map(label => result[label])
|
||||
}),
|
||||
)
|
||||
return this.labels.map(label => result[label])
|
||||
}),
|
||||
)
|
||||
|
||||
private getCount(label: string, pkgs: PkgInfo[]): number {
|
||||
switch (label) {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Emver } from '@start9labs/shared'
|
||||
import { map, shareReplay } from 'rxjs/operators'
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
pairwise,
|
||||
shareReplay,
|
||||
startWith,
|
||||
tap,
|
||||
} from 'rxjs/operators'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
@@ -9,9 +17,10 @@ import {
|
||||
InstalledPackageDataEntry,
|
||||
PackageMainStatus,
|
||||
} from './patch-db/data-model'
|
||||
import * as deepEqual from 'fast-deep-equal'
|
||||
|
||||
export type PackageDependencyErrors = Record<string, DependencyErrors>
|
||||
export type DependencyErrors = Record<string, DependencyError | null>
|
||||
export type AllDependencyErrors = Record<string, PkgDependencyErrors>
|
||||
export type PkgDependencyErrors = Record<string, DependencyError | null>
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -26,13 +35,14 @@ export class DepErrorService {
|
||||
}))
|
||||
.sort((a, b) => (b.depth > a.depth ? -1 : 1))
|
||||
.reduce(
|
||||
(errors, { id }): PackageDependencyErrors => ({
|
||||
(errors, { id }): AllDependencyErrors => ({
|
||||
...errors,
|
||||
[id]: this.getDepErrors(pkgs, id, errors),
|
||||
}),
|
||||
{} as PackageDependencyErrors,
|
||||
{} as AllDependencyErrors,
|
||||
),
|
||||
),
|
||||
distinctUntilChanged(deepEqual),
|
||||
shareReplay(1),
|
||||
)
|
||||
|
||||
@@ -41,21 +51,28 @@ export class DepErrorService {
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
getPkgDepErrors$(pkgId: string) {
|
||||
return this.depErrors$.pipe(
|
||||
map(depErrors => depErrors[pkgId]),
|
||||
distinctUntilChanged(deepEqual),
|
||||
)
|
||||
}
|
||||
|
||||
private getDepErrors(
|
||||
pkgs: DataModel['package-data'],
|
||||
pkgId: string,
|
||||
outerErrors: PackageDependencyErrors,
|
||||
): DependencyErrors {
|
||||
outerErrors: AllDependencyErrors,
|
||||
): PkgDependencyErrors {
|
||||
const pkgInstalled = pkgs[pkgId].installed
|
||||
|
||||
if (!pkgInstalled) return {}
|
||||
|
||||
return currentDeps(pkgs, pkgId).reduce(
|
||||
(innerErrors, depId): DependencyErrors => ({
|
||||
(innerErrors, depId): PkgDependencyErrors => ({
|
||||
...innerErrors,
|
||||
[depId]: this.getDepError(pkgs, pkgInstalled, depId, outerErrors),
|
||||
}),
|
||||
{} as DependencyErrors,
|
||||
{} as PkgDependencyErrors,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -63,7 +80,7 @@ export class DepErrorService {
|
||||
pkgs: DataModel['package-data'],
|
||||
pkgInstalled: InstalledPackageDataEntry,
|
||||
depId: string,
|
||||
outerErrors: PackageDependencyErrors,
|
||||
outerErrors: AllDependencyErrors,
|
||||
): DependencyError | null {
|
||||
const depInstalled = pkgs[depId]?.installed
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { isEmptyObject } from '@start9labs/shared'
|
||||
import {
|
||||
InstalledPackageDataEntry,
|
||||
MainStatusStarting,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
Status,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { PackageDependencyErrors } from './dep-error.service'
|
||||
import { PkgDependencyErrors } from './dep-error.service'
|
||||
|
||||
export interface PackageStatus {
|
||||
primary: PrimaryStatus
|
||||
@@ -17,7 +16,7 @@ export interface PackageStatus {
|
||||
|
||||
export function renderPkgStatus(
|
||||
pkg: PackageDataEntry,
|
||||
depErrors: PackageDependencyErrors,
|
||||
depErrors: PkgDependencyErrors,
|
||||
): PackageStatus {
|
||||
let primary: PrimaryStatus
|
||||
let dependency: DependencyStatus | null = null
|
||||
@@ -25,7 +24,7 @@ export function renderPkgStatus(
|
||||
|
||||
if (pkg.state === PackageState.Installed && pkg.installed) {
|
||||
primary = getPrimaryStatus(pkg.installed.status)
|
||||
dependency = getDependencyStatus(pkg.installed, depErrors)
|
||||
dependency = getDependencyStatus(depErrors)
|
||||
health = getHealthStatus(
|
||||
pkg.installed.status,
|
||||
!isEmptyObject(pkg.manifest['health-checks']),
|
||||
@@ -47,11 +46,8 @@ function getPrimaryStatus(status: Status): PrimaryStatus {
|
||||
}
|
||||
}
|
||||
|
||||
function getDependencyStatus(
|
||||
pkg: InstalledPackageDataEntry,
|
||||
depErrors: PackageDependencyErrors,
|
||||
): DependencyStatus {
|
||||
return Object.values(depErrors[pkg.manifest.id]).some(err => !!err)
|
||||
function getDependencyStatus(depErrors: PkgDependencyErrors): DependencyStatus {
|
||||
return Object.values(depErrors).some(err => !!err)
|
||||
? DependencyStatus.Warning
|
||||
: DependencyStatus.Satisfied
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
import { ProgressData } from 'src/app/types/progress-data'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { packageLoadingProgress } from './package-loading-progress'
|
||||
import { PackageDependencyErrors } from '../services/dep-error.service'
|
||||
import { PkgDependencyErrors } from '../services/dep-error.service'
|
||||
|
||||
export function getPackageInfo(
|
||||
entry: PackageDataEntry,
|
||||
depErrors: PackageDependencyErrors,
|
||||
depErrors: PkgDependencyErrors,
|
||||
): PkgInfo {
|
||||
const statuses = renderPkgStatus(entry, depErrors)
|
||||
const primaryRendering = PrimaryRendering[statuses.primary]
|
||||
|
||||
Reference in New Issue
Block a user