mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
fix: fix building UI project (#2794)
* fix: fix building UI project * fix makefile * inputspec instead of config --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
2
Makefile
2
Makefile
@@ -295,7 +295,7 @@ web/dist/raw/setup-wizard/index.html: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC)
|
|||||||
touch web/dist/raw/setup-wizard/index.html
|
touch web/dist/raw/setup-wizard/index.html
|
||||||
|
|
||||||
web/dist/raw/install-wizard/index.html: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
web/dist/raw/install-wizard/index.html: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||||
npm --prefix web run build:install-wiz
|
npm --prefix web run build:install
|
||||||
touch web/dist/raw/install-wizard/index.html
|
touch web/dist/raw/install-wizard/index.html
|
||||||
|
|
||||||
$(COMPRESSED_WEB_UIS): $(WEB_UIS) $(ENVIRONMENT_FILE)
|
$(COMPRESSED_WEB_UIS): $(WEB_UIS) $(ENVIRONMENT_FILE)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export class S9pk {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return new S9pk(manifest, archive, source.length)
|
return new S9pk(manifest, archive, source.size)
|
||||||
}
|
}
|
||||||
async icon(): Promise<DataUrl> {
|
async icon(): Promise<DataUrl> {
|
||||||
const iconName = Object.keys(this.archive.contents.contents).find(
|
const iconName = Object.keys(this.archive.contents.contents).find(
|
||||||
|
|||||||
3337
sdk/base/package-lock.json
generated
3337
sdk/base/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
3609
sdk/package/package-lock.json
generated
3609
sdk/package/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
7864
web/package-lock.json
generated
7864
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,8 +12,7 @@
|
|||||||
"check:install": "tsc --project projects/install-wizard/tsconfig.json --noEmit --skipLibCheck",
|
"check:install": "tsc --project projects/install-wizard/tsconfig.json --noEmit --skipLibCheck",
|
||||||
"check:setup": "tsc --project projects/setup-wizard/tsconfig.json --noEmit --skipLibCheck",
|
"check:setup": "tsc --project projects/setup-wizard/tsconfig.json --noEmit --skipLibCheck",
|
||||||
"check:ui": "tsc --project projects/ui/tsconfig.json --noEmit --skipLibCheck",
|
"check:ui": "tsc --project projects/ui/tsconfig.json --noEmit --skipLibCheck",
|
||||||
"build:deps": "rm -rf .angular/cache && (cd ../patch-db/client && npm ci && npm run build) && (cd ../sdk && make bundle)",
|
"build:deps": "rimraf .angular/cache && (cd ../sdk && make bundle) && (cd ../patch-db/client && npm ci && npm run build)",
|
||||||
"build:deps:win": "rimraf .angular/cache && (cd ../sdk && npm ci && npm run build) && (cd ../patch-db/client && npm ci && npm run build)",
|
|
||||||
"build:install": "ng run install-wizard:build",
|
"build:install": "ng run install-wizard:build",
|
||||||
"build:setup": "ng run setup-wizard:build",
|
"build:setup": "ng run setup-wizard:build",
|
||||||
"build:ui": "ng run ui:build",
|
"build:ui": "ng run ui:build",
|
||||||
@@ -44,24 +43,22 @@
|
|||||||
"@angular/router": "^17.3.1",
|
"@angular/router": "^17.3.1",
|
||||||
"@angular/service-worker": "^17.3.1",
|
"@angular/service-worker": "^17.3.1",
|
||||||
"@materia-ui/ngx-monaco-editor": "^6.0.0",
|
"@materia-ui/ngx-monaco-editor": "^6.0.0",
|
||||||
"@ng-web-apis/common": "^3.2.3",
|
|
||||||
"@ng-web-apis/mutation-observer": "^3.2.3",
|
|
||||||
"@ng-web-apis/resize-observer": "^3.2.3",
|
|
||||||
"@noble/curves": "^1.4.0",
|
"@noble/curves": "^1.4.0",
|
||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@start9labs/argon2": "^0.2.2",
|
"@start9labs/argon2": "^0.2.2",
|
||||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||||
"@taiga-ui/addon-charts": "4.0.0-rc.7",
|
"@taiga-ui/addon-charts": "4.16.0",
|
||||||
"@taiga-ui/addon-commerce": "4.0.0-rc.7",
|
"@taiga-ui/addon-commerce": "4.16.0",
|
||||||
"@taiga-ui/addon-mobile": "4.0.0-rc.7",
|
"@taiga-ui/addon-mobile": "4.16.0",
|
||||||
"@taiga-ui/cdk": "4.0.0-rc.7",
|
"@taiga-ui/cdk": "4.16.0",
|
||||||
"@taiga-ui/core": "4.0.0-rc.7",
|
"@taiga-ui/core": "4.16.0",
|
||||||
"@taiga-ui/event-plugins": "^4.0.1",
|
"@taiga-ui/event-plugins": "4.3.1",
|
||||||
"@taiga-ui/icons": "4.0.0-rc.7",
|
"@taiga-ui/icons": "4.16.0",
|
||||||
"@taiga-ui/kit": "4.0.0-rc.7",
|
"@taiga-ui/kit": "4.16.0",
|
||||||
"@taiga-ui/layout": "4.0.0-rc.7",
|
"@taiga-ui/layout": "4.16.0",
|
||||||
"@taiga-ui/legacy": "4.0.0-rc.7",
|
"@taiga-ui/legacy": "4.16.0",
|
||||||
"@taiga-ui/styles": "4.0.0-rc.7",
|
"@taiga-ui/polymorpheus": "4.7.4",
|
||||||
|
"@taiga-ui/styles": "4.16.0",
|
||||||
"@tinkoff/ng-dompurify": "4.0.0",
|
"@tinkoff/ng-dompurify": "4.0.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
@@ -88,7 +85,7 @@
|
|||||||
"rxjs": "^7.5.6",
|
"rxjs": "^7.5.6",
|
||||||
"swiper": "^8.2.4",
|
"swiper": "^8.2.4",
|
||||||
"ts-matches": "^5.5.1",
|
"ts-matches": "^5.5.1",
|
||||||
"tslib": "^2.6.3",
|
"tslib": "^2.8.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"zone.js": "^0.14.2"
|
"zone.js": "^0.14.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<tui-notification *ngIf="error$ | async as error" status="error" safeLinks>
|
<tui-notification
|
||||||
|
*ngIf="error$ | async as error"
|
||||||
|
appearance="negative"
|
||||||
|
safeLinks
|
||||||
|
>
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</tui-notification>
|
</tui-notification>
|
||||||
|
|
||||||
|
|||||||
@@ -31,15 +31,6 @@ export class DurationToSecondsPipe implements PipeTransform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertBytes(bytes: number) {
|
|
||||||
if (bytes === 0) return '0 Bytes'
|
|
||||||
|
|
||||||
const k = 1000
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
||||||
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
|
||||||
const unitsToSeconds: Record<string, number> = {
|
const unitsToSeconds: Record<string, number> = {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class ErrorService extends ErrorHandler {
|
|||||||
this.alerts
|
this.alerts
|
||||||
.open(getErrorMessage(error, link), {
|
.open(getErrorMessage(error, link), {
|
||||||
label: 'Error',
|
label: 'Error',
|
||||||
status: 'error',
|
appearance: 'negative',
|
||||||
})
|
})
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
[tuiAlert]="!!(visible$ | async)"
|
[tuiAlert]="!!(visible$ | async)"
|
||||||
[tuiAlertOptions]="{
|
[tuiAlertOptions]="{
|
||||||
label: 'StartOS download complete!',
|
label: 'StartOS download complete!',
|
||||||
status: 'success',
|
appearance: 'positive',
|
||||||
autoClose: 0
|
autoClose: 0,
|
||||||
}"
|
}"
|
||||||
(tuiAlertChange)="onDismiss()"
|
(tuiAlertChange)="onDismiss()"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||||
import { AbstractControl, FormArrayName } from '@angular/forms'
|
import { AbstractControl, FormArrayName } from '@angular/forms'
|
||||||
import { CT } from '@start9labs/start-sdk'
|
|
||||||
import {
|
import {
|
||||||
TUI_ANIMATIONS_SPEED,
|
TUI_ANIMATIONS_SPEED,
|
||||||
TuiDialogService,
|
TuiDialogService,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export class FormControlComponent<
|
|||||||
this.alerts
|
this.alerts
|
||||||
.open<boolean>(this.warning, {
|
.open<boolean>(this.warning, {
|
||||||
label: 'Warning',
|
label: 'Warning',
|
||||||
status: 'warning',
|
appearance: 'warning',
|
||||||
closeable: false,
|
closeable: false,
|
||||||
autoClose: 0,
|
autoClose: 0,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { TuiFileLike } from '@taiga-ui/kit'
|
import { TuiFileLike } from '@taiga-ui/kit'
|
||||||
import { CT } from '@start9labs/start-sdk'
|
import { IST } from '@start9labs/start-sdk'
|
||||||
import { Control } from '../control'
|
import { Control } from '../control'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -8,4 +8,7 @@ import { Control } from '../control'
|
|||||||
templateUrl: './form-file.component.html',
|
templateUrl: './form-file.component.html',
|
||||||
styleUrls: ['./form-file.component.scss'],
|
styleUrls: ['./form-file.component.scss'],
|
||||||
})
|
})
|
||||||
export class FormFileComponent extends Control<CT.ValueSpecFile, TuiFileLike> {}
|
export class FormFileComponent extends Control<
|
||||||
|
IST.ValueSpecFile,
|
||||||
|
TuiFileLike
|
||||||
|
> {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CB, CT, T, utils } from '@start9labs/start-sdk'
|
import { ISB, IST, T, utils } from '@start9labs/start-sdk'
|
||||||
import { TuiDialogOptions } from '@taiga-ui/core'
|
import { TuiDialogOptions } from '@taiga-ui/core'
|
||||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||||
import { NetworkInfo } from 'src/app/services/patch-db/data-model'
|
import { NetworkInfo } from 'src/app/services/patch-db/data-model'
|
||||||
@@ -22,7 +22,7 @@ export const REMOVE: Partial<TuiDialogOptions<TuiConfirmData>> = {
|
|||||||
export function getClearnetSpec({
|
export function getClearnetSpec({
|
||||||
domains,
|
domains,
|
||||||
start9ToSubdomain,
|
start9ToSubdomain,
|
||||||
}: NetworkInfo): Promise<CT.InputSpec> {
|
}: NetworkInfo): Promise<IST.InputSpec> {
|
||||||
const start9ToDomain = `${start9ToSubdomain?.value}.start9.to`
|
const start9ToDomain = `${start9ToSubdomain?.value}.start9.to`
|
||||||
const base = start9ToSubdomain ? { [start9ToDomain]: start9ToDomain } : {}
|
const base = start9ToSubdomain ? { [start9ToDomain]: start9ToDomain } : {}
|
||||||
|
|
||||||
@@ -34,15 +34,16 @@ export function getClearnetSpec({
|
|||||||
}, base)
|
}, base)
|
||||||
|
|
||||||
return configBuilderToSpec(
|
return configBuilderToSpec(
|
||||||
CB.Config.of({
|
ISB.InputSpec.of({
|
||||||
domain: CB.Value.select({
|
domain: ISB.Value.select({
|
||||||
name: 'Domain',
|
name: 'Domain',
|
||||||
required: { default: null },
|
default: domains[0].value,
|
||||||
values,
|
values,
|
||||||
}),
|
}),
|
||||||
subdomain: CB.Value.text({
|
subdomain: ISB.Value.text({
|
||||||
name: 'Subdomain',
|
name: 'Subdomain',
|
||||||
required: false,
|
required: false,
|
||||||
|
default: '',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const RUNNING = ['running', 'starting', 'restarting']
|
|||||||
*tuiLet="hasUnmet() | async as hasUnmet"
|
*tuiLet="hasUnmet() | async as hasUnmet"
|
||||||
tuiIconButton
|
tuiIconButton
|
||||||
iconStart="@tui.play"
|
iconStart="@tui.play"
|
||||||
[disabled]="status().primary !== 'stopped' || !pkg().status.configured"
|
[disabled]="status().primary !== 'stopped'"
|
||||||
(click)="actions.start(manifest(), !!hasUnmet)"
|
(click)="actions.start(manifest(), !!hasUnmet)"
|
||||||
>
|
>
|
||||||
Start
|
Start
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ export class StatusComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
!this.hasDepErrors && // no deps error
|
!this.hasDepErrors && // no deps error
|
||||||
!!this.pkg.status.configured && // no config needed
|
// @TODO Matt how do we handle this now?
|
||||||
|
// !!this.pkg.status.configured && // no config needed
|
||||||
status.health !== 'failure' // no health issues
|
status.health !== 'failure' // no health issues
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -86,8 +87,9 @@ export class StatusComponent {
|
|||||||
return 'Running'
|
return 'Running'
|
||||||
case 'stopped':
|
case 'stopped':
|
||||||
return 'Stopped'
|
return 'Stopped'
|
||||||
case 'needsConfig':
|
// @TODO Matt just dropping this?
|
||||||
return 'Needs Config'
|
// case 'needsConfig':
|
||||||
|
// return 'Needs Config'
|
||||||
case 'updating':
|
case 'updating':
|
||||||
return 'Updating...'
|
return 'Updating...'
|
||||||
case 'stopping':
|
case 'stopping':
|
||||||
@@ -111,8 +113,9 @@ export class StatusComponent {
|
|||||||
switch (this.getStatus(this.pkg).primary) {
|
switch (this.getStatus(this.pkg).primary) {
|
||||||
case 'running':
|
case 'running':
|
||||||
return 'var(--tui-status-positive)'
|
return 'var(--tui-status-positive)'
|
||||||
case 'needsConfig':
|
// @TODO Matt just dropping this?
|
||||||
return 'var(--tui-status-warning)'
|
// case 'needsConfig':
|
||||||
|
// return 'var(--tui-status-warning)'
|
||||||
case 'installing':
|
case 'installing':
|
||||||
case 'updating':
|
case 'updating':
|
||||||
case 'stopping':
|
case 'stopping':
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export class UILaunchComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isRunning(): boolean {
|
get isRunning(): boolean {
|
||||||
return this.pkg.status.main.status === 'running'
|
return this.pkg.status.main === 'running'
|
||||||
}
|
}
|
||||||
|
|
||||||
get first(): T.ServiceInterface | undefined {
|
get first(): T.ServiceInterface | undefined {
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
|
||||||
import { CopyService } from '@start9labs/shared'
|
|
||||||
import { TuiDialogContext, TuiButton } from '@taiga-ui/core'
|
|
||||||
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus'
|
|
||||||
import { QrCodeModule } from 'ng-qrcode'
|
|
||||||
import { ActionResponse } from 'src/app/services/api/api.types'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: `
|
|
||||||
{{ context.data.message }}
|
|
||||||
<ng-container *ngIf="context.data.value">
|
|
||||||
<qr-code
|
|
||||||
*ngIf="context.data.qr"
|
|
||||||
size="240"
|
|
||||||
[value]="context.data.value"
|
|
||||||
></qr-code>
|
|
||||||
<p>
|
|
||||||
{{ context.data.value }}
|
|
||||||
<button
|
|
||||||
*ngIf="context.data.copyable"
|
|
||||||
tuiIconButton
|
|
||||||
appearance="flat"
|
|
||||||
iconStart="@tui.copy"
|
|
||||||
(click)="copyService.copy(context.data.value)"
|
|
||||||
>
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</ng-container>
|
|
||||||
`,
|
|
||||||
styles: [
|
|
||||||
`
|
|
||||||
qr-code {
|
|
||||||
margin: 1rem auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule, QrCodeModule, TuiButton],
|
|
||||||
})
|
|
||||||
export class ServiceActionSuccessComponent {
|
|
||||||
readonly copyService = inject(CopyService)
|
|
||||||
readonly context =
|
|
||||||
inject<TuiDialogContext<void, ActionResponse>>(POLYMORPHEUS_CONTEXT)
|
|
||||||
}
|
|
||||||
@@ -103,7 +103,9 @@ export class ServiceActionsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get canConfigure(): boolean {
|
get canConfigure(): boolean {
|
||||||
return !this.service.pkg.status.configured
|
// @TODO Matt should we just drop this?
|
||||||
|
// return !this.service.pkg.status.configured
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@tuiPure
|
@tuiPure
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
|
import { TuiTitle } from '@taiga-ui/core'
|
||||||
|
import { TuiAccordion, TuiFade } from '@taiga-ui/kit'
|
||||||
|
import { ActionSuccessMemberComponent } from './action-success-member.component'
|
||||||
|
import { GroupResult } from './types'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-action-success-group',
|
||||||
|
template: `
|
||||||
|
@for (member of group.value; track $index) {
|
||||||
|
<p>
|
||||||
|
@if (member.type === 'single') {
|
||||||
|
<app-action-success-member [member]="member" />
|
||||||
|
}
|
||||||
|
@if (member.type === 'group') {
|
||||||
|
<tui-accordion-item>
|
||||||
|
<div tuiFade>{{ member.name }}</div>
|
||||||
|
<ng-template tuiAccordionItemContent>
|
||||||
|
<app-action-success-group [group]="member" />
|
||||||
|
</ng-template>
|
||||||
|
</tui-accordion-item>
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
p:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [TuiTitle, ActionSuccessMemberComponent, TuiAccordion, TuiFade],
|
||||||
|
})
|
||||||
|
export class ActionSuccessGroupComponent {
|
||||||
|
@Input()
|
||||||
|
group!: GroupResult
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
TemplateRef,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
import { TuiButton, TuiDialogService, TuiTitle } from '@taiga-ui/core'
|
||||||
|
import {
|
||||||
|
TuiInputModule,
|
||||||
|
TuiTextfieldComponent,
|
||||||
|
TuiTextfieldControllerModule,
|
||||||
|
} from '@taiga-ui/legacy'
|
||||||
|
import { QrCodeModule } from 'ng-qrcode'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-action-success-member',
|
||||||
|
template: `
|
||||||
|
<tui-input
|
||||||
|
[readOnly]="true"
|
||||||
|
[ngModel]="member.value"
|
||||||
|
[tuiTextfieldCustomContent]="actions"
|
||||||
|
>
|
||||||
|
{{ member.name }}
|
||||||
|
<input
|
||||||
|
tuiTextfieldLegacy
|
||||||
|
[style.border-inline-end-width.rem]="border"
|
||||||
|
[type]="member.masked && masked ? 'password' : 'text'"
|
||||||
|
/>
|
||||||
|
</tui-input>
|
||||||
|
@if (member.description) {
|
||||||
|
<label [style.padding-top.rem]="0.25" tuiTitle>
|
||||||
|
<span tuiSubtitle [style.opacity]="0.8">{{ member.description }}</span>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<ng-template #actions>
|
||||||
|
@if (member.masked) {
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
appearance="icon"
|
||||||
|
size="s"
|
||||||
|
type="button"
|
||||||
|
tabindex="-1"
|
||||||
|
[iconStart]="masked ? '@tui.eye' : '@tui.eye-off'"
|
||||||
|
[style.pointer-events]="'auto'"
|
||||||
|
(click)="masked = !masked"
|
||||||
|
>
|
||||||
|
Reveal/Hide
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if (member.copyable) {
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
appearance="icon"
|
||||||
|
size="s"
|
||||||
|
type="button"
|
||||||
|
tabindex="-1"
|
||||||
|
iconStart="@tui.copy"
|
||||||
|
[style.pointer-events]="'auto'"
|
||||||
|
(click)="copy()"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if (member.qr) {
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
appearance="icon"
|
||||||
|
size="s"
|
||||||
|
type="button"
|
||||||
|
tabindex="-1"
|
||||||
|
iconStart="@tui.qr-code"
|
||||||
|
[style.pointer-events]="'auto'"
|
||||||
|
(click)="show(qr)"
|
||||||
|
>
|
||||||
|
Show QR
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #qr>
|
||||||
|
<qr-code
|
||||||
|
[value]="member.value"
|
||||||
|
[style.filter]="member.masked && masked ? 'blur(0.5rem)' : null"
|
||||||
|
size="350"
|
||||||
|
/>
|
||||||
|
@if (member.masked && masked) {
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
class="reveal"
|
||||||
|
iconStart="@tui.eye"
|
||||||
|
[style.border-radius.%]="100"
|
||||||
|
(click)="masked = false"
|
||||||
|
>
|
||||||
|
Reveal
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||||
|
|
||||||
|
.reveal {
|
||||||
|
@include center-all();
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiInputModule,
|
||||||
|
TuiTextfieldControllerModule,
|
||||||
|
TuiButton,
|
||||||
|
QrCodeModule,
|
||||||
|
TuiTitle,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ActionSuccessMemberComponent {
|
||||||
|
@ViewChild(TuiTextfieldComponent, { read: ElementRef })
|
||||||
|
private readonly input!: ElementRef<HTMLInputElement>
|
||||||
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
member!: T.ActionResultMember & { type: 'single' }
|
||||||
|
|
||||||
|
masked = true
|
||||||
|
|
||||||
|
get border(): number {
|
||||||
|
let border = 0
|
||||||
|
|
||||||
|
if (this.member.masked) border += 2
|
||||||
|
if (this.member.copyable) border += 2
|
||||||
|
if (this.member.qr) border += 2
|
||||||
|
|
||||||
|
return border
|
||||||
|
}
|
||||||
|
|
||||||
|
show(template: TemplateRef<any>) {
|
||||||
|
const masked = this.masked
|
||||||
|
|
||||||
|
this.masked = this.member.masked
|
||||||
|
this.dialogs
|
||||||
|
.open(template, { label: 'Scan this QR', size: 's' })
|
||||||
|
.subscribe({
|
||||||
|
complete: () => (this.masked = masked),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
copy() {
|
||||||
|
const el = this.input.nativeElement
|
||||||
|
|
||||||
|
if (!el) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
el.type = 'text'
|
||||||
|
el.focus()
|
||||||
|
el.select()
|
||||||
|
el.ownerDocument.execCommand('copy')
|
||||||
|
el.type = this.masked && this.member.masked ? 'password' : 'text'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
Input,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { TuiButton } from '@taiga-ui/core'
|
||||||
|
import {
|
||||||
|
TuiInputModule,
|
||||||
|
TuiTextfieldComponent,
|
||||||
|
TuiTextfieldControllerModule,
|
||||||
|
} from '@taiga-ui/legacy'
|
||||||
|
import { QrCodeModule } from 'ng-qrcode'
|
||||||
|
import { SingleResult } from './types'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-action-success-single',
|
||||||
|
template: `
|
||||||
|
<p class="qr"><ng-container *ngTemplateOutlet="qr" /></p>
|
||||||
|
<tui-input
|
||||||
|
[readOnly]="true"
|
||||||
|
[ngModel]="single.value"
|
||||||
|
[tuiTextfieldLabelOutside]="true"
|
||||||
|
[tuiTextfieldCustomContent]="actions"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
tuiTextfieldLegacy
|
||||||
|
[style.border-inline-end-width.rem]="border"
|
||||||
|
[type]="single.masked && masked ? 'password' : 'text'"
|
||||||
|
/>
|
||||||
|
</tui-input>
|
||||||
|
<ng-template #actions>
|
||||||
|
@if (single.masked) {
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
appearance="icon"
|
||||||
|
size="s"
|
||||||
|
type="button"
|
||||||
|
tabindex="-1"
|
||||||
|
[iconStart]="masked ? '@tui.eye' : '@tui.eye-off'"
|
||||||
|
[style.pointer-events]="'auto'"
|
||||||
|
(click)="masked = !masked"
|
||||||
|
>
|
||||||
|
Reveal/Hide
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if (single.copyable) {
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
appearance="icon"
|
||||||
|
size="s"
|
||||||
|
type="button"
|
||||||
|
tabindex="-1"
|
||||||
|
iconStart="@tui.copy"
|
||||||
|
[style.pointer-events]="'auto'"
|
||||||
|
(click)="copy()"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #qr>
|
||||||
|
<qr-code
|
||||||
|
[value]="single.value"
|
||||||
|
[style.filter]="single.masked && masked ? 'blur(0.5rem)' : null"
|
||||||
|
size="350"
|
||||||
|
/>
|
||||||
|
@if (single.masked && masked) {
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
class="reveal"
|
||||||
|
iconStart="@tui.eye"
|
||||||
|
[style.border-radius.%]="100"
|
||||||
|
(click)="masked = false"
|
||||||
|
>
|
||||||
|
Reveal
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||||
|
|
||||||
|
.reveal {
|
||||||
|
@include center-all();
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
TuiInputModule,
|
||||||
|
TuiTextfieldControllerModule,
|
||||||
|
TuiButton,
|
||||||
|
QrCodeModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ActionSuccessSingleComponent {
|
||||||
|
@ViewChild(TuiTextfieldComponent, { read: ElementRef })
|
||||||
|
private readonly input!: ElementRef<HTMLInputElement>
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
single!: SingleResult
|
||||||
|
|
||||||
|
masked = true
|
||||||
|
|
||||||
|
get border(): number {
|
||||||
|
let border = 0
|
||||||
|
|
||||||
|
if (this.single.masked) border += 2
|
||||||
|
if (this.single.copyable) border += 2
|
||||||
|
|
||||||
|
return border
|
||||||
|
}
|
||||||
|
|
||||||
|
copy() {
|
||||||
|
const el = this.input.nativeElement
|
||||||
|
|
||||||
|
if (!el) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
el.type = 'text'
|
||||||
|
el.focus()
|
||||||
|
el.select()
|
||||||
|
el.ownerDocument.execCommand('copy')
|
||||||
|
el.type = this.masked && this.single.masked ? 'password' : 'text'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Component, inject } from '@angular/core'
|
||||||
|
import { TuiDialogContext } from '@taiga-ui/core'
|
||||||
|
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus'
|
||||||
|
import { ActionSuccessGroupComponent } from './action-success-group.component'
|
||||||
|
import { ActionSuccessSingleComponent } from './action-success-single.component'
|
||||||
|
import { ActionResponseWithResult } from './types'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
@if (data.message) {
|
||||||
|
<p>{{ data.message }}</p>
|
||||||
|
}
|
||||||
|
@if (single) {
|
||||||
|
<app-action-success-single [single]="single" />
|
||||||
|
}
|
||||||
|
@if (group) {
|
||||||
|
<app-action-success-group [group]="group" />
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
imports: [ActionSuccessGroupComponent, ActionSuccessSingleComponent],
|
||||||
|
})
|
||||||
|
export class ActionSuccessPage {
|
||||||
|
readonly data =
|
||||||
|
inject<TuiDialogContext<void, ActionResponseWithResult>>(
|
||||||
|
POLYMORPHEUS_CONTEXT,
|
||||||
|
).data
|
||||||
|
|
||||||
|
readonly single = this.data.result.type === 'single' ? this.data.result : null
|
||||||
|
readonly group = this.data.result.type === 'group' ? this.data.result : null
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { RR } from 'src/app/services/api/api.types'
|
||||||
|
|
||||||
|
type ActionResponse = NonNullable<RR.ActionRes>
|
||||||
|
type ActionResult = NonNullable<ActionResponse['result']>
|
||||||
|
export type ActionResponseWithResult = ActionResponse & { result: ActionResult }
|
||||||
|
export type SingleResult = ActionResult & { type: 'single' }
|
||||||
|
export type GroupResult = ActionResult & { type: 'group' }
|
||||||
@@ -55,7 +55,8 @@ export class ServicePropertiesModal {
|
|||||||
this.loading$.next(true)
|
this.loading$.next(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.properties = await this.api.getPackageProperties({ id: this.id })
|
// @TODO Matt this needs complete rework, right?
|
||||||
|
// this.properties = await this.api.getPackageProperties({ id: this.id })
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import { T } from '@start9labs/start-sdk'
|
|||||||
import { TuiDialogService } from '@taiga-ui/core'
|
import { TuiDialogService } from '@taiga-ui/core'
|
||||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
import { from } from 'rxjs'
|
import { from } from 'rxjs'
|
||||||
import {
|
// @TODO Alex implement config
|
||||||
ConfigModal,
|
// import {
|
||||||
PackageConfigData,
|
// ConfigModal,
|
||||||
} from 'src/app/routes/portal/modals/config.component'
|
// PackageConfigData,
|
||||||
|
// } from 'src/app/routes/portal/modals/config.component'
|
||||||
import { ServiceAdditionalModal } from 'src/app/routes/portal/routes/service/modals/additional.component'
|
import { ServiceAdditionalModal } from 'src/app/routes/portal/routes/service/modals/additional.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
@@ -131,9 +132,9 @@ export class ToMenuPipe implements PipeTransform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private openConfig({ title, id }: T.Manifest) {
|
private openConfig({ title, id }: T.Manifest) {
|
||||||
this.formDialog.open<PackageConfigData>(ConfigModal, {
|
// this.formDialog.open<PackageConfigData>(ConfigModal, {
|
||||||
label: `${title} configuration`,
|
// label: `${title} configuration`,
|
||||||
data: { pkgId: id },
|
// data: { pkgId: id },
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { ServiceActionComponent } from '../components/action.component'
|
import { ServiceActionComponent } from '../components/action.component'
|
||||||
import { ServiceActionSuccessComponent } from '../components/action-success.component'
|
import { ActionSuccessPage } from '../modals/action-success/action-success.page'
|
||||||
import { GroupActionsPipe } from '../pipes/group-actions.pipe'
|
import { GroupActionsPipe } from '../pipes/group-actions.pipe'
|
||||||
import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
|
import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
@@ -50,7 +50,7 @@ import { getAllPackages, getManifest } from 'src/app/utils/get-package-data'
|
|||||||
[action]="{
|
[action]="{
|
||||||
name: action.name,
|
name: action.name,
|
||||||
description: action.description,
|
description: action.description,
|
||||||
icon: '@tui.circle-play'
|
icon: '@tui.circle-play',
|
||||||
}"
|
}"
|
||||||
(click)="handleAction(action)"
|
(click)="handleAction(action)"
|
||||||
></button>
|
></button>
|
||||||
@@ -92,45 +92,46 @@ export class ServiceActionsRoute {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handleAction(action: WithId<T.ActionMetadata>) {
|
async handleAction(action: WithId<T.ActionMetadata>) {
|
||||||
if (action.disabled) {
|
// @TODO Matt this needs complete rework, right?
|
||||||
this.dialogs
|
// if (action.disabled) {
|
||||||
.open(action.disabled, {
|
// this.dialogs
|
||||||
label: 'Forbidden',
|
// .open(action.disabled, {
|
||||||
size: 's',
|
// label: 'Forbidden',
|
||||||
})
|
// size: 's',
|
||||||
.subscribe()
|
// })
|
||||||
} else {
|
// .subscribe()
|
||||||
if (action.input && !isEmptyObject(action.input)) {
|
// } else {
|
||||||
this.formDialog.open(FormComponent, {
|
// if (action.input && !isEmptyObject(action.input)) {
|
||||||
label: action.name,
|
// this.formDialog.open(FormComponent, {
|
||||||
data: {
|
// label: action.name,
|
||||||
spec: action.input,
|
// data: {
|
||||||
buttons: [
|
// spec: action.input,
|
||||||
{
|
// buttons: [
|
||||||
text: 'Execute',
|
// {
|
||||||
handler: async (value: any) =>
|
// text: 'Execute',
|
||||||
this.executeAction(action.id, value),
|
// handler: async (value: any) =>
|
||||||
},
|
// this.executeAction(action.id, value),
|
||||||
],
|
// },
|
||||||
},
|
// ],
|
||||||
})
|
// },
|
||||||
} else {
|
// })
|
||||||
this.dialogs
|
// } else {
|
||||||
.open(TUI_CONFIRM, {
|
// this.dialogs
|
||||||
label: 'Confirm',
|
// .open(TUI_CONFIRM, {
|
||||||
size: 's',
|
// label: 'Confirm',
|
||||||
data: {
|
// size: 's',
|
||||||
content: `Are you sure you want to execute action "${
|
// data: {
|
||||||
action.name
|
// content: `Are you sure you want to execute action "${
|
||||||
}"? ${action.warning || ''}`,
|
// action.name
|
||||||
yes: 'Execute',
|
// }"? ${action.warning || ''}`,
|
||||||
no: 'Cancel',
|
// yes: 'Execute',
|
||||||
},
|
// no: 'Cancel',
|
||||||
})
|
// },
|
||||||
.pipe(filter(Boolean))
|
// })
|
||||||
.subscribe(() => this.executeAction(action.id))
|
// .pipe(filter(Boolean))
|
||||||
}
|
// .subscribe(() => this.executeAction(action.id))
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryUninstall(pkg: PackageDataEntry): Promise<void> {
|
async tryUninstall(pkg: PackageDataEntry): Promise<void> {
|
||||||
@@ -181,22 +182,20 @@ export class ServiceActionsRoute {
|
|||||||
const loader = this.loader.open('Executing action...').subscribe()
|
const loader = this.loader.open('Executing action...').subscribe()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.embassyApi.executePackageAction({
|
// @TODO Matt this needs complete rework, right?
|
||||||
id: this.id,
|
// const data = await this.embassyApi.executePackageAction({
|
||||||
actionId,
|
// id: this.id,
|
||||||
input,
|
// actionId,
|
||||||
})
|
// input,
|
||||||
|
// })
|
||||||
|
|
||||||
timer(500)
|
timer(500)
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
this.dialogs.open(
|
this.dialogs.open(new PolymorpheusComponent(ActionSuccessPage), {
|
||||||
new PolymorpheusComponent(ServiceActionSuccessComponent),
|
label: 'Execution Complete',
|
||||||
{
|
// data,
|
||||||
label: 'Execution Complete',
|
}),
|
||||||
data,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subscribe()
|
.subscribe()
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import { isEmptyObject } from '@start9labs/shared'
|
|||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { combineLatest, map, switchMap } from 'rxjs'
|
import { combineLatest, map, switchMap } from 'rxjs'
|
||||||
import {
|
// @TODO Alex implement config
|
||||||
ConfigModal,
|
// import {
|
||||||
PackageConfigData,
|
// ConfigModal,
|
||||||
} from 'src/app/routes/portal/modals/config.component'
|
// PackageConfigData,
|
||||||
|
// } from 'src/app/routes/portal/modals/config.component'
|
||||||
import { ServiceBackupsComponent } from 'src/app/routes/portal/routes/service/components/backups.component'
|
import { ServiceBackupsComponent } from 'src/app/routes/portal/routes/service/components/backups.component'
|
||||||
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe'
|
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
@@ -180,9 +181,7 @@ export class ServiceRoute {
|
|||||||
)
|
)
|
||||||
|
|
||||||
readonly health$ = this.pkgId$.pipe(
|
readonly health$ = this.pkgId$.pipe(
|
||||||
switchMap(pkgId =>
|
switchMap(pkgId => this.patch.watch$('packageData', pkgId, 'status')),
|
||||||
this.patch.watch$('packageData', pkgId, 'status', 'main'),
|
|
||||||
),
|
|
||||||
map(toHealthCheck),
|
map(toHealthCheck),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -264,10 +263,11 @@ export class ServiceRoute {
|
|||||||
errorText = 'Incorrect version'
|
errorText = 'Incorrect version'
|
||||||
fixText = 'Update'
|
fixText = 'Update'
|
||||||
fixAction = () => this.fixDep(pkg, pkgManifest, 'update', depId)
|
fixAction = () => this.fixDep(pkg, pkgManifest, 'update', depId)
|
||||||
} else if (depError.type === 'configUnsatisfied') {
|
// @TODO Matt do we just remove this case?
|
||||||
errorText = 'Config not satisfied'
|
// } else if (depError.type === 'configUnsatisfied') {
|
||||||
fixText = 'Auto config'
|
// errorText = 'Config not satisfied'
|
||||||
fixAction = () => this.fixDep(pkg, pkgManifest, 'configure', depId)
|
// fixText = 'Auto config'
|
||||||
|
// fixAction = () => this.fixDep(pkg, pkgManifest, 'configure', depId)
|
||||||
} else if (depError.type === 'notRunning') {
|
} else if (depError.type === 'notRunning') {
|
||||||
errorText = 'Not running'
|
errorText = 'Not running'
|
||||||
fixText = 'Start'
|
fixText = 'Start'
|
||||||
@@ -296,13 +296,13 @@ export class ServiceRoute {
|
|||||||
case 'update':
|
case 'update':
|
||||||
return this.installDep(pkg, pkgManifest, depId)
|
return this.installDep(pkg, pkgManifest, depId)
|
||||||
case 'configure':
|
case 'configure':
|
||||||
return this.formDialog.open<PackageConfigData>(ConfigModal, {
|
// return this.formDialog.open<PackageConfigData>(ConfigModal, {
|
||||||
label: `${pkg.currentDependencies[depId].title} config`,
|
// label: `${pkg.currentDependencies[depId].title} config`,
|
||||||
data: {
|
// data: {
|
||||||
pkgId: depId,
|
// pkgId: depId,
|
||||||
dependentInfo: pkgManifest,
|
// dependentInfo: pkgManifest,
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,8 +329,10 @@ export class ServiceRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toHealthCheck(main: T.MainStatus): T.NamedHealthCheckResult[] | null {
|
function toHealthCheck(
|
||||||
return main.status !== 'running' || isEmptyObject(main.health)
|
status: T.MainStatus,
|
||||||
|
): T.NamedHealthCheckResult[] | null {
|
||||||
|
return status.main !== 'running' || isEmptyObject(status.health)
|
||||||
? null
|
? null
|
||||||
: Object.values(main.health)
|
: Object.values(status.health)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import { RecoverOption } from '../types/recover-option'
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
tuiCheckbox
|
tuiCheckbox
|
||||||
[disabled]="option.installed || option.newerStartOs"
|
[disabled]="option.installed || option.newerOs"
|
||||||
[(ngModel)]="option.checked"
|
[(ngModel)]="option.checked"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -85,8 +85,8 @@ export class BackupsRecoverModal {
|
|||||||
.watch$('packageData')
|
.watch$('packageData')
|
||||||
.pipe(take(1))
|
.pipe(take(1))
|
||||||
|
|
||||||
readonly toMessage = ({ newerStartOs, installed, title }: RecoverOption) => {
|
readonly toMessage = ({ newerOs, installed, title }: RecoverOption) => {
|
||||||
if (newerStartOs) {
|
if (newerOs) {
|
||||||
return {
|
return {
|
||||||
text: `Unavailable. Backup was made on a newer version of StartOS.`,
|
text: `Unavailable. Backup was made on a newer version of StartOS.`,
|
||||||
color: 'var(--tui-status-negative)',
|
color: 'var(--tui-status-negative)',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
signal,
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { ErrorService, Exver } from '@start9labs/shared'
|
import { ErrorService, Exver } from '@start9labs/shared'
|
||||||
|
import { Version } from '@start9labs/start-sdk'
|
||||||
import {
|
import {
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiDialogContext,
|
TuiDialogContext,
|
||||||
@@ -116,9 +117,8 @@ export class BackupsTargetModal {
|
|||||||
hasBackup(target: BackupTarget): boolean {
|
hasBackup(target: BackupTarget): boolean {
|
||||||
return (
|
return (
|
||||||
target.startOs?.[this.serverId] &&
|
target.startOs?.[this.serverId] &&
|
||||||
this.exver.compareOsVersion(
|
Version.parse(target.startOs[this.serverId].version).compare(
|
||||||
target.startOs[this.serverId].version,
|
Version.parse('0.3.6'),
|
||||||
'0.3.6',
|
|
||||||
) !== 'less'
|
) !== 'less'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||||
import { Exver } from '@start9labs/shared'
|
|
||||||
import { map, Observable } from 'rxjs'
|
import { map, Observable } from 'rxjs'
|
||||||
import { PackageBackupInfo } from 'src/app/services/api/api.types'
|
import { PackageBackupInfo } from 'src/app/services/api/api.types'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
@@ -7,20 +6,12 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
|||||||
import { RecoverOption } from '../types/recover-option'
|
import { RecoverOption } from '../types/recover-option'
|
||||||
import { Version } from '@start9labs/start-sdk'
|
import { Version } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
export interface AppRecoverOption extends PackageBackupInfo {
|
|
||||||
id: string
|
|
||||||
checked: boolean
|
|
||||||
installed: boolean
|
|
||||||
newerOS: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'toOptions',
|
name: 'toOptions',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class ToOptionsPipe implements PipeTransform {
|
export class ToOptionsPipe implements PipeTransform {
|
||||||
private readonly config = inject(ConfigService)
|
private readonly config = inject(ConfigService)
|
||||||
private readonly exver = inject(Exver)
|
|
||||||
|
|
||||||
transform(
|
transform(
|
||||||
packageData$: Observable<Record<string, PackageDataEntry>>,
|
packageData$: Observable<Record<string, PackageDataEntry>>,
|
||||||
@@ -34,7 +25,7 @@ export class ToOptionsPipe implements PipeTransform {
|
|||||||
id,
|
id,
|
||||||
installed: !!packageData[id],
|
installed: !!packageData[id],
|
||||||
checked: false,
|
checked: false,
|
||||||
newerOS:
|
newerOs:
|
||||||
Version.parse(packageBackups[id].osVersion).compare(
|
Version.parse(packageBackups[id].osVersion).compare(
|
||||||
Version.parse(this.config.version),
|
Version.parse(this.config.version),
|
||||||
) === 'greater',
|
) === 'greater',
|
||||||
|
|||||||
@@ -1,92 +1,103 @@
|
|||||||
import { CB } from '@start9labs/start-sdk'
|
import { ISB } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
export const dropboxSpec = CB.Config.of({
|
export const dropboxSpec = ISB.InputSpec.of({
|
||||||
name: CB.Value.text({
|
name: ISB.Value.text({
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
description: 'A friendly name for this Dropbox target',
|
description: 'A friendly name for this Dropbox target',
|
||||||
placeholder: 'My Dropbox',
|
placeholder: 'My Dropbox',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
}),
|
}),
|
||||||
token: CB.Value.text({
|
token: ISB.Value.text({
|
||||||
name: 'Access Token',
|
name: 'Access Token',
|
||||||
description: 'The secret access token for your custom Dropbox app',
|
description: 'The secret access token for your custom Dropbox app',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
masked: true,
|
masked: true,
|
||||||
}),
|
}),
|
||||||
path: CB.Value.text({
|
path: ISB.Value.text({
|
||||||
name: 'Path',
|
name: 'Path',
|
||||||
description: 'The fully qualified path to the backup directory',
|
description: 'The fully qualified path to the backup directory',
|
||||||
placeholder: 'e.g. /Desktop/my-folder',
|
placeholder: 'e.g. /Desktop/my-folder',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const googleDriveSpec = CB.Config.of({
|
export const googleDriveSpec = ISB.InputSpec.of({
|
||||||
name: CB.Value.text({
|
name: ISB.Value.text({
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
description: 'A friendly name for this Google Drive target',
|
description: 'A friendly name for this Google Drive target',
|
||||||
placeholder: 'My Google Drive',
|
placeholder: 'My Google Drive',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
}),
|
}),
|
||||||
path: CB.Value.text({
|
path: ISB.Value.text({
|
||||||
name: 'Path',
|
name: 'Path',
|
||||||
description: 'The fully qualified path to the backup directory',
|
description: 'The fully qualified path to the backup directory',
|
||||||
placeholder: 'e.g. /Desktop/my-folder',
|
placeholder: 'e.g. /Desktop/my-folder',
|
||||||
required: { default: null },
|
required: true,
|
||||||
}),
|
default: null,
|
||||||
key: CB.Value.file({
|
|
||||||
name: 'Private Key File',
|
|
||||||
description:
|
|
||||||
'Your Google Drive service account private key file (.json file)',
|
|
||||||
required: { default: null },
|
|
||||||
extensions: ['json'],
|
|
||||||
}),
|
}),
|
||||||
|
// @TODO Matt do we just drop file specs?
|
||||||
|
// key: ISB.Value.file({
|
||||||
|
// name: 'Private Key File',
|
||||||
|
// description:
|
||||||
|
// 'Your Google Drive service account private key file (.json file)',
|
||||||
|
// required: { default: null },
|
||||||
|
// extensions: ['json'],
|
||||||
|
// }),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const cifsSpec = CB.Config.of({
|
export const cifsSpec = ISB.InputSpec.of({
|
||||||
name: CB.Value.text({
|
name: ISB.Value.text({
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
description: 'A friendly name for this Network Folder',
|
description: 'A friendly name for this Network Folder',
|
||||||
placeholder: 'My Network Folder',
|
placeholder: 'My Network Folder',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
}),
|
}),
|
||||||
hostname: CB.Value.text({
|
hostname: ISB.Value.text({
|
||||||
name: 'Hostname',
|
name: 'Hostname',
|
||||||
description:
|
description:
|
||||||
'The hostname of your target device on the Local Area Network.',
|
'The hostname of your target device on the Local Area Network.',
|
||||||
warning: null,
|
warning: null,
|
||||||
placeholder: `e.g. 'My Computer' OR 'my-computer.local'`,
|
placeholder: `e.g. 'My Computer' OR 'my-computer.local'`,
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
patterns: [],
|
patterns: [],
|
||||||
}),
|
}),
|
||||||
path: CB.Value.text({
|
path: ISB.Value.text({
|
||||||
name: 'Path',
|
name: 'Path',
|
||||||
description: `On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder).\n\n On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).`,
|
description: `On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder).\n\n On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).`,
|
||||||
placeholder: 'e.g. my-shared-folder or /Desktop/my-folder',
|
placeholder: 'e.g. my-shared-folder or /Desktop/my-folder',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
}),
|
}),
|
||||||
username: CB.Value.text({
|
username: ISB.Value.text({
|
||||||
name: 'Username',
|
name: 'Username',
|
||||||
description: `On Linux, this is the samba username you created when sharing the folder.\n\n On Mac and Windows, this is the username of the user who is sharing the folder.`,
|
description: `On Linux, this is the samba username you created when sharing the folder.\n\n On Mac and Windows, this is the username of the user who is sharing the folder.`,
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
placeholder: 'My Network Folder',
|
placeholder: 'My Network Folder',
|
||||||
}),
|
}),
|
||||||
password: CB.Value.text({
|
password: ISB.Value.text({
|
||||||
name: 'Password',
|
name: 'Password',
|
||||||
description: `On Linux, this is the samba password you created when sharing the folder.\n\n On Mac and Windows, this is the password of the user who is sharing the folder.`,
|
description: `On Linux, this is the samba password you created when sharing the folder.\n\n On Mac and Windows, this is the password of the user who is sharing the folder.`,
|
||||||
required: false,
|
required: false,
|
||||||
masked: true,
|
masked: true,
|
||||||
|
default: null,
|
||||||
placeholder: 'My Network Folder',
|
placeholder: 'My Network Folder',
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const remoteBackupTargetSpec = CB.Config.of({
|
export const remoteBackupTargetSpec = ISB.InputSpec.of({
|
||||||
type: CB.Value.union(
|
type: ISB.Value.union(
|
||||||
{
|
{
|
||||||
name: 'Target Type',
|
name: 'Target Type',
|
||||||
required: { default: 'dropbox' },
|
default: 'dropbox',
|
||||||
},
|
},
|
||||||
CB.Variants.of({
|
ISB.Variants.of({
|
||||||
dropbox: {
|
dropbox: {
|
||||||
name: 'Dropbox',
|
name: 'Dropbox',
|
||||||
spec: dropboxSpec,
|
spec: dropboxSpec,
|
||||||
@@ -103,17 +114,19 @@ export const remoteBackupTargetSpec = CB.Config.of({
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const diskBackupTargetSpec = CB.Config.of({
|
export const diskBackupTargetSpec = ISB.InputSpec.of({
|
||||||
name: CB.Value.text({
|
name: ISB.Value.text({
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
description: 'A friendly name for this physical target',
|
description: 'A friendly name for this physical target',
|
||||||
placeholder: 'My Physical Target',
|
placeholder: 'My Physical Target',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
}),
|
}),
|
||||||
path: CB.Value.text({
|
path: ISB.Value.text({
|
||||||
name: 'Path',
|
name: 'Path',
|
||||||
description: 'The fully qualified path to the backup directory',
|
description: 'The fully qualified path to the backup directory',
|
||||||
placeholder: 'e.g. /Backups/my-folder',
|
placeholder: 'e.g. /Backups/my-folder',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
|||||||
selector: 'marketplace-notification',
|
selector: 'marketplace-notification',
|
||||||
template: `
|
template: `
|
||||||
<tui-notification
|
<tui-notification
|
||||||
[status]="status || 'warning'"
|
[appearance]="status || 'warning'"
|
||||||
icon=""
|
icon=""
|
||||||
class="notification-wrapper"
|
class="notification-wrapper"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { CT } from '@start9labs/start-sdk'
|
import { IST } from '@start9labs/start-sdk'
|
||||||
import { TuiDialogOptions } from '@taiga-ui/core'
|
import { TuiDialogOptions } from '@taiga-ui/core'
|
||||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||||
|
|
||||||
export function getMarketplaceValueSpec(): CT.ValueSpecObject {
|
export function getMarketplaceValueSpec(): IST.ValueSpecObject {
|
||||||
return {
|
return {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
name: 'Add Custom Registry',
|
name: 'Add Custom Registry',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'settings-sync',
|
selector: 'settings-sync',
|
||||||
template: `
|
template: `
|
||||||
<tui-notification status="warning">
|
<tui-notification appearance="warning">
|
||||||
<div tuiCell [style.padding]="0">
|
<div tuiCell [style.padding]="0">
|
||||||
<div tuiTitle>
|
<div tuiTitle>
|
||||||
Clock sync failure
|
Clock sync failure
|
||||||
|
|||||||
@@ -1,42 +1,45 @@
|
|||||||
import { CB } from '@start9labs/start-sdk'
|
import { ISB } from '@start9labs/start-sdk'
|
||||||
import { Proxy } from 'src/app/services/patch-db/data-model'
|
import { Proxy } from 'src/app/services/patch-db/data-model'
|
||||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||||
|
|
||||||
const auth = CB.Config.of({
|
const auth = ISB.InputSpec.of({
|
||||||
username: CB.Value.text({
|
username: ISB.Value.text({
|
||||||
name: 'Username',
|
name: 'Username',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
}),
|
}),
|
||||||
password: CB.Value.text({
|
password: ISB.Value.text({
|
||||||
name: 'Password',
|
name: 'Password',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
masked: true,
|
masked: true,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
function getStrategyUnion(proxies: Proxy[]) {
|
function getStrategyUnion(proxies: Proxy[]) {
|
||||||
const inboundProxies = proxies
|
const inboundProxies: Record<string, string> = proxies
|
||||||
.filter(p => p.type === 'inbound-outbound')
|
.filter(p => p.type === 'inbound-outbound')
|
||||||
.reduce((prev, curr) => {
|
.reduce(
|
||||||
return {
|
(prev, curr) => ({
|
||||||
[curr.id]: curr.name,
|
[curr.id]: curr.name,
|
||||||
...prev,
|
...prev,
|
||||||
}
|
}),
|
||||||
}, {})
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
return CB.Value.union(
|
return ISB.Value.union(
|
||||||
{
|
{
|
||||||
name: 'Networking Strategy',
|
name: 'Networking Strategy',
|
||||||
required: { default: null },
|
default: 'local',
|
||||||
description: `<h5>Local</h5>Select this option if you do not mind exposing your home/business IP address to the Internet. This option requires configuring router settings, which StartOS can do automatically if you have an OpenWRT router
|
description: `<h5>Local</h5>Select this option if you do not mind exposing your home/business IP address to the Internet. This option requires configuring router settings, which StartOS can do automatically if you have an OpenWRT router
|
||||||
<h5>Proxy</h5>Select this option is you prefer to hide your home/business IP address from the Internet. This option requires running your own Virtual Private Server (VPS) <i>or</i> paying service provider such as Static Wire
|
<h5>Proxy</h5>Select this option is you prefer to hide your home/business IP address from the Internet. This option requires running your own Virtual Private Server (VPS) <i>or</i> paying service provider such as Static Wire
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
CB.Variants.of({
|
ISB.Variants.of({
|
||||||
local: {
|
local: {
|
||||||
name: 'Local',
|
name: 'Local',
|
||||||
spec: CB.Config.of({
|
spec: ISB.InputSpec.of({
|
||||||
ipStrategy: CB.Value.select({
|
ipStrategy: ISB.Value.select({
|
||||||
name: 'IP Strategy',
|
name: 'IP Strategy',
|
||||||
description: `<h5>IPv6 Only (recommended)</h5><b>Requirements</b>:<ol><li>ISP IPv6 support</li><li>OpenWRT (recommended) or Linksys router</li></ol><b>Pros</b>: Ready for IPv6 Internet. Enhanced privacy. Run multiple clearnet servers from the same network
|
description: `<h5>IPv6 Only (recommended)</h5><b>Requirements</b>:<ol><li>ISP IPv6 support</li><li>OpenWRT (recommended) or Linksys router</li></ol><b>Pros</b>: Ready for IPv6 Internet. Enhanced privacy. Run multiple clearnet servers from the same network
|
||||||
<b>Cons</b>: Interfaces using this domain will only be accessible to people whose ISP supports IPv6
|
<b>Cons</b>: Interfaces using this domain will only be accessible to people whose ISP supports IPv6
|
||||||
@@ -45,7 +48,7 @@ function getStrategyUnion(proxies: Proxy[]) {
|
|||||||
<h5>IPv4 Only</h5><b>Pros</b>: Accessible by anyone
|
<h5>IPv4 Only</h5><b>Pros</b>: Accessible by anyone
|
||||||
<b>Cons</b>: Less private, as IPv4 addresses are closely correlated with geographic areas. Cannot run multiple clearnet servers from the same network
|
<b>Cons</b>: Less private, as IPv4 addresses are closely correlated with geographic areas. Cannot run multiple clearnet servers from the same network
|
||||||
`,
|
`,
|
||||||
required: { default: 'ipv6' },
|
default: 'ipv6',
|
||||||
values: {
|
values: {
|
||||||
ipv6: 'IPv6 Only',
|
ipv6: 'IPv6 Only',
|
||||||
ipv4: 'IPv4 Only',
|
ipv4: 'IPv4 Only',
|
||||||
@@ -56,10 +59,10 @@ function getStrategyUnion(proxies: Proxy[]) {
|
|||||||
},
|
},
|
||||||
proxy: {
|
proxy: {
|
||||||
name: 'Proxy',
|
name: 'Proxy',
|
||||||
spec: CB.Config.of({
|
spec: ISB.InputSpec.of({
|
||||||
proxyId: CB.Value.select({
|
proxyId: ISB.Value.select({
|
||||||
name: 'Select Proxy',
|
name: 'Select Proxy',
|
||||||
required: { default: null },
|
default: proxies.filter(p => p.type === 'inbound-outbound')[0].id,
|
||||||
values: inboundProxies,
|
values: inboundProxies,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -70,7 +73,7 @@ function getStrategyUnion(proxies: Proxy[]) {
|
|||||||
|
|
||||||
export function getStart9ToSpec(proxies: Proxy[]) {
|
export function getStart9ToSpec(proxies: Proxy[]) {
|
||||||
return configBuilderToSpec(
|
return configBuilderToSpec(
|
||||||
CB.Config.of({
|
ISB.InputSpec.of({
|
||||||
strategy: getStrategyUnion(proxies),
|
strategy: getStrategyUnion(proxies),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -78,21 +81,22 @@ export function getStart9ToSpec(proxies: Proxy[]) {
|
|||||||
|
|
||||||
export function getCustomSpec(proxies: Proxy[]) {
|
export function getCustomSpec(proxies: Proxy[]) {
|
||||||
return configBuilderToSpec(
|
return configBuilderToSpec(
|
||||||
CB.Config.of({
|
ISB.InputSpec.of({
|
||||||
hostname: CB.Value.text({
|
hostname: ISB.Value.text({
|
||||||
name: 'Hostname',
|
name: 'Hostname',
|
||||||
required: { default: null },
|
required: true,
|
||||||
|
default: null,
|
||||||
placeholder: 'yourdomain.com',
|
placeholder: 'yourdomain.com',
|
||||||
}),
|
}),
|
||||||
provider: CB.Value.union(
|
provider: ISB.Value.union(
|
||||||
{
|
{
|
||||||
name: 'Dynamic DNS Provider',
|
name: 'Dynamic DNS Provider',
|
||||||
required: { default: 'start9' },
|
default: 'start9',
|
||||||
},
|
},
|
||||||
CB.Variants.of({
|
ISB.Variants.of({
|
||||||
start9: {
|
start9: {
|
||||||
name: 'Start9',
|
name: 'Start9',
|
||||||
spec: CB.Config.of({}),
|
spec: ISB.InputSpec.of({}),
|
||||||
},
|
},
|
||||||
njalla: {
|
njalla: {
|
||||||
name: 'Njalla',
|
name: 'Njalla',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
UntypedFormGroup,
|
UntypedFormGroup,
|
||||||
} from '@angular/forms'
|
} from '@angular/forms'
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
import { config, CT } from '@start9labs/start-sdk'
|
import { IST, inputSpec } from '@start9labs/start-sdk'
|
||||||
import { TuiButton, TuiDialogService } from '@taiga-ui/core'
|
import { TuiButton, TuiDialogService } from '@taiga-ui/core'
|
||||||
import { TuiInputModule } from '@taiga-ui/legacy'
|
import { TuiInputModule } from '@taiga-ui/legacy'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
@@ -80,8 +80,8 @@ export class SettingsEmailComponent {
|
|||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
|
|
||||||
testAddress = ''
|
testAddress = ''
|
||||||
readonly spec: Promise<CT.InputSpec> = configBuilderToSpec(
|
readonly spec: Promise<IST.InputSpec> = configBuilderToSpec(
|
||||||
config.constants.customSmtp,
|
inputSpec.constants.customSmtp,
|
||||||
)
|
)
|
||||||
readonly form$ = this.patch
|
readonly form$ = this.patch
|
||||||
.watch$('serverInfo', 'smtp')
|
.watch$('serverInfo', 'smtp')
|
||||||
@@ -96,7 +96,7 @@ export class SettingsEmailComponent {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.configureEmail(
|
await this.api.configureEmail(
|
||||||
config.constants.customSmtp.validator.unsafeCast(value),
|
inputSpec.constants.customSmtp.validator.unsafeCast(value),
|
||||||
)
|
)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CB } from '@start9labs/start-sdk'
|
import { ISB } from '@start9labs/start-sdk'
|
||||||
import { TuiDialogOptions } from '@taiga-ui/core'
|
import { TuiDialogOptions } from '@taiga-ui/core'
|
||||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||||
|
|
||||||
@@ -12,17 +12,19 @@ export const DELETE_OPTIONS: Partial<TuiDialogOptions<TuiConfirmData>> = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const wireguardSpec = CB.Config.of({
|
export const wireguardSpec = ISB.InputSpec.of({
|
||||||
name: CB.Value.text({
|
name: ISB.Value.text({
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
description: 'A friendly name to help you remember and identify this proxy',
|
description: 'A friendly name to help you remember and identify this proxy',
|
||||||
required: { default: null },
|
required: true,
|
||||||
}),
|
default: null,
|
||||||
config: CB.Value.file({
|
|
||||||
name: 'Wiregaurd Config',
|
|
||||||
required: { default: null },
|
|
||||||
extensions: ['.conf'],
|
|
||||||
}),
|
}),
|
||||||
|
// @TODO Matt same here
|
||||||
|
// config: ISB.Value.file({
|
||||||
|
// name: 'Wiregaurd Config',
|
||||||
|
// required: { default: null },
|
||||||
|
// extensions: ['.conf'],
|
||||||
|
// }),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type WireguardSpec = typeof wireguardSpec.validator._TYPE
|
export type WireguardSpec = typeof wireguardSpec.validator._TYPE
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
|||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
import { TuiDialogOptions, TuiButton } from '@taiga-ui/core'
|
import { TuiDialogOptions, TuiButton } from '@taiga-ui/core'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import { Observable } from 'rxjs'
|
||||||
import {
|
import {
|
||||||
FormComponent,
|
FormComponent,
|
||||||
FormContext,
|
FormContext,
|
||||||
} from 'src/app/routes/portal/components/form.component'
|
} from 'src/app/routes/portal/components/form.component'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel, Proxy } from 'src/app/services/patch-db/data-model'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ProxiesTableComponent } from './table.component'
|
import { ProxiesTableComponent } from './table.component'
|
||||||
@@ -40,11 +41,9 @@ export class SettingsProxiesComponent {
|
|||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
private readonly formDialog = inject(FormDialogService)
|
private readonly formDialog = inject(FormDialogService)
|
||||||
|
|
||||||
readonly proxies$ = inject<PatchDB<DataModel>>(PatchDB).watch$(
|
readonly proxies$: Observable<Proxy[]> = inject<PatchDB<DataModel>>(
|
||||||
'serverInfo',
|
PatchDB,
|
||||||
'network',
|
).watch$('serverInfo', 'network', 'proxies')
|
||||||
'proxies',
|
|
||||||
)
|
|
||||||
|
|
||||||
async add() {
|
async add() {
|
||||||
const options: Partial<TuiDialogOptions<FormContext<WireguardSpec>>> = {
|
const options: Partial<TuiDialogOptions<FormContext<WireguardSpec>>> = {
|
||||||
@@ -63,7 +62,8 @@ export class SettingsProxiesComponent {
|
|||||||
this.formDialog.open(FormComponent, options)
|
this.formDialog.open(FormComponent, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async save({ name, config }: WireguardSpec): Promise<boolean> {
|
// @TODO fix type to be WireguardSpec
|
||||||
|
private async save({ name, config }: any): Promise<boolean> {
|
||||||
const loader = this.loader.open('Saving...').subscribe()
|
const loader = this.loader.open('Saving...').subscribe()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
import { CB } from '@start9labs/start-sdk'
|
import { ISB } from '@start9labs/start-sdk'
|
||||||
import {
|
import {
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiDialogOptions,
|
TuiDialogOptions,
|
||||||
@@ -172,8 +172,8 @@ export class ProxiesTableComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async rename(proxy: Proxy) {
|
async rename(proxy: Proxy) {
|
||||||
const spec = { name: 'Name', required: { default: proxy.name } }
|
const spec = { name: 'Name', required: true, default: proxy.name }
|
||||||
const name = await CB.Value.text(spec).build({} as any)
|
const name = await ISB.Value.text(spec).build({} as any)
|
||||||
const options: Partial<TuiDialogOptions<FormContext<{ name: string }>>> = {
|
const options: Partial<TuiDialogOptions<FormContext<{ name: string }>>> = {
|
||||||
label: `Rename ${proxy.name}`,
|
label: `Rename ${proxy.name}`,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { TuiNotification } from '@taiga-ui/core'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'router-info',
|
selector: 'router-info',
|
||||||
template: `
|
template: `
|
||||||
<tui-notification [status]="enabled ? 'success' : 'warning'">
|
<tui-notification [appearance]="enabled ? 'positive' : 'warning'">
|
||||||
<ng-container *ngIf="enabled; else disabled">
|
<ng-container *ngIf="enabled; else disabled">
|
||||||
<strong>UPnP Enabled!</strong>
|
<strong>UPnP Enabled!</strong>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CT } from '@start9labs/start-sdk'
|
import { IST } from '@start9labs/start-sdk'
|
||||||
import { AvailableWifi } from 'src/app/services/api/api.types'
|
import { AvailableWifi } from 'src/app/services/api/api.types'
|
||||||
import { RR } from 'src/app/services/api/api.types'
|
import { RR } from 'src/app/services/api/api.types'
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export function parseWifi(res: RR.GetWifiRes): WifiData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const wifiSpec: CT.ValueSpecObject = {
|
export const wifiSpec: IST.ValueSpecObject = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
name: 'WiFi Credentials',
|
name: 'WiFi Credentials',
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export class SettingsWifiComponent {
|
|||||||
this.alerts
|
this.alerts
|
||||||
.open('Check credentials and try again', {
|
.open('Check credentials and try again', {
|
||||||
label: 'Failed to connect',
|
label: 'Failed to connect',
|
||||||
status: 'warning',
|
appearance: 'warning',
|
||||||
})
|
})
|
||||||
.subscribe()
|
.subscribe()
|
||||||
break
|
break
|
||||||
@@ -188,7 +188,7 @@ export class SettingsWifiComponent {
|
|||||||
if (newWifi.connected === ssid) {
|
if (newWifi.connected === ssid) {
|
||||||
this.update$.next(parseWifi(newWifi))
|
this.update$.next(parseWifi(newWifi))
|
||||||
this.alerts
|
this.alerts
|
||||||
.open('Connection successful!', { status: 'success' })
|
.open('Connection successful!', { appearance: 'positive' })
|
||||||
.subscribe()
|
.subscribe()
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CT } from '@start9labs/start-sdk'
|
import { IST } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
export const wifiSpec: CT.ValueSpecObject = {
|
export const wifiSpec: IST.ValueSpecObject = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
name: 'WiFi Credentials',
|
name: 'WiFi Credentials',
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CB } from '@start9labs/start-sdk'
|
import { ISB } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
export interface SettingBtn {
|
export interface SettingBtn {
|
||||||
title: string
|
title: string
|
||||||
@@ -8,26 +8,23 @@ export interface SettingBtn {
|
|||||||
routerLink?: string
|
routerLink?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const passwordSpec = CB.Config.of({
|
export const passwordSpec = ISB.InputSpec.of({
|
||||||
currentPassword: CB.Value.text({
|
currentPassword: ISB.Value.text({
|
||||||
name: 'Current Password',
|
name: 'Current Password',
|
||||||
required: {
|
required: true,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
|
||||||
masked: true,
|
masked: true,
|
||||||
}),
|
}),
|
||||||
newPassword1: CB.Value.text({
|
newPassword1: ISB.Value.text({
|
||||||
name: 'New Password',
|
name: 'New Password',
|
||||||
required: {
|
required: true,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
|
||||||
masked: true,
|
masked: true,
|
||||||
}),
|
}),
|
||||||
newPassword2: CB.Value.text({
|
newPassword2: ISB.Value.text({
|
||||||
name: 'Retype New Password',
|
name: 'Retype New Password',
|
||||||
required: {
|
required: true,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
|
||||||
masked: true,
|
masked: true,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export class SideloadPackageComponent {
|
|||||||
await this.router.navigate(['/portal/service', this.package.id])
|
await this.router.navigate(['/portal/service', this.package.id])
|
||||||
|
|
||||||
this.alerts
|
this.alerts
|
||||||
.open('Package uploaded successfully', { status: 'success' })
|
.open('Package uploaded successfully', { appearance: 'positive' })
|
||||||
.subscribe()
|
.subscribe()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { inject, Injectable } from '@angular/core'
|
||||||
import { AlertController } from '@ionic/angular'
|
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
import { TuiDialogService } from '@taiga-ui/core'
|
import { TuiDialogService } from '@taiga-ui/core'
|
||||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
import { TUI_CONFIRM } from '@taiga-ui/kit'
|
||||||
|
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
|
import { filter } from 'rxjs'
|
||||||
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
|
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
@@ -29,14 +30,11 @@ const allowedStatuses = {
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ActionService {
|
export class ActionService {
|
||||||
constructor(
|
private readonly api = inject(ApiService)
|
||||||
private readonly api: ApiService,
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
private readonly dialogs: TuiDialogService,
|
private readonly errorService = inject(ErrorService)
|
||||||
private readonly alertCtrl: AlertController,
|
private readonly loader = inject(LoadingService)
|
||||||
private readonly errorService: ErrorService,
|
private readonly formDialog = inject(FormDialogService)
|
||||||
private readonly loader: LoadingService,
|
|
||||||
private readonly formDialog: FormDialogService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async present(data: PackageActionData) {
|
async present(data: PackageActionData) {
|
||||||
const { pkgInfo, actionInfo } = data
|
const { pkgInfo, actionInfo } = data
|
||||||
@@ -53,25 +51,18 @@ export class ActionService {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (actionInfo.metadata.warning) {
|
if (actionInfo.metadata.warning) {
|
||||||
const alert = await this.alertCtrl.create({
|
this.dialogs
|
||||||
header: 'Warning',
|
.open(TUI_CONFIRM, {
|
||||||
message: actionInfo.metadata.warning,
|
label: 'Warning',
|
||||||
buttons: [
|
size: 's',
|
||||||
{
|
data: {
|
||||||
text: 'Cancel',
|
no: 'Cancel',
|
||||||
role: 'cancel',
|
yes: 'Run',
|
||||||
|
content: actionInfo.metadata.warning,
|
||||||
},
|
},
|
||||||
{
|
})
|
||||||
text: 'Run',
|
.pipe(filter(Boolean))
|
||||||
handler: () => {
|
.subscribe(() => this.execute(pkgInfo.id, actionInfo.id))
|
||||||
this.execute(pkgInfo.id, actionInfo.id)
|
|
||||||
},
|
|
||||||
cssClass: 'enter-click',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
cssClass: 'alert-warning-message',
|
|
||||||
})
|
|
||||||
await alert.present()
|
|
||||||
} else {
|
} else {
|
||||||
this.execute(pkgInfo.id, actionInfo.id)
|
this.execute(pkgInfo.id, actionInfo.id)
|
||||||
}
|
}
|
||||||
@@ -92,15 +83,18 @@ export class ActionService {
|
|||||||
} else {
|
} else {
|
||||||
error = `There is no status for which this action may be run. This is a bug. Please file an issue with the service maintainer.`
|
error = `There is no status for which this action may be run. This is a bug. Please file an issue with the service maintainer.`
|
||||||
}
|
}
|
||||||
const alert = await this.alertCtrl.create({
|
|
||||||
header: 'Forbidden',
|
this.dialogs
|
||||||
message:
|
.open(
|
||||||
error ||
|
error ||
|
||||||
`Action "${actionInfo.metadata.name}" can only be executed when service is ${statusesStr}`,
|
`Action "${actionInfo.metadata.name}" can only be executed when service is ${statusesStr}`,
|
||||||
buttons: ['OK'],
|
{
|
||||||
cssClass: 'alert-error-message enter-click',
|
label: 'Forbidden',
|
||||||
})
|
size: 's',
|
||||||
await alert.present()
|
},
|
||||||
|
)
|
||||||
|
.pipe(filter(Boolean))
|
||||||
|
.subscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
|
|||||||
import { TuiConfirmData, TUI_CONFIRM } from '@taiga-ui/kit'
|
import { TuiConfirmData, TUI_CONFIRM } from '@taiga-ui/kit'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { defaultIfEmpty, filter, firstValueFrom } from 'rxjs'
|
import { defaultIfEmpty, filter, firstValueFrom } from 'rxjs'
|
||||||
import {
|
// @TODO Alex implement config
|
||||||
ConfigModal,
|
// import {
|
||||||
PackageConfigData,
|
// ConfigModal,
|
||||||
} from 'src/app/routes/portal/modals/config.component'
|
// PackageConfigData,
|
||||||
|
// } from 'src/app/routes/portal/modals/config.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
@@ -27,10 +28,10 @@ export class ActionsService {
|
|||||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||||
|
|
||||||
configure(manifest: T.Manifest): void {
|
configure(manifest: T.Manifest): void {
|
||||||
this.formDialog.open<PackageConfigData>(ConfigModal, {
|
// this.formDialog.open<PackageConfigData>(ConfigModal, {
|
||||||
label: `${manifest.title} configuration`,
|
// label: `${manifest.title} configuration`,
|
||||||
data: { pkgId: manifest.id },
|
// data: { pkgId: manifest.id },
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(manifest: T.Manifest, unmet: boolean): Promise<void> {
|
async start(manifest: T.Manifest, unmet: boolean): Promise<void> {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
ServerStatusInfo,
|
ServerStatusInfo,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { RR, ServerNotifications } from './api.types'
|
import { RR, ServerMetrics, ServerNotifications } from './api.types'
|
||||||
import { BTC_ICON, LND_ICON, PROXY_ICON, REGISTRY_ICON } from './api-icons'
|
import { BTC_ICON, LND_ICON, PROXY_ICON, REGISTRY_ICON } from './api-icons'
|
||||||
import { Log } from '@start9labs/shared'
|
import { Log } from '@start9labs/shared'
|
||||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ import {
|
|||||||
DomainInfo,
|
DomainInfo,
|
||||||
NetworkStrategy,
|
NetworkStrategy,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { FetchLogsReq, FetchLogsRes } from '@start9labs/shared'
|
import { FetchLogsReq, FetchLogsRes, Log } from '@start9labs/shared'
|
||||||
import { config } from '@start9labs/start-sdk'
|
|
||||||
import { Dump } from 'patch-db-client'
|
import { Dump } from 'patch-db-client'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared'
|
import { StartOSDiskInfo } from '@start9labs/shared'
|
||||||
import { IST, T } from '@start9labs/start-sdk'
|
import { IST, T } from '@start9labs/start-sdk'
|
||||||
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
|
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
|
||||||
|
|
||||||
@@ -232,8 +231,7 @@ export module RR {
|
|||||||
|
|
||||||
// email
|
// email
|
||||||
|
|
||||||
export type ConfigureEmailReq =
|
export type ConfigureEmailReq = T.SmtpValue // email.configure
|
||||||
typeof config.constants.customSmtp.validator._TYPE // email.configure
|
|
||||||
export type ConfigureEmailRes = null
|
export type ConfigureEmailRes = null
|
||||||
|
|
||||||
export type TestEmailReq = ConfigureEmailReq & { to: string } // email.test
|
export type TestEmailReq = ConfigureEmailReq & { to: string } // email.test
|
||||||
@@ -328,9 +326,18 @@ export module RR {
|
|||||||
export type CreateBackupRes = null
|
export type CreateBackupRes = null
|
||||||
|
|
||||||
// package
|
// package
|
||||||
|
// @TODO Matt I just copy-pasted those types from minor
|
||||||
export type GetPackageLogsReq = ServerLogsReq & { id: string } // package.logs
|
export type GetPackageLogsReq = {
|
||||||
export type GetPackageLogsRes = LogsRes
|
id: string
|
||||||
|
before: boolean
|
||||||
|
cursor?: string
|
||||||
|
limit?: number
|
||||||
|
} // package.logs
|
||||||
|
export type GetPackageLogsRes = {
|
||||||
|
entries: Log[]
|
||||||
|
startCursor?: string
|
||||||
|
endCursor?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow
|
export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow
|
||||||
export type FollowPackageLogsRes = FollowServerLogsRes
|
export type FollowPackageLogsRes = FollowServerLogsRes
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ function listValidators(spec: IST.ValueSpecList): ValidatorFn[] {
|
|||||||
return validators
|
return validators
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileValidators(spec: CT.ValueSpecFile): ValidatorFn[] {
|
function fileValidators(spec: IST.ValueSpecFile): ValidatorFn[] {
|
||||||
const validators: ValidatorFn[] = []
|
const validators: ValidatorFn[] = []
|
||||||
|
|
||||||
if (spec.required) {
|
if (spec.required) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { BackupJob, ServerNotifications } from '../api/api.types'
|
import { BackupJob, ServerNotifications } from '../api/api.types'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
import { config } from '@start9labs/start-sdk'
|
|
||||||
|
|
||||||
export type DataModel = {
|
export type DataModel = {
|
||||||
ui: UIData
|
ui: UIData
|
||||||
@@ -51,7 +50,7 @@ export type ServerInfo = {
|
|||||||
pubkey: string
|
pubkey: string
|
||||||
caFingerprint: string
|
caFingerprint: string
|
||||||
ntpSynced: boolean
|
ntpSynced: boolean
|
||||||
smtp: typeof config.constants.customSmtp.validator._TYPE
|
smtp: T.SmtpValue | null
|
||||||
passwordHash: string
|
passwordHash: string
|
||||||
platform: string
|
platform: string
|
||||||
arch: string
|
arch: string
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { FormDialogService } from 'src/app/services/form-dialog.service'
|
|||||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||||
import { ApiService } from './api/embassy-api.service'
|
import { ApiService } from './api/embassy-api.service'
|
||||||
import { DataModel } from './patch-db/data-model'
|
import { DataModel } from './patch-db/data-model'
|
||||||
import { CB } from '@start9labs/start-sdk'
|
import { ISB } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -29,18 +29,19 @@ export class ProxyService {
|
|||||||
const network = await firstValueFrom(
|
const network = await firstValueFrom(
|
||||||
this.patch.watch$('serverInfo', 'network'),
|
this.patch.watch$('serverInfo', 'network'),
|
||||||
)
|
)
|
||||||
const config = CB.Config.of({
|
const config = ISB.InputSpec.of({
|
||||||
proxyId: CB.Value.select({
|
proxyId: ISB.Value.select({
|
||||||
name: 'Select Proxy',
|
name: 'Select Proxy',
|
||||||
required: { default: current },
|
default: current || '',
|
||||||
values: network.proxies
|
values: network.proxies
|
||||||
.filter(p => p.type === 'outbound' || p.type === 'inbound-outbound')
|
.filter(p => p.type === 'outbound' || p.type === 'inbound-outbound')
|
||||||
.reduce((prev, curr) => {
|
.reduce<Record<string, string>>(
|
||||||
return {
|
(prev, curr) => ({
|
||||||
[curr.id]: curr.name,
|
[curr.id]: curr.name,
|
||||||
...prev,
|
...prev,
|
||||||
}
|
}),
|
||||||
}, {}),
|
{},
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export class StateService extends Observable<RR.ServerState | null> {
|
|||||||
.open('Trying to reach server', {
|
.open('Trying to reach server', {
|
||||||
label: 'State unknown',
|
label: 'State unknown',
|
||||||
autoClose: 0,
|
autoClose: 0,
|
||||||
status: 'error',
|
appearance: 'negative',
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(
|
takeUntil(
|
||||||
@@ -106,7 +106,7 @@ export class StateService extends Observable<RR.ServerState | null> {
|
|||||||
),
|
),
|
||||||
this.alerts.open('Connection restored', {
|
this.alerts.open('Connection restored', {
|
||||||
label: 'Server reached',
|
label: 'Server reached',
|
||||||
status: 'success',
|
appearance: 'positive',
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user