mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
update marketplace url to reflect build version (#2914)
* update marketplace url to reflect build version * adjust marketplace config * use helper function to compare urls * rework some registry stuff * #2900, #2899, and other registry changes * alpha.1 * trailing / * add startosRegistry * fix migration --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
This commit is contained in:
@@ -5,11 +5,6 @@
|
||||
"url": "rpc",
|
||||
"version": "v1"
|
||||
},
|
||||
"marketplace": {
|
||||
"start9": "https://registry.start9.com/",
|
||||
"community": "https://community-registry.start9.com/"
|
||||
},
|
||||
"startosRegistry": "https://registry.start9.com/",
|
||||
"mocks": {
|
||||
"maskAs": "tor",
|
||||
"maskAsHttps": true,
|
||||
|
||||
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "startos-ui",
|
||||
"version": "0.4.0-alpha.0",
|
||||
"version": "0.4.0-alpha.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "startos-ui",
|
||||
"version": "0.4.0-alpha.0",
|
||||
"version": "0.4.0-alpha.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "startos-ui",
|
||||
"version": "0.4.0-alpha.0",
|
||||
"version": "0.4.0-alpha.1",
|
||||
"author": "Start9 Labs, Inc",
|
||||
"homepage": "https://start9.com/",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
{
|
||||
"name": null,
|
||||
"marketplace": {
|
||||
"selectedUrl": "https://registry.start9.com/",
|
||||
"knownHosts": {
|
||||
"https://registry.start9.com/": {
|
||||
"name": "Start9 Registry"
|
||||
},
|
||||
"https://community-registry.start9.com/": {
|
||||
"name": "Community Registry"
|
||||
}
|
||||
}
|
||||
"registries": {
|
||||
"https://registry.start9.com/": "Start9 Registry",
|
||||
"https://community-registry.start9.com/": "Community Registry"
|
||||
},
|
||||
"gaming": {
|
||||
"snake": {
|
||||
"highScore": 0
|
||||
}
|
||||
},
|
||||
"ackInstructions": {},
|
||||
"theme": "Dark"
|
||||
"startosRegisrty": "https://registry.start9.com/",
|
||||
"snakeHighScore": 0,
|
||||
"ackInstructions": {}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
[style.border-radius.%]="!registry ? 100 : null"
|
||||
size="60px"
|
||||
[url]="registry?.url || ''"
|
||||
[marketplace]="iconConfig"
|
||||
/>
|
||||
<h1 [tuiSkeleton]="!registry">
|
||||
{{ registry?.info?.name || 'Unnamed Registry' }}
|
||||
@@ -27,7 +26,6 @@
|
||||
[style.height.px]="42"
|
||||
[style.border-radius.%]="100"
|
||||
[url]="registry?.url || ''"
|
||||
[marketplace]="iconConfig"
|
||||
[tuiSkeleton]="!registry"
|
||||
/>
|
||||
<tui-drawer
|
||||
|
||||
@@ -60,7 +60,6 @@ header {
|
||||
}
|
||||
|
||||
store-icon {
|
||||
border-radius: 100%;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
OnDestroy,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import { MarketplaceConfig } from '@start9labs/shared'
|
||||
import { Subject, takeUntil } from 'rxjs'
|
||||
import { AbstractCategoryService } from '../../services/category.service'
|
||||
import { StoreDataWithUrl } from '../../types'
|
||||
@@ -18,9 +17,6 @@ import { StoreDataWithUrl } from '../../types'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MenuComponent implements OnDestroy {
|
||||
@Input({ required: true })
|
||||
iconConfig!: MarketplaceConfig
|
||||
|
||||
@Input({ required: true })
|
||||
registry!: StoreDataWithUrl | null
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { StoreIconComponentModule } from './store-icon/store-icon.component.module'
|
||||
import { MarketplaceConfig } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: '[registry]',
|
||||
template: `
|
||||
<store-icon [url]="registry.url" [marketplace]="marketplace" size="40px" />
|
||||
<store-icon [url]="registry.url" size="40px" />
|
||||
<div tuiTitle>
|
||||
{{ registry.name }}
|
||||
<div tuiSubtitle>{{ registry.url }}</div>
|
||||
@@ -24,8 +23,5 @@ import { MarketplaceConfig } from '@start9labs/shared'
|
||||
})
|
||||
export class MarketplaceRegistryComponent {
|
||||
@Input()
|
||||
marketplace!: MarketplaceConfig
|
||||
|
||||
@Input()
|
||||
registry!: { url: string; selected: boolean; name?: string }
|
||||
registry!: { url: string; selected: boolean; name: string }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import { MarketplaceConfig, sameUrl } from '@start9labs/shared'
|
||||
import { knownRegistries, sameUrl } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'store-icon',
|
||||
@@ -9,13 +9,13 @@ import { MarketplaceConfig, sameUrl } from '@start9labs/shared'
|
||||
[style.border-radius.%]="100"
|
||||
[style.max-width]="size || '100%'"
|
||||
[src]="icon"
|
||||
alt="Marketplace Icon"
|
||||
alt="Registry Icon"
|
||||
/>
|
||||
<ng-template #noIcon>
|
||||
<img
|
||||
[style.max-width]="size || '100%'"
|
||||
src="assets/img/storefront-outline.png"
|
||||
alt="Marketplace Icon"
|
||||
alt="Registry Icon"
|
||||
/>
|
||||
</ng-template>
|
||||
`,
|
||||
@@ -27,17 +27,20 @@ export class StoreIconComponent {
|
||||
url = ''
|
||||
@Input()
|
||||
size?: string
|
||||
@Input({ required: true })
|
||||
marketplace!: MarketplaceConfig
|
||||
|
||||
get icon() {
|
||||
const { start9, community } = this.marketplace
|
||||
const { start9Alpha, start9Beta, start9, community } = knownRegistries
|
||||
|
||||
if (sameUrl(this.url, start9)) {
|
||||
if (sameUrl(this.url, start9Alpha)) {
|
||||
return 'assets/img/icon_alpha.png'
|
||||
} else if (sameUrl(this.url, start9Beta)) {
|
||||
return 'assets/img/icon_beta.png'
|
||||
} else if (sameUrl(this.url, start9)) {
|
||||
return 'assets/img/icon_transparent.png'
|
||||
} else if (sameUrl(this.url, community)) {
|
||||
return 'assets/img/community-store.png'
|
||||
return 'assets/img/community-icon.png'
|
||||
} else {
|
||||
return 'assets/img/storefront-outline.png'
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +39,11 @@ export class CategoriesComponent {
|
||||
readonly categoryChange = new EventEmitter<string>()
|
||||
|
||||
readonly fallback: Record<string, T.Category> = {
|
||||
a: { name: '', description: { short: 'a', long: 'a' } },
|
||||
b: { name: '', description: { short: 'a', long: 'a' } },
|
||||
c: { name: '', description: { short: 'a', long: 'a' } },
|
||||
d: { name: '', description: { short: 'a', long: 'a' } },
|
||||
e: { name: '', description: { short: 'a', long: 'a' } },
|
||||
a: { name: '' },
|
||||
b: { name: '' },
|
||||
c: { name: '' },
|
||||
d: { name: '' },
|
||||
e: { name: '' },
|
||||
}
|
||||
|
||||
switchCategory(category: string): void {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { T } from '@start9labs/start-sdk'
|
||||
|
||||
export type GetPackageReq = {
|
||||
id: string
|
||||
version: string | null
|
||||
targetVersion: string | null
|
||||
otherVersions: 'short'
|
||||
}
|
||||
export type GetPackageRes = T.GetPackageResponse & {
|
||||
@@ -12,7 +12,7 @@ export type GetPackageRes = T.GetPackageResponse & {
|
||||
|
||||
export type GetPackagesReq = {
|
||||
id: null
|
||||
version: null
|
||||
targetVersion: null
|
||||
otherVersions: 'short'
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export type GetPackagesRes = {
|
||||
|
||||
export type StoreIdentity = {
|
||||
url: string
|
||||
name?: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type Marketplace = Record<string, StoreData | null>
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
BIN
web/projects/shared/assets/img/icon_alpha.png
Normal file
BIN
web/projects/shared/assets/img/icon_alpha.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
web/projects/shared/assets/img/icon_beta.png
Normal file
BIN
web/projects/shared/assets/img/icon_beta.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 42 KiB |
@@ -7,11 +7,6 @@ export type WorkspaceConfig = {
|
||||
url: string
|
||||
version: string
|
||||
}
|
||||
marketplace: MarketplaceConfig
|
||||
startosRegistry:
|
||||
| 'https://alpha-registry-x.start9.com/'
|
||||
| 'https://beta-registry.start9.com/'
|
||||
| 'https://registry.start9.com/'
|
||||
mocks: {
|
||||
maskAs: 'tor' | 'local' | 'localhost' | 'ipv4' | 'ipv6' | 'clearnet'
|
||||
maskAsHttps: boolean
|
||||
@@ -20,7 +15,13 @@ export type WorkspaceConfig = {
|
||||
}
|
||||
}
|
||||
|
||||
export interface MarketplaceConfig {
|
||||
start9: 'https://registry.start9.com/'
|
||||
community: 'https://community-registry.start9.com/'
|
||||
}
|
||||
export const defaultRegistries = {
|
||||
start9: 'https://registry.start9.com/',
|
||||
community: 'https://community-registry.start9.com/',
|
||||
} as const
|
||||
|
||||
export const knownRegistries = {
|
||||
...defaultRegistries,
|
||||
start9Alpha: 'https://alpha-registry-x.start9.com/',
|
||||
start9Beta: 'https://beta-registry.start9.com/',
|
||||
} as const
|
||||
|
||||
@@ -127,8 +127,8 @@ export class MarketplaceControlsComponent {
|
||||
async tryInstall() {
|
||||
const currentUrl = this.file
|
||||
? null
|
||||
: await firstValueFrom(this.marketplaceService.getRegistryUrl$())
|
||||
const originalUrl = this.localPkg?.registry || ''
|
||||
: await firstValueFrom(this.marketplaceService.getCurrentRegistryUrl$())
|
||||
const originalUrl = this.localPkg?.registry || null
|
||||
|
||||
if (!this.localPkg) {
|
||||
if (await this.alerts.alertInstall(this.pkg)) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { MenuModule } from '@start9labs/marketplace'
|
||||
import { TuiIcon, TuiButton, TuiAppearance } from '@taiga-ui/core'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { MARKETPLACE_REGISTRY } from '../modals/registry.component'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { DialogService, i18nPipe } from '@start9labs/shared'
|
||||
@@ -11,7 +10,7 @@ import { DialogService, i18nPipe } from '@start9labs/shared'
|
||||
standalone: true,
|
||||
selector: 'marketplace-menu',
|
||||
template: `
|
||||
<menu [iconConfig]="marketplace" [registry]="registry$ | async">
|
||||
<menu [registry]="registry$ | async">
|
||||
<button
|
||||
slot="desktop"
|
||||
tuiIconButton
|
||||
@@ -54,9 +53,8 @@ import { DialogService, i18nPipe } from '@start9labs/shared'
|
||||
})
|
||||
export class MarketplaceMenuComponent {
|
||||
private readonly dialog = inject(DialogService)
|
||||
readonly marketplace = inject(ConfigService).marketplace
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
readonly registry$ = this.marketplaceService.getRegistry$()
|
||||
readonly registry$ = this.marketplaceService.getCurrentRegistry$()
|
||||
|
||||
changeRegistry() {
|
||||
this.dialog
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Component, inject, Input } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { i18nPipe, knownRegistries, sameUrl } from '@start9labs/shared'
|
||||
import { TuiNotification } from '@taiga-ui/core'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -57,24 +56,24 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
imports: [TuiNotification, i18nPipe],
|
||||
})
|
||||
export class MarketplaceNotificationComponent {
|
||||
private readonly marketplace = inject(ConfigService).marketplace
|
||||
|
||||
@Input() url = ''
|
||||
|
||||
get status() {
|
||||
if (this.url === this.marketplace.start9) {
|
||||
const { start9, community, start9Beta, start9Alpha } = knownRegistries
|
||||
|
||||
if (sameUrl(this.url, start9)) {
|
||||
return 'success'
|
||||
}
|
||||
|
||||
if (this.url === this.marketplace.community) {
|
||||
if (sameUrl(this.url, community)) {
|
||||
return 'info'
|
||||
}
|
||||
|
||||
if (this.url.includes('beta')) {
|
||||
if (sameUrl(this.url, start9Beta)) {
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
if (this.url.includes('alpha')) {
|
||||
if (sameUrl(this.url, start9Alpha)) {
|
||||
return 'error'
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
FilterPackagesPipe,
|
||||
FilterPackagesPipeModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { defaultRegistries, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiScrollbar } from '@taiga-ui/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { tap, withLatestFrom } from 'rxjs'
|
||||
@@ -17,6 +17,7 @@ import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { MarketplaceMenuComponent } from './components/menu.component'
|
||||
import { MarketplaceNotificationComponent } from './components/notification.component'
|
||||
import { MarketplaceTileComponent } from './components/tile.component'
|
||||
import { StorageService } from 'src/app/services/storage.service'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -161,16 +162,19 @@ export default class MarketplaceComponent {
|
||||
private readonly categoryService = inject(AbstractCategoryService)
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
private readonly router = inject(Router)
|
||||
private readonly patch = inject(PatchDB<DataModel>)
|
||||
private readonly storage = inject(StorageService)
|
||||
private readonly route = inject(ActivatedRoute)
|
||||
.queryParamMap.pipe(
|
||||
takeUntilDestroyed(),
|
||||
withLatestFrom(this.patch.watch$('ui', 'marketplace', 'selectedUrl')),
|
||||
tap(([params, selectedUrl]) => {
|
||||
tap(params => {
|
||||
const registry = params.get('registry')
|
||||
if (!registry) {
|
||||
this.router.navigate([], {
|
||||
queryParams: { registry: selectedUrl },
|
||||
queryParams: {
|
||||
registry:
|
||||
this.storage.get('selectedRegistry') ||
|
||||
defaultRegistries.start9,
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
})
|
||||
} else {
|
||||
@@ -180,8 +184,8 @@ export default class MarketplaceComponent {
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
readonly url$ = this.marketplaceService.getRegistryUrl$()
|
||||
readonly url$ = this.marketplaceService.getCurrentRegistryUrl$()
|
||||
readonly category$ = this.categoryService.getCategory$()
|
||||
readonly query$ = this.categoryService.getQuery$()
|
||||
readonly registry$ = this.marketplaceService.getRegistry$()
|
||||
readonly registry$ = this.marketplaceService.getCurrentRegistry$()
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ export class MarketplacePreviewComponent {
|
||||
|
||||
readonly flavors$ = this.flavor$.pipe(
|
||||
switchMap(current =>
|
||||
this.marketplaceService.getRegistry$().pipe(
|
||||
this.marketplaceService.getCurrentRegistry$().pipe(
|
||||
map(({ packages }) =>
|
||||
packages.filter(
|
||||
({ id, flavor }) => id === this.pkgId && flavor !== current,
|
||||
|
||||
@@ -14,36 +14,28 @@ import {
|
||||
sameUrl,
|
||||
toUrl,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDialogContext,
|
||||
TuiDialogOptions,
|
||||
TuiIcon,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiButton, TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { combineLatest, filter, firstValueFrom, map, Subscription } from 'rxjs'
|
||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { IST, utils } from '@start9labs/start-sdk'
|
||||
import { StorageService } from 'src/app/services/storage.service'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
@if (stores$ | async; as stores) {
|
||||
@if (registries$ | async; as registries) {
|
||||
<h3 class="g-title">{{ 'Default Registries' | i18n }}</h3>
|
||||
@for (registry of stores.standard; track $index) {
|
||||
@for (registry of registries.standard; track $index) {
|
||||
<button
|
||||
tuiCell
|
||||
[disabled]="registry.selected"
|
||||
[marketplace]="marketplaceConfig"
|
||||
[registry]="registry"
|
||||
(click)="connect(registry.url)"
|
||||
></button>
|
||||
@@ -53,19 +45,18 @@ import { IST, utils } from '@start9labs/start-sdk'
|
||||
<tui-icon icon="@tui.plus" [style.margin-inline.rem]="'0.5'" />
|
||||
<div tuiTitle>{{ 'Add custom registry' | i18n }}</div>
|
||||
</button>
|
||||
@for (registry of stores.alt; track $index) {
|
||||
@for (registry of registries.alt; track $index) {
|
||||
<div class="connect-container">
|
||||
<button
|
||||
tuiCell
|
||||
[registry]="registry"
|
||||
[marketplace]="marketplaceConfig"
|
||||
(click)="connect(registry.url)"
|
||||
></button>
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="icon"
|
||||
iconStart="@tui.trash-2"
|
||||
(click)="delete(registry.url, registry.name)"
|
||||
(click)="delete(registry.url)"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
@@ -103,26 +94,28 @@ export class MarketplaceRegistryModal {
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
private readonly context = injectContext<TuiDialogContext>()
|
||||
private readonly router = inject(Router)
|
||||
private readonly hosts$ = inject<PatchDB<DataModel>>(PatchDB).watch$(
|
||||
private readonly rawRegistries$ = inject<PatchDB<DataModel>>(PatchDB).watch$(
|
||||
'ui',
|
||||
'marketplace',
|
||||
'knownHosts',
|
||||
'registries',
|
||||
)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
readonly marketplaceConfig = inject(ConfigService).marketplace
|
||||
private readonly storage = inject(StorageService)
|
||||
|
||||
readonly stores$ = combineLatest([
|
||||
this.marketplaceService.getKnownHosts$(),
|
||||
this.marketplaceService.getRegistryUrl$(),
|
||||
readonly registries$ = combineLatest([
|
||||
this.marketplaceService.getRegistries$(),
|
||||
this.marketplaceService.getCurrentRegistryUrl$(),
|
||||
]).pipe(
|
||||
map(([stores, selectedUrl]) =>
|
||||
stores.map(s => ({
|
||||
map(([registries, currentUrl]) =>
|
||||
registries.map(s => ({
|
||||
...s,
|
||||
selected: sameUrl(s.url, selectedUrl),
|
||||
selected: sameUrl(s.url, currentUrl),
|
||||
})),
|
||||
),
|
||||
// 0 and 1 are prod and community, 2 and beyond are alts
|
||||
map(stores => ({ standard: stores.slice(0, 2), alt: stores.slice(2) })),
|
||||
map(registries => ({
|
||||
standard: registries.slice(0, 2),
|
||||
alt: registries.slice(2),
|
||||
})),
|
||||
)
|
||||
|
||||
add() {
|
||||
@@ -147,7 +140,7 @@ export class MarketplaceRegistryModal {
|
||||
})
|
||||
}
|
||||
|
||||
delete(url: string, name: string = '') {
|
||||
delete(url: string) {
|
||||
this.dialog
|
||||
.openConfirm({
|
||||
label: 'Confirm',
|
||||
@@ -161,19 +154,21 @@ export class MarketplaceRegistryModal {
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe(async () => {
|
||||
const loader = this.loader.open('Deleting').subscribe()
|
||||
const hosts = await firstValueFrom(this.hosts$)
|
||||
const filtered: { [url: string]: UIStore } = Object.keys(hosts)
|
||||
const rawRegistries = await firstValueFrom(this.rawRegistries$)
|
||||
const filtered: { [url: string]: string | null } = Object.keys(
|
||||
rawRegistries,
|
||||
)
|
||||
.filter(key => !sameUrl(key, url))
|
||||
.reduce(
|
||||
(prev, curr) => ({
|
||||
...prev,
|
||||
[curr]: hosts[curr],
|
||||
[curr]: rawRegistries[curr],
|
||||
}),
|
||||
{},
|
||||
)
|
||||
|
||||
try {
|
||||
await this.api.setDbValue(['marketplace', 'knownHosts'], filtered)
|
||||
await this.api.setDbValue(['registries'], filtered)
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
@@ -195,7 +190,7 @@ export class MarketplaceRegistryModal {
|
||||
queryParams: { registry: url },
|
||||
queryParamsHandling: 'merge',
|
||||
})
|
||||
this.api.setDbValue<string>(['marketplace', 'selectedUrl'], url)
|
||||
this.storage.set('selectedRegistry', url)
|
||||
this.context.$implicit.complete()
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
@@ -255,8 +250,8 @@ export class MarketplaceRegistryModal {
|
||||
loader: Subscription,
|
||||
): Promise<void> {
|
||||
// Error on duplicates
|
||||
const hosts = await firstValueFrom(this.hosts$)
|
||||
const currentUrls = Object.keys(hosts).map(toUrl)
|
||||
const rawRegistries = await firstValueFrom(this.rawRegistries$)
|
||||
const currentUrls = Object.keys(rawRegistries).map(toUrl)
|
||||
if (currentUrls.includes(url))
|
||||
throw new Error(this.i18n.transform('Registry already added'))
|
||||
|
||||
@@ -274,7 +269,7 @@ export class MarketplaceRegistryModal {
|
||||
loader.closed = false
|
||||
loader.add(this.loader.open('Saving').subscribe())
|
||||
|
||||
await this.api.setDbValue(['marketplace', 'knownHosts', url], { name })
|
||||
await this.api.setDbValue(['altRegistries', url], name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
import { inject, Injectable } from '@angular/core'
|
||||
import { MarketplacePkgBase } from '@start9labs/marketplace'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { defaultIfEmpty, firstValueFrom } from 'rxjs'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MarketplaceAlertsService {
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly marketplace$ = inject<PatchDB<DataModel>>(PatchDB).watch$(
|
||||
'ui',
|
||||
'marketplace',
|
||||
)
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
async alertMarketplace(url: string, originalUrl: string): Promise<boolean> {
|
||||
const marketplaces = await firstValueFrom(this.marketplace$)
|
||||
const name = marketplaces.knownHosts[url]?.name || url
|
||||
const source = marketplaces.knownHosts[originalUrl]?.name || originalUrl
|
||||
const message = source
|
||||
? `${this.i18n.transform('installed from')} ${source}`
|
||||
async alertMarketplace(
|
||||
url: string,
|
||||
originalUrl: string | null,
|
||||
): Promise<boolean> {
|
||||
const registries = await firstValueFrom(
|
||||
this.marketplaceService.getRegistries$(),
|
||||
)
|
||||
const message = originalUrl
|
||||
? `${this.i18n.transform('installed from')} ${registries.find(h => h.url === originalUrl) || originalUrl}`
|
||||
: this.i18n.transform('sideloaded')
|
||||
|
||||
const currentName = registries.find(h => h.url === url) || url
|
||||
|
||||
return new Promise(async resolve => {
|
||||
this.dialog
|
||||
.openConfirm<boolean>({
|
||||
@@ -31,7 +32,7 @@ export class MarketplaceAlertsService {
|
||||
size: 's',
|
||||
data: {
|
||||
content:
|
||||
`${this.i18n.transform('This service was originally')} ${message}, ${this.i18n.transform('but you are currently connected to')} ${name}. ${this.i18n.transform('To install from')} ${name} ${this.i18n.transform('anyway, click "Continue".')}` as i18nKey,
|
||||
`${this.i18n.transform('This service was originally')} ${message}, ${this.i18n.transform('but you are currently connected to')} ${currentName}. ${this.i18n.transform('To install from')} ${currentName} ${this.i18n.transform('anyway, click "Continue".')}` as i18nKey,
|
||||
yes: 'Continue',
|
||||
no: 'Cancel',
|
||||
},
|
||||
|
||||
@@ -205,7 +205,6 @@ export default class SystemAcmeComponent {
|
||||
}
|
||||
|
||||
private async saveAcme(providerUrl: string, contact: string[]) {
|
||||
console.log(providerUrl, contact)
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
try {
|
||||
|
||||
@@ -242,10 +242,9 @@ export default class SystemGeneralComponent {
|
||||
readonly translation: TuiStringHandler<TuiContext<Languages>> = ({
|
||||
$implicit,
|
||||
}) => this.i18n.transform($implicit)!
|
||||
readonly score = toSignal(
|
||||
this.patch.watch$('ui', 'gaming', 'snake', 'highScore'),
|
||||
{ initialValue: 0 },
|
||||
)
|
||||
readonly score = toSignal(this.patch.watch$('ui', 'snakeHighScore'), {
|
||||
initialValue: 0,
|
||||
})
|
||||
|
||||
get language(): Languages | undefined {
|
||||
return this.languages.find(lang => lang === this.i18nService.language)
|
||||
|
||||
@@ -37,10 +37,7 @@ export class SnekDirective {
|
||||
const loader = this.loader.open('Saving high score').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.setDbValue<number>(
|
||||
['gaming', 'snake', 'highScore'],
|
||||
score,
|
||||
)
|
||||
await this.api.setDbValue<number>(['snakeHighScore'], score)
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
|
||||
@@ -16,6 +16,9 @@ import { TuiDialogContext, TuiScrollbar, TuiButton } from '@taiga-ui/core'
|
||||
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { OSService } from 'src/app/services/os.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -61,13 +64,19 @@ export class SystemUpdateModal {
|
||||
private readonly errorService: ErrorService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly os: OSService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
async update() {
|
||||
const loader = this.loader.open('Beginning update').subscribe()
|
||||
|
||||
const { startosRegistry } = await firstValueFrom(this.patch.watch$('ui'))
|
||||
|
||||
try {
|
||||
await this.embassyApi.updateServer()
|
||||
await this.embassyApi.updateServer({
|
||||
targetVersion: `=${this.versions[0]!.version}`,
|
||||
registry: startosRegistry,
|
||||
})
|
||||
this.context.$implicit.complete()
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
|
||||
@@ -22,7 +22,6 @@ import { TuiCell } from '@taiga-ui/layout'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { combineLatest, map, tap } from 'rxjs'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import {
|
||||
DataModel,
|
||||
@@ -66,7 +65,7 @@ interface UpdatesData {
|
||||
(click)="current.set(registry)"
|
||||
>
|
||||
<tui-avatar>
|
||||
<store-icon [url]="registry.url" [marketplace]="mp" />
|
||||
<store-icon [url]="registry.url" />
|
||||
</tui-avatar>
|
||||
<span tuiTitle>
|
||||
<b tuiFade>{{ registry.name }}</b>
|
||||
@@ -221,15 +220,17 @@ 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)
|
||||
.getRegistries$(true)
|
||||
.pipe(
|
||||
tap(([store]) => !this.isMobile && store && this.current.set(store)),
|
||||
tap(
|
||||
([registry]) =>
|
||||
!this.isMobile && registry && this.current.set(registry),
|
||||
),
|
||||
),
|
||||
marketplace: this.marketplaceService.marketplace$,
|
||||
localPkgs: inject<PatchDB<DataModel>>(PatchDB)
|
||||
|
||||
@@ -30,7 +30,7 @@ export namespace Mock {
|
||||
shuttingDown: false,
|
||||
}
|
||||
|
||||
export const RegistryOSUpdate: RR.GetRegistryOsUpdateRes = {
|
||||
export const RegistryOSUpdate: RR.CheckOsUpdateRes = {
|
||||
'0.3.6-alpha.17': {
|
||||
headline: 'v0.3.6-alpha.17',
|
||||
releaseNotes: '',
|
||||
@@ -101,16 +101,16 @@ export namespace Mock {
|
||||
},
|
||||
img: {},
|
||||
},
|
||||
'0.4.0-alpha.0': {
|
||||
headline: 'v0.4.0-alpha.0',
|
||||
'0.4.0-alpha.1': {
|
||||
headline: 'v0.4.0-alpha.1',
|
||||
releaseNotes: '',
|
||||
sourceVersion: '>=0.3.5:0 <=0.4.0-alpha.0:0',
|
||||
sourceVersion: '>=0.3.5:0 <=0.4.0-alpha.1:0',
|
||||
authorized: ['G24CSA5HNYEPIXJNMK7ZM4KD5SX5N6X4'],
|
||||
iso: {},
|
||||
squashfs: {
|
||||
aarch64: {
|
||||
publishedAt: '2025-04-21T20:58:48.140749883Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_aarch64.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_aarch64.squashfs',
|
||||
commitment: {
|
||||
hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=',
|
||||
size: 1343500288,
|
||||
@@ -122,7 +122,7 @@ export namespace Mock {
|
||||
},
|
||||
'aarch64-nonfree': {
|
||||
publishedAt: '2025-04-21T21:07:00.249285116Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_aarch64-nonfree.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_aarch64-nonfree.squashfs',
|
||||
commitment: {
|
||||
hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=',
|
||||
size: 1653075968,
|
||||
@@ -134,7 +134,7 @@ export namespace Mock {
|
||||
},
|
||||
raspberrypi: {
|
||||
publishedAt: '2025-04-21T21:16:12.933319237Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_raspberrypi.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_raspberrypi.squashfs',
|
||||
commitment: {
|
||||
hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=',
|
||||
size: 1490731008,
|
||||
@@ -146,7 +146,7 @@ export namespace Mock {
|
||||
},
|
||||
x86_64: {
|
||||
publishedAt: '2025-04-21T21:14:20.246908903Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_x86_64.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_x86_64.squashfs',
|
||||
commitment: {
|
||||
hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=',
|
||||
size: 1411657728,
|
||||
@@ -158,7 +158,7 @@ export namespace Mock {
|
||||
},
|
||||
'x86_64-nonfree': {
|
||||
publishedAt: '2025-04-21T21:15:17.955265284Z',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_x86_64-nonfree.squashfs',
|
||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_x86_64-nonfree.squashfs',
|
||||
commitment: {
|
||||
hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=',
|
||||
size: 1731035136,
|
||||
@@ -179,27 +179,21 @@ export namespace Mock {
|
||||
categories: {
|
||||
bitcoin: {
|
||||
name: 'Bitcoin',
|
||||
description: mockDescription,
|
||||
},
|
||||
featured: {
|
||||
name: 'Featured',
|
||||
description: mockDescription,
|
||||
},
|
||||
lightning: {
|
||||
name: 'Lightning',
|
||||
description: mockDescription,
|
||||
},
|
||||
communications: {
|
||||
name: 'Communications',
|
||||
description: mockDescription,
|
||||
},
|
||||
data: {
|
||||
name: 'Data',
|
||||
description: mockDescription,
|
||||
},
|
||||
ai: {
|
||||
name: 'AI',
|
||||
description: mockDescription,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { StartOSDiskInfo, FetchLogsReq, FetchLogsRes } from '@start9labs/shared'
|
||||
import { IST, T } from '@start9labs/start-sdk'
|
||||
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
|
||||
import {
|
||||
GetPackageReq,
|
||||
GetPackageRes,
|
||||
GetPackagesReq,
|
||||
GetPackagesRes,
|
||||
} from '@start9labs/marketplace'
|
||||
|
||||
export namespace RR {
|
||||
// websocket
|
||||
@@ -86,7 +92,7 @@ export namespace RR {
|
||||
metrics: ServerMetrics
|
||||
}
|
||||
|
||||
export type UpdateServerReq = { registry: string } // server.update
|
||||
export type UpdateServerReq = { registry: string; targetVersion: string } // server.update
|
||||
export type UpdateServerRes = 'updating' | 'no-updates'
|
||||
|
||||
export type RestartServerReq = {} // server.restart
|
||||
@@ -362,8 +368,17 @@ export namespace RR {
|
||||
// registry
|
||||
|
||||
/** these are returned in ASCENDING order. the newest available version will be the LAST in the object */
|
||||
export type CheckOSUpdateReq = { serverId: string }
|
||||
export type GetRegistryOsUpdateRes = { [version: string]: T.OsVersionInfo }
|
||||
export type CheckOsUpdateReq = { registry: string; serverId: string }
|
||||
export type CheckOsUpdateRes = { [version: string]: T.OsVersionInfo }
|
||||
|
||||
export type GetRegistryInfoReq = { registry: string }
|
||||
export type GetRegistryInfoRes = T.RegistryInfo
|
||||
|
||||
export type GetRegistryPackageReq = GetPackageReq & { registry: string }
|
||||
export type GetRegistryPackageRes = GetPackageRes
|
||||
|
||||
export type GetRegistryPackagesReq = GetPackagesReq & { registry: string }
|
||||
export type GetRegistryPackagesRes = GetPackagesRes
|
||||
}
|
||||
|
||||
export type Breakages = {
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
GetPackageRes,
|
||||
GetPackagesRes,
|
||||
MarketplacePkg,
|
||||
} from '@start9labs/marketplace'
|
||||
import { RPCOptions } from '@start9labs/shared'
|
||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { RR } from './api.types'
|
||||
import { WebSocketSubject } from 'rxjs/webSocket'
|
||||
@@ -113,7 +108,7 @@ export abstract class ApiService {
|
||||
params: RR.FollowServerMetricsReq,
|
||||
): Promise<RR.FollowServerMetricsRes>
|
||||
|
||||
abstract updateServer(url?: string): Promise<RR.UpdateServerRes>
|
||||
abstract updateServer(params: RR.UpdateServerReq): Promise<RR.UpdateServerRes>
|
||||
|
||||
abstract restartServer(
|
||||
params: RR.RestartServerReq,
|
||||
@@ -145,24 +140,21 @@ export abstract class ApiService {
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
abstract registryRequest<T>(
|
||||
registryUrl: string,
|
||||
options: RPCOptions,
|
||||
): Promise<T>
|
||||
|
||||
abstract checkOSUpdate(
|
||||
qp: RR.CheckOSUpdateReq,
|
||||
): Promise<RR.GetRegistryOsUpdateRes>
|
||||
params: RR.CheckOsUpdateReq,
|
||||
): Promise<RR.CheckOsUpdateRes>
|
||||
|
||||
abstract getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo>
|
||||
abstract getRegistryInfo(
|
||||
params: RR.GetRegistryInfoReq,
|
||||
): Promise<RR.GetRegistryInfoRes>
|
||||
|
||||
abstract getRegistryPackage(
|
||||
url: string,
|
||||
id: string,
|
||||
versionRange: string | null,
|
||||
): Promise<GetPackageRes>
|
||||
params: RR.GetRegistryPackageReq,
|
||||
): Promise<RR.GetRegistryPackageRes>
|
||||
|
||||
abstract getRegistryPackages(registryUrl: string): Promise<GetPackagesRes>
|
||||
abstract getRegistryPackages(
|
||||
params: RR.GetRegistryPackagesReq,
|
||||
): Promise<RR.GetRegistryPackagesRes>
|
||||
|
||||
// notification
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { PATCH_CACHE } from 'src/app/services/patch-db/patch-db-source'
|
||||
import { ApiService } from './embassy-api.service'
|
||||
import { RR } from './api.types'
|
||||
import { ConfigService } from '../config.service'
|
||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket'
|
||||
import { Observable, filter, firstValueFrom } from 'rxjs'
|
||||
import { AuthService } from '../auth.service'
|
||||
@@ -18,13 +17,7 @@ import { DOCUMENT } from '@angular/common'
|
||||
import { DataModel } from '../patch-db/data-model'
|
||||
import { Dump, pathFromArray } from 'patch-db-client'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import {
|
||||
GetPackageReq,
|
||||
GetPackageRes,
|
||||
GetPackagesReq,
|
||||
GetPackagesRes,
|
||||
MarketplacePkg,
|
||||
} from '@start9labs/marketplace'
|
||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||
import { blake3 } from '@noble/hashes/blake3'
|
||||
|
||||
@Injectable()
|
||||
@@ -32,7 +25,6 @@ export class LiveApiService extends ApiService {
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private readonly document: Document,
|
||||
private readonly http: HttpService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly auth: AuthService,
|
||||
@Inject(PATCH_CACHE) private readonly cache$: Observable<Dump<DataModel>>,
|
||||
) {
|
||||
@@ -248,10 +240,7 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'server.metrics.follow', params })
|
||||
}
|
||||
|
||||
async updateServer(url?: string): Promise<RR.UpdateServerRes> {
|
||||
const params = {
|
||||
registry: url || this.config.startosRegistry,
|
||||
}
|
||||
async updateServer(params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
|
||||
return this.rpcRequest({ method: 'server.update', params })
|
||||
}
|
||||
|
||||
@@ -283,61 +272,38 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
async registryRequest<T>(
|
||||
registryUrl: string,
|
||||
options: RPCOptions,
|
||||
): Promise<T> {
|
||||
return this.rpcRequest({
|
||||
...options,
|
||||
method: `registry.${options.method}`,
|
||||
params: { registry: registryUrl, ...options.params },
|
||||
})
|
||||
}
|
||||
|
||||
async checkOSUpdate(
|
||||
qp: RR.CheckOSUpdateReq,
|
||||
): Promise<RR.GetRegistryOsUpdateRes> {
|
||||
const { serverId } = qp
|
||||
|
||||
return this.registryRequest(this.config.startosRegistry, {
|
||||
method: 'os.version.get',
|
||||
params: { serverId },
|
||||
})
|
||||
}
|
||||
|
||||
async getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo> {
|
||||
return this.registryRequest(registryUrl, {
|
||||
method: 'info',
|
||||
params: {},
|
||||
})
|
||||
}
|
||||
|
||||
async getRegistryPackage(
|
||||
registryUrl: string,
|
||||
id: string,
|
||||
versionRange: string | null,
|
||||
): Promise<GetPackageRes> {
|
||||
const params: GetPackageReq = {
|
||||
id,
|
||||
version: versionRange,
|
||||
otherVersions: 'short',
|
||||
}
|
||||
|
||||
return this.registryRequest<GetPackageRes>(registryUrl, {
|
||||
method: 'package.get',
|
||||
params: RR.CheckOsUpdateReq,
|
||||
): Promise<RR.CheckOsUpdateRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'registry.os.version.get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
async getRegistryPackages(registryUrl: string): Promise<GetPackagesRes> {
|
||||
const params: GetPackagesReq = {
|
||||
id: null,
|
||||
version: null,
|
||||
otherVersions: 'short',
|
||||
}
|
||||
async getRegistryInfo(
|
||||
params: RR.GetRegistryInfoReq,
|
||||
): Promise<RR.GetRegistryInfoRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'registry.info',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
return this.registryRequest<GetPackagesRes>(registryUrl, {
|
||||
method: 'package.get',
|
||||
async getRegistryPackage(
|
||||
params: RR.GetRegistryPackageReq,
|
||||
): Promise<RR.GetRegistryPackageRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'registry.package.get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
async getRegistryPackages(
|
||||
params: RR.GetRegistryPackagesReq,
|
||||
): Promise<RR.GetRegistryPackagesRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'registry.package.get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ export class MockApiService extends ApiService {
|
||||
pathArr: Array<string | number>,
|
||||
value: T,
|
||||
): Promise<RR.SetDBValueRes> {
|
||||
console.warn(pathArr, value)
|
||||
const pointer = pathFromArray(pathArr)
|
||||
const params: RR.SetDBValueReq<T> = { pointer, value }
|
||||
await pauseFor(2000)
|
||||
@@ -367,7 +368,7 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
async updateServer(url?: string): Promise<RR.UpdateServerRes> {
|
||||
async updateServer(params?: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
|
||||
await pauseFor(2000)
|
||||
const initialProgress = {
|
||||
size: null,
|
||||
@@ -475,41 +476,37 @@ export class MockApiService extends ApiService {
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
async registryRequest(
|
||||
registryUrl: string,
|
||||
options: RPCOptions,
|
||||
): Promise<any> {
|
||||
await pauseFor(2000)
|
||||
|
||||
return Error('do not call directly')
|
||||
}
|
||||
|
||||
async checkOSUpdate(
|
||||
qp: RR.CheckOSUpdateReq,
|
||||
): Promise<RR.GetRegistryOsUpdateRes> {
|
||||
params: RR.CheckOsUpdateReq,
|
||||
): Promise<RR.CheckOsUpdateRes> {
|
||||
await pauseFor(2000)
|
||||
return Mock.RegistryOSUpdate
|
||||
}
|
||||
|
||||
async getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo> {
|
||||
async getRegistryInfo(
|
||||
params: RR.GetRegistryInfoReq,
|
||||
): Promise<RR.GetRegistryInfoRes> {
|
||||
await pauseFor(2000)
|
||||
return Mock.RegistryInfo
|
||||
}
|
||||
|
||||
async getRegistryPackage(
|
||||
url: string,
|
||||
id: string,
|
||||
versionRange: string,
|
||||
): Promise<GetPackageRes> {
|
||||
params: RR.GetRegistryPackageReq,
|
||||
): Promise<RR.GetRegistryPackageRes> {
|
||||
await pauseFor(2000)
|
||||
if (!versionRange || versionRange === '=*') {
|
||||
|
||||
const { targetVersion, id } = params
|
||||
|
||||
if (!targetVersion || targetVersion === '=*') {
|
||||
return Mock.RegistryPackages[id]!
|
||||
} else {
|
||||
return Mock.OtherPackageVersions[id]![versionRange]!
|
||||
return Mock.OtherPackageVersions[id]![targetVersion]!
|
||||
}
|
||||
}
|
||||
|
||||
async getRegistryPackages(registryUrl: string): Promise<GetPackagesRes> {
|
||||
async getRegistryPackages(
|
||||
params: RR.GetRegistryPackagesReq,
|
||||
): Promise<RR.GetRegistryPackagesRes> {
|
||||
await pauseFor(2000)
|
||||
return Mock.RegistryPackages
|
||||
}
|
||||
|
||||
@@ -6,26 +6,14 @@ const version = require('../../../../../../package.json').version
|
||||
export const mockPatchData: DataModel = {
|
||||
ui: {
|
||||
name: `Matt's Server`,
|
||||
theme: 'Dark',
|
||||
marketplace: {
|
||||
selectedUrl: 'https://registry.start9.com/',
|
||||
knownHosts: {
|
||||
'https://registry.start9.com/': {
|
||||
name: 'Start9 Registry',
|
||||
},
|
||||
'https://community-registry.start9.com/': {},
|
||||
'https://beta-registry.start9.com/': {
|
||||
name: 'Dark9',
|
||||
},
|
||||
},
|
||||
registries: {
|
||||
'https://registry.start9.com/': 'Start9 Registry',
|
||||
'https://community-registry.start9.com/': 'Community Registry',
|
||||
},
|
||||
gaming: {
|
||||
snake: {
|
||||
highScore: 0,
|
||||
},
|
||||
},
|
||||
language: 'english',
|
||||
startosRegistry: 'https://registry.start9.com/',
|
||||
snakeHighScore: 0,
|
||||
ackInstructions: {},
|
||||
language: 'english',
|
||||
},
|
||||
serverInfo: {
|
||||
arch: 'x86_64',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { PackageDataEntry } from './patch-db/data-model'
|
||||
const {
|
||||
gitHash,
|
||||
useMocks,
|
||||
ui: { api, marketplace, mocks, startosRegistry },
|
||||
ui: { api, mocks },
|
||||
} = require('../../../../../config.json') as WorkspaceConfig
|
||||
|
||||
@Injectable({
|
||||
@@ -26,8 +26,6 @@ export class ConfigService {
|
||||
mocks = mocks
|
||||
gitHash = gitHash
|
||||
api = api
|
||||
marketplace = marketplace
|
||||
startosRegistry = startosRegistry
|
||||
skipStartupAlerts = useMocks && mocks.skipStartupAlerts
|
||||
supportsWebSockets = !!window.WebSocket
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
StoreDataWithUrl,
|
||||
StoreIdentity,
|
||||
} from '@start9labs/marketplace'
|
||||
import { Exver, sameUrl } from '@start9labs/shared'
|
||||
import { Exver, defaultRegistries, sameUrl } from '@start9labs/shared'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
@@ -31,32 +31,28 @@ import {
|
||||
} from 'rxjs'
|
||||
import { RR } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { ClientStorageService } from './client-storage.service'
|
||||
import { ConfigService } from './config.service'
|
||||
|
||||
const { start9, community } = defaultRegistries
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MarketplaceService {
|
||||
private readonly registryUrlSubject$ = new ReplaySubject<string>(1)
|
||||
private readonly registryUrl$ = this.registryUrlSubject$.pipe(
|
||||
private readonly currentRegistryUrlSubject$ = new ReplaySubject<string>(1)
|
||||
private readonly currentRegistryUrl$ = this.currentRegistryUrlSubject$.pipe(
|
||||
distinctUntilChanged(),
|
||||
)
|
||||
|
||||
private readonly registry$: Observable<StoreDataWithUrl> =
|
||||
this.registryUrl$.pipe(
|
||||
private readonly currentRegistry$: Observable<StoreDataWithUrl> =
|
||||
this.currentRegistryUrl$.pipe(
|
||||
switchMap(url => this.fetchRegistry$(url)),
|
||||
filter(Boolean),
|
||||
map(registry => {
|
||||
// @TODO Aiden let's drop description. We do not use it. categories should just be Record<string, string>
|
||||
registry.info.categories = {
|
||||
all: {
|
||||
name: 'All',
|
||||
description: {
|
||||
short: 'All registry packages',
|
||||
long: 'An unfiltered list of all packages available on this registry.',
|
||||
},
|
||||
},
|
||||
...registry.info.categories,
|
||||
}
|
||||
@@ -66,36 +62,27 @@ export class MarketplaceService {
|
||||
shareReplay(1),
|
||||
)
|
||||
|
||||
private readonly knownHosts$: Observable<StoreIdentity[]> = this.patch
|
||||
.watch$('ui', 'marketplace', 'knownHosts')
|
||||
private readonly registries$: Observable<StoreIdentity[]> = this.patch
|
||||
.watch$('ui', 'registries')
|
||||
.pipe(
|
||||
map(hosts => {
|
||||
const { start9, community } = this.config.marketplace
|
||||
let arr = [
|
||||
toStoreIdentity(start9, hosts[start9]),
|
||||
toStoreIdentity(community, {
|
||||
...hosts[community],
|
||||
name: 'Community Registry',
|
||||
}),
|
||||
]
|
||||
|
||||
return arr.concat(
|
||||
Object.entries(hosts)
|
||||
.filter(([url, _]) => ![start9, community].includes(url as any))
|
||||
.map(([url, store]) => toStoreIdentity(url, store)),
|
||||
)
|
||||
}),
|
||||
map(registries => [
|
||||
toStoreIdentity(start9, registries[start9]),
|
||||
toStoreIdentity(community, registries[community]),
|
||||
...Object.entries(registries)
|
||||
.filter(([url, _]) => ![start9, community].includes(url as any))
|
||||
.map(([url, name]) => toStoreIdentity(url, name)),
|
||||
]),
|
||||
)
|
||||
|
||||
private readonly filteredKnownHosts$: Observable<StoreIdentity[]> =
|
||||
private readonly filteredRegistries$: Observable<StoreIdentity[]> =
|
||||
combineLatest([
|
||||
this.clientStorageService.showDevTools$,
|
||||
this.knownHosts$,
|
||||
this.registries$,
|
||||
]).pipe(
|
||||
map(([devMode, knownHosts]) =>
|
||||
map(([devMode, registries]) =>
|
||||
devMode
|
||||
? knownHosts
|
||||
: knownHosts.filter(
|
||||
? registries
|
||||
: registries.filter(
|
||||
({ url }) => !url.includes('alpha') && !url.includes('beta'),
|
||||
),
|
||||
),
|
||||
@@ -103,7 +90,7 @@ export class MarketplaceService {
|
||||
|
||||
private readonly requestErrors$ = new BehaviorSubject<string[]>([])
|
||||
|
||||
readonly marketplace$: Observable<Marketplace> = this.knownHosts$.pipe(
|
||||
readonly marketplace$: Observable<Marketplace> = this.registries$.pipe(
|
||||
startWith<StoreIdentity[]>([]),
|
||||
pairwise(),
|
||||
mergeMap(([prev, curr]) =>
|
||||
@@ -112,7 +99,8 @@ export class MarketplaceService {
|
||||
mergeMap(({ url, name }) =>
|
||||
this.fetchRegistry$(url).pipe(
|
||||
tap(data => {
|
||||
if (data?.info.name) this.updateStoreName(url, name, data.info.name)
|
||||
if (data?.info.name)
|
||||
this.updateRegistryName(url, name, data.info.name)
|
||||
}),
|
||||
map<StoreData | null, [string, StoreData | null]>(data => [url, data]),
|
||||
startWith<[string, StoreData | null]>([url, null]),
|
||||
@@ -132,27 +120,25 @@ export class MarketplaceService {
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly config: ConfigService,
|
||||
private readonly clientStorageService: ClientStorageService,
|
||||
private readonly exver: Exver,
|
||||
) {}
|
||||
|
||||
getKnownHosts$(filtered = false): Observable<StoreIdentity[]> {
|
||||
getRegistries$(filtered = false): Observable<StoreIdentity[]> {
|
||||
// option to filter out hosts containing 'alpha' or 'beta' substrings in registryURL
|
||||
return filtered ? this.filteredKnownHosts$ : this.knownHosts$
|
||||
return filtered ? this.filteredRegistries$ : this.registries$
|
||||
}
|
||||
|
||||
getRegistryUrl$() {
|
||||
return this.registryUrl$
|
||||
getCurrentRegistryUrl$() {
|
||||
return this.currentRegistryUrl$
|
||||
}
|
||||
|
||||
setRegistryUrl(url: string | null) {
|
||||
const registryUrl = url || this.config.marketplace.start9
|
||||
this.registryUrlSubject$.next(registryUrl)
|
||||
setRegistryUrl(url: string) {
|
||||
this.currentRegistryUrlSubject$.next(url)
|
||||
}
|
||||
|
||||
getRegistry$(): Observable<StoreDataWithUrl> {
|
||||
return this.registry$
|
||||
getCurrentRegistry$(): Observable<StoreDataWithUrl> {
|
||||
return this.currentRegistry$
|
||||
}
|
||||
|
||||
getPackage$(
|
||||
@@ -161,7 +147,7 @@ export class MarketplaceService {
|
||||
flavor: string | null,
|
||||
registryUrl?: string,
|
||||
): Observable<MarketplacePkg> {
|
||||
return this.registry$.pipe(
|
||||
return this.currentRegistry$.pipe(
|
||||
switchMap(registry => {
|
||||
const url = registryUrl || registry.url
|
||||
const pkg = registry.packages.find(
|
||||
@@ -176,17 +162,12 @@ export class MarketplaceService {
|
||||
}
|
||||
|
||||
fetchInfo$(url: string): Observable<T.RegistryInfo> {
|
||||
return from(this.api.getRegistryInfo(url)).pipe(
|
||||
return from(this.api.getRegistryInfo({ registry: url })).pipe(
|
||||
map(info => ({
|
||||
...info,
|
||||
// @TODO Aiden let's drop description. We do not use it. categories should just be Record<string, string>
|
||||
categories: {
|
||||
all: {
|
||||
name: 'All',
|
||||
description: {
|
||||
short: '',
|
||||
long: '',
|
||||
},
|
||||
},
|
||||
...info.categories,
|
||||
},
|
||||
@@ -202,7 +183,7 @@ export class MarketplaceService {
|
||||
}
|
||||
|
||||
private fetchRegistry$(url: string): Observable<StoreDataWithUrl | null> {
|
||||
console.log('FETCHING REGISTRY: ', url)
|
||||
console.warn('FETCHING REGISTRY: ', url)
|
||||
return combineLatest([this.fetchInfo$(url), this.fetchPackages$(url)]).pipe(
|
||||
map(([info, packages]) => ({ info, packages, url })),
|
||||
catchError(e => {
|
||||
@@ -214,7 +195,14 @@ export class MarketplaceService {
|
||||
}
|
||||
|
||||
private fetchPackages$(url: string): Observable<MarketplacePkg[]> {
|
||||
return from(this.api.getRegistryPackages(url)).pipe(
|
||||
return from(
|
||||
this.api.getRegistryPackages({
|
||||
registry: url,
|
||||
id: null,
|
||||
targetVersion: null,
|
||||
otherVersions: 'short',
|
||||
}),
|
||||
).pipe(
|
||||
map(packages => {
|
||||
return Object.entries(packages).flatMap(([id, pkgInfo]) =>
|
||||
Object.keys(pkgInfo.best).map(version =>
|
||||
@@ -237,7 +225,12 @@ export class MarketplaceService {
|
||||
flavor: string | null,
|
||||
): Observable<MarketplacePkg> {
|
||||
return from(
|
||||
this.api.getRegistryPackage(url, id, version ? `=${version}` : null),
|
||||
this.api.getRegistryPackage({
|
||||
registry: url,
|
||||
id,
|
||||
targetVersion: version ? `=${version}` : null,
|
||||
otherVersions: 'short',
|
||||
}),
|
||||
).pipe(
|
||||
map(pkgInfo =>
|
||||
this.convertRegistryPkgToMarketplacePkg(id, version, flavor, pkgInfo),
|
||||
@@ -288,23 +281,21 @@ export class MarketplaceService {
|
||||
await this.api.installPackage(params)
|
||||
}
|
||||
|
||||
private async updateStoreName(
|
||||
private async updateRegistryName(
|
||||
url: string,
|
||||
oldName: string | undefined,
|
||||
oldName: string | null,
|
||||
newName: string,
|
||||
): Promise<void> {
|
||||
console.warn(oldName, newName)
|
||||
if (oldName !== newName) {
|
||||
this.api.setDbValue<string>(
|
||||
['marketplace', 'knownHosts', url, 'name'],
|
||||
newName,
|
||||
)
|
||||
this.api.setDbValue<string>(['registries', url], newName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toStoreIdentity(url: string, uiStore: UIStore): StoreIdentity {
|
||||
function toStoreIdentity(url: string, name?: string | null): StoreIdentity {
|
||||
return {
|
||||
url,
|
||||
...uiStore,
|
||||
name: name || url,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { BehaviorSubject, distinctUntilChanged, map, combineLatest } from 'rxjs'
|
||||
import {
|
||||
BehaviorSubject,
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
combineLatest,
|
||||
firstValueFrom,
|
||||
} from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { getServerInfo } from 'src/app/utils/get-server-info'
|
||||
import { DataModel } from './patch-db/data-model'
|
||||
@@ -11,7 +17,7 @@ import { RR } from './api/api.types'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class OSService {
|
||||
osUpdate?: RR.GetRegistryOsUpdateRes
|
||||
osUpdate?: RR.CheckOsUpdateRes
|
||||
updateAvailable$ = new BehaviorSubject<boolean>(false)
|
||||
|
||||
readonly updating$ = this.patch.watch$('serverInfo', 'statusInfo').pipe(
|
||||
@@ -47,7 +53,12 @@ export class OSService {
|
||||
|
||||
async loadOS(): Promise<void> {
|
||||
const { version, id } = await getServerInfo(this.patch)
|
||||
this.osUpdate = await this.api.checkOSUpdate({ serverId: id })
|
||||
const { startosRegistry } = await firstValueFrom(this.patch.watch$('ui'))
|
||||
|
||||
this.osUpdate = await this.api.checkOSUpdate({
|
||||
registry: startosRegistry,
|
||||
serverId: id,
|
||||
})
|
||||
const [latestVersion, _] = Object.entries(this.osUpdate).at(-1)!
|
||||
const updateAvailable =
|
||||
Version.parse(latestVersion).compare(Version.parse(version)) === 'greater'
|
||||
|
||||
@@ -5,30 +5,13 @@ export type DataModel = T.Public & { ui: UIData; packageData: AllPackageData }
|
||||
|
||||
export type UIData = {
|
||||
name: string | null
|
||||
marketplace: UIMarketplaceData
|
||||
gaming: {
|
||||
snake: {
|
||||
highScore: number
|
||||
}
|
||||
}
|
||||
registries: Record<string, string | null>
|
||||
ackInstructions: Record<string, boolean>
|
||||
theme: string
|
||||
snakeHighScore: number
|
||||
startosRegistry: string
|
||||
language: Languages
|
||||
}
|
||||
|
||||
export type UIMarketplaceData = {
|
||||
selectedUrl: string
|
||||
knownHosts: {
|
||||
'https://registry.start9.com/': UIStore
|
||||
'https://community-registry.start9.com/': UIStore
|
||||
[url: string]: UIStore
|
||||
}
|
||||
}
|
||||
|
||||
export type UIStore = {
|
||||
name?: string
|
||||
}
|
||||
|
||||
export type NetworkInfo = T.NetworkInfo & {
|
||||
// @TODO 041
|
||||
// start9To: {
|
||||
|
||||
Reference in New Issue
Block a user