mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Merge branch 'integration/new-container-runtime' of github.com:Start9Labs/start-os into integration/new-container-runtime
This commit is contained in:
@@ -240,6 +240,11 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
)
|
||||
},
|
||||
|
||||
Mounts: {
|
||||
of() {
|
||||
return Mounts.of<Manifest>()
|
||||
},
|
||||
},
|
||||
HealthCheck: {
|
||||
of: healthCheck,
|
||||
},
|
||||
|
||||
2
web/package-lock.json
generated
2
web/package-lock.json
generated
@@ -1973,7 +1973,7 @@
|
||||
},
|
||||
"../sdk/dist": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-rev0.lib0.rc8.beta7",
|
||||
"version": "0.4.0-rev0.lib0.rc8.beta10",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
|
||||
@@ -20,11 +20,18 @@ import {
|
||||
} from 'rxjs'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { DataModel, PackageState } from 'src/app/services/patch-db/data-model'
|
||||
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
|
||||
import { Emver, THEME } from '@start9labs/shared'
|
||||
import { ConnectionService } from 'src/app/services/connection.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import {
|
||||
getManifest,
|
||||
isInstalled,
|
||||
isInstalling,
|
||||
isRestoring,
|
||||
isUpdating,
|
||||
} from 'src/app/util/get-package-data'
|
||||
|
||||
@Component({
|
||||
selector: 'app-menu',
|
||||
@@ -79,8 +86,14 @@ export class MenuComponent {
|
||||
filter(([prev, curr]) =>
|
||||
Object.values(prev).some(
|
||||
p =>
|
||||
p['install-progress'] &&
|
||||
!curr[p.manifest.id]?.['install-progress'],
|
||||
[
|
||||
PackageState.Installing,
|
||||
PackageState.Updating,
|
||||
PackageState.Restoring,
|
||||
].includes(p['state-info'].state) &&
|
||||
[PackageState.Installed, PackageState.Removing].includes(
|
||||
curr[getManifest(p).id]['state-info'].state,
|
||||
),
|
||||
),
|
||||
),
|
||||
map(([_, curr]) => curr),
|
||||
@@ -97,9 +110,10 @@ export class MenuComponent {
|
||||
Object.entries(marketplace).reduce((list, [_, store]) => {
|
||||
store?.packages.forEach(({ manifest: { id, version } }) => {
|
||||
if (
|
||||
local[id] &&
|
||||
this.emver.compare(
|
||||
version,
|
||||
local[id]?.installed?.manifest.version || '',
|
||||
getManifest(local[id]).version || '',
|
||||
) === 1
|
||||
)
|
||||
list.add(id)
|
||||
|
||||
@@ -12,12 +12,9 @@
|
||||
. This may take a while
|
||||
</span>
|
||||
|
||||
<span *ngIf="installProgress">
|
||||
<ion-text
|
||||
*ngIf="installProgress | installProgressDisplay as progress"
|
||||
color="primary"
|
||||
>
|
||||
{{ progress }}
|
||||
<span *ngIf="installingInfo">
|
||||
<ion-text color="primary">
|
||||
{{ installingInfo.progress.overall | installingProgressString }}
|
||||
</ion-text>
|
||||
</span>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { UnitConversionPipesModule } from '@start9labs/shared'
|
||||
import { StatusComponent } from './status.component'
|
||||
import { InstallProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module'
|
||||
import { InstallingProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [StatusComponent],
|
||||
@@ -11,7 +11,7 @@ import { InstallProgressPipeModule } from 'src/app/pipes/install-progress/instal
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
UnitConversionPipesModule,
|
||||
InstallProgressPipeModule,
|
||||
InstallingProgressPipeModule,
|
||||
],
|
||||
exports: [StatusComponent],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { ConnectionService } from 'src/app/services/connection.service'
|
||||
import { InstallProgress } from 'src/app/services/patch-db/data-model'
|
||||
import { InstallingInfo } from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
PrimaryRendering,
|
||||
PrimaryStatus,
|
||||
@@ -20,7 +20,7 @@ export class StatusComponent {
|
||||
@Input() size?: string
|
||||
@Input() style?: string = 'regular'
|
||||
@Input() weight?: string = 'normal'
|
||||
@Input() installProgress?: InstallProgress
|
||||
@Input() installingInfo?: InstallingInfo
|
||||
@Input() sigtermTimeout?: string | null = null
|
||||
|
||||
readonly connected$ = this.connectionService.connected$
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-content class="ion-padding" *ngIf="pkg['state-info'].manifest as manifest">
|
||||
<!-- loading -->
|
||||
<text-spinner
|
||||
*ngIf="loading; else notLoading"
|
||||
@@ -25,14 +25,14 @@
|
||||
</ion-item>
|
||||
|
||||
<ng-template #noError>
|
||||
<ng-container *ngIf="configForm && !pkg.installed?.status?.configured">
|
||||
<ng-container *ngIf="configForm && !pkg.status.configured">
|
||||
<ng-container *ngIf="!original; else hasOriginal">
|
||||
<h2
|
||||
*ngIf="!configForm.dirty"
|
||||
class="ion-padding-bottom header-details"
|
||||
>
|
||||
<ion-text color="success">
|
||||
{{ pkg.manifest.title }} has been automatically configured with
|
||||
{{ manifest.title }} has been automatically configured with
|
||||
recommended defaults. Make whatever changes you want, then click
|
||||
"Save".
|
||||
</ion-text>
|
||||
@@ -59,19 +59,19 @@
|
||||
<h2 style="display: flex; align-items: center">
|
||||
<img
|
||||
style="width: 18px; margin: 4px"
|
||||
[src]="pkg['static-files'].icon"
|
||||
[alt]="pkg.manifest.title"
|
||||
[src]="pkg.icon"
|
||||
[alt]="manifest.title"
|
||||
/>
|
||||
<ion-text
|
||||
style="margin: 5px; font-family: 'Montserrat'; font-size: 18px"
|
||||
>
|
||||
{{ pkg.manifest.title }}
|
||||
{{ manifest.title }}
|
||||
</ion-text>
|
||||
</h2>
|
||||
<p>
|
||||
<ion-text color="dark">
|
||||
The following modifications have been made to {{
|
||||
pkg.manifest.title }} to satisfy {{ dependentInfo.title }}:
|
||||
The following modifications have been made to {{ manifest.title }}
|
||||
to satisfy {{ dependentInfo.title }}:
|
||||
<ul>
|
||||
<li *ngFor="let d of diff" [innerHtml]="d"></li>
|
||||
</ul>
|
||||
@@ -85,8 +85,7 @@
|
||||
<ion-item *ngIf="!hasOptions">
|
||||
<ion-label>
|
||||
<p>
|
||||
No config options for {{ pkg.manifest.title }} {{
|
||||
pkg.manifest.version }}.
|
||||
No config options for {{ manifest.title }} {{ manifest.version }}.
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||
import {
|
||||
DataModel,
|
||||
InstalledState,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
@@ -26,7 +27,12 @@ import {
|
||||
} from 'src/app/services/form.service'
|
||||
import { compare, Operation, getValueByPointer } from 'fast-json-patch'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { getAllPackages, getPackage } from 'src/app/util/get-package-data'
|
||||
import {
|
||||
getAllPackages,
|
||||
getManifest,
|
||||
getPackage,
|
||||
isInstalled,
|
||||
} from 'src/app/util/get-package-data'
|
||||
import { Breakages } from 'src/app/services/api/api.types'
|
||||
|
||||
@Component({
|
||||
@@ -39,7 +45,7 @@ export class AppConfigPage {
|
||||
|
||||
@Input() dependentInfo?: DependentInfo
|
||||
|
||||
pkg!: PackageDataEntry
|
||||
pkg!: PackageDataEntry<InstalledState>
|
||||
loadingText = ''
|
||||
|
||||
configSpec?: ConfigSpec
|
||||
@@ -68,10 +74,11 @@ export class AppConfigPage {
|
||||
async ngOnInit() {
|
||||
try {
|
||||
const pkg = await getPackage(this.patch, this.pkgId)
|
||||
if (!pkg) return
|
||||
if (!pkg || !isInstalled(pkg)) return
|
||||
|
||||
this.pkg = pkg
|
||||
|
||||
if (!this.pkg.manifest.config) return
|
||||
if (!this.pkg['state-info'].manifest.config) return
|
||||
|
||||
let newConfig: object | undefined
|
||||
let patch: Operation[] | undefined
|
||||
@@ -210,7 +217,7 @@ export class AppConfigPage {
|
||||
'As a result of this change, the following services will no longer work properly and may crash:<ul>'
|
||||
const localPkgs = await getAllPackages(this.patch)
|
||||
const bullets = Object.keys(breakages).map(id => {
|
||||
const title = localPkgs[id].manifest.title
|
||||
const title = getManifest(localPkgs[id]).title
|
||||
return `<li><b>${title}</b></li>`
|
||||
})
|
||||
message = `${message}${bullets}</ul>`
|
||||
|
||||
@@ -4,6 +4,7 @@ import { map, take } from 'rxjs/operators'
|
||||
import { DataModel, PackageState } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
import { getManifest } from 'src/app/util/get-package-data'
|
||||
|
||||
@Component({
|
||||
selector: 'backup-select',
|
||||
@@ -32,13 +33,13 @@ export class BackupSelectPage {
|
||||
map(pkgs => {
|
||||
return Object.values(pkgs)
|
||||
.map(pkg => {
|
||||
const { id, title } = pkg.manifest
|
||||
const { id, title } = getManifest(pkg)
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
icon: pkg['static-files'].icon,
|
||||
disabled: pkg.state !== PackageState.Installed,
|
||||
checked: pkg.state === PackageState.Installed,
|
||||
icon: pkg.icon,
|
||||
disabled: pkg['state-info'].state !== PackageState.Installed,
|
||||
checked: false,
|
||||
}
|
||||
})
|
||||
.sort((a, b) =>
|
||||
|
||||
@@ -8,30 +8,34 @@
|
||||
</ion-header>
|
||||
|
||||
<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>
|
||||
<app-actions-item
|
||||
[action]="{
|
||||
name: 'Uninstall',
|
||||
description: 'This will uninstall the service from StartOS and delete all data permanently.',
|
||||
icon: 'trash-outline'
|
||||
}"
|
||||
(click)="tryUninstall(pkg)"
|
||||
></app-actions-item>
|
||||
<ng-container *ngIf="pkg$ | async as pkg">
|
||||
<ion-item-group
|
||||
*ngIf="pkg['state-info'].state === 'installed' && pkg['state-info'].manifest as manifest"
|
||||
>
|
||||
<!-- ** standard actions ** -->
|
||||
<ion-item-divider>Standard Actions</ion-item-divider>
|
||||
<app-actions-item
|
||||
[action]="{
|
||||
name: 'Uninstall',
|
||||
description: 'This will uninstall the service from StartOS and delete all data permanently.',
|
||||
icon: 'trash-outline'
|
||||
}"
|
||||
(click)="tryUninstall(pkg)"
|
||||
></app-actions-item>
|
||||
|
||||
<!-- ** specific actions ** -->
|
||||
<ion-item-divider *ngIf="!(pkg.manifest.actions | empty)">
|
||||
Actions for {{ pkg.manifest.title }}
|
||||
</ion-item-divider>
|
||||
<app-actions-item
|
||||
*ngFor="let action of pkg.manifest.actions | keyvalue: asIsOrder"
|
||||
[action]="{
|
||||
name: action.value.name,
|
||||
description: action.value.description,
|
||||
icon: 'play-circle-outline'
|
||||
}"
|
||||
(click)="handleAction(pkg, action)"
|
||||
></app-actions-item>
|
||||
</ion-item-group>
|
||||
<!-- ** specific actions ** -->
|
||||
<ion-item-divider *ngIf="!(manifest.actions | empty)">
|
||||
Actions for {{ manifest.title }}
|
||||
</ion-item-divider>
|
||||
<app-actions-item
|
||||
*ngFor="let action of manifest.actions | keyvalue: asIsOrder"
|
||||
[action]="{
|
||||
name: action.value.name,
|
||||
description: action.value.description,
|
||||
icon: 'play-circle-outline'
|
||||
}"
|
||||
(click)="handleAction(pkg.status, action)"
|
||||
></app-actions-item>
|
||||
</ion-item-group>
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
|
||||
@@ -11,13 +11,17 @@ import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
Action,
|
||||
DataModel,
|
||||
InstalledState,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
StateInfo,
|
||||
Status,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||
import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared'
|
||||
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { getManifest } from 'src/app/util/get-package-data'
|
||||
|
||||
@Component({
|
||||
selector: 'app-actions',
|
||||
@@ -40,11 +44,7 @@ export class AppActionsPage {
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
async handleAction(
|
||||
pkg: PackageDataEntry,
|
||||
action: { key: string; value: Action },
|
||||
) {
|
||||
const status = pkg.installed?.status
|
||||
async handleAction(status: Status, action: { key: string; value: Action }) {
|
||||
if (
|
||||
status &&
|
||||
(action.value['allowed-statuses'] as PackageMainStatus[]).includes(
|
||||
@@ -120,7 +120,7 @@ export class AppActionsPage {
|
||||
}
|
||||
|
||||
async tryUninstall(pkg: PackageDataEntry): Promise<void> {
|
||||
const { title, alerts } = pkg.manifest
|
||||
const { title, alerts } = getManifest(pkg)
|
||||
|
||||
let message =
|
||||
alerts.uninstall ||
|
||||
|
||||
@@ -29,7 +29,7 @@ export class AppInterfacesPage {
|
||||
readonly pkgId = getPkgId(this.route)
|
||||
|
||||
readonly serviceInterfaces$ = this.patch
|
||||
.watch$('package-data', this.pkgId, 'installed', 'service-interfaces')
|
||||
.watch$('package-data', this.pkgId, 'service-interfaces')
|
||||
.pipe(
|
||||
map(interfaces => {
|
||||
const sorted = Object.values(interfaces)
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
<ion-item
|
||||
button
|
||||
*ngIf="pkg.entry.manifest as manifest"
|
||||
*ngIf="pkg.entry | toManifest as manifest"
|
||||
detail="false"
|
||||
class="service-card"
|
||||
[routerLink]="['/services', manifest.id]"
|
||||
>
|
||||
<app-list-icon slot="start" [pkg]="pkg"></app-list-icon>
|
||||
<ion-thumbnail slot="start">
|
||||
<img alt="" [src]="pkg.entry['static-files'].icon" />
|
||||
<img alt="" [src]="pkg.entry.icon" />
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2 ticker>{{ manifest.title }}</h2>
|
||||
<p>{{ manifest.version | displayEmver }}</p>
|
||||
<status
|
||||
[rendering]="pkg.primaryRendering"
|
||||
[installProgress]="pkg.entry['install-progress']"
|
||||
[installingInfo]="$any(pkg.entry['state-info'])['installing-info']"
|
||||
weight="bold"
|
||||
size="small"
|
||||
[sigtermTimeout]="sigtermTimeout"
|
||||
></status>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
*ngIf="
|
||||
pkg.entry.installed && (pkg.entry.installed['service-interfaces'] | hasUi)
|
||||
"
|
||||
*ngIf="pkg.entry['service-interfaces'] | hasUi"
|
||||
slot="end"
|
||||
fill="clear"
|
||||
color="primary"
|
||||
(click)="launchUi($event, pkg.entry.installed['service-interfaces'])"
|
||||
[disabled]="!(pkg.entry.state | isLaunchable: pkgMainStatus.status)"
|
||||
(click)="launchUi($event, pkg.entry['service-interfaces'])"
|
||||
[disabled]="
|
||||
!(pkg.entry['state-info'].state | isLaunchable: pkgMainStatus.status)
|
||||
"
|
||||
>
|
||||
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
|
||||
</ion-button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import {
|
||||
InstalledPackageDataEntry,
|
||||
MainStatus,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { PkgInfo } from 'src/app/util/get-package-info'
|
||||
@@ -20,7 +20,7 @@ export class AppListPkgComponent {
|
||||
|
||||
get pkgMainStatus(): MainStatus {
|
||||
return (
|
||||
this.pkg.entry.installed?.status.main || {
|
||||
this.pkg.entry.status.main || {
|
||||
status: PackageMainStatus.Stopped,
|
||||
}
|
||||
)
|
||||
@@ -32,10 +32,7 @@ export class AppListPkgComponent {
|
||||
: null
|
||||
}
|
||||
|
||||
launchUi(
|
||||
e: Event,
|
||||
interfaces: InstalledPackageDataEntry['service-interfaces'],
|
||||
): void {
|
||||
launchUi(e: Event, interfaces: PackageDataEntry['service-interfaces']): void {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.launcherService.launch(interfaces)
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
sizeMd="6"
|
||||
>
|
||||
<app-list-pkg
|
||||
*ngIf="pkg.manifest.id | packageInfo | async as info"
|
||||
*ngIf="(pkg | toManifest).id | packageInfo | async as info"
|
||||
[pkg]="info"
|
||||
></app-list-pkg>
|
||||
</ion-col>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { filter, map, pairwise, startWith } from 'rxjs/operators'
|
||||
import { getManifest } from 'src/app/util/get-package-data'
|
||||
|
||||
@Component({
|
||||
selector: 'app-list',
|
||||
@@ -20,7 +21,7 @@ export class AppListPage {
|
||||
}),
|
||||
map(([_, pkgs]) =>
|
||||
pkgs.sort((a, b) =>
|
||||
b.manifest.title.toLowerCase() > a.manifest.title.toLowerCase()
|
||||
getManifest(b).title.toLowerCase() > getManifest(a).title.toLowerCase()
|
||||
? -1
|
||||
: 1,
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { Observable, combineLatest, firstValueFrom } from 'rxjs'
|
||||
import { Observable, combineLatest } from 'rxjs'
|
||||
import { filter, map } from 'rxjs/operators'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { getPackageInfo, PkgInfo } from '../../../util/get-package-info'
|
||||
|
||||
@@ -41,7 +41,7 @@ export class AppPropertiesPage {
|
||||
unmasked: { [key: string]: boolean } = {}
|
||||
|
||||
stopped$ = this.patch
|
||||
.watch$('package-data', this.pkgId, 'installed', 'status', 'main', 'status')
|
||||
.watch$('package-data', this.pkgId, 'status', 'main', 'status')
|
||||
.pipe(map(status => status === PackageMainStatus.Stopped))
|
||||
|
||||
@ViewChild(IonBackButtonDelegate, { static: false })
|
||||
|
||||
@@ -18,7 +18,7 @@ import { AppShowAdditionalComponent } from './components/app-show-additional/app
|
||||
import { HealthColorPipe } from './pipes/health-color.pipe'
|
||||
import { ToHealthChecksPipe } from './pipes/to-health-checks.pipe'
|
||||
import { ToButtonsPipe } from './pipes/to-buttons.pipe'
|
||||
import { ProgressDataPipe } from './pipes/progress-data.pipe'
|
||||
import { InstallingProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -31,7 +31,6 @@ const routes: Routes = [
|
||||
declarations: [
|
||||
AppShowPage,
|
||||
HealthColorPipe,
|
||||
ProgressDataPipe,
|
||||
ToHealthChecksPipe,
|
||||
ToButtonsPipe,
|
||||
AppShowHeaderComponent,
|
||||
@@ -44,7 +43,7 @@ const routes: Routes = [
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
StatusComponentModule,
|
||||
InstallingProgressPipeModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
AppConfigPageModule,
|
||||
@@ -52,6 +51,7 @@ const routes: Routes = [
|
||||
LaunchablePipeModule,
|
||||
UiPipeModule,
|
||||
ResponsiveColModule,
|
||||
StatusComponentModule,
|
||||
],
|
||||
})
|
||||
export class AppShowPageModule {}
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
<!-- ** installing, updating, restoring ** -->
|
||||
<ng-container *ngIf="showProgress(pkg); else installed">
|
||||
<app-show-progress
|
||||
*ngIf="pkg | progressData as progressData"
|
||||
[pkg]="pkg"
|
||||
[progressData]="progressData"
|
||||
*ngIf="pkg['state-info']['installing-info'] as installingInfo"
|
||||
[phases]="installingInfo.progress.phases"
|
||||
></app-show-progress>
|
||||
</ng-container>
|
||||
|
||||
@@ -19,11 +18,13 @@
|
||||
<!-- ** status ** -->
|
||||
<app-show-status [pkg]="pkg" [status]="status"></app-show-status>
|
||||
<!-- ** installed && !backing-up ** -->
|
||||
<ng-container *ngIf="isInstalled(pkg) && !isBackingUp(status)">
|
||||
<ng-container
|
||||
*ngIf="isInstalled(pkg) && status.primary !== 'backing-up'"
|
||||
>
|
||||
<!-- ** health checks ** -->
|
||||
<app-show-health-checks
|
||||
*ngIf="isRunning(status)"
|
||||
[pkg]="pkg"
|
||||
*ngIf="status.primary === 'running'"
|
||||
[manifest]="pkg['state-info'].manifest"
|
||||
></app-show-health-checks>
|
||||
<!-- ** dependencies ** -->
|
||||
<app-show-dependencies
|
||||
@@ -33,7 +34,9 @@
|
||||
<!-- ** menu ** -->
|
||||
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
|
||||
<!-- ** additional ** -->
|
||||
<app-show-additional [pkg]="pkg"></app-show-additional>
|
||||
<app-show-additional
|
||||
[manifest]="pkg['state-info'].manifest"
|
||||
></app-show-additional>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
</ng-template>
|
||||
|
||||
@@ -3,16 +3,12 @@ import { NavController } from '@ionic/angular'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
InstalledPackageDataEntry,
|
||||
InstallingState,
|
||||
Manifest,
|
||||
PackageDataEntry,
|
||||
PackageState,
|
||||
UpdatingState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
PackageStatus,
|
||||
PrimaryStatus,
|
||||
renderPkgStatus,
|
||||
} from 'src/app/services/pkg-status-rendering.service'
|
||||
import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||
import { map, tap } from 'rxjs/operators'
|
||||
import { ActivatedRoute, NavigationExtras } from '@angular/router'
|
||||
import { getPkgId } from '@start9labs/shared'
|
||||
@@ -24,6 +20,13 @@ import {
|
||||
PkgDependencyErrors,
|
||||
} from 'src/app/services/dep-error.service'
|
||||
import { combineLatest } from 'rxjs'
|
||||
import {
|
||||
getManifest,
|
||||
isInstalled,
|
||||
isInstalling,
|
||||
isRestoring,
|
||||
isUpdating,
|
||||
} from 'src/app/util/get-package-data'
|
||||
|
||||
export interface DependencyInfo {
|
||||
id: string
|
||||
@@ -35,12 +38,6 @@ export interface DependencyInfo {
|
||||
action: () => any
|
||||
}
|
||||
|
||||
const STATES = [
|
||||
PackageState.Installing,
|
||||
PackageState.Updating,
|
||||
PackageState.Restoring,
|
||||
]
|
||||
|
||||
@Component({
|
||||
selector: 'app-show',
|
||||
templateUrl: './app-show.page.html',
|
||||
@@ -66,6 +63,8 @@ export class AppShowPage {
|
||||
}),
|
||||
)
|
||||
|
||||
isInstalled = isInstalled
|
||||
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly navCtrl: NavController,
|
||||
@@ -74,55 +73,44 @@ export class AppShowPage {
|
||||
private readonly depErrorService: DepErrorService,
|
||||
) {}
|
||||
|
||||
isInstalled({ state }: PackageDataEntry): boolean {
|
||||
return state === PackageState.Installed
|
||||
}
|
||||
|
||||
isRunning({ primary }: PackageStatus): boolean {
|
||||
return primary === PrimaryStatus.Running
|
||||
}
|
||||
|
||||
isBackingUp({ primary }: PackageStatus): boolean {
|
||||
return primary === PrimaryStatus.BackingUp
|
||||
}
|
||||
|
||||
showProgress({ state }: PackageDataEntry): boolean {
|
||||
return STATES.includes(state)
|
||||
showProgress(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<InstallingState | UpdatingState> {
|
||||
return isInstalling(pkg) || isUpdating(pkg) || isRestoring(pkg)
|
||||
}
|
||||
|
||||
private getDepInfo(
|
||||
pkg: PackageDataEntry,
|
||||
depErrors: PkgDependencyErrors,
|
||||
): DependencyInfo[] {
|
||||
const pkgInstalled = pkg.installed
|
||||
const manifest = getManifest(pkg)
|
||||
|
||||
if (!pkgInstalled) return []
|
||||
|
||||
return Object.keys(pkgInstalled['current-dependencies'])
|
||||
.filter(id => !!pkgInstalled.manifest.dependencies[id])
|
||||
.map(id => this.getDepValues(pkgInstalled, id, depErrors))
|
||||
return Object.keys(pkg['current-dependencies'])
|
||||
.filter(id => !!manifest.dependencies[id])
|
||||
.map(id => this.getDepValues(pkg, manifest, id, depErrors))
|
||||
}
|
||||
|
||||
private getDepValues(
|
||||
pkgInstalled: InstalledPackageDataEntry,
|
||||
pkg: PackageDataEntry,
|
||||
manifest: Manifest,
|
||||
depId: string,
|
||||
depErrors: PkgDependencyErrors,
|
||||
): DependencyInfo {
|
||||
const { errorText, fixText, fixAction } = this.getDepErrors(
|
||||
pkgInstalled,
|
||||
manifest,
|
||||
depId,
|
||||
depErrors,
|
||||
)
|
||||
|
||||
const depInfo = pkgInstalled['dependency-info'][depId]
|
||||
const depInfo = pkg['dependency-info'][depId]
|
||||
|
||||
return {
|
||||
id: depId,
|
||||
version: pkgInstalled.manifest.dependencies[depId].version, // do we want this version range?
|
||||
version: manifest.dependencies[depId].version, // do we want this version range?
|
||||
title: depInfo?.title || depId,
|
||||
icon: depInfo?.icon || '',
|
||||
errorText: errorText
|
||||
? `${errorText}. ${pkgInstalled.manifest.title} will not work as expected.`
|
||||
? `${errorText}. ${manifest.title} will not work as expected.`
|
||||
: '',
|
||||
actionText: fixText || 'View',
|
||||
action:
|
||||
@@ -131,11 +119,10 @@ export class AppShowPage {
|
||||
}
|
||||
|
||||
private getDepErrors(
|
||||
pkgInstalled: InstalledPackageDataEntry,
|
||||
manifest: Manifest,
|
||||
depId: string,
|
||||
depErrors: PkgDependencyErrors,
|
||||
) {
|
||||
const pkgManifest = pkgInstalled.manifest
|
||||
const depError = depErrors[depId]
|
||||
|
||||
let errorText: string | null = null
|
||||
@@ -146,15 +133,15 @@ export class AppShowPage {
|
||||
if (depError.type === DependencyErrorType.NotInstalled) {
|
||||
errorText = 'Not installed'
|
||||
fixText = 'Install'
|
||||
fixAction = () => this.fixDep(pkgManifest, 'install', depId)
|
||||
fixAction = () => this.fixDep(manifest, 'install', depId)
|
||||
} else if (depError.type === DependencyErrorType.IncorrectVersion) {
|
||||
errorText = 'Incorrect version'
|
||||
fixText = 'Update'
|
||||
fixAction = () => this.fixDep(pkgManifest, 'update', depId)
|
||||
fixAction = () => this.fixDep(manifest, 'update', depId)
|
||||
} else if (depError.type === DependencyErrorType.ConfigUnsatisfied) {
|
||||
errorText = 'Config not satisfied'
|
||||
fixText = 'Auto config'
|
||||
fixAction = () => this.fixDep(pkgManifest, 'configure', depId)
|
||||
fixAction = () => this.fixDep(manifest, 'configure', depId)
|
||||
} else if (depError.type === DependencyErrorType.NotRunning) {
|
||||
errorText = 'Not running'
|
||||
fixText = 'Start'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<ion-item-divider>Additional Info</ion-item-divider>
|
||||
<ion-grid *ngIf="pkg.manifest as manifest">
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
||||
<ion-item-group>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ModalController, ToastController } from '@ionic/angular'
|
||||
import { copyToClipboard, MarkdownComponent } from '@start9labs/shared'
|
||||
import { from } from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { Manifest } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-additional',
|
||||
@@ -12,7 +12,7 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
})
|
||||
export class AppShowAdditionalComponent {
|
||||
@Input()
|
||||
pkg!: PackageDataEntry
|
||||
manifest!: Manifest
|
||||
|
||||
constructor(
|
||||
private readonly modalCtrl: ModalController,
|
||||
@@ -35,10 +35,16 @@ export class AppShowAdditionalComponent {
|
||||
}
|
||||
|
||||
async presentModalLicense() {
|
||||
const { id, version } = this.manifest
|
||||
|
||||
const modal = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
title: 'License',
|
||||
content: from(this.api.getStatic(this.pkg['static-files']['license'])),
|
||||
content: from(
|
||||
this.api.getStatic(
|
||||
`/public/package-data/${id}/${version}/LICENSE.md`,
|
||||
),
|
||||
),
|
||||
},
|
||||
component: MarkdownComponent,
|
||||
})
|
||||
|
||||
@@ -4,15 +4,12 @@
|
||||
<ion-back-button defaultHref="services"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<div class="header">
|
||||
<img class="logo" [src]="pkg['static-files'].icon" alt="" />
|
||||
<ion-label>
|
||||
<h1
|
||||
class="montserrat"
|
||||
[class.less-large]="pkg.manifest.title.length > 20"
|
||||
>
|
||||
{{ pkg.manifest.title }}
|
||||
<img class="logo" [src]="pkg.icon" alt="" />
|
||||
<ion-label *ngIf="pkg | toManifest as manifest">
|
||||
<h1 class="montserrat" [class.less-large]="manifest.title.length > 20">
|
||||
{{ manifest.title }}
|
||||
</h1>
|
||||
<h2>{{ pkg.manifest.version | displayEmver }}</h2>
|
||||
<h2>{{ manifest.version | displayEmver }}</h2>
|
||||
</ion-label>
|
||||
</div>
|
||||
</ion-toolbar>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<ng-container
|
||||
*ngIf="pkg | toHealthChecks | async | keyvalue: asIsOrder as checks"
|
||||
*ngIf="manifest | toHealthChecks | async | keyvalue: asIsOrder as checks"
|
||||
>
|
||||
<ng-container *ngIf="checks.length">
|
||||
<ion-item-divider>Health Checks</ion-item-divider>
|
||||
@@ -34,7 +34,7 @@
|
||||
></ion-icon>
|
||||
<ion-label>
|
||||
<h2 class="bold">
|
||||
{{ pkg.manifest['health-checks'][health.key].name }}
|
||||
{{ manifest['health-checks'][health.key].name }}
|
||||
</h2>
|
||||
<ion-text [color]="result | healthColor">
|
||||
<p>
|
||||
@@ -49,13 +49,11 @@
|
||||
<span
|
||||
*ngIf="
|
||||
result === HealthResult.Success &&
|
||||
pkg.manifest['health-checks'][health.key]['success-message']
|
||||
manifest['health-checks'][health.key]['success-message']
|
||||
"
|
||||
>
|
||||
:
|
||||
{{
|
||||
pkg.manifest['health-checks'][health.key]['success-message']
|
||||
}}
|
||||
{{ manifest['health-checks'][health.key]['success-message'] }}
|
||||
</span>
|
||||
</p>
|
||||
</ion-text>
|
||||
@@ -70,7 +68,7 @@
|
||||
></ion-spinner>
|
||||
<ion-label>
|
||||
<h2 class="bold">
|
||||
{{ pkg.manifest['health-checks'][health.key].name }}
|
||||
{{ manifest['health-checks'][health.key].name }}
|
||||
</h2>
|
||||
<p class="primary">Awaiting result...</p>
|
||||
</ion-label>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import { ConnectionService } from 'src/app/services/connection.service'
|
||||
import {
|
||||
HealthResult,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { HealthResult, Manifest } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-health-checks',
|
||||
@@ -13,7 +10,7 @@ import {
|
||||
})
|
||||
export class AppShowHealthChecksComponent {
|
||||
@Input()
|
||||
pkg!: PackageDataEntry
|
||||
manifest!: Manifest
|
||||
|
||||
HealthResult = HealthResult
|
||||
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<p>Downloading: {{ progressData.downloadProgress }}%</p>
|
||||
<ion-progress-bar
|
||||
[color]="getColor('download-complete')"
|
||||
[value]="progressData.downloadProgress / 100"
|
||||
[buffer]="!progressData.downloadProgress ? 0 : 1"
|
||||
></ion-progress-bar>
|
||||
|
||||
<p>Validating: {{ progressData.validateProgress }}%</p>
|
||||
<ion-progress-bar
|
||||
[color]="getColor('validation-complete')"
|
||||
[value]="progressData.validateProgress / 100"
|
||||
[buffer]="validationBuffer"
|
||||
></ion-progress-bar>
|
||||
|
||||
<p>Unpacking: {{ progressData.unpackProgress }}%</p>
|
||||
<ion-progress-bar
|
||||
[color]="getColor('unpack-complete')"
|
||||
[value]="progressData.unpackProgress / 100"
|
||||
[buffer]="unpackingBuffer"
|
||||
></ion-progress-bar>
|
||||
<ng-container *ngFor="let phase of phases">
|
||||
<p>
|
||||
{{ phase.name }}
|
||||
<span *ngIf="phase.progress | installingProgress as progress">
|
||||
: {{ progress * 100 }}%
|
||||
</span>
|
||||
</p>
|
||||
<ion-progress-bar
|
||||
[type]="
|
||||
phase.progress === true ||
|
||||
(phase.progress !== false && phase.progress.total)
|
||||
? 'determinate'
|
||||
: 'indeterminate'
|
||||
"
|
||||
[color]="phase.progress === true ? 'success' : 'secondary'"
|
||||
[value]="phase.progress | installingProgress"
|
||||
></ion-progress-bar>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import {
|
||||
InstallProgress,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { ProgressData } from 'src/app/types/progress-data'
|
||||
import { FullProgress } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-progress',
|
||||
@@ -13,26 +9,5 @@ import { ProgressData } from 'src/app/types/progress-data'
|
||||
})
|
||||
export class AppShowProgressComponent {
|
||||
@Input()
|
||||
pkg!: PackageDataEntry
|
||||
|
||||
@Input()
|
||||
progressData!: ProgressData
|
||||
|
||||
get unpackingBuffer(): number {
|
||||
return this.progressData.validateProgress === 100 &&
|
||||
!this.progressData.unpackProgress
|
||||
? 0
|
||||
: 1
|
||||
}
|
||||
|
||||
get validationBuffer(): number {
|
||||
return this.progressData.downloadProgress === 100 &&
|
||||
!this.progressData.validateProgress
|
||||
? 0
|
||||
: 1
|
||||
}
|
||||
|
||||
getColor(action: keyof InstallProgress): string {
|
||||
return this.pkg['install-progress']?.[action] ? 'success' : 'secondary'
|
||||
}
|
||||
phases!: FullProgress['phases']
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
<status
|
||||
size="x-large"
|
||||
weight="600"
|
||||
[installProgress]="pkg['install-progress']"
|
||||
[installingInfo]="$any(pkg['state-info'])['installing-info']"
|
||||
[rendering]="PR[status.primary]"
|
||||
[sigtermTimeout]="sigtermTimeout"
|
||||
></status>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ng-container *ngIf="isInstalled && (connected$ | async)">
|
||||
<ng-container *ngIf="isInstalled(pkg) && (connected$ | async)">
|
||||
<ion-grid>
|
||||
<ion-row style="padding-left: 12px">
|
||||
<ion-col>
|
||||
@@ -59,7 +59,9 @@
|
||||
*ngIf="pkgStatus && interfaces && (interfaces | hasUi)"
|
||||
class="action-button"
|
||||
color="primary"
|
||||
[disabled]="!(pkg.state | isLaunchable: pkgStatus.main.status)"
|
||||
[disabled]="
|
||||
!(pkg['state-info'].state | isLaunchable: pkgStatus.main.status)
|
||||
"
|
||||
(click)="launchUi(interfaces)"
|
||||
>
|
||||
<ion-icon slot="start" name="open-outline"></ion-icon>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
PrimaryStatus,
|
||||
} from 'src/app/services/pkg-status-rendering.service'
|
||||
import {
|
||||
InstalledPackageDataEntry,
|
||||
Manifest,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
@@ -18,6 +18,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ModalService } from 'src/app/services/modal.service'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { ConnectionService } from 'src/app/services/connection.service'
|
||||
import { isInstalled, getManifest } from 'src/app/util/get-package-data'
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-status',
|
||||
@@ -34,6 +35,8 @@ export class AppShowStatusComponent {
|
||||
|
||||
PR = PrimaryRendering
|
||||
|
||||
isInstalled = isInstalled
|
||||
|
||||
readonly connected$ = this.connectionService.connected$
|
||||
|
||||
constructor(
|
||||
@@ -46,18 +49,16 @@ export class AppShowStatusComponent {
|
||||
private readonly connectionService: ConnectionService,
|
||||
) {}
|
||||
|
||||
get interfaces():
|
||||
| InstalledPackageDataEntry['service-interfaces']
|
||||
| undefined {
|
||||
return this.pkg.installed?.['service-interfaces']
|
||||
get interfaces(): PackageDataEntry['service-interfaces'] {
|
||||
return this.pkg['service-interfaces']
|
||||
}
|
||||
|
||||
get pkgStatus(): Status | null {
|
||||
return this.pkg.installed?.status || null
|
||||
get pkgStatus(): Status {
|
||||
return this.pkg.status
|
||||
}
|
||||
|
||||
get isInstalled(): boolean {
|
||||
return this.pkg.state === PackageState.Installed
|
||||
get manifest(): Manifest {
|
||||
return getManifest(this.pkg)
|
||||
}
|
||||
|
||||
get isRunning(): boolean {
|
||||
@@ -82,25 +83,25 @@ export class AppShowStatusComponent {
|
||||
: null
|
||||
}
|
||||
|
||||
launchUi(interfaces: InstalledPackageDataEntry['service-interfaces']): void {
|
||||
launchUi(interfaces: PackageDataEntry['service-interfaces']): void {
|
||||
this.launcherService.launch(interfaces)
|
||||
}
|
||||
|
||||
async presentModalConfig(): Promise<void> {
|
||||
return this.modalService.presentModalConfig({
|
||||
pkgId: this.id,
|
||||
pkgId: this.manifest.id,
|
||||
})
|
||||
}
|
||||
|
||||
async tryStart(): Promise<void> {
|
||||
if (this.status.dependency === 'warning') {
|
||||
const depErrMsg = `${this.pkg.manifest.title} has unmet dependencies. It will not work as expected.`
|
||||
const depErrMsg = `${this.manifest.title} has unmet dependencies. It will not work as expected.`
|
||||
const proceed = await this.presentAlertStart(depErrMsg)
|
||||
|
||||
if (!proceed) return
|
||||
}
|
||||
|
||||
const alertMsg = this.pkg.manifest.alerts.start
|
||||
const alertMsg = this.manifest.alerts.start
|
||||
|
||||
if (alertMsg) {
|
||||
const proceed = await this.presentAlertStart(alertMsg)
|
||||
@@ -112,7 +113,7 @@ export class AppShowStatusComponent {
|
||||
}
|
||||
|
||||
async tryStop(): Promise<void> {
|
||||
const { title, alerts } = this.pkg.manifest
|
||||
const { title, alerts } = this.manifest
|
||||
|
||||
let message = alerts.stop || ''
|
||||
if (hasCurrentDeps(this.pkg)) {
|
||||
@@ -150,7 +151,7 @@ export class AppShowStatusComponent {
|
||||
if (hasCurrentDeps(this.pkg)) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Warning',
|
||||
message: `Services that depend on ${this.pkg.manifest.title} may temporarily experiences issues`,
|
||||
message: `Services that depend on ${this.manifest.title} may temporarily experiences issues`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
@@ -173,10 +174,6 @@ export class AppShowStatusComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private get id(): string {
|
||||
return this.pkg.manifest.id
|
||||
}
|
||||
|
||||
private async start(): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: `Starting...`,
|
||||
@@ -184,7 +181,7 @@ export class AppShowStatusComponent {
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.startPackage({ id: this.id })
|
||||
await this.embassyApi.startPackage({ id: this.manifest.id })
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
@@ -199,7 +196,7 @@ export class AppShowStatusComponent {
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.stopPackage({ id: this.id })
|
||||
await this.embassyApi.stopPackage({ id: this.manifest.id })
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
@@ -214,7 +211,7 @@ export class AppShowStatusComponent {
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.restartPackage({ id: this.id })
|
||||
await this.embassyApi.restartPackage({ id: this.manifest.id })
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { ProgressData } from 'src/app/types/progress-data'
|
||||
import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
|
||||
|
||||
@Pipe({
|
||||
name: 'progressData',
|
||||
})
|
||||
export class ProgressDataPipe implements PipeTransform {
|
||||
transform(pkg: PackageDataEntry): ProgressData | null {
|
||||
return packageLoadingProgress(pkg['install-progress'])
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,15 @@ import { ModalController, NavController } from '@ionic/angular'
|
||||
import { MarkdownComponent } from '@start9labs/shared'
|
||||
import {
|
||||
DataModel,
|
||||
InstalledState,
|
||||
Manifest,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { ModalService } from 'src/app/services/modal.service'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { from, map, Observable } from 'rxjs'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { getManifest } from 'src/app/util/get-package-data'
|
||||
|
||||
export interface Button {
|
||||
title: string
|
||||
@@ -33,26 +36,26 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
transform(pkg: PackageDataEntry): Button[] {
|
||||
const pkgTitle = pkg.manifest.title
|
||||
transform(pkg: PackageDataEntry<InstalledState>): Button[] {
|
||||
const manifest = pkg['state-info'].manifest
|
||||
|
||||
return [
|
||||
// instructions
|
||||
{
|
||||
action: () => this.presentModalInstructions(pkg),
|
||||
action: () => this.presentModalInstructions(manifest),
|
||||
title: 'Instructions',
|
||||
description: `Understand how to use ${pkgTitle}`,
|
||||
description: `Understand how to use ${manifest.title}`,
|
||||
icon: 'list-outline',
|
||||
highlighted$: this.patch
|
||||
.watch$('ui', 'ack-instructions', pkg.manifest.id)
|
||||
.watch$('ui', 'ack-instructions', manifest.id)
|
||||
.pipe(map(seen => !seen)),
|
||||
},
|
||||
// config
|
||||
{
|
||||
action: async () =>
|
||||
this.modalService.presentModalConfig({ pkgId: pkg.manifest.id }),
|
||||
this.modalService.presentModalConfig({ pkgId: manifest.id }),
|
||||
title: 'Config',
|
||||
description: `Customize ${pkgTitle}`,
|
||||
description: `Customize ${manifest.title}`,
|
||||
icon: 'options-outline',
|
||||
},
|
||||
// properties
|
||||
@@ -71,7 +74,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
action: () =>
|
||||
this.navCtrl.navigateForward(['actions'], { relativeTo: this.route }),
|
||||
title: 'Actions',
|
||||
description: `Uninstall and other commands specific to ${pkgTitle}`,
|
||||
description: `Uninstall and other commands specific to ${manifest.title}`,
|
||||
icon: 'flash-outline',
|
||||
},
|
||||
// interfaces
|
||||
@@ -97,16 +100,18 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
]
|
||||
}
|
||||
|
||||
private async presentModalInstructions(pkg: PackageDataEntry) {
|
||||
private async presentModalInstructions(manifest: Manifest) {
|
||||
this.apiService
|
||||
.setDbValue<boolean>(['ack-instructions', pkg.manifest.id], true)
|
||||
.setDbValue<boolean>(['ack-instructions', manifest.id], true)
|
||||
.catch(e => console.error('Failed to mark instructions as seen', e))
|
||||
|
||||
const modal = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
title: 'Instructions',
|
||||
content: from(
|
||||
this.apiService.getStatic(pkg['static-files']['instructions']),
|
||||
this.apiService.getStatic(
|
||||
`/public/package-data/${manifest.id}/${manifest.version}/INSTRUCTIONS.md`,
|
||||
),
|
||||
),
|
||||
},
|
||||
component: MarkdownComponent,
|
||||
@@ -115,17 +120,22 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
private viewInMarketplaceButton(pkg: PackageDataEntry): Button {
|
||||
const url = pkg.installed?.['marketplace-url']
|
||||
private viewInMarketplaceButton(
|
||||
pkg: PackageDataEntry<InstalledState>,
|
||||
): Button {
|
||||
const url = pkg['marketplace-url']
|
||||
const queryParams = url ? { url } : {}
|
||||
|
||||
let button: Button = {
|
||||
title: 'Marketplace Listing',
|
||||
icon: 'storefront-outline',
|
||||
action: () =>
|
||||
this.navCtrl.navigateForward([`marketplace/${pkg.manifest.id}`], {
|
||||
queryParams,
|
||||
}),
|
||||
this.navCtrl.navigateForward(
|
||||
[`marketplace/${pkg['state-info'].manifest.id}`],
|
||||
{
|
||||
queryParams,
|
||||
},
|
||||
),
|
||||
disabled: false,
|
||||
description: 'View service in the marketplace',
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core'
|
||||
import {
|
||||
DataModel,
|
||||
HealthCheckResult,
|
||||
PackageDataEntry,
|
||||
Manifest,
|
||||
PackageMainStatus,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { isEmptyObject } from '@start9labs/shared'
|
||||
@@ -17,15 +17,15 @@ export class ToHealthChecksPipe implements PipeTransform {
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
|
||||
transform(
|
||||
pkg: PackageDataEntry,
|
||||
manifest: Manifest,
|
||||
): Observable<Record<string, HealthCheckResult | null>> | null {
|
||||
const healthChecks = Object.keys(pkg.manifest['health-checks']).reduce(
|
||||
const healthChecks = Object.keys(manifest['health-checks']).reduce(
|
||||
(obj, key) => ({ ...obj, [key]: null }),
|
||||
{},
|
||||
)
|
||||
|
||||
const healthChecks$ = this.patch
|
||||
.watch$('package-data', pkg.manifest.id, 'installed', 'status', 'main')
|
||||
.watch$('package-data', manifest.id, 'status', 'main')
|
||||
.pipe(
|
||||
map(main => {
|
||||
// Question: is this ok or do we have to use Object.keys
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
View Installed
|
||||
</ion-button>
|
||||
<ng-container *ngIf="localPkg; else install">
|
||||
<ng-container *ngIf="localPkg.state === PackageState.Installed">
|
||||
<ng-container *ngIf="localPkg['state-info'].state === 'installed'">
|
||||
<ion-button
|
||||
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === -1"
|
||||
expand="block"
|
||||
|
||||
@@ -24,7 +24,7 @@ import { ClientStorageService } from 'src/app/services/client-storage.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { getAllPackages } from 'src/app/util/get-package-data'
|
||||
import { getAllPackages, getManifest } from 'src/app/util/get-package-data'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
import { dryUpdate } from 'src/app/util/dry-update'
|
||||
|
||||
@@ -46,8 +46,6 @@ export class MarketplaceShowControlsComponent {
|
||||
|
||||
readonly showDevTools$ = this.ClientStorageService.showDevTools$
|
||||
|
||||
readonly PackageState = PackageState
|
||||
|
||||
constructor(
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly ClientStorageService: ClientStorageService,
|
||||
@@ -60,7 +58,7 @@ export class MarketplaceShowControlsComponent {
|
||||
) {}
|
||||
|
||||
get localVersion(): string {
|
||||
return this.localPkg?.manifest.version || ''
|
||||
return this.localPkg ? getManifest(this.localPkg).version : ''
|
||||
}
|
||||
|
||||
async tryInstall() {
|
||||
@@ -72,7 +70,7 @@ export class MarketplaceShowControlsComponent {
|
||||
if (!this.localPkg) {
|
||||
this.alertInstall(url)
|
||||
} else {
|
||||
const originalUrl = this.localPkg.installed?.['marketplace-url']
|
||||
const originalUrl = this.localPkg['marketplace-url']
|
||||
|
||||
if (!sameUrl(url, originalUrl)) {
|
||||
const proceed = await this.presentAlertDifferentMarketplace(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<ng-container *ngIf="localPkg" [ngSwitch]="localPkg.state">
|
||||
<div *ngSwitchCase="PackageState.Installed">
|
||||
<ng-container *ngIf="localPkg">
|
||||
<div *ngIf="isInstalled(localPkg)">
|
||||
<ion-text
|
||||
*ngIf="(version | compareEmver: localVersion) !== 1"
|
||||
color="primary"
|
||||
@@ -13,15 +13,24 @@
|
||||
Update Available
|
||||
</ion-text>
|
||||
</div>
|
||||
<div *ngSwitchCase="PackageState.Removing">
|
||||
|
||||
<div *ngIf="isRemoving(localPkg)">
|
||||
<ion-text color="danger">
|
||||
Removing
|
||||
<span class="loading-dots"></span>
|
||||
</ion-text>
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
|
||||
<div
|
||||
*ngIf="
|
||||
isInstalling(localPkg) || isUpdating(localPkg) || isRestoring(localPkg)
|
||||
"
|
||||
>
|
||||
<ion-text
|
||||
*ngIf="localPkg['install-progress'] | installProgressDisplay as progress"
|
||||
*ngIf="
|
||||
localPkg['state-info']['installing-info']!.progress.overall
|
||||
| installingProgressString as progress
|
||||
"
|
||||
color="primary"
|
||||
>
|
||||
Installing
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
PackageDataEntry,
|
||||
PackageState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
isInstalled,
|
||||
isInstalling,
|
||||
isUpdating,
|
||||
isRemoving,
|
||||
isRestoring,
|
||||
getManifest,
|
||||
} from 'src/app/util/get-package-data'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-status',
|
||||
@@ -14,9 +19,13 @@ export class MarketplaceStatusComponent {
|
||||
|
||||
@Input() localPkg?: PackageDataEntry
|
||||
|
||||
PackageState = PackageState
|
||||
isInstalled = isInstalled
|
||||
isInstalling = isInstalling
|
||||
isUpdating = isUpdating
|
||||
isRemoving = isRemoving
|
||||
isRestoring = isRestoring
|
||||
|
||||
get localVersion(): string {
|
||||
return this.localPkg?.manifest.version || ''
|
||||
return this.localPkg ? getManifest(this.localPkg).version : ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { EmverPipesModule } from '@start9labs/shared'
|
||||
|
||||
import { InstallProgressPipeModule } from '../../../pipes/install-progress/install-progress.module'
|
||||
import { InstallingProgressPipeModule } from '../../../pipes/install-progress/install-progress.module'
|
||||
import { MarketplaceStatusComponent } from './marketplace-status.component'
|
||||
|
||||
@NgModule({
|
||||
@@ -11,7 +10,7 @@ import { MarketplaceStatusComponent } from './marketplace-status.component'
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
EmverPipesModule,
|
||||
InstallProgressPipeModule,
|
||||
InstallingProgressPipeModule,
|
||||
],
|
||||
declarations: [MarketplaceStatusComponent],
|
||||
exports: [MarketplaceStatusComponent],
|
||||
|
||||
@@ -6,6 +6,7 @@ import { NotificationsPage } from './notifications.page'
|
||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
|
||||
import { UiPipeModule } from 'src/app/pipes/ui/ui.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -22,6 +23,7 @@ const routes: Routes = [
|
||||
BadgeMenuComponentModule,
|
||||
SharedPipesModule,
|
||||
BackupReportPageModule,
|
||||
UiPipeModule,
|
||||
],
|
||||
declarations: [NotificationsPage],
|
||||
})
|
||||
|
||||
@@ -87,8 +87,8 @@
|
||||
<h2>
|
||||
<b>
|
||||
<span *ngIf="not['package-id'] as pkgId">
|
||||
<!-- @TODO remove $any when Angular gets smart enough -->
|
||||
{{ $any(packageData[pkgId])?.manifest.title || pkgId }} -
|
||||
{{ packageData[pkgId] ? (packageData[pkgId] |
|
||||
toManifest).title : pkgId }} -
|
||||
</span>
|
||||
<ion-text [color]="getColor(not)">{{ not.title }}</ion-text>
|
||||
</b>
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<ng-container *ngFor="let pkg of pkgs | keyvalue">
|
||||
<ion-item *ngIf="backupProgress[pkg.key] as pkgProgress">
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="pkg.value['static-files'].icon" />
|
||||
<img [src]="pkg.value.icon" />
|
||||
</ion-avatar>
|
||||
<ion-label>{{ pkg.value.manifest.title }}</ion-label>
|
||||
<ion-label>{{ (pkg.value | toManifest).title }}</ion-label>
|
||||
<!-- complete -->
|
||||
<ion-note
|
||||
*ngIf="pkgProgress.complete; else incomplete"
|
||||
@@ -35,10 +35,7 @@
|
||||
>
|
||||
<!-- active -->
|
||||
<ion-note
|
||||
*ngIf="
|
||||
pkgStatus === PackageMainStatus.BackingUp;
|
||||
else queued
|
||||
"
|
||||
*ngIf="pkgStatus === 'backing-up'; else queued"
|
||||
class="inline"
|
||||
slot="end"
|
||||
>
|
||||
|
||||
@@ -25,8 +25,6 @@ export class BackingUpComponent {
|
||||
'backup-progress',
|
||||
)
|
||||
|
||||
PackageMainStatus = PackageMainStatus
|
||||
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
}
|
||||
|
||||
@@ -35,14 +33,7 @@ export class BackingUpComponent {
|
||||
})
|
||||
export class PkgMainStatusPipe implements PipeTransform {
|
||||
transform(pkgId: string): Observable<PackageMainStatus> {
|
||||
return this.patch.watch$(
|
||||
'package-data',
|
||||
pkgId,
|
||||
'installed',
|
||||
'status',
|
||||
'main',
|
||||
'status',
|
||||
)
|
||||
return this.patch.watch$('package-data', pkgId, 'status', 'main', 'status')
|
||||
}
|
||||
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { BackupDrivesComponentModule } from 'src/app/components/backup-drives/ba
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
import { BackupSelectPageModule } from 'src/app/modals/backup-select/backup-select.module'
|
||||
import { PkgMainStatusPipe } from './backing-up/backing-up.component'
|
||||
import { UiPipeModule } from 'src/app/pipes/ui/ui.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -24,6 +25,7 @@ const routes: Routes = [
|
||||
SharedPipesModule,
|
||||
BackupDrivesComponentModule,
|
||||
BackupSelectPageModule,
|
||||
UiPipeModule,
|
||||
],
|
||||
declarations: [ServerBackupPage, BackingUpComponent, PkgMainStatusPipe],
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '@start9labs/shared'
|
||||
import { SkeletonListComponentModule } from 'src/app/components/skeleton-list/skeleton-list.component.module'
|
||||
import { RoundProgressModule } from 'angular-svg-round-progressbar'
|
||||
import { InstallProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module'
|
||||
import { InstallingProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module'
|
||||
import { StoreIconComponentModule } from 'src/app/components/store-icon/store-icon.component.module'
|
||||
import { MimeTypePipeModule } from '@start9labs/marketplace'
|
||||
|
||||
@@ -34,7 +34,7 @@ const routes: Routes = [
|
||||
SkeletonListComponentModule,
|
||||
MarkdownPipeModule,
|
||||
RoundProgressModule,
|
||||
InstallProgressPipeModule,
|
||||
InstallingProgressPipeModule,
|
||||
StoreIconComponentModule,
|
||||
EmverPipesModule,
|
||||
MimeTypePipeModule,
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
<h1 style="line-height: 1.3">{{ pkg.manifest.title }}</h1>
|
||||
<h2 class="inline">
|
||||
<span>
|
||||
{{ local.installed?.manifest?.version || '' |
|
||||
displayEmver }}
|
||||
{{ local['state-info'].manifest.version | displayEmver
|
||||
}}
|
||||
</span>
|
||||
|
||||
<ion-icon name="arrow-forward"></ion-icon>
|
||||
@@ -57,8 +57,8 @@
|
||||
</ion-label>
|
||||
<div slot="end" style="margin-left: 4px">
|
||||
<round-progress
|
||||
*ngIf="local.state === 'updating' else notUpdating"
|
||||
[current]="local['install-progress'] | installProgress"
|
||||
*ngIf="local['state-info'].state === 'updating' else notUpdating"
|
||||
[current]="(local['state-info']['installing-info'].progress.overall | installingProgress) || 0"
|
||||
[max]="100"
|
||||
[radius]="13"
|
||||
[stroke]="3"
|
||||
|
||||
@@ -2,7 +2,9 @@ import { Component, Inject } from '@angular/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
InstalledState,
|
||||
PackageDataEntry,
|
||||
UpdatingState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import {
|
||||
@@ -14,16 +16,20 @@ import {
|
||||
} from '@start9labs/marketplace'
|
||||
import { Emver, isEmptyObject } from '@start9labs/shared'
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { combineLatest, Observable } from 'rxjs'
|
||||
import { combineLatest, map, Observable } from 'rxjs'
|
||||
import { AlertController, NavController } from '@ionic/angular'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { getAllPackages } from 'src/app/util/get-package-data'
|
||||
import {
|
||||
getAllPackages,
|
||||
isInstalled,
|
||||
isUpdating,
|
||||
} from 'src/app/util/get-package-data'
|
||||
import { dryUpdate } from 'src/app/util/dry-update'
|
||||
|
||||
interface UpdatesData {
|
||||
hosts: StoreIdentity[]
|
||||
marketplace: Marketplace
|
||||
localPkgs: Record<string, PackageDataEntry>
|
||||
localPkgs: Record<string, PackageDataEntry<InstalledState | UpdatingState>>
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
@@ -36,7 +42,14 @@ export class UpdatesPage {
|
||||
readonly data$: Observable<UpdatesData> = combineLatest({
|
||||
hosts: this.marketplaceService.getKnownHosts$(true),
|
||||
marketplace: this.marketplaceService.getMarketplace$(),
|
||||
localPkgs: this.patch.watch$('package-data'),
|
||||
localPkgs: this.patch.watch$('package-data').pipe(
|
||||
map(pkgs =>
|
||||
Object.values(pkgs).reduce((acc, curr) => {
|
||||
if (isInstalled(curr) || isUpdating(curr)) return { ...acc, curr }
|
||||
return acc
|
||||
}, {} as Record<string, PackageDataEntry<InstalledState | UpdatingState>>),
|
||||
),
|
||||
),
|
||||
errors: this.marketplaceService.getRequestErrors$(),
|
||||
})
|
||||
|
||||
@@ -154,14 +167,17 @@ export class FilterUpdatesPipe implements PipeTransform {
|
||||
|
||||
transform(
|
||||
pkgs: MarketplacePkg[],
|
||||
local: Record<string, PackageDataEntry | undefined>,
|
||||
local: Record<string, PackageDataEntry<InstalledState | UpdatingState>>,
|
||||
): MarketplacePkg[] {
|
||||
return pkgs.filter(
|
||||
({ manifest }) =>
|
||||
return pkgs.filter(({ manifest }) => {
|
||||
const localPkg = local[manifest.id]
|
||||
return (
|
||||
localPkg &&
|
||||
this.emver.compare(
|
||||
manifest.version,
|
||||
local[manifest.id]?.installed?.manifest.version || '',
|
||||
) === 1,
|
||||
)
|
||||
localPkg['state-info'].manifest.version,
|
||||
) === 1
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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'
|
||||
import { getManifest } from 'src/app/util/get-package-data'
|
||||
|
||||
@Component({
|
||||
selector: 'widget-health',
|
||||
@@ -31,7 +32,7 @@ export class HealthComponent {
|
||||
]).pipe(
|
||||
map(([data, depErrors]) => {
|
||||
const pkgs = Object.values<PackageDataEntry>(data).map(pkg =>
|
||||
getPackageInfo(pkg, depErrors[pkg.manifest.id]),
|
||||
getPackageInfo(pkg, depErrors[getManifest(pkg).id]),
|
||||
)
|
||||
const result = this.labels.reduce<Record<string, number>>(
|
||||
(acc, label) => ({
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import {
|
||||
InstallProgressDisplayPipe,
|
||||
InstallProgressPipe,
|
||||
InstallingProgressDisplayPipe,
|
||||
InstallingProgressPipe,
|
||||
} from './install-progress.pipe'
|
||||
|
||||
@NgModule({
|
||||
declarations: [InstallProgressPipe, InstallProgressDisplayPipe],
|
||||
exports: [InstallProgressPipe, InstallProgressDisplayPipe],
|
||||
declarations: [InstallingProgressPipe, InstallingProgressDisplayPipe],
|
||||
exports: [InstallingProgressPipe, InstallingProgressDisplayPipe],
|
||||
})
|
||||
export class InstallProgressPipeModule {}
|
||||
export class InstallingProgressPipeModule {}
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { InstallProgress } from 'src/app/services/patch-db/data-model'
|
||||
import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
|
||||
import { Progress } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Pipe({
|
||||
name: 'installProgress',
|
||||
name: 'installingProgressString',
|
||||
})
|
||||
export class InstallProgressPipe implements PipeTransform {
|
||||
transform(installProgress?: InstallProgress): number {
|
||||
return packageLoadingProgress(installProgress)?.totalProgress || 0
|
||||
export class InstallingProgressDisplayPipe implements PipeTransform {
|
||||
transform(progress: Progress): string {
|
||||
if (progress === true) return 'finalizing'
|
||||
if (progress === false || !progress.total) return 'unknown %'
|
||||
const percentage = Math.round((100 * progress.done) / progress.total)
|
||||
|
||||
return percentage < 99 ? String(percentage) + '%' : 'finalizing'
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'installProgressDisplay',
|
||||
name: 'installingProgress',
|
||||
})
|
||||
export class InstallProgressDisplayPipe implements PipeTransform {
|
||||
transform(installProgress?: InstallProgress): string {
|
||||
const totalProgress =
|
||||
packageLoadingProgress(installProgress)?.totalProgress || 0
|
||||
|
||||
return totalProgress < 99 ? totalProgress + '%' : 'finalizing'
|
||||
export class InstallingProgressPipe implements PipeTransform {
|
||||
transform(progress: Progress): number | null {
|
||||
if (progress === true) return 1
|
||||
if (progress === false || !progress.total) return null
|
||||
return Number((progress.done / progress.total).toFixed(2))
|
||||
}
|
||||
}
|
||||
|
||||
function getProgress(progress: Progress): number | null {
|
||||
if (progress === true) return 1
|
||||
if (progress === false || !progress.total) return null
|
||||
return Number((progress.done / progress.total).toFixed(2))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { UiPipe } from './ui.pipe'
|
||||
import { ToManifestPipe, UiPipe } from './ui.pipe'
|
||||
|
||||
@NgModule({
|
||||
declarations: [UiPipe],
|
||||
exports: [UiPipe],
|
||||
declarations: [UiPipe, ToManifestPipe],
|
||||
exports: [UiPipe, ToManifestPipe],
|
||||
})
|
||||
export class UiPipeModule {}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { InstalledPackageDataEntry } from '../../services/patch-db/data-model'
|
||||
import { Manifest, PackageDataEntry } from '../../services/patch-db/data-model'
|
||||
import { hasUi } from '../../services/config.service'
|
||||
import { getManifest } from 'src/app/util/get-package-data'
|
||||
|
||||
@Pipe({
|
||||
name: 'hasUi',
|
||||
})
|
||||
export class UiPipe implements PipeTransform {
|
||||
transform(
|
||||
interfaces: InstalledPackageDataEntry['service-interfaces'],
|
||||
): boolean {
|
||||
transform(interfaces: PackageDataEntry['service-interfaces']): boolean {
|
||||
return interfaces ? hasUi(interfaces) : false
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'toManifest',
|
||||
})
|
||||
export class ToManifestPipe implements PipeTransform {
|
||||
transform(pkg: PackageDataEntry): Manifest {
|
||||
return getManifest(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,14 @@ import {
|
||||
} from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
InstallProgress,
|
||||
FullProgress,
|
||||
InstallingState,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
ServerStatus,
|
||||
StateInfo,
|
||||
UpdatingState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { CifsBackupTarget, RR } from './api.types'
|
||||
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
|
||||
@@ -39,14 +42,34 @@ import { AuthService } from '../auth.service'
|
||||
import { ConnectionService } from '../connection.service'
|
||||
import { StoreInfo } from '@start9labs/marketplace'
|
||||
|
||||
const PROGRESS: InstallProgress = {
|
||||
size: 120,
|
||||
downloaded: 0,
|
||||
'download-complete': false,
|
||||
validated: 0,
|
||||
'validation-complete': false,
|
||||
unpacked: 0,
|
||||
'unpack-complete': false,
|
||||
const PROGRESS: FullProgress = {
|
||||
overall: {
|
||||
done: 0,
|
||||
total: 120,
|
||||
},
|
||||
phases: [
|
||||
{
|
||||
name: 'Downloading',
|
||||
progress: {
|
||||
done: 0,
|
||||
total: 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Validating',
|
||||
progress: {
|
||||
done: 0,
|
||||
total: 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Installing',
|
||||
progress: {
|
||||
done: 0,
|
||||
total: 40,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -656,15 +679,30 @@ export class MockApiService extends ApiService {
|
||||
this.updateProgress(params.id)
|
||||
}, 1000)
|
||||
|
||||
const patch: Operation<PackageDataEntry>[] = [
|
||||
const manifest = Mock.LocalPkgs[params.id]['state-info'].manifest
|
||||
|
||||
const patch: Operation<
|
||||
PackageDataEntry<InstallingState | UpdatingState>
|
||||
>[] = [
|
||||
{
|
||||
op: PatchOp.ADD,
|
||||
path: `/package-data/${params.id}`,
|
||||
value: {
|
||||
...Mock.LocalPkgs[params.id],
|
||||
// state: PackageState.Installing,
|
||||
state: PackageState.Updating,
|
||||
'install-progress': { ...PROGRESS },
|
||||
'state-info': {
|
||||
// if installing
|
||||
state: PackageState.Installing,
|
||||
|
||||
// if updating
|
||||
// state: PackageState.Updating,
|
||||
// manifest,
|
||||
|
||||
// both
|
||||
'installing-info': {
|
||||
'new-manifest': manifest,
|
||||
progress: PROGRESS,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -720,9 +758,13 @@ export class MockApiService extends ApiService {
|
||||
path: `/package-data/${id}`,
|
||||
value: {
|
||||
...Mock.LocalPkgs[id],
|
||||
state: PackageState.Restoring,
|
||||
'install-progress': { ...PROGRESS },
|
||||
installed: undefined,
|
||||
'state-info': {
|
||||
state: PackageState.Restoring,
|
||||
'installing-info': {
|
||||
'new-manifest': Mock.LocalPkgs[id]['state-info'].manifest!,
|
||||
progress: PROGRESS,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -948,7 +990,7 @@ export class MockApiService extends ApiService {
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${params.id}/state`,
|
||||
path: `/package-data/${params.id}/state-info/state`,
|
||||
value: PackageState.Removing,
|
||||
},
|
||||
]
|
||||
@@ -977,55 +1019,100 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
|
||||
private async updateProgress(id: string): Promise<void> {
|
||||
const progress = { ...PROGRESS }
|
||||
const phases = [
|
||||
{ progress: 'downloaded', completion: 'download-complete' },
|
||||
{ progress: 'validated', completion: 'validation-complete' },
|
||||
{ progress: 'unpacked', completion: 'unpack-complete' },
|
||||
] as const
|
||||
const progress = JSON.parse(JSON.stringify(PROGRESS))
|
||||
|
||||
for (let phase of phases) {
|
||||
let i = progress[phase.progress]
|
||||
const size = progress?.size || 0
|
||||
while (i < size) {
|
||||
await pauseFor(250)
|
||||
i = Math.min(i + 5, size)
|
||||
progress[phase.progress] = i
|
||||
for (let [i, phase] of progress.phases.entries()) {
|
||||
if (typeof phase.progress !== 'object' || !phase.progress.total) {
|
||||
await pauseFor(2000)
|
||||
|
||||
if (i === progress.size) {
|
||||
progress[phase.completion] = true
|
||||
}
|
||||
|
||||
const patch = [
|
||||
const patches: Operation<any>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/install-progress`,
|
||||
value: { ...progress },
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/phases/${i}/progress`,
|
||||
value: true,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
// overall
|
||||
if (typeof progress.overall === 'object' && progress.overall.total) {
|
||||
const step = progress.overall.total / progress.phases.length
|
||||
|
||||
progress.overall.done += step
|
||||
|
||||
patches.push({
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/overall/done`,
|
||||
value: progress.overall.done,
|
||||
})
|
||||
}
|
||||
|
||||
this.mockRevision(patches)
|
||||
} else {
|
||||
const step = phase.progress.total / 4
|
||||
|
||||
while (phase.progress.done < phase.progress.total) {
|
||||
await pauseFor(500)
|
||||
|
||||
phase.progress.done += step
|
||||
|
||||
const patches: Operation<any>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/phases/${i}/progress/done`,
|
||||
value: phase.progress.done,
|
||||
},
|
||||
]
|
||||
|
||||
// overall
|
||||
if (typeof progress.overall === 'object' && progress.overall.total) {
|
||||
const step = progress.overall.total / progress.phases.length / 4
|
||||
|
||||
progress.overall.done += step
|
||||
|
||||
patches.push({
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/overall/done`,
|
||||
value: progress.overall.done,
|
||||
})
|
||||
}
|
||||
|
||||
this.mockRevision(patches)
|
||||
|
||||
if (phase.progress.done === phase.progress.total) {
|
||||
await pauseFor(250)
|
||||
this.mockRevision([
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/phases/${i}/progress`,
|
||||
value: true,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const patch2: Operation<any>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state`,
|
||||
value: PackageState.Installed,
|
||||
await pauseFor(1000)
|
||||
this.mockRevision([
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/overall`,
|
||||
value: true,
|
||||
},
|
||||
])
|
||||
|
||||
await pauseFor(1000)
|
||||
const patch2: Operation<StateInfo>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info`,
|
||||
value: {
|
||||
state: PackageState.Installed,
|
||||
manifest: Mock.LocalPkgs[id]['state-info'].manifest,
|
||||
},
|
||||
{
|
||||
op: PatchOp.ADD,
|
||||
path: `/package-data/${id}/installed`,
|
||||
value: { ...Mock.LocalPkgs[id].installed },
|
||||
},
|
||||
{
|
||||
op: PatchOp.REMOVE,
|
||||
path: `/package-data/${id}/install-progress`,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch2)
|
||||
}, 1000)
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch2)
|
||||
}
|
||||
|
||||
private async updateOSProgress() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ import { Inject, Injectable } from '@angular/core'
|
||||
import { WorkspaceConfig } from '@start9labs/shared'
|
||||
import { types } from '@start9labs/start-sdk'
|
||||
import {
|
||||
InstalledPackageDataEntry,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
@@ -64,7 +64,7 @@ export class ConfigService {
|
||||
|
||||
/** ${scheme}://${username}@${host}:${externalPort}${suffix} */
|
||||
launchableAddress(
|
||||
interfaces: InstalledPackageDataEntry['service-interfaces'],
|
||||
interfaces: PackageDataEntry['service-interfaces'],
|
||||
): string {
|
||||
const ui = Object.values(interfaces).find(i => i.type === 'ui')
|
||||
|
||||
@@ -128,7 +128,7 @@ export class ConfigService {
|
||||
}
|
||||
|
||||
export function hasUi(
|
||||
interfaces: InstalledPackageDataEntry['service-interfaces'],
|
||||
interfaces: PackageDataEntry['service-interfaces'],
|
||||
): boolean {
|
||||
return Object.values(interfaces).some(iface => iface.type === 'ui')
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
HealthCheckResult,
|
||||
HealthResult,
|
||||
InstalledPackageDataEntry,
|
||||
InstalledState,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
} from './patch-db/data-model'
|
||||
import * as deepEqual from 'fast-deep-equal'
|
||||
import { isInstalled } from '../util/get-package-data'
|
||||
|
||||
export type AllDependencyErrors = Record<string, PkgDependencyErrors>
|
||||
export type PkgDependencyErrors = Record<string, DependencyError | null>
|
||||
@@ -55,14 +57,14 @@ export class DepErrorService {
|
||||
pkgId: string,
|
||||
outerErrors: AllDependencyErrors,
|
||||
): PkgDependencyErrors {
|
||||
const pkgInstalled = pkgs[pkgId].installed
|
||||
const pkg = pkgs[pkgId]
|
||||
|
||||
if (!pkgInstalled) return {}
|
||||
if (!isInstalled(pkg)) return {}
|
||||
|
||||
return currentDeps(pkgs, pkgId).reduce(
|
||||
(innerErrors, depId): PkgDependencyErrors => ({
|
||||
...innerErrors,
|
||||
[depId]: this.getDepError(pkgs, pkgInstalled, depId, outerErrors),
|
||||
[depId]: this.getDepError(pkgs, pkg, depId, outerErrors),
|
||||
}),
|
||||
{} as PkgDependencyErrors,
|
||||
)
|
||||
@@ -70,21 +72,21 @@ export class DepErrorService {
|
||||
|
||||
private getDepError(
|
||||
pkgs: DataModel['package-data'],
|
||||
pkgInstalled: InstalledPackageDataEntry,
|
||||
pkg: PackageDataEntry<InstalledState>,
|
||||
depId: string,
|
||||
outerErrors: AllDependencyErrors,
|
||||
): DependencyError | null {
|
||||
const depInstalled = pkgs[depId]?.installed
|
||||
const dep = pkgs[depId]
|
||||
|
||||
// not installed
|
||||
if (!depInstalled) {
|
||||
if (!dep || dep['state-info'].state !== PackageState.Installed) {
|
||||
return {
|
||||
type: DependencyErrorType.NotInstalled,
|
||||
}
|
||||
}
|
||||
|
||||
const pkgManifest = pkgInstalled.manifest
|
||||
const depManifest = depInstalled.manifest
|
||||
const pkgManifest = pkg['state-info'].manifest
|
||||
const depManifest = dep['state-info'].manifest
|
||||
|
||||
// incorrect version
|
||||
if (
|
||||
@@ -102,16 +104,14 @@ export class DepErrorService {
|
||||
|
||||
// invalid config
|
||||
if (
|
||||
Object.values(pkgInstalled.status['dependency-config-errors']).some(
|
||||
err => !!err,
|
||||
)
|
||||
Object.values(pkg.status['dependency-config-errors']).some(err => !!err)
|
||||
) {
|
||||
return {
|
||||
type: DependencyErrorType.ConfigUnsatisfied,
|
||||
}
|
||||
}
|
||||
|
||||
const depStatus = depInstalled.status.main.status
|
||||
const depStatus = dep.status.main.status
|
||||
|
||||
// not running
|
||||
if (
|
||||
@@ -125,12 +125,8 @@ export class DepErrorService {
|
||||
|
||||
// health check failure
|
||||
if (depStatus === PackageMainStatus.Running) {
|
||||
for (let id of pkgInstalled['current-dependencies'][depId][
|
||||
'health-checks'
|
||||
]) {
|
||||
if (
|
||||
depInstalled.status.main.health[id]?.result !== HealthResult.Success
|
||||
) {
|
||||
for (let id of pkg['current-dependencies'][depId]['health-checks']) {
|
||||
if (dep.status.main.health[id]?.result !== HealthResult.Success) {
|
||||
return {
|
||||
type: DependencyErrorType.HealthChecksFailed,
|
||||
}
|
||||
@@ -154,9 +150,9 @@ export class DepErrorService {
|
||||
}
|
||||
|
||||
function currentDeps(pkgs: DataModel['package-data'], id: string): string[] {
|
||||
return Object.keys(
|
||||
pkgs[id]?.installed?.['current-dependencies'] || {},
|
||||
).filter(depId => depId !== id)
|
||||
return Object.keys(pkgs[id]?.['current-dependencies'] || {}).filter(
|
||||
depId => depId !== id,
|
||||
)
|
||||
}
|
||||
|
||||
function dependencyDepth(
|
||||
|
||||
@@ -107,31 +107,11 @@ export enum ServerStatus {
|
||||
BackingUp = 'backing-up',
|
||||
}
|
||||
|
||||
export interface PackageDataEntry {
|
||||
state: PackageState
|
||||
'static-files': {
|
||||
license: Url
|
||||
instructions: Url
|
||||
icon: Url
|
||||
}
|
||||
manifest: Manifest
|
||||
installed?: InstalledPackageDataEntry // exists when: installed, updating
|
||||
'install-progress'?: InstallProgress // exists when: installing, updating
|
||||
}
|
||||
|
||||
export enum PackageState {
|
||||
Installing = 'installing',
|
||||
Installed = 'installed',
|
||||
Updating = 'updating',
|
||||
Removing = 'removing',
|
||||
Restoring = 'restoring',
|
||||
}
|
||||
|
||||
export interface InstalledPackageDataEntry {
|
||||
export type PackageDataEntry<T extends StateInfo = StateInfo> = {
|
||||
'state-info': T
|
||||
icon: Url
|
||||
status: Status
|
||||
manifest: Manifest
|
||||
'last-backup': string | null
|
||||
'system-pointers': any[]
|
||||
'current-dependents': { [id: string]: CurrentDependencyInfo }
|
||||
'current-dependencies': { [id: string]: CurrentDependencyInfo }
|
||||
'dependency-info': {
|
||||
@@ -145,6 +125,32 @@ export interface InstalledPackageDataEntry {
|
||||
'developer-key': string
|
||||
}
|
||||
|
||||
export type StateInfo = InstalledState | InstallingState | UpdatingState
|
||||
|
||||
export type InstalledState = {
|
||||
state: PackageState.Installed | PackageState.Removing
|
||||
manifest: Manifest
|
||||
}
|
||||
|
||||
export type InstallingState = {
|
||||
state: PackageState.Installing | PackageState.Restoring
|
||||
'installing-info': InstallingInfo
|
||||
}
|
||||
|
||||
export type UpdatingState = {
|
||||
state: PackageState.Updating
|
||||
'installing-info': InstallingInfo
|
||||
manifest: Manifest
|
||||
}
|
||||
|
||||
export enum PackageState {
|
||||
Installing = 'installing',
|
||||
Installed = 'installed',
|
||||
Updating = 'updating',
|
||||
Removing = 'removing',
|
||||
Restoring = 'restoring',
|
||||
}
|
||||
|
||||
export interface CurrentDependencyInfo {
|
||||
pointers: any[]
|
||||
'health-checks': string[] // array of health check IDs
|
||||
@@ -354,12 +360,13 @@ export interface HealthCheckResultFailure {
|
||||
error: string
|
||||
}
|
||||
|
||||
export interface InstallProgress {
|
||||
readonly size: number | null
|
||||
readonly downloaded: number
|
||||
readonly 'download-complete': boolean
|
||||
readonly validated: number
|
||||
readonly 'validation-complete': boolean
|
||||
readonly unpacked: number
|
||||
readonly 'unpack-complete': boolean
|
||||
export type InstallingInfo = {
|
||||
progress: FullProgress
|
||||
'new-manifest': Manifest
|
||||
}
|
||||
|
||||
export type FullProgress = {
|
||||
overall: Progress
|
||||
phases: { name: string; progress: Progress }[]
|
||||
}
|
||||
export type Progress = boolean | { done: number; total: number | null } // false means indeterminate. true means complete
|
||||
|
||||
@@ -22,15 +22,15 @@ export function renderPkgStatus(
|
||||
let dependency: DependencyStatus | null = null
|
||||
let health: HealthStatus | null = null
|
||||
|
||||
if (pkg.state === PackageState.Installed && pkg.installed) {
|
||||
primary = getPrimaryStatus(pkg.installed.status)
|
||||
if (pkg['state-info'].state === PackageState.Installed) {
|
||||
primary = getPrimaryStatus(pkg.status)
|
||||
dependency = getDependencyStatus(depErrors)
|
||||
health = getHealthStatus(
|
||||
pkg.installed.status,
|
||||
!isEmptyObject(pkg.manifest['health-checks']),
|
||||
pkg.status,
|
||||
!isEmptyObject(pkg['state-info'].manifest['health-checks']),
|
||||
)
|
||||
} else {
|
||||
primary = pkg.state as string as PrimaryStatus
|
||||
primary = pkg['state-info'].state as string as PrimaryStatus
|
||||
}
|
||||
|
||||
return { primary, dependency, health }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import { WINDOW } from '@ng-web-apis/common'
|
||||
import { InstalledPackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { ConfigService } from './config.service'
|
||||
|
||||
@Injectable({
|
||||
@@ -12,7 +12,7 @@ export class UiLauncherService {
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
launch(interfaces: InstalledPackageDataEntry['service-interfaces']): void {
|
||||
launch(interfaces: PackageDataEntry['service-interfaces']): void {
|
||||
this.windowRef.open(
|
||||
this.config.launchableAddress(interfaces),
|
||||
'_blank',
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export interface ProgressData {
|
||||
totalProgress: number
|
||||
downloadProgress: number
|
||||
validateProgress: number
|
||||
unpackProgress: number
|
||||
isComplete: boolean
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Emver } from '@start9labs/shared'
|
||||
import { DataModel } from '../services/patch-db/data-model'
|
||||
import { getManifest } from './get-package-data'
|
||||
|
||||
export function dryUpdate(
|
||||
{ id, version }: { id: string; version: string },
|
||||
@@ -9,9 +10,10 @@ export function dryUpdate(
|
||||
return Object.values(pkgs)
|
||||
.filter(
|
||||
pkg =>
|
||||
Object.keys(pkg.installed?.['current-dependencies'] || {}).some(
|
||||
Object.keys(pkg['current-dependencies'] || {}).some(
|
||||
pkgId => pkgId === id,
|
||||
) && !emver.satisfies(version, pkg.manifest.dependencies[id].version),
|
||||
) &&
|
||||
!emver.satisfies(version, getManifest(pkg).dependencies[id].version),
|
||||
)
|
||||
.map(pkg => pkg.manifest.title)
|
||||
.map(pkg => getManifest(pkg).title)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
InstalledState,
|
||||
InstallingState,
|
||||
Manifest,
|
||||
PackageDataEntry,
|
||||
PackageState,
|
||||
UpdatingState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
|
||||
@@ -17,3 +22,41 @@ export async function getAllPackages(
|
||||
): Promise<DataModel['package-data']> {
|
||||
return firstValueFrom(patch.watch$('package-data'))
|
||||
}
|
||||
|
||||
export function getManifest(pkg: PackageDataEntry): Manifest {
|
||||
if (isInstalled(pkg) || isRemoving(pkg)) return pkg['state-info'].manifest
|
||||
|
||||
return (pkg['state-info'] as InstallingState)['installing-info'][
|
||||
'new-manifest'
|
||||
]
|
||||
}
|
||||
|
||||
export function isInstalled(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<InstalledState> {
|
||||
return pkg['state-info'].state === PackageState.Installed
|
||||
}
|
||||
|
||||
export function isRemoving(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<InstalledState> {
|
||||
return pkg['state-info'].state === PackageState.Removing
|
||||
}
|
||||
|
||||
export function isInstalling(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<InstallingState> {
|
||||
return pkg['state-info'].state === PackageState.Installing
|
||||
}
|
||||
|
||||
export function isRestoring(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<InstallingState> {
|
||||
return pkg['state-info'].state === PackageState.Restoring
|
||||
}
|
||||
|
||||
export function isUpdating(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<UpdatingState> {
|
||||
return pkg['state-info'].state === PackageState.Updating
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@ import {
|
||||
renderPkgStatus,
|
||||
StatusRendering,
|
||||
} from '../services/pkg-status-rendering.service'
|
||||
import { ProgressData } from 'src/app/types/progress-data'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { packageLoadingProgress } from './package-loading-progress'
|
||||
import { PkgDependencyErrors } from '../services/dep-error.service'
|
||||
|
||||
export function getPackageInfo(
|
||||
@@ -23,7 +21,6 @@ export function getPackageInfo(
|
||||
entry,
|
||||
primaryRendering,
|
||||
primaryStatus: statuses.primary,
|
||||
installProgress: packageLoadingProgress(entry['install-progress']),
|
||||
error:
|
||||
statuses.health === HealthStatus.Failure ||
|
||||
statuses.dependency === DependencyStatus.Warning,
|
||||
@@ -40,7 +37,6 @@ export interface PkgInfo {
|
||||
entry: PackageDataEntry
|
||||
primaryRendering: StatusRendering
|
||||
primaryStatus: PrimaryStatus
|
||||
installProgress: ProgressData | null
|
||||
error: boolean
|
||||
warning: boolean
|
||||
transitioning: boolean
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { PackageDataEntry } from '../services/patch-db/data-model'
|
||||
import { getManifest } from './get-package-data'
|
||||
|
||||
export function hasCurrentDeps(pkg: PackageDataEntry): boolean {
|
||||
return !!Object.keys(pkg.installed?.['current-dependents'] || {}).filter(
|
||||
depId => depId !== pkg.manifest.id,
|
||||
return !!Object.keys(pkg['current-dependents']).filter(
|
||||
depId => depId !== getManifest(pkg).id,
|
||||
).length
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { isEmptyObject } from '@start9labs/shared'
|
||||
import { ProgressData } from 'src/app/types/progress-data'
|
||||
import { InstallProgress } from '../services/patch-db/data-model'
|
||||
|
||||
export function packageLoadingProgress(
|
||||
loadData?: InstallProgress,
|
||||
): ProgressData | null {
|
||||
if (!loadData || isEmptyObject(loadData)) {
|
||||
return null
|
||||
}
|
||||
|
||||
let {
|
||||
downloaded,
|
||||
validated,
|
||||
unpacked,
|
||||
size,
|
||||
'download-complete': downloadComplete,
|
||||
'validation-complete': validationComplete,
|
||||
'unpack-complete': unpackComplete,
|
||||
} = loadData
|
||||
|
||||
// only permit 100% when "complete" == true
|
||||
size = size || 0
|
||||
downloaded = downloadComplete ? size : Math.max(downloaded - 1, 0)
|
||||
validated = validationComplete ? size : Math.max(validated - 1, 0)
|
||||
unpacked = unpackComplete ? size : Math.max(unpacked - 1, 0)
|
||||
|
||||
const downloadWeight = 1
|
||||
const validateWeight = 0.2
|
||||
const unpackWeight = 0.7
|
||||
|
||||
const numerator = Math.floor(
|
||||
downloadWeight * downloaded +
|
||||
validateWeight * validated +
|
||||
unpackWeight * unpacked,
|
||||
)
|
||||
|
||||
const denominator = Math.floor(
|
||||
size * (downloadWeight + validateWeight + unpackWeight),
|
||||
)
|
||||
const totalProgress = Math.floor((100 * numerator) / denominator)
|
||||
|
||||
return {
|
||||
totalProgress,
|
||||
downloadProgress: Math.floor((100 * downloaded) / size),
|
||||
validateProgress: Math.floor((100 * validated) / size),
|
||||
unpackProgress: Math.floor((100 * unpacked) / size),
|
||||
isComplete: downloadComplete && validationComplete && unpackComplete,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user