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:
Lucy
2025-04-29 16:12:21 -04:00
committed by GitHub
parent 2adf34fbaf
commit 5c473eb9cc
63 changed files with 733 additions and 470 deletions

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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": {}
}

View File

@@ -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

View File

@@ -60,7 +60,6 @@ header {
}
store-icon {
border-radius: 100%;
height: 64px;
}

View File

@@ -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

View File

@@ -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 }
}

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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>

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

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

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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

View File

@@ -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'
}

View File

@@ -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$()
}

View File

@@ -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,

View File

@@ -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)
}
}

View File

@@ -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',
},

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,
},
},
}

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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,
})
}

View File

@@ -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
}

View File

@@ -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',

View File

@@ -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

View File

@@ -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,
}
}

View File

@@ -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'

View File

@@ -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: {