mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
Rework PackageDataEntry for new strategy (#2573)
* rework PackageDataEntry for new strategy * fix type error * fix issues with manifest fetching * mock installs working
This commit is contained in:
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',
|
||||
|
||||
Reference in New Issue
Block a user