mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
feat: refactor updates (#2860)
This commit is contained in:
20
web/package-lock.json
generated
20
web/package-lock.json
generated
@@ -32,6 +32,7 @@
|
|||||||
"@taiga-ui/cdk": "4.30.0",
|
"@taiga-ui/cdk": "4.30.0",
|
||||||
"@taiga-ui/core": "4.30.0",
|
"@taiga-ui/core": "4.30.0",
|
||||||
"@taiga-ui/event-plugins": "4.5.0",
|
"@taiga-ui/event-plugins": "4.5.0",
|
||||||
|
"@taiga-ui/experimental": "4.30.0",
|
||||||
"@taiga-ui/icons": "4.30.0",
|
"@taiga-ui/icons": "4.30.0",
|
||||||
"@taiga-ui/kit": "4.30.0",
|
"@taiga-ui/kit": "4.30.0",
|
||||||
"@taiga-ui/layout": "4.30.0",
|
"@taiga-ui/layout": "4.30.0",
|
||||||
@@ -4567,6 +4568,25 @@
|
|||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@taiga-ui/experimental": {
|
||||||
|
"version": "4.30.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.30.0.tgz",
|
||||||
|
"integrity": "sha512-0GWkBinW+tqQIFkWQbTqMBTkKGZhju3RslKRCYbjal/hfcIuSAsEPZqLqIQqVqJNz6AhaIpT0UQ+I7QXzx1/yw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": ">=2.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": ">=16.0.0",
|
||||||
|
"@angular/core": ">=16.0.0",
|
||||||
|
"@taiga-ui/addon-commerce": "^4.30.0",
|
||||||
|
"@taiga-ui/cdk": "^4.30.0",
|
||||||
|
"@taiga-ui/core": "^4.30.0",
|
||||||
|
"@taiga-ui/kit": "^4.30.0",
|
||||||
|
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||||
|
"rxjs": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@taiga-ui/i18n": {
|
"node_modules/@taiga-ui/i18n": {
|
||||||
"version": "4.30.0",
|
"version": "4.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.30.0.tgz",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"@taiga-ui/cdk": "4.30.0",
|
"@taiga-ui/cdk": "4.30.0",
|
||||||
"@taiga-ui/core": "4.30.0",
|
"@taiga-ui/core": "4.30.0",
|
||||||
"@taiga-ui/event-plugins": "4.5.0",
|
"@taiga-ui/event-plugins": "4.5.0",
|
||||||
|
"@taiga-ui/experimental": "4.30.0",
|
||||||
"@taiga-ui/icons": "4.30.0",
|
"@taiga-ui/icons": "4.30.0",
|
||||||
"@taiga-ui/kit": "4.30.0",
|
"@taiga-ui/kit": "4.30.0",
|
||||||
"@taiga-ui/layout": "4.30.0",
|
"@taiga-ui/layout": "4.30.0",
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ const ROUTES: Routes = [
|
|||||||
loadComponent: () => import('./routes/sideload/sideload.component'),
|
loadComponent: () => import('./routes/sideload/sideload.component'),
|
||||||
data: toNavigationItem('/portal/sideload'),
|
data: toNavigationItem('/portal/sideload'),
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// title: systemTabResolver,
|
title: systemTabResolver,
|
||||||
// path: 'updates',
|
path: 'updates',
|
||||||
// loadComponent: () => import('./routes/updates/updates.component'),
|
loadComponent: () => import('./routes/updates/updates.component'),
|
||||||
// data: toNavigationItem('/portal/updates'),
|
data: toNavigationItem('/portal/updates'),
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
title: systemTabResolver,
|
title: systemTabResolver,
|
||||||
path: 'metrics',
|
path: 'metrics',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class InstallingProgressDisplayPipe implements PipeTransform {
|
|||||||
name: 'installingProgress',
|
name: 'installingProgress',
|
||||||
})
|
})
|
||||||
export class InstallingProgressPipe implements PipeTransform {
|
export class InstallingProgressPipe implements PipeTransform {
|
||||||
transform(progress: T.Progress): number {
|
transform(progress: T.Progress = false): number {
|
||||||
if (progress === true) return 100
|
if (progress === true) return 100
|
||||||
if (progress === false || progress === null || !progress.total) return 0
|
if (progress === false || progress === null || !progress.total) return 0
|
||||||
return Math.floor((100 * progress.done) / progress.total)
|
return Math.floor((100 * progress.done) / progress.total)
|
||||||
|
|||||||
@@ -1,40 +1,36 @@
|
|||||||
// import { inject, Pipe, PipeTransform } from '@angular/core'
|
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||||
// import { Exver } from '@start9labs/shared'
|
import { Exver } from '@start9labs/shared'
|
||||||
// import { MarketplacePkg } from '@start9labs/marketplace'
|
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||||
// import {
|
import {
|
||||||
// InstalledState,
|
InstalledState,
|
||||||
// PackageDataEntry,
|
PackageDataEntry,
|
||||||
// UpdatingState,
|
UpdatingState,
|
||||||
// } from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
// @Pipe({
|
@Pipe({
|
||||||
// name: 'filterUpdates',
|
name: 'filterUpdates',
|
||||||
// standalone: true,
|
standalone: true,
|
||||||
// })
|
})
|
||||||
// export class FilterUpdatesPipe implements PipeTransform {
|
export class FilterUpdatesPipe implements PipeTransform {
|
||||||
// private readonly exver = inject(Exver)
|
private readonly exver = inject(Exver)
|
||||||
|
|
||||||
// transform(
|
transform(
|
||||||
// pkgs?: MarketplacePkg[],
|
pkgs: MarketplacePkg[],
|
||||||
// local: Record<
|
local: Record<
|
||||||
// string,
|
string,
|
||||||
// PackageDataEntry<InstalledState | UpdatingState>
|
PackageDataEntry<InstalledState | UpdatingState>
|
||||||
// > = {},
|
> = {},
|
||||||
// ): MarketplacePkg[] | null {
|
): MarketplacePkg[] {
|
||||||
// return (
|
return pkgs.filter(({ id, version, flavor }) => {
|
||||||
// pkgs?.filter(
|
const localPkg = local[id]
|
||||||
// ({ id, version, flavor }) =>
|
return (
|
||||||
// local[id] &&
|
localPkg &&
|
||||||
// this.exver.getFlavor(getVersion(local, id)) === flavor &&
|
this.exver.getFlavor(localPkg.stateInfo.manifest.version) === flavor &&
|
||||||
// this.exver.compareExver(version, getVersion(local, id)) === 1,
|
this.exver.compareExver(
|
||||||
// ) || null
|
version,
|
||||||
// )
|
localPkg.stateInfo.manifest.version,
|
||||||
// }
|
) === 1
|
||||||
// }
|
)
|
||||||
|
})
|
||||||
// function getVersion(
|
}
|
||||||
// local: Record<string, PackageDataEntry<InstalledState | UpdatingState>>,
|
}
|
||||||
// id: string,
|
|
||||||
// ): string {
|
|
||||||
// return local[id].stateInfo.manifest.version
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,199 +1,311 @@
|
|||||||
// import { Component, inject, Input } from '@angular/core'
|
import { DatePipe } from '@angular/common'
|
||||||
// import { RouterLink } from '@angular/router'
|
import {
|
||||||
// import {
|
ChangeDetectionStrategy,
|
||||||
// MarketplacePkg,
|
Component,
|
||||||
// } from '@start9labs/marketplace'
|
inject,
|
||||||
// import {
|
input,
|
||||||
// MarkdownPipeModule,
|
signal,
|
||||||
// SafeLinksDirective,
|
} from '@angular/core'
|
||||||
// SharedPipesModule,
|
import { RouterLink } from '@angular/router'
|
||||||
// } from '@start9labs/shared'
|
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||||
// import {
|
import { MarkdownPipeModule, SafeLinksDirective } from '@start9labs/shared'
|
||||||
// TuiDialogService,
|
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||||
// TuiLoader,
|
import {
|
||||||
// TuiIcon,
|
TuiButton,
|
||||||
// TuiLink,
|
TuiIcon,
|
||||||
// TuiButton,
|
TuiLink,
|
||||||
// } from '@taiga-ui/core'
|
TuiLoader,
|
||||||
// import {
|
TuiTitle,
|
||||||
// TuiProgress,
|
} from '@taiga-ui/core'
|
||||||
// TuiAccordion,
|
import { TuiExpand } from '@taiga-ui/experimental'
|
||||||
// TuiAvatar,
|
import {
|
||||||
// TUI_CONFIRM,
|
TUI_CONFIRM,
|
||||||
// } from '@taiga-ui/kit'
|
TuiAvatar,
|
||||||
// import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
TuiChevron,
|
||||||
// import { PatchDB } from 'patch-db-client'
|
TuiFade,
|
||||||
// import { InstallingProgressPipe } from 'src/app/routes/portal/routes/services/pipes/install-progress.pipe'
|
TuiProgressCircle,
|
||||||
// import { MarketplaceService } from 'src/app/services/marketplace.service'
|
} from '@taiga-ui/kit'
|
||||||
// import {
|
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||||
// DataModel,
|
import { PatchDB } from 'patch-db-client'
|
||||||
// InstalledState,
|
import { defaultIfEmpty, firstValueFrom } from 'rxjs'
|
||||||
// PackageDataEntry,
|
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/services/pipes/install-progress.pipe'
|
||||||
// UpdatingState,
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
// } from 'src/app/services/patch-db/data-model'
|
import {
|
||||||
// import { getAllPackages } from 'src/app/utils/get-package-data'
|
DataModel,
|
||||||
// import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
InstalledState,
|
||||||
|
PackageDataEntry,
|
||||||
|
UpdatingState,
|
||||||
|
} from 'src/app/services/patch-db/data-model'
|
||||||
|
import { getAllPackages } from 'src/app/utils/get-package-data'
|
||||||
|
import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
||||||
|
import UpdatesComponent from './updates.component'
|
||||||
|
|
||||||
// @Component({
|
@Component({
|
||||||
// selector: 'updates-item',
|
standalone: true,
|
||||||
// template: `
|
selector: 'updates-item',
|
||||||
// <tui-accordion-item borders="top-bottom">
|
template: `
|
||||||
// <div class="g-action">
|
<tr (click)="expanded.set(!expanded())">
|
||||||
// <tui-avatar size="s">
|
<td>
|
||||||
// <img alt="" [src]="marketplacePkg.icon" />
|
<div [style.gap.rem]="0.75">
|
||||||
// </tui-avatar>
|
<tui-avatar size="s"><img alt="" [src]="item().icon" /></tui-avatar>
|
||||||
// <div [style.flex]="1" [style.overflow]="'hidden'">
|
<span tuiTitle [style.margin]="'-0.125rem 0 0'">
|
||||||
// <strong>{{ marketplacePkg.title }}</strong>
|
<b tuiFade>{{ item().title }}</b>
|
||||||
// <div>
|
<span tuiSubtitle tuiFade class="mobile">
|
||||||
// {{ localPkg.stateInfo.manifest.version }}
|
<span class="g-secondary">
|
||||||
// <tui-icon icon="@tui.arrow-right" [style.font-size.rem]="1" />
|
{{ local().stateInfo.manifest.version }}
|
||||||
// <span [style.color]="'var(--tui-text-positive)'">
|
</span>
|
||||||
// {{ marketplacePkg.version }}
|
<tui-icon icon="@tui.arrow-right" />
|
||||||
// </span>
|
<span class="g-positive">{{ item().version }}</span>
|
||||||
// </div>
|
</span>
|
||||||
// <div [style.color]="'var(--tui-text-negative)'">{{ errors }}</div>
|
</span>
|
||||||
// </div>
|
</div>
|
||||||
// @if (localPkg.stateInfo.state === 'updating') {
|
</td>
|
||||||
// <tui-progress-circle
|
<td class="desktop">
|
||||||
// class="g-positive"
|
<div>
|
||||||
// size="s"
|
<span class="g-secondary">
|
||||||
// [max]="1"
|
{{ local().stateInfo.manifest.version }}
|
||||||
// [value]="
|
</span>
|
||||||
// (localPkg.stateInfo.installingInfo.progress.overall
|
<tui-icon icon="@tui.arrow-right" />
|
||||||
// | installingProgress) || 0
|
<span class="g-positive">{{ item().version }}</span>
|
||||||
// "
|
</div>
|
||||||
// />
|
</td>
|
||||||
// } @else {
|
<td class="desktop">{{ item().gitHash }}</td>
|
||||||
// @if (ready) {
|
<td class="desktop">{{ item().s9pk.publishedAt | date }}</td>
|
||||||
// <button
|
<td>
|
||||||
// tuiButton
|
<div>
|
||||||
// size="s"
|
@if (local().stateInfo.state === 'updating') {
|
||||||
// [appearance]="errors ? 'destructive' : 'primary'"
|
<tui-progress-circle
|
||||||
// (click.stop)="onClick()"
|
class="g-positive"
|
||||||
// >
|
size="xs"
|
||||||
// {{ errors ? 'Retry' : 'Update' }}
|
[max]="100"
|
||||||
// </button>
|
[value]="
|
||||||
// } @else {
|
(local().stateInfo.installingInfo?.progress?.overall
|
||||||
// <tui-loader [style.width.rem]="2" [inheritColor]="true" />
|
| installingProgress) || 0
|
||||||
// }
|
"
|
||||||
// }
|
/>
|
||||||
// </div>
|
} @else {
|
||||||
// <ng-template tuiAccordionItemContent>
|
@if (ready()) {
|
||||||
// <strong>What's new</strong>
|
<button
|
||||||
// <p
|
tuiButton
|
||||||
// safeLinks
|
iconStart="@tui.arrow-big-up-dash"
|
||||||
// [innerHTML]="marketplacePkg.releaseNotes | markdown | dompurify"
|
[appearance]="error() ? 'destructive' : 'primary'"
|
||||||
// ></p>
|
(click.stop)="onClick()"
|
||||||
// <a
|
>
|
||||||
// tuiLink
|
{{ error() ? 'Retry' : 'Update' }}
|
||||||
// iconEnd="@tui.external-link"
|
</button>
|
||||||
// routerLink="/marketplace"
|
} @else {
|
||||||
// [queryParams]="{ url: url, id: marketplacePkg.id }"
|
<tui-loader [style.width.rem]="2" [inheritColor]="true" />
|
||||||
// >
|
}
|
||||||
// View listing
|
}
|
||||||
// </a>
|
<button tuiIconButton appearance="icon" [tuiChevron]="expanded()">
|
||||||
// </ng-template>
|
Show more
|
||||||
// </tui-accordion-item>
|
</button>
|
||||||
// `,
|
</div>
|
||||||
// styles: [
|
</td>
|
||||||
// `
|
</tr>
|
||||||
// :host {
|
<tr>
|
||||||
// display: block;
|
<td colspan="5">
|
||||||
// --tui-background-neutral-1-hover: transparent;
|
@if (error()) {
|
||||||
|
<p class="g-negative">{{ error() }}</p>
|
||||||
|
}
|
||||||
|
<tui-expand [expanded]="expanded()">
|
||||||
|
<p tuiTitle class="mobile">
|
||||||
|
<b>Package Hash</b>
|
||||||
|
<span tuiSubtitle>{{ item().gitHash }}</span>
|
||||||
|
</p>
|
||||||
|
<p tuiTitle class="mobile">
|
||||||
|
<b>Published</b>
|
||||||
|
<span tuiSubtitle>{{ item().s9pk.publishedAt | date }}</span>
|
||||||
|
</p>
|
||||||
|
<p tuiTitle>
|
||||||
|
<span>
|
||||||
|
<a
|
||||||
|
tuiLink
|
||||||
|
iconEnd="@tui.external-link"
|
||||||
|
routerLink="/portal/marketplace"
|
||||||
|
[queryParams]="{ url: parent.current()?.url, id: item().id }"
|
||||||
|
>
|
||||||
|
View listing
|
||||||
|
</a>
|
||||||
|
<b>What's new</b>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
safeLinks
|
||||||
|
[innerHTML]="item().releaseNotes | markdown | dompurify"
|
||||||
|
></p>
|
||||||
|
</tui-expand>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||||
|
|
||||||
// &:not(:last-child) {
|
:host {
|
||||||
// border-bottom: 1px solid var(--tui-background-neutral-1);
|
display: contents;
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
// `,
|
|
||||||
// ],
|
|
||||||
// standalone: true,
|
|
||||||
// imports: [
|
|
||||||
// RouterLink,
|
|
||||||
// MarkdownPipeModule,
|
|
||||||
// NgDompurifyModule,
|
|
||||||
// SafeLinksDirective,
|
|
||||||
// SharedPipesModule,
|
|
||||||
// TuiProgress,
|
|
||||||
// TuiAccordion,
|
|
||||||
// TuiAvatar,
|
|
||||||
// TuiIcon,
|
|
||||||
// TuiButton,
|
|
||||||
// TuiLink,
|
|
||||||
// TuiLoader,
|
|
||||||
// InstallingProgressPipe,
|
|
||||||
// ],
|
|
||||||
// })
|
|
||||||
// export class UpdatesItemComponent {
|
|
||||||
// private readonly dialogs = inject(TuiDialogService)
|
|
||||||
// private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
|
||||||
// private readonly marketplaceService = inject(MarketplaceService)
|
|
||||||
|
|
||||||
// @Input({ required: true })
|
tui-icon {
|
||||||
// marketplacePkg!: MarketplacePkg
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
// @Input({ required: true })
|
div {
|
||||||
// localPkg!: PackageDataEntry<InstalledState | UpdatingState>
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
// @Input({ required: true })
|
tr:first-child {
|
||||||
// url!: string
|
min-height: var(--tui-height-l);
|
||||||
|
word-break: break-word;
|
||||||
|
clip-path: inset(0 round var(--tui-radius-s));
|
||||||
|
cursor: pointer;
|
||||||
|
@include transition(background);
|
||||||
|
|
||||||
// get pkgId(): string {
|
@media ($tui-mouse) {
|
||||||
// return this.marketplacePkg.id
|
&:hover {
|
||||||
// }
|
background: var(--tui-background-neutral-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get errors(): string {
|
td {
|
||||||
// return this.marketplaceService.updateErrors[this.pkgId]
|
min-width: 0;
|
||||||
// }
|
vertical-align: middle;
|
||||||
|
|
||||||
// get ready(): boolean {
|
&:first-child {
|
||||||
// return !this.marketplaceService.updateQueue[this.pkgId]
|
white-space: nowrap;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// async onClick() {
|
&:last-child {
|
||||||
// const { id } = this.marketplacePkg
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
// delete this.marketplaceService.updateErrors[id]
|
div {
|
||||||
// this.marketplaceService.updateQueue[id] = true
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if (hasCurrentDeps(id, await getAllPackages(this.patch))) {
|
&[colspan]:only-child {
|
||||||
// const proceed = await this.alert()
|
padding: 0 3rem;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
// if (proceed) {
|
[tuiLink] {
|
||||||
// await this.update()
|
float: right;
|
||||||
// } else {
|
}
|
||||||
// delete this.marketplaceService.updateQueue[id]
|
}
|
||||||
// }
|
}
|
||||||
// } else {
|
|
||||||
// await this.update()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private async update() {
|
[tuiTitle] {
|
||||||
// const { id, version } = this.marketplacePkg
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
// try {
|
.mobile {
|
||||||
// await this.marketplaceService.installPackage(id, version, this.url)
|
display: none;
|
||||||
// delete this.marketplaceService.updateQueue[id]
|
}
|
||||||
// } catch (e: any) {
|
|
||||||
// delete this.marketplaceService.updateQueue[id]
|
|
||||||
// this.marketplaceService.updateErrors[id] = e.message
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private async alert(): Promise<boolean> {
|
:host-context(tui-root._mobile) {
|
||||||
// return new Promise(async resolve => {
|
tr:first-child {
|
||||||
// this.dialogs
|
display: grid;
|
||||||
// .open<boolean>(TUI_CONFIRM, {
|
grid-template-columns: 1fr min-content;
|
||||||
// label: 'Warning',
|
align-items: center;
|
||||||
// size: 's',
|
padding: 0.5rem 0;
|
||||||
// data: {
|
}
|
||||||
// content: `Services that depend on ${this.localPkg.stateInfo.manifest.title} will no longer work properly and may crash`,
|
|
||||||
// yes: 'Continue',
|
td[colspan]:only-child {
|
||||||
// no: 'Cancel',
|
padding: 0 0.5rem;
|
||||||
// },
|
}
|
||||||
// })
|
|
||||||
// .subscribe(response => resolve(response))
|
[tuiButton] {
|
||||||
// })
|
font-size: 0;
|
||||||
// }
|
gap: 0;
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
.desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [
|
||||||
|
RouterLink,
|
||||||
|
TuiExpand,
|
||||||
|
TuiButton,
|
||||||
|
TuiChevron,
|
||||||
|
TuiAvatar,
|
||||||
|
TuiLink,
|
||||||
|
TuiIcon,
|
||||||
|
TuiLoader,
|
||||||
|
TuiProgressCircle,
|
||||||
|
TuiTitle,
|
||||||
|
MarkdownPipeModule,
|
||||||
|
NgDompurifyModule,
|
||||||
|
SafeLinksDirective,
|
||||||
|
DatePipe,
|
||||||
|
InstallingProgressPipe,
|
||||||
|
TuiFade,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class UpdatesItemComponent {
|
||||||
|
private readonly dialogs = inject(TuiResponsiveDialogService)
|
||||||
|
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||||
|
private readonly service = inject(MarketplaceService)
|
||||||
|
|
||||||
|
readonly parent = inject(UpdatesComponent)
|
||||||
|
readonly expanded = signal(false)
|
||||||
|
readonly error = signal('')
|
||||||
|
readonly ready = signal(true)
|
||||||
|
|
||||||
|
readonly item = input.required<MarketplacePkg>()
|
||||||
|
readonly local =
|
||||||
|
input.required<PackageDataEntry<InstalledState | UpdatingState>>()
|
||||||
|
|
||||||
|
async onClick() {
|
||||||
|
this.ready.set(false)
|
||||||
|
this.error.set('')
|
||||||
|
|
||||||
|
if (hasCurrentDeps(this.item().id, await getAllPackages(this.patch))) {
|
||||||
|
if (await this.alert()) {
|
||||||
|
await this.update()
|
||||||
|
} else {
|
||||||
|
this.ready.set(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await this.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async update() {
|
||||||
|
const { id, version } = this.item()
|
||||||
|
const url = this.parent.current()?.url || ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.service.installPackage(id, version, url)
|
||||||
|
this.ready.set(true)
|
||||||
|
} catch (e: any) {
|
||||||
|
this.ready.set(true)
|
||||||
|
this.error.set(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async alert(): Promise<boolean> {
|
||||||
|
return firstValueFrom(
|
||||||
|
this.dialogs
|
||||||
|
.open<boolean>(TUI_CONFIRM, {
|
||||||
|
label: 'Warning',
|
||||||
|
size: 's',
|
||||||
|
data: {
|
||||||
|
content: `Services that depend on ${this.local().stateInfo.manifest.title} will no longer work properly and may crash`,
|
||||||
|
yes: 'Continue',
|
||||||
|
no: 'Cancel',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(defaultIfEmpty(false)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,95 +1,251 @@
|
|||||||
// import { CommonModule } from '@angular/common'
|
import {
|
||||||
// import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
ChangeDetectionStrategy,
|
||||||
// import {
|
Component,
|
||||||
// StoreIconComponentModule,
|
inject,
|
||||||
// } from '@start9labs/marketplace'
|
signal,
|
||||||
// import { TuiAvatar } from '@taiga-ui/kit'
|
} from '@angular/core'
|
||||||
// import { TuiCell } from '@taiga-ui/layout'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
// import { PatchDB } from 'patch-db-client'
|
import {
|
||||||
// import { combineLatest, map } from 'rxjs'
|
Marketplace,
|
||||||
// import { FilterUpdatesPipe } from 'src/app/routes/portal/routes/updates/filter-updates.pipe'
|
StoreIconComponentModule,
|
||||||
// import { UpdatesItemComponent } from 'src/app/routes/portal/routes/updates/item.component'
|
StoreIdentity,
|
||||||
// import { ConfigService } from 'src/app/services/config.service'
|
} from '@start9labs/marketplace'
|
||||||
// import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { TuiTable } from '@taiga-ui/addon-table'
|
||||||
// import {
|
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||||
// DataModel,
|
import { TuiButton, TuiNotification, TuiTitle } from '@taiga-ui/core'
|
||||||
// InstalledState,
|
import {
|
||||||
// PackageDataEntry,
|
TuiAvatar,
|
||||||
// UpdatingState,
|
TuiBadgeNotification,
|
||||||
// } from 'src/app/services/patch-db/data-model'
|
TuiFade,
|
||||||
// import { isInstalled, isUpdating } from 'src/app/utils/get-package-data'
|
TuiSkeleton,
|
||||||
|
} from '@taiga-ui/kit'
|
||||||
|
import { TuiCell } from '@taiga-ui/layout'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import { combineLatest, map, tap } from 'rxjs'
|
||||||
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
|
import {
|
||||||
|
DataModel,
|
||||||
|
InstalledState,
|
||||||
|
PackageDataEntry,
|
||||||
|
UpdatingState,
|
||||||
|
} from 'src/app/services/patch-db/data-model'
|
||||||
|
import { TitleDirective } from 'src/app/services/title.service'
|
||||||
|
import { isInstalled, isUpdating } from 'src/app/utils/get-package-data'
|
||||||
|
import { FilterUpdatesPipe } from './filter-updates.pipe'
|
||||||
|
import { UpdatesItemComponent } from './item.component'
|
||||||
|
|
||||||
// @Component({
|
interface UpdatesData {
|
||||||
// template: `
|
hosts: StoreIdentity[]
|
||||||
// @if (data$ | async; as data) {
|
marketplace: Marketplace
|
||||||
// @for (host of data.hosts; track host) {
|
localPkgs: Record<string, PackageDataEntry<InstalledState | UpdatingState>>
|
||||||
// <h3 class="g-title">
|
errors: string[]
|
||||||
// <store-icon [url]="host.url" [marketplace]="mp" size="26px" />
|
}
|
||||||
// {{ host.name }}
|
|
||||||
// </h3>
|
|
||||||
// @if (data.errors.includes(host.url)) {
|
|
||||||
// <p class="g-negative">Request Failed</p>
|
|
||||||
// }
|
|
||||||
// @if (data.mp[host.url]?.packages | filterUpdates: data.local; as pkgs) {
|
|
||||||
// @for (pkg of pkgs; track pkg) {
|
|
||||||
// <updates-item
|
|
||||||
// [marketplacePkg]="pkg"
|
|
||||||
// [localPkg]="data.local[pkg.id]"
|
|
||||||
// [url]="host.url"
|
|
||||||
// />
|
|
||||||
// } @empty {
|
|
||||||
// <p>All services are up to date!</p>
|
|
||||||
// }
|
|
||||||
// } @else {
|
|
||||||
// @for (i of [0, 1, 2]; track i) {
|
|
||||||
// <section tuiCell>
|
|
||||||
// <tui-avatar class="tui-skeleton" />
|
|
||||||
// <span class="tui-skeleton">Loading update item</span>
|
|
||||||
// <span class="tui-skeleton" [style.margin-left]="'auto'">
|
|
||||||
// Loading actions
|
|
||||||
// </span>
|
|
||||||
// </section>
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// `,
|
|
||||||
// host: { class: 'g-page' },
|
|
||||||
// changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
// standalone: true,
|
|
||||||
// imports: [
|
|
||||||
// CommonModule,
|
|
||||||
// TuiCell,
|
|
||||||
// TuiAvatar,
|
|
||||||
// StoreIconComponentModule,
|
|
||||||
// FilterUpdatesPipe,
|
|
||||||
// UpdatesItemComponent,
|
|
||||||
// ],
|
|
||||||
// })
|
|
||||||
// export default class UpdatesComponent {
|
|
||||||
// private readonly marketplaceService = inject(MarketplaceService)
|
|
||||||
|
|
||||||
// readonly mp = inject(ConfigService).marketplace
|
@Component({
|
||||||
// readonly data$ = combineLatest({
|
template: `
|
||||||
// hosts: this.marketplaceService.getKnownHosts$(true),
|
<ng-container *title>
|
||||||
// mp: this.marketplaceService.getMarketplace$(),
|
<label>Updates</label>
|
||||||
// local: inject<PatchDB<DataModel>>(PatchDB)
|
@if (current()) {
|
||||||
// .watch$('packageData')
|
<button
|
||||||
// .pipe(
|
tuiIconButton
|
||||||
// map(pkgs =>
|
iconStart="@tui.arrow-left"
|
||||||
// Object.entries(pkgs).reduce(
|
(click)="current.set(null)"
|
||||||
// (acc, [id, val]) => {
|
>
|
||||||
// if (isInstalled(val) || isUpdating(val))
|
Back
|
||||||
// return { ...acc, [id]: val }
|
</button>
|
||||||
// return acc
|
{{ current()?.name }}
|
||||||
// },
|
}
|
||||||
// {} as Record<
|
</ng-container>
|
||||||
// string,
|
<aside class="g-aside">
|
||||||
// PackageDataEntry<InstalledState | UpdatingState>
|
@for (registry of data()?.hosts; track $index) {
|
||||||
// >,
|
<button
|
||||||
// ),
|
tuiCell
|
||||||
// ),
|
[class.g-secondary]="current()?.url !== registry.url"
|
||||||
// ),
|
(click)="current.set(registry)"
|
||||||
// errors: this.marketplaceService.getRequestErrors$(),
|
>
|
||||||
// })
|
<tui-avatar>
|
||||||
// }
|
<store-icon [url]="registry.url" [marketplace]="mp" />
|
||||||
|
</tui-avatar>
|
||||||
|
<span tuiTitle>
|
||||||
|
<b tuiFade>{{ registry.name }}</b>
|
||||||
|
<span tuiSubtitle tuiFade>{{ clear(registry.url) }}</span>
|
||||||
|
</span>
|
||||||
|
@if (
|
||||||
|
(
|
||||||
|
data()?.marketplace?.[registry.url]?.packages || []
|
||||||
|
| filterUpdates: data()?.localPkgs
|
||||||
|
).length;
|
||||||
|
as length
|
||||||
|
) {
|
||||||
|
<tui-badge-notification>
|
||||||
|
{{ length }}
|
||||||
|
</tui-badge-notification>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</aside>
|
||||||
|
<section class="g-subpage">
|
||||||
|
@if (data()?.errors?.includes(current()?.url || '')) {
|
||||||
|
<tui-notification appearance="negative">
|
||||||
|
Request Failed
|
||||||
|
</tui-notification>
|
||||||
|
}
|
||||||
|
<section class="g-card" [style.padding]="'0 1rem 1rem'">
|
||||||
|
<table tuiTable class="g-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th tuiTh>Name</th>
|
||||||
|
<th tuiTh>Version</th>
|
||||||
|
<th tuiTh>Package Hash</th>
|
||||||
|
<th tuiTh>Published</th>
|
||||||
|
<th tuiTh></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@if (
|
||||||
|
data()?.marketplace?.[current()?.url || '']?.packages;
|
||||||
|
as packages
|
||||||
|
) {
|
||||||
|
@if (packages | filterUpdates: data()?.localPkgs; as updates) {
|
||||||
|
@for (pkg of updates; track $index) {
|
||||||
|
<updates-item
|
||||||
|
[item]="pkg"
|
||||||
|
[local]="data()?.localPkgs?.[pkg.id]!"
|
||||||
|
/>
|
||||||
|
} @empty {
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">All services are up to date!</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" [tuiSkeleton]="true">Loading</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" [tuiSkeleton]="true">Loading</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
color: var(--tui-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
label:not(:last-child) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[tuiCell] {
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:not(.g-secondary) {
|
||||||
|
background: var(--tui-background-neutral-1);
|
||||||
|
box-shadow: inset 0 0 0 1px var(--tui-background-neutral-1-hover);
|
||||||
|
color: var(--tui-text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
clip-path: inset(0.5rem round var(--tui-radius-s));
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(tui-root._mobile) {
|
||||||
|
aside {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[tuiCell] {
|
||||||
|
color: var(--tui-text-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host._selected {
|
||||||
|
aside {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host:not(._selected) {
|
||||||
|
section {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
host: {
|
||||||
|
class: 'g-page',
|
||||||
|
'[class._selected]': 'current()',
|
||||||
|
},
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
TuiCell,
|
||||||
|
TuiAvatar,
|
||||||
|
TuiTitle,
|
||||||
|
TuiNotification,
|
||||||
|
TuiSkeleton,
|
||||||
|
TuiTable,
|
||||||
|
TuiBadgeNotification,
|
||||||
|
TuiFade,
|
||||||
|
TuiButton,
|
||||||
|
StoreIconComponentModule,
|
||||||
|
FilterUpdatesPipe,
|
||||||
|
UpdatesItemComponent,
|
||||||
|
TitleDirective,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export default class UpdatesComponent {
|
||||||
|
private readonly isMobile = inject(TUI_IS_MOBILE)
|
||||||
|
private readonly marketplaceService = inject(MarketplaceService)
|
||||||
|
|
||||||
|
readonly mp = inject(ConfigService).marketplace
|
||||||
|
readonly current = signal<StoreIdentity | null>(null)
|
||||||
|
|
||||||
|
readonly data = toSignal<UpdatesData>(
|
||||||
|
combineLatest({
|
||||||
|
hosts: this.marketplaceService
|
||||||
|
.getKnownHosts$(true)
|
||||||
|
.pipe(tap(([store]) => !this.isMobile && this.current.set(store))),
|
||||||
|
marketplace: this.marketplaceService.marketplace$,
|
||||||
|
localPkgs: inject<PatchDB<DataModel>>(PatchDB)
|
||||||
|
.watch$('packageData')
|
||||||
|
.pipe(
|
||||||
|
map(pkgs =>
|
||||||
|
Object.entries(pkgs).reduce<
|
||||||
|
Record<string, PackageDataEntry<InstalledState | UpdatingState>>
|
||||||
|
>(
|
||||||
|
(acc, [id, val]) =>
|
||||||
|
isInstalled(val) || isUpdating(val)
|
||||||
|
? { ...acc, [id]: val }
|
||||||
|
: acc,
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
errors: this.marketplaceService.getRequestErrors$(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
clear(url: string): string {
|
||||||
|
return url.replace(/https?:\/\//, '').replace(/\/$/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1054,11 +1054,11 @@ export class MockApiService extends ApiService {
|
|||||||
...Mock.LocalPkgs[params.id],
|
...Mock.LocalPkgs[params.id],
|
||||||
stateInfo: {
|
stateInfo: {
|
||||||
// if installing
|
// if installing
|
||||||
state: 'installing',
|
// state: 'installing',
|
||||||
|
|
||||||
// if updating
|
// if updating
|
||||||
// state: 'updating',
|
state: 'updating',
|
||||||
// manifest: mockPatchData.packageData[params.id].stateInfo.manifest!,
|
manifest: mockPatchData.packageData[params.id].stateInfo.manifest!,
|
||||||
|
|
||||||
// both
|
// both
|
||||||
installingInfo: {
|
installingInfo: {
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import {
|
|||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
pairwise,
|
pairwise,
|
||||||
|
shareReplay,
|
||||||
startWith,
|
startWith,
|
||||||
switchMap,
|
switchMap,
|
||||||
} from 'rxjs'
|
} from 'rxjs'
|
||||||
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import { EOSService } from 'src/app/services/eos.service'
|
import { EOSService } from 'src/app/services/eos.service'
|
||||||
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import { NotificationService } from 'src/app/services/notification.service'
|
import { NotificationService } from 'src/app/services/notification.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
|
||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
import { getManifest } from 'src/app/utils/get-package-data'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -54,35 +55,35 @@ export class BadgeService {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// private readonly updates$ = combineLatest([
|
private readonly updates$ = combineLatest([
|
||||||
// this.marketplaceService.getMarketplace$(true),
|
this.marketplaceService.marketplace$,
|
||||||
// this.local$,
|
this.local$,
|
||||||
// ]).pipe(
|
]).pipe(
|
||||||
// map(
|
map(
|
||||||
// ([marketplace, local]) =>
|
([marketplace, local]) =>
|
||||||
// Object.entries(marketplace).reduce(
|
Object.entries(marketplace).reduce(
|
||||||
// (list, [_, store]) =>
|
(list, [_, store]) =>
|
||||||
// store?.packages.reduce(
|
store?.packages.reduce(
|
||||||
// (result, { id, version }) =>
|
(result, { id, version }) =>
|
||||||
// local[id] &&
|
local[id] &&
|
||||||
// this.exver.compareExver(
|
this.exver.compareExver(
|
||||||
// version,
|
version,
|
||||||
// getManifest(local[id]).version,
|
getManifest(local[id]).version,
|
||||||
// ) === 1
|
) === 1
|
||||||
// ? result.add(id)
|
? result.add(id)
|
||||||
// : result,
|
: result,
|
||||||
// list,
|
list,
|
||||||
// ) || list,
|
) || list,
|
||||||
// new Set<string>(),
|
new Set<string>(),
|
||||||
// ).size,
|
).size,
|
||||||
// ),
|
),
|
||||||
// shareReplay(1),
|
shareReplay(1),
|
||||||
// )
|
)
|
||||||
|
|
||||||
getCount(id: string): Observable<number> {
|
getCount(id: string): Observable<number> {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
// case '/portal/updates':
|
case '/portal/updates':
|
||||||
// return this.updates$
|
return this.updates$
|
||||||
case '/portal/system':
|
case '/portal/system':
|
||||||
return this.system$
|
return this.system$
|
||||||
case '/portal/notifications':
|
case '/portal/notifications':
|
||||||
|
|||||||
@@ -1,32 +1,39 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
StoreIdentity,
|
|
||||||
MarketplacePkg,
|
|
||||||
GetPackageRes,
|
GetPackageRes,
|
||||||
|
Marketplace,
|
||||||
|
MarketplacePkg,
|
||||||
|
StoreData,
|
||||||
StoreDataWithUrl,
|
StoreDataWithUrl,
|
||||||
|
StoreIdentity,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
|
import { Exver, sameUrl } from '@start9labs/shared'
|
||||||
|
import { T } from '@start9labs/start-sdk'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
catchError,
|
catchError,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
|
distinctUntilChanged,
|
||||||
filter,
|
filter,
|
||||||
from,
|
from,
|
||||||
map,
|
map,
|
||||||
|
mergeMap,
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of,
|
||||||
shareReplay,
|
pairwise,
|
||||||
switchMap,
|
|
||||||
distinctUntilChanged,
|
|
||||||
ReplaySubject,
|
ReplaySubject,
|
||||||
|
scan,
|
||||||
|
shareReplay,
|
||||||
|
startWith,
|
||||||
|
switchMap,
|
||||||
|
tap,
|
||||||
} from 'rxjs'
|
} from 'rxjs'
|
||||||
import { RR } from 'src/app/services/api/api.types'
|
import { RR } from 'src/app/services/api/api.types'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
||||||
import { ConfigService } from './config.service'
|
|
||||||
import { Exver } from '@start9labs/shared'
|
|
||||||
import { ClientStorageService } from './client-storage.service'
|
import { ClientStorageService } from './client-storage.service'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { ConfigService } from './config.service'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -66,7 +73,10 @@ export class MarketplaceService {
|
|||||||
const { start9, community } = this.config.marketplace
|
const { start9, community } = this.config.marketplace
|
||||||
let arr = [
|
let arr = [
|
||||||
toStoreIdentity(start9, hosts[start9]),
|
toStoreIdentity(start9, hosts[start9]),
|
||||||
toStoreIdentity(community, hosts[community]),
|
toStoreIdentity(community, {
|
||||||
|
...hosts[community],
|
||||||
|
name: 'Community Registry',
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
return arr.concat(
|
return arr.concat(
|
||||||
@@ -93,6 +103,32 @@ export class MarketplaceService {
|
|||||||
|
|
||||||
private readonly requestErrors$ = new BehaviorSubject<string[]>([])
|
private readonly requestErrors$ = new BehaviorSubject<string[]>([])
|
||||||
|
|
||||||
|
readonly marketplace$: Observable<Marketplace> = this.knownHosts$.pipe(
|
||||||
|
startWith<StoreIdentity[]>([]),
|
||||||
|
pairwise(),
|
||||||
|
mergeMap(([prev, curr]) =>
|
||||||
|
curr.filter(c => !prev.find(p => sameUrl(c.url, p.url))),
|
||||||
|
),
|
||||||
|
mergeMap(({ url, name }) =>
|
||||||
|
this.fetchRegistry$(url).pipe(
|
||||||
|
tap(data => {
|
||||||
|
if (data?.info.name) this.updateStoreName(url, name, data.info.name)
|
||||||
|
}),
|
||||||
|
map<StoreData | null, [string, StoreData | null]>(data => [url, data]),
|
||||||
|
startWith<[string, StoreData | null]>([url, null]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
scan<[string, StoreData | null], Record<string, StoreData | null>>(
|
||||||
|
(requests, [url, store]) => {
|
||||||
|
requests[url] = store
|
||||||
|
|
||||||
|
return requests
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
|
)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly api: ApiService,
|
private readonly api: ApiService,
|
||||||
private readonly patch: PatchDB<DataModel>,
|
private readonly patch: PatchDB<DataModel>,
|
||||||
@@ -230,10 +266,6 @@ export class MarketplaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI only
|
|
||||||
readonly updateErrors: Record<string, string> = {}
|
|
||||||
readonly updateQueue: Record<string, boolean> = {}
|
|
||||||
|
|
||||||
getRequestErrors$(): Observable<string[]> {
|
getRequestErrors$(): Observable<string[]> {
|
||||||
return this.requestErrors$
|
return this.requestErrors$
|
||||||
}
|
}
|
||||||
@@ -251,6 +283,19 @@ export class MarketplaceService {
|
|||||||
|
|
||||||
await this.api.installPackage(params)
|
await this.api.installPackage(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateStoreName(
|
||||||
|
url: string,
|
||||||
|
oldName: string | undefined,
|
||||||
|
newName: string,
|
||||||
|
): Promise<void> {
|
||||||
|
if (oldName !== newName) {
|
||||||
|
this.api.setDbValue<string>(
|
||||||
|
['marketplace', 'knownHosts', url, 'name'],
|
||||||
|
newName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toStoreIdentity(url: string, uiStore: UIStore): StoreIdentity {
|
function toStoreIdentity(url: string, uiStore: UIStore): StoreIdentity {
|
||||||
|
|||||||
@@ -16,11 +16,10 @@ export const SYSTEM_UTILITIES: Record<string, { icon: string; title: string }> =
|
|||||||
icon: '@tui.upload',
|
icon: '@tui.upload',
|
||||||
title: 'Sideload',
|
title: 'Sideload',
|
||||||
},
|
},
|
||||||
// @TODO 040
|
'/portal/updates': {
|
||||||
// '/portal/updates': {
|
icon: '@tui.globe',
|
||||||
// icon: '@tui.globe',
|
title: 'Updates',
|
||||||
// title: 'Updates',
|
},
|
||||||
// },
|
|
||||||
// @TODO 041
|
// @TODO 041
|
||||||
// '/portal/backups': {
|
// '/portal/backups': {
|
||||||
// icon: '@tui.save',
|
// icon: '@tui.save',
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ hr {
|
|||||||
min-height: fit-content;
|
min-height: fit-content;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.g-aside {
|
.g-aside {
|
||||||
@@ -76,7 +77,7 @@ hr {
|
|||||||
top: 1px;
|
top: 1px;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
width: 16rem;
|
width: 18rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
box-shadow: 1px 0 var(--tui-border-normal);
|
box-shadow: 1px 0 var(--tui-border-normal);
|
||||||
|
|||||||
Reference in New Issue
Block a user