This commit is contained in:
Matt Hill
2024-03-25 13:31:30 -06:00
parent 4ab7300376
commit d202cb731d
15 changed files with 80 additions and 109 deletions

View File

@@ -25,6 +25,7 @@ import {
ServerNotifications, ServerNotifications,
} from 'src/app/services/api/api.types' } from 'src/app/services/api/api.types'
import { NotificationService } from '../../services/notification.service' import { NotificationService } from '../../services/notification.service'
import { ToManifestPipe } from '../../pipes/to-manifest'
@Component({ @Component({
selector: 'header-notifications', selector: 'header-notifications',
@@ -47,7 +48,11 @@ import { NotificationService } from '../../services/notification.service'
[notification]="not" [notification]="not"
> >
<ng-container *ngIf="not.packageId as pkgId"> <ng-container *ngIf="not.packageId as pkgId">
{{ $any(packageData[pkgId])?.manifest.title || pkgId }} {{
packageData[pkgId]
? (packageData[pkgId] | toManifest).title
: pkgId
}}
</ng-container> </ng-container>
<button <button
style="align-self: flex-start; flex-shrink: 0;" style="align-self: flex-start; flex-shrink: 0;"
@@ -98,6 +103,7 @@ import { NotificationService } from '../../services/notification.service'
TuiCellModule, TuiCellModule,
TuiAvatarStackModule, TuiAvatarStackModule,
TuiTitleModule, TuiTitleModule,
ToManifestPipe,
], ],
}) })
export class HeaderNotificationsComponent { export class HeaderNotificationsComponent {

View File

@@ -9,6 +9,7 @@ import { WINDOW } from '@ng-web-apis/common'
import { CopyService } from '@start9labs/shared' import { CopyService } from '@start9labs/shared'
import { TuiDialogService } from '@taiga-ui/core' import { TuiDialogService } from '@taiga-ui/core'
import { import {
TuiBadgeModule,
TuiButtonModule, TuiButtonModule,
TuiCellModule, TuiCellModule,
TuiTitleModule, TuiTitleModule,
@@ -22,12 +23,12 @@ import { mask } from 'src/app/util/mask'
selector: 'app-interface-address', selector: 'app-interface-address',
template: ` template: `
<div tuiCell> <div tuiCell>
<h3 tuiTitle>
<span tuiSubtitle>{{ isMasked ? mask : address }}</span>
</h3>
<tui-badge appearance="success"> <tui-badge appearance="success">
{{ label }} {{ label }}
</tui-badge> </tui-badge>
<h3 tuiTitle>
<span tuiSubtitle>{{ isMasked ? mask : address }}</span>
</h3>
<button <button
*ngIf="isUi" *ngIf="isUi"
tuiIconButton tuiIconButton
@@ -51,11 +52,25 @@ import { mask } from 'src/app/util/mask'
appearance="icon" appearance="icon"
(click)="copyService.copy(address)" (click)="copyService.copy(address)"
> >
Copy QR code Copy URL
</button>
<button
tuiIconButton
iconLeft="tuiIconTrash"
appearance="icon"
(click)="destroy()"
>
Destroy
</button> </button>
</div> </div>
`, `,
imports: [NgIf, TuiCellModule, TuiTitleModule, TuiButtonModule], imports: [
NgIf,
TuiCellModule,
TuiTitleModule,
TuiButtonModule,
TuiBadgeModule,
],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class InterfaceAddressComponent { export class InterfaceAddressComponent {
@@ -84,4 +99,6 @@ export class InterfaceAddressComponent {
}) })
.subscribe() .subscribe()
} }
destroy() {}
} }

View File

@@ -43,26 +43,17 @@ type ClearnetForm = {
<strong>View instructions</strong> <strong>View instructions</strong>
</a> </a>
</em> </em>
<ng-container @for (
*ngIf=" address of interface.serviceInterface.addresses.clearnet;
interface.serviceInterface.addresses.clearnet as addresses; track $index
else empty ) {
"
>
<app-interface-address <app-interface-address
*ngFor="let address of addresses"
[label]="address.label" [label]="address.label"
[address]="address.url" [address]="address.url"
[isMasked]="interface.serviceInterface.masked" [isMasked]="interface.serviceInterface.masked"
[isUi]="interface.serviceInterface.type === 'ui'" [isUi]="interface.serviceInterface.type === 'ui'"
/> />
<div [style.display]="'flex'" [style.gap.rem]="1"> } @empty {
<button tuiButton size="s" appearance="danger-solid" (click)="remove()">
Remove
</button>
</div>
</ng-container>
<ng-template #empty>
<button <button
tuiButton tuiButton
iconLeft="tuiIconPlus" iconLeft="tuiIconPlus"
@@ -71,7 +62,7 @@ type ClearnetForm = {
> >
Add Address Add Address
</button> </button>
</ng-template> }
`, `,
imports: [NgForOf, InterfaceAddressComponent, NgIf, TuiButtonModule], imports: [NgForOf, InterfaceAddressComponent, NgIf, TuiButtonModule],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,

View File

@@ -20,26 +20,14 @@ import { InterfaceAddressComponent } from './interface-addresses.component'
</a> </a>
</em> </em>
<ng-container @for (address of interface.serviceInterface.addresses.local; track $index) {
*ngIf="
interface.serviceInterface.addresses.local as addresses;
else empty
"
>
<app-interface-address <app-interface-address
*ngFor="let address of addresses"
[label]="address.label" [label]="address.label"
[address]="address.url" [address]="address.url"
[isMasked]="interface.serviceInterface.masked" [isMasked]="interface.serviceInterface.masked"
[isUi]="interface.serviceInterface.type === 'ui'" [isUi]="interface.serviceInterface.type === 'ui'"
/> />
<div [style.display]="'flex'" [style.gap.rem]="1"> } @empty {
<button tuiButton size="s" appearance="danger-solid" (click)="remove()">
Remove
</button>
</div>
</ng-container>
<ng-template #empty>
<button <button
tuiButton tuiButton
iconLeft="tuiIconPlus" iconLeft="tuiIconPlus"
@@ -48,7 +36,7 @@ import { InterfaceAddressComponent } from './interface-addresses.component'
> >
Add Address Add Address
</button> </button>
</ng-template> }
`, `,
imports: [NgForOf, NgIf, InterfaceAddressComponent, TuiButtonModule], imports: [NgForOf, NgIf, InterfaceAddressComponent, TuiButtonModule],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,

View File

@@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { InterfaceAddressComponent } from './interface-addresses.component' import { InterfaceAddressComponent } from './interface-addresses.component'
import { InterfaceComponent } from './interface.component' import { InterfaceComponent } from './interface.component'
import { NgForOf, NgIf } from '@angular/common' import { NgForOf, NgIf } from '@angular/common'
import { TuiButtonModule } from '@taiga-ui/experimental'
@Component({ @Component({
standalone: true, standalone: true,
@@ -19,23 +20,14 @@ import { NgForOf, NgIf } from '@angular/common'
</a> </a>
</em> </em>
<ng-container @for (address of interface.serviceInterface.addresses.tor; track $index) {
*ngIf="interface.serviceInterface.addresses.tor as addresses; else empty"
>
<app-interface-address <app-interface-address
*ngFor="let address of addresses"
[label]="address.label" [label]="address.label"
[address]="address.url" [address]="address.url"
[isMasked]="interface.serviceInterface.masked" [isMasked]="interface.serviceInterface.masked"
[isUi]="interface.serviceInterface.type === 'ui'" [isUi]="interface.serviceInterface.type === 'ui'"
/> />
<div [style.display]="'flex'" [style.gap.rem]="1"> } @empty {
<button tuiButton size="s" appearance="danger-solid" (click)="remove()">
Remove
</button>
</div>
</ng-container>
<ng-template #empty>
<button <button
tuiButton tuiButton
iconLeft="tuiIconPlus" iconLeft="tuiIconPlus"
@@ -44,17 +36,9 @@ import { NgForOf, NgIf } from '@angular/common'
> >
Add Address Add Address
</button> </button>
</ng-template> }
<app-interface-address
*ngFor="let address of interface.serviceInterface.addresses.tor"
[label]="address.label"
[address]="address.url"
[isMasked]="interface.serviceInterface.masked"
[isUi]="interface.serviceInterface.type === 'ui'"
/>
`, `,
imports: [NgForOf, NgIf, InterfaceAddressComponent], imports: [NgForOf, NgIf, InterfaceAddressComponent, TuiButtonModule],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class InterfaceTorComponent { export class InterfaceTorComponent {

View File

@@ -40,10 +40,10 @@ import { Manifest } from '@start9labs/marketplace'
</button> </button>
} @else { } @else {
<button <button
*tuiLet="hasUnmet(pkg) | async as hasUnmet" *tuiLet="hasUnmet() | async as hasUnmet"
tuiIconButton tuiIconButton
iconLeft="tuiIconPlay" iconLeft="tuiIconPlay"
[disabled]="!this.pkg.status.configured" [disabled]="!pkg.status.configured"
(click)="actions.start(manifest, !!hasUnmet)" (click)="actions.start(manifest, !!hasUnmet)"
> >
Start Start
@@ -77,11 +77,11 @@ export class ControlsComponent {
readonly actions = inject(ActionsService) readonly actions = inject(ActionsService)
@tuiPure @tuiPure
hasUnmet(pkg: PackageDataEntry): Observable<boolean> { hasUnmet(): Observable<boolean> {
const id = getManifest(pkg).id const id = this.manifest.id
return this.errors.getPkgDepErrors$(id).pipe( return this.errors.getPkgDepErrors$(id).pipe(
map(errors => map(errors =>
Object.keys(pkg.currentDependencies) Object.keys(this.pkg.currentDependencies)
.map(id => !!(errors[id] as any)?.[id]) // @TODO fix .map(id => !!(errors[id] as any)?.[id]) // @TODO fix
.some(Boolean), .some(Boolean),
), ),

View File

@@ -28,7 +28,7 @@ import { getManifest } from 'src/app/util/get-package-data'
<fieldset <fieldset
appControls appControls
[disabled]=" [disabled]="
this.pkg.stateInfo.state !== 'installed' || !(connected$ | async) pkg.stateInfo.state !== 'installed' || !(connected$ | async)
" "
[pkg]="pkg" [pkg]="pkg"
></fieldset> ></fieldset>

View File

@@ -54,7 +54,7 @@ export class StatusComponent {
} }
get loading(): boolean { get loading(): boolean {
return !!this.pkg.stateInfo || this.color === 'var(--tui-info-fill)' return this.color === 'var(--tui-info-fill)'
} }
@tuiPure @tuiPure
@@ -94,10 +94,6 @@ export class StatusComponent {
} }
get color(): string { get color(): string {
if (this.pkg.stateInfo.installingInfo) {
return 'var(--tui-info-fill)'
}
switch (this.getStatus(this.pkg).primary) { switch (this.getStatus(this.pkg).primary) {
case PrimaryStatus.Running: case PrimaryStatus.Running:
return 'var(--tui-success-fill)' return 'var(--tui-success-fill)'

View File

@@ -84,7 +84,6 @@ import { InstallingProgressPipe } from 'src/app/apps/portal/routes/service/pipes
<service-interface-list <service-interface-list
[pkg]="service.pkg" [pkg]="service.pkg"
[status]="service.status" [status]="service.status"
]
/> />
@if ( @if (

View File

@@ -27,7 +27,7 @@ import { Domain } from 'src/app/services/patch-db/data-model'
<tbody> <tbody>
<tr *ngFor="let domain of domains"> <tr *ngFor="let domain of domains">
<td>{{ domain.value }}</td> <td>{{ domain.value }}</td>
<td>{{ domain.createdAt | date : 'short' }}</td> <td>{{ domain.createdAt | date: 'short' }}</td>
<td>{{ domain.provider }}</td> <td>{{ domain.provider }}</td>
<td>{{ getStrategy(domain) }}</td> <td>{{ getStrategy(domain) }}</td>
<td> <td>
@@ -69,11 +69,7 @@ export class DomainsTableComponent {
readonly delete = new EventEmitter<Domain>() readonly delete = new EventEmitter<Domain>()
getStrategy(domain: any) { getStrategy(domain: any) {
return ( return domain.networkStrategy.ipStrategy || domain.networkStrategy.proxy
domain.networkStrategy.ipStrategy ||
domain.networkStrategy.proxyId ||
'Primary Proxy'
)
} }
onUsedBy({ value, usedBy }: Domain) { onUsedBy({ value, usedBy }: Domain) {

View File

@@ -45,12 +45,6 @@ export class SettingsService {
icon: 'tuiIconKey', icon: 'tuiIconKey',
action: () => this.promptNewPassword(), action: () => this.promptNewPassword(),
}, },
{
title: 'Experimental Features',
description: 'Try out new and potentially unstable new features',
icon: 'tuiIconThermometer',
routerLink: 'experimental',
},
], ],
Network: [ Network: [
{ {
@@ -134,7 +128,7 @@ export class SettingsService {
.open(TUI_PROMPT, { .open(TUI_PROMPT, {
label: this.isTor ? 'Warning' : 'Confirm', label: this.isTor ? 'Warning' : 'Confirm',
data: { data: {
content: '', content: '@TODO how to display a checkbox in here?',
yes: 'Reset', yes: 'Reset',
no: 'Cancel', no: 'Cancel',
}, },

View File

@@ -6,7 +6,6 @@ import {
PackageDataEntry, PackageDataEntry,
UpdatingState, UpdatingState,
} from 'src/app/services/patch-db/data-model' } from 'src/app/services/patch-db/data-model'
import { hasCurrentDeps } from 'src/app/util/has-deps'
@Pipe({ @Pipe({
name: 'filterUpdates', name: 'filterUpdates',

View File

@@ -6,7 +6,7 @@ import {
} from '@start9labs/marketplace' } from '@start9labs/marketplace'
import { TuiAvatarModule, TuiCellModule } from '@taiga-ui/experimental' import { TuiAvatarModule, TuiCellModule } from '@taiga-ui/experimental'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { combineLatest, map } from 'rxjs' import { combineLatest, map, scan } from 'rxjs'
import { MarketplaceService } from 'src/app/services/marketplace.service' import { MarketplaceService } from 'src/app/services/marketplace.service'
import { import {
DataModel, DataModel,
@@ -79,9 +79,10 @@ export default class UpdatesComponent {
.watch$('packageData') .watch$('packageData')
.pipe( .pipe(
map(pkgs => map(pkgs =>
Object.values(pkgs).reduce( Object.entries(pkgs).reduce(
(acc, curr) => { (acc, [id, val]) => {
if (isInstalled(curr) || isUpdating(curr)) return { ...acc, curr } if (isInstalled(val) || isUpdating(val))
return { ...acc, [id]: val }
return acc return acc
}, },
{} as Record< {} as Record<

View File

@@ -63,8 +63,9 @@ export class BadgeService {
(list, [_, store]) => (list, [_, store]) =>
store?.packages.reduce( store?.packages.reduce(
(result, { manifest: { id, version } }) => (result, { manifest: { id, version } }) =>
this.emver.compare(version, getManifest(local[id])?.version) === local[id] &&
1 this.emver.compare(version, getManifest(local[id]).version) ===
1
? result.add(id) ? result.add(id)
: result, : result,
list, list,
@@ -72,6 +73,7 @@ export class BadgeService {
new Set<string>(), new Set<string>(),
).size, ).size,
), ),
// @TODO shareReplay is preventing the badge from decrementing
shareReplay(1), shareReplay(1),
) )

View File

@@ -340,7 +340,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/ui/domainInfo', path: '/serverInfo/ui/domainInfo',
value: params.domainInfo, value: params.domainInfo,
}, },
] ]
@@ -430,7 +430,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/network/outboundProxy', path: '/serverInfo/network/outboundProxy',
value: params.proxy, value: params.proxy,
}, },
] ]
@@ -526,7 +526,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/network/proxies', path: '/serverInfo/network/proxies',
value: [ value: [
{ {
id: 'abcd-efgh-ijkl-mnop', id: 'abcd-efgh-ijkl-mnop',
@@ -553,7 +553,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: `/server-info/network/proxies/0/name`, path: `/serverInfo/network/proxies/0/name`,
value: params.name, value: params.name,
}, },
] ]
@@ -567,7 +567,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/network/proxies', path: '/serverInfo/network/proxies',
value: [], value: [],
}, },
] ]
@@ -586,7 +586,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/network/start9ToSubdomain', path: '/serverInfo/network/start9ToSubdomain',
value: { value: {
value: 'xyz', value: 'xyz',
createdAt: new Date(), createdAt: new Date(),
@@ -607,7 +607,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/network/start9ToSubdomain', path: '/serverInfo/network/start9ToSubdomain',
value: null, value: null,
}, },
] ]
@@ -622,7 +622,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/network/domains', path: '/serverInfo/network/domains',
value: [ value: [
{ {
value: params.hostname, value: params.hostname,
@@ -644,7 +644,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/network/domains', path: '/serverInfo/network/domains',
value: [], value: [],
}, },
] ]
@@ -663,7 +663,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/network/wanConfig/forwards/0/override', path: '/serverInfo/network/wanConfig/forwards/0/override',
value: params.port, value: params.port,
}, },
] ]
@@ -679,7 +679,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/network/wifi/enabled', path: '/serverInfo/network/wifi/enabled',
value: params.enable, value: params.enable,
}, },
] ]
@@ -722,7 +722,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: '/server-info/smtp', path: '/serverInfo/smtp',
value: params, value: params,
}, },
] ]
@@ -969,8 +969,6 @@ export class MockApiService extends ApiService {
this.updateProgress(params.id) this.updateProgress(params.id)
}, 1000) }, 1000)
const manifest = Mock.LocalPkgs[params.id].stateInfo.manifest
const patch: Operation< const patch: Operation<
PackageDataEntry<InstallingState | UpdatingState> PackageDataEntry<InstallingState | UpdatingState>
>[] = [ >[] = [
@@ -981,15 +979,15 @@ export class MockApiService extends ApiService {
...Mock.LocalPkgs[params.id], ...Mock.LocalPkgs[params.id],
stateInfo: { stateInfo: {
// if installing // if installing
state: PackageState.Installing, // state: PackageState.Installing,
// if updating // if updating
// state: PackageState.Updating, state: PackageState.Updating,
// manifest, manifest: mockPatchData.packageData[params.id].stateInfo.manifest!,
// both // both
installingInfo: { installingInfo: {
newManifest: manifest, newManifest: Mock.LocalPkgs[params.id].stateInfo.manifest,
progress: PROGRESS, progress: PROGRESS,
}, },
}, },
@@ -1285,7 +1283,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: `/package-data/${params.packageId}/installed/interfaceInfo/${params.interfaceId}/addressInfo/domainInfo`, path: `/packageData/${params.packageId}/installed/interfaceInfo/${params.interfaceId}/addressInfo/domainInfo`,
value: params.domainInfo, value: params.domainInfo,
}, },
] ]
@@ -1301,7 +1299,7 @@ export class MockApiService extends ApiService {
const patch = [ const patch = [
{ {
op: PatchOp.REPLACE, op: PatchOp.REPLACE,
path: `/package-data/${params.packageId}/installed/outboundProxy`, path: `/packageData/${params.packageId}/installed/outboundProxy`,
value: params.proxy, value: params.proxy,
}, },
] ]