fix a few, more to go (#2869)

* fix a few, more to go

* chore: comments (#2871)

* chore: comments

* chore: typo

* chore: stricter typescript (#2872)

* chore: comments

* chore: stricter typescript

---------

Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>

* minor styling

---------

Co-authored-by: Alex Inkin <alexander@inkin.ru>
This commit is contained in:
Matt Hill
2025-04-12 09:53:03 -06:00
committed by GitHub
parent 6a312e3fdd
commit 2e6e9635c3
55 changed files with 440 additions and 343 deletions

View File

@@ -501,9 +501,9 @@ export class MockApiService extends ApiService {
): Promise<GetPackageRes> {
await pauseFor(2000)
if (!versionRange || versionRange === '=*') {
return Mock.RegistryPackages[id]
return Mock.RegistryPackages[id]!
} else {
return Mock.OtherPackageVersions[id][versionRange]
return Mock.OtherPackageVersions[id]![versionRange]!
}
}
@@ -870,7 +870,7 @@ export class MockApiService extends ApiService {
this.mockRevision([
{
...appPatch[0],
...appPatch[0]!,
value: 'stopped',
},
])
@@ -1055,18 +1055,18 @@ export class MockApiService extends ApiService {
op: PatchOp.ADD,
path: `/packageData/${params.id}`,
value: {
...Mock.LocalPkgs[params.id],
...Mock.LocalPkgs[params.id]!,
stateInfo: {
// if installing
// state: 'installing',
// if updating
state: 'updating',
manifest: mockPatchData.packageData[params.id].stateInfo.manifest!,
manifest: mockPatchData.packageData[params.id]?.stateInfo.manifest!,
// both
installingInfo: {
newManifest: Mock.LocalPkgs[params.id].stateInfo.manifest,
newManifest: Mock.LocalPkgs[params.id]?.stateInfo.manifest!,
progress: PROGRESS,
},
},
@@ -1123,11 +1123,11 @@ export class MockApiService extends ApiService {
op: PatchOp.ADD,
path: `/packageData/${id}`,
value: {
...Mock.LocalPkgs[id],
...Mock.LocalPkgs[id]!,
stateInfo: {
state: 'restoring',
installingInfo: {
newManifest: Mock.LocalPkgs[id].stateInfo.manifest!,
newManifest: Mock.LocalPkgs[id]?.stateInfo.manifest!,
progress: PROGRESS,
},
},
@@ -1640,7 +1640,7 @@ export class MockApiService extends ApiService {
) {
await pauseFor(2000)
progress.phases[i].progress = true
progress.phases[i]!.progress = true
if (
progress.overall &&
@@ -1671,7 +1671,7 @@ export class MockApiService extends ApiService {
if (phase.progress.done === phase.progress.total) {
await pauseFor(250)
progress.phases[i].progress = true
progress.phases[i]!.progress = true
}
}
}
@@ -1777,7 +1777,7 @@ export class MockApiService extends ApiService {
path: `/packageData/${id}/stateInfo`,
value: {
state: 'installed',
manifest: Mock.LocalPkgs[id].stateInfo.manifest,
manifest: Mock.LocalPkgs[id]?.stateInfo.manifest!,
},
},
]

View File

@@ -47,7 +47,8 @@ export class BadgeService {
return (
!curr[id] ||
(p.stateInfo.installingInfo && !curr[id].stateInfo.installingInfo)
(p.stateInfo.installingInfo &&
!curr[id]?.stateInfo.installingInfo)
)
}),
),
@@ -70,7 +71,7 @@ export class BadgeService {
local[id] &&
this.exver.compareExver(
version,
getManifest(local[id]).version,
getManifest(local[id]!).version,
) === 1
? result.add(id)
: result,

View File

@@ -59,7 +59,7 @@ export class ConfigService {
(this.hostname.startsWith('192.168.') ||
this.hostname.startsWith('10.') ||
(this.hostname.startsWith('172.') &&
!![this.hostname.split('.').map(Number)[1]].filter(
!![this.hostname.split('.').map(Number)[1] || NaN].filter(
n => n >= 16 && n < 32,
).length))
}
@@ -107,13 +107,14 @@ export class ConfigService {
if (!host) return ''
let hostnameInfo = host.hostnameInfo[ui.addressInfo.internalPort]
hostnameInfo = hostnameInfo.filter(
h =>
this.isLocalhost() ||
h.kind !== 'ip' ||
h.hostname.kind !== 'ipv6' ||
!h.hostname.value.startsWith('fe80::'),
)
hostnameInfo =
hostnameInfo?.filter(
h =>
this.isLocalhost() ||
h.kind !== 'ip' ||
h.hostname.kind !== 'ipv6' ||
!h.hostname.value.startsWith('fe80::'),
) || []
if (this.isLocalhost()) {
const local = hostnameInfo.find(
h => h.kind === 'ip' && h.hostname.kind === 'local',

View File

@@ -2,9 +2,16 @@ import { inject, Injectable } from '@angular/core'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
import { TuiConfirmData, TUI_CONFIRM } from '@taiga-ui/kit'
import { TUI_CONFIRM, TuiConfirmData } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { defaultIfEmpty, filter, firstValueFrom } from 'rxjs'
import {
defaultIfEmpty,
defer,
filter,
firstValueFrom,
of,
switchMap,
} from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { getAllPackages } from 'src/app/utils/get-package-data'
@@ -20,53 +27,16 @@ export class ControlsService {
private readonly api = inject(ApiService)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
async start(manifest: T.Manifest, unmet: boolean): Promise<void> {
const deps = `${manifest.title} has unmet dependencies. It will not work as expected.`
async start({ title, alerts, id }: T.Manifest, unmet: boolean) {
const deps = `${title} has unmet dependencies. It will not work as expected.`
if (
(!unmet || (await this.alert(deps))) &&
(!manifest.alerts.start || (await this.alert(manifest.alerts.start)))
(unmet && !(await this.alert(deps))) ||
(alerts.start && !(await this.alert(alerts.start)))
) {
this.doStart(manifest.id)
}
}
async stop({ id, title, alerts }: T.Manifest): Promise<void> {
let content = alerts.stop || ''
if (hasCurrentDeps(id, await getAllPackages(this.patch))) {
const depMessage = `Services that depend on ${title} will no longer work properly and may crash`
content = content ? `${content}.\n\n${depMessage}` : depMessage
return
}
if (content) {
this.dialogs
.open(TUI_CONFIRM, getOptions(content, 'Stop'))
.pipe(filter(Boolean))
.subscribe(() => this.doStop(id))
} else {
this.doStop(id)
}
}
async restart({ id, title }: T.Manifest): Promise<void> {
if (hasCurrentDeps(id, await getAllPackages(this.patch))) {
this.dialogs
.open(
TUI_CONFIRM,
getOptions(
`Services that depend on ${title} may temporarily experiences issues`,
'Restart',
),
)
.pipe(filter(Boolean))
.subscribe(() => this.doRestart(id))
} else {
this.doRestart(id)
}
}
private async doStart(id: string): Promise<void> {
const loader = this.loader.open(`Starting...`).subscribe()
try {
@@ -78,28 +48,55 @@ export class ControlsService {
}
}
private async doStop(id: string): Promise<void> {
const loader = this.loader.open(`Stopping...`).subscribe()
async stop({ id, title, alerts }: T.Manifest) {
const depMessage = `Services that depend on ${title} will no longer work properly and may crash`
let content = alerts.stop || ''
try {
await this.api.stopPackage({ id })
} catch (e: any) {
this.errorService.handleError(e)
} finally {
loader.unsubscribe()
if (hasCurrentDeps(id, await getAllPackages(this.patch))) {
content = content ? `${content}.\n\n${depMessage}` : depMessage
}
defer(() =>
content
? this.dialogs
.open(TUI_CONFIRM, getOptions(content, 'Stop'))
.pipe(filter(Boolean))
: of(null),
).subscribe(async () => {
const loader = this.loader.open(`Stopping...`).subscribe()
try {
await this.api.stopPackage({ id })
} catch (e: any) {
this.errorService.handleError(e)
} finally {
loader.unsubscribe()
}
})
}
private async doRestart(id: string): Promise<void> {
const loader = this.loader.open(`Restarting...`).subscribe()
async restart({ id, title }: T.Manifest) {
const packages = await getAllPackages(this.patch)
const options = getOptions(
`Services that depend on ${title} may temporarily experiences issues`,
'Restart',
)
try {
await this.api.restartPackage({ id })
} catch (e: any) {
this.errorService.handleError(e)
} finally {
loader.unsubscribe()
}
defer(() =>
hasCurrentDeps(id, packages)
? this.dialogs.open(TUI_CONFIRM, options).pipe(filter(Boolean))
: of(null),
).subscribe(async () => {
const loader = this.loader.open(`Restarting...`).subscribe()
try {
await this.api.restartPackage({ id })
} catch (e: any) {
this.errorService.handleError(e)
} finally {
loader.unsubscribe()
}
})
}
private alert(content: string): Promise<boolean> {

View File

@@ -60,7 +60,7 @@ export class DepErrorService {
): PkgDependencyErrors {
const pkg = pkgs[pkgId]
if (!isInstalled(pkg)) return {}
if (!pkg || !isInstalled(pkg)) return {}
return currentDeps(pkgs, pkgId).reduce(
(innerErrors, depId): PkgDependencyErrors => ({
@@ -88,17 +88,14 @@ export class DepErrorService {
const currentDep = pkg.currentDependencies[depId]
const depManifest = dep.stateInfo.manifest
const expected = currentDep?.versionRange || ''
// incorrect version
if (!this.exver.satisfies(depManifest.version, currentDep.versionRange)) {
if (
depManifest.satisfies.some(
v => !this.exver.satisfies(v, currentDep.versionRange),
)
) {
if (!this.exver.satisfies(depManifest.version, expected)) {
if (depManifest.satisfies.some(v => !this.exver.satisfies(v, expected))) {
return {
expected,
type: 'incorrectVersion',
expected: currentDep.versionRange,
received: depManifest.version,
}
}
@@ -128,10 +125,10 @@ export class DepErrorService {
}
// health check failure
if (depStatus === 'running' && currentDep.kind === 'running') {
if (depStatus === 'running' && currentDep?.kind === 'running') {
for (let id of currentDep.healthChecks) {
const check = dep.status.health[id]
if (check?.result !== 'success') {
if (check && check?.result !== 'success') {
return {
type: 'healthChecksFailed',
check,
@@ -142,7 +139,7 @@ export class DepErrorService {
// transitive
const transitiveError = currentDeps(pkgs, depId).some(transitiveId =>
Object.values(outerErrors[transitiveId]).some(err => !!err),
Object.values(outerErrors[transitiveId] || {}).some(err => !!err),
)
if (transitiveError) {

View File

@@ -42,9 +42,12 @@ export class FormService {
const selected = valid ? value?.selection : spec.default
const selection = this.getUnionSelectSpec(spec, selected)
const group = this.getFormGroup({ selection })
const control = selected ? spec.variants[selected].spec : {}
const control = selected ? spec.variants[selected]?.spec : {}
group.setControl('value', this.getFormGroup(control, [], value?.value))
group.setControl(
'value',
this.getFormGroup(control || {}, [], value?.value),
)
return group
}
@@ -410,7 +413,12 @@ function listObjEquals(
if (!uniqueBy) {
return false
} else if (typeof uniqueBy === 'string') {
return uniqueByEquals(spec.spec[uniqueBy], val1[uniqueBy], val2[uniqueBy])
const uniqueBySpec = spec.spec[uniqueBy]
return (
!!uniqueBySpec &&
uniqueByEquals(uniqueBySpec, val1[uniqueBy], val2[uniqueBy])
)
} else if ('any' in uniqueBy) {
for (let unique of uniqueBy.any) {
if (listObjEquals(unique, spec, val1, val2)) {
@@ -522,8 +530,12 @@ export function convertValuesRecursive(
convertValuesRecursive(valueSpec.spec, group.get(key) as UntypedFormGroup)
} else if (valueSpec.type === 'union') {
const formGr = group.get(key) as UntypedFormGroup
const spec = valueSpec.variants[formGr.controls['selection'].value].spec
convertValuesRecursive(spec, formGr)
const value = formGr.controls['selection']?.value
const spec = !!value && valueSpec.variants[value]?.spec
if (spec) {
convertValuesRecursive(spec, formGr)
}
} else if (valueSpec.type === 'list') {
const formArr = group.get(key) as UntypedFormArray
const { controls } = formArr

View File

@@ -249,20 +249,23 @@ export class MarketplaceService {
flavor: string | null,
pkgInfo: GetPackageRes,
): MarketplacePkg {
version =
const ver =
version ||
Object.keys(pkgInfo.best).find(v => this.exver.getFlavor(v) === flavor) ||
null
const best = ver && pkgInfo.best[ver]
return !version || !pkgInfo.best[version]
? ({} as MarketplacePkg)
: {
id,
version,
flavor,
...pkgInfo,
...pkgInfo.best[version],
}
if (!best) {
return {} as MarketplacePkg
}
return {
id,
flavor,
version: ver || '',
...pkgInfo,
...best,
}
}
getRequestErrors$(): Observable<string[]> {