diff --git a/web/projects/ui/src/app/app.component.html b/web/projects/ui/src/app/app.component.html index 0742fad74..f26e0cee2 100644 --- a/web/projects/ui/src/app/app.component.html +++ b/web/projects/ui/src/app/app.component.html @@ -53,7 +53,6 @@ (click)="onResize(drawer)" > - + Add clearnet to expose this interface to the public Internet. + + View instructions + + + + +
+ + +
+
+ + + + `, + imports: [InterfaceComponent, NgIf, TuiButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InterfaceClearnetComponent { + private readonly formDialog = inject(FormDialogService) + private readonly loader = inject(LoadingService) + private readonly errorService = inject(ErrorService) + private readonly api = inject(ApiService) + private readonly dialogs = inject(TuiDialogService) + readonly interfaces = inject(InterfacesComponent) + + @Input({ required: true }) network!: NetworkInfo + + getClearnet(clearnet: DomainInfo): string { + return getClearnetAddress('https', clearnet) + } + + async add() { + const { domainInfo } = this.interfaces.addressInfo + const { domain = '', subdomain = '' } = domainInfo || {} + const options: Partial>> = { + label: 'Select Domain/Subdomain', + data: { + value: { domain, subdomain }, + spec: await getClearnetSpec(this.network), + buttons: [ + { + text: 'Manage domains', + link: 'portal/system/settings/domains', + }, + { + text: 'Save', + handler: async value => this.save(value), + }, + ], + }, + } + this.formDialog.open(FormComponent, options) + } + + remove() { + this.dialogs + .open(TUI_PROMPT, REMOVE) + .pipe(filter(Boolean)) + .subscribe(async () => { + const loader = this.loader.open('Removing...').subscribe() + + try { + if (this.interfaces.packageContext) { + await this.api.setInterfaceClearnetAddress({ + ...this.interfaces.packageContext, + domainInfo: null, + }) + } else { + await this.api.setServerClearnetAddress({ domainInfo: null }) + } + } catch (e: any) { + this.errorService.handleError(e) + } finally { + loader.unsubscribe() + } + }) + } + + private async save(domainInfo: ClearnetForm): Promise { + const loader = this.loader.open('Saving...').subscribe() + + try { + if (this.interfaces.packageContext) { + await this.api.setInterfaceClearnetAddress({ + ...this.interfaces.packageContext, + domainInfo, + }) + } else { + await this.api.setServerClearnetAddress({ domainInfo }) + } + return true + } catch (e: any) { + this.errorService.handleError(e) + return false + } finally { + loader.unsubscribe() + } + } +} diff --git a/web/projects/ui/src/app/apps/portal/components/interfaces/interface-local.component.ts b/web/projects/ui/src/app/apps/portal/components/interfaces/interface-local.component.ts new file mode 100644 index 000000000..73a21a1d5 --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/components/interfaces/interface-local.component.ts @@ -0,0 +1,59 @@ +import { CommonModule } from '@angular/common' +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { TuiButtonModule } from '@taiga-ui/experimental' +import { InterfacesComponent } from './interfaces.component' +import { InterfaceComponent } from './interface.component' + +@Component({ + standalone: true, + selector: 'app-interface-local', + template: ` + + Local addresses can only be accessed while connected to the same Local + Area Network (LAN) as your server, either directly or using a VPN. + + View instructions + + + + Download Root CA + + + + + + + `, + imports: [InterfaceComponent, CommonModule, TuiButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InterfaceLocalComponent { + readonly interfaces = inject(InterfacesComponent) +} diff --git a/web/projects/ui/src/app/apps/portal/components/interfaces/interface-tor.component.ts b/web/projects/ui/src/app/apps/portal/components/interfaces/interface-tor.component.ts new file mode 100644 index 000000000..a15f93e2a --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/components/interfaces/interface-tor.component.ts @@ -0,0 +1,31 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { InterfaceComponent } from './interface.component' +import { InterfacesComponent } from './interfaces.component' + +@Component({ + standalone: true, + selector: 'app-interface-tor', + template: ` + + Use a Tor-enabled browser to access this address. Tor connections can be + slow and unreliable. + + View instructions + + + + `, + imports: [InterfaceComponent], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InterfaceTorComponent { + readonly interfaces = inject(InterfacesComponent) +} diff --git a/web/projects/ui/src/app/apps/portal/components/interfaces/interface.component.ts b/web/projects/ui/src/app/apps/portal/components/interfaces/interface.component.ts new file mode 100644 index 000000000..33a2d5f62 --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/components/interfaces/interface.component.ts @@ -0,0 +1,79 @@ +import { NgIf } from '@angular/common' +import { + ChangeDetectionStrategy, + Component, + inject, + Input, +} from '@angular/core' +import { WINDOW } from '@ng-web-apis/common' +import { CopyService } from '@start9labs/shared' +import { TuiDialogService } from '@taiga-ui/core' +import { + TuiButtonModule, + TuiCellModule, + TuiTitleModule, +} from '@taiga-ui/experimental' +import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' +import { QRComponent } from 'src/app/common/qr/qr.component' + +@Component({ + standalone: true, + selector: 'app-interface', + template: ` +
+

+ {{ label }} + {{ hostname }} +

+ + + +
+ `, + imports: [NgIf, TuiCellModule, TuiTitleModule, TuiButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InterfaceComponent { + private readonly window = inject(WINDOW) + private readonly dialogs = inject(TuiDialogService) + readonly copyService = inject(CopyService) + + @Input({ required: true }) label = '' + @Input({ required: true }) hostname = '' + @Input({ required: true }) isUi = false + + launch(url: string): void { + this.window.open(url, '_blank', 'noreferrer') + } + + showQR(data: string) { + this.dialogs + .open(new PolymorpheusComponent(QRComponent), { + size: 'auto', + data, + }) + .subscribe() + } +} diff --git a/web/projects/ui/src/app/apps/portal/components/interfaces/interface.utils.ts b/web/projects/ui/src/app/apps/portal/components/interfaces/interface.utils.ts new file mode 100644 index 000000000..461b1a984 --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/components/interfaces/interface.utils.ts @@ -0,0 +1,46 @@ +import { Config } from '@start9labs/start-sdk/lib/config/builder/config' +import { Value } from '@start9labs/start-sdk/lib/config/builder/value' +import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes' +import { TuiDialogOptions } from '@taiga-ui/core' +import { TuiPromptData } from '@taiga-ui/kit' +import { NetworkInfo } from 'src/app/services/patch-db/data-model' +import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' + +export const REMOVE: Partial> = { + label: 'Confirm', + size: 's', + data: { + content: 'Remove clearnet address?', + yes: 'Remove', + no: 'Cancel', + }, +} + +export function getClearnetSpec({ + domains, + start9ToSubdomain, +}: NetworkInfo): Promise { + const start9ToDomain = `${start9ToSubdomain?.value}.start9.to` + const base = start9ToSubdomain ? { [start9ToDomain]: start9ToDomain } : {} + + const values = domains.reduce((prev, curr) => { + return { + [curr.value]: curr.value, + ...prev, + } + }, base) + + return configBuilderToSpec( + Config.of({ + domain: Value.select({ + name: 'Domain', + required: { default: null }, + values, + }), + subdomain: Value.text({ + name: 'Subdomain', + required: false, + }), + }), + ) +} diff --git a/web/projects/ui/src/app/apps/portal/components/interfaces/interfaces.component.ts b/web/projects/ui/src/app/apps/portal/components/interfaces/interfaces.component.ts new file mode 100644 index 000000000..635f62486 --- /dev/null +++ b/web/projects/ui/src/app/apps/portal/components/interfaces/interfaces.component.ts @@ -0,0 +1,55 @@ +import { CommonModule } from '@angular/common' +import { + ChangeDetectionStrategy, + Component, + inject, + Input, +} from '@angular/core' +import { TuiCardModule, TuiSurfaceModule } from '@taiga-ui/experimental' +import { PatchDB } from 'patch-db-client' +import { InterfaceClearnetComponent } from 'src/app/apps/portal/components/interfaces/interface-clearnet.component' +import { InterfaceLocalComponent } from 'src/app/apps/portal/components/interfaces/interface-local.component' +import { InterfaceTorComponent } from 'src/app/apps/portal/components/interfaces/interface-tor.component' +import { AddressInfo, DataModel } from 'src/app/services/patch-db/data-model' + +@Component({ + standalone: true, + selector: 'app-interfaces', + template: ` +

Clearnet

+ + +

Tor

+ + +

Local

+ + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + InterfaceTorComponent, + InterfaceLocalComponent, + InterfaceClearnetComponent, + TuiCardModule, + TuiSurfaceModule, + ], +}) +export class InterfacesComponent { + readonly network$ = inject(PatchDB).watch$( + 'server-info', + 'network', + ) + + @Input() packageContext?: { + packageId: string + interfaceId: string + } + @Input({ required: true }) addressInfo!: AddressInfo + @Input({ required: true }) isUi!: boolean +} diff --git a/web/projects/ui/src/app/apps/portal/routes/service/routes/interface.component.ts b/web/projects/ui/src/app/apps/portal/routes/service/routes/interface.component.ts index e49c0e01b..1212d4875 100644 --- a/web/projects/ui/src/app/apps/portal/routes/service/routes/interface.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/service/routes/interface.component.ts @@ -1,23 +1,23 @@ import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { InterfaceAddressesComponentModule } from 'src/app/common/interface-addresses/interface-addresses.module' -import { DataModel } from 'src/app/services/patch-db/data-model' import { ActivatedRoute } from '@angular/router' import { getPkgId } from '@start9labs/shared' +import { PatchDB } from 'patch-db-client' +import { InterfacesComponent } from 'src/app/apps/portal/components/interfaces/interfaces.component' +import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ template: ` - + /> `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [CommonModule, InterfaceAddressesComponentModule], + imports: [CommonModule, InterfacesComponent], }) export class ServiceInterfaceRoute { private readonly route = inject(ActivatedRoute) diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/interfaces/interfaces.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/interfaces/interfaces.component.ts index e2595a4d1..53c4d4853 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/interfaces/interfaces.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/interfaces/interfaces.component.ts @@ -1,21 +1,21 @@ import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { PatchDB } from 'patch-db-client' -import { InterfaceAddressesComponentModule } from 'src/app/common/interface-addresses/interface-addresses.module' +import { InterfacesComponent } from 'src/app/apps/portal/components/interfaces/interfaces.component' import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ template: ` - + /> `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [CommonModule, InterfaceAddressesComponentModule], + imports: [CommonModule, InterfacesComponent], }) export class SettingsInterfacesComponent { readonly ui$ = inject(PatchDB).watch$('server-info', 'ui') diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts index 68c8ef5ef..fad13a89e 100644 --- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts +++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts @@ -24,18 +24,18 @@ import { TuiToggleModule, } from '@taiga-ui/experimental' import { PatchDB } from 'patch-db-client' -import { catchError, defer, merge, Observable, of, Subject, map } from 'rxjs' +import { catchError, defer, map, merge, Observable, of, Subject } from 'rxjs' import { FormComponent, FormContext, } from 'src/app/apps/portal/components/form.component' import { ApiService } from 'src/app/services/api/embassy-api.service' +import { FormDialogService } from 'src/app/services/form-dialog.service' import { DataModel } from 'src/app/services/patch-db/data-model' import { WifiInfoComponent } from './info.component' import { WifiTableComponent } from './table.component' import { parseWifi, WifiData, WiFiForm } from './utils' -import { wifiSpec } from '../../../../../../ui/pages/system/wifi/wifi.const' -import { FormDialogService } from 'src/app/services/form-dialog.service' +import { wifiSpec } from './wifi.const' @Component({ template: ` diff --git a/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.const.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.const.ts similarity index 100% rename from web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.const.ts rename to web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.const.ts diff --git a/web/projects/ui/src/app/apps/ui/modals/backup-report/backup-report.component.html b/web/projects/ui/src/app/apps/ui/modals/backup-report/backup-report.component.html deleted file mode 100644 index e0dac9074..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/backup-report/backup-report.component.html +++ /dev/null @@ -1,32 +0,0 @@ - - - Completed: {{ timestamp | date : 'medium' }} - - - -

System data

-

- {{ system.result }} -

-
- -
- - -

{{ pkg.key }}

-

- - {{ pkg.value.error ? 'Failed: ' + pkg.value.error : 'Succeeded' }} - -

-
- -
-
diff --git a/web/projects/ui/src/app/apps/ui/modals/backup-report/backup-report.component.ts b/web/projects/ui/src/app/apps/ui/modals/backup-report/backup-report.component.ts deleted file mode 100644 index bbf0ceff4..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/backup-report/backup-report.component.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Component, Inject } from '@angular/core' -import { BackupReport } from 'src/app/services/api/api.types' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' - -@Component({ - selector: 'backup-report', - templateUrl: './backup-report.component.html', -}) -export class BackupReportComponent { - readonly system: { - result: string - icon: 'remove' | 'remove-circle-outline' | 'checkmark' - color: 'dark' | 'danger' | 'success' - } - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext< - void, - { report: BackupReport; timestamp: string } - >, - ) { - if (!this.report.server.attempted) { - this.system = { - result: 'Not Attempted', - icon: 'remove', - color: 'dark', - } - } else if (this.report.server.error) { - this.system = { - result: `Failed: ${this.report.server.error}`, - icon: 'remove-circle-outline', - color: 'danger', - } - } else { - this.system = { - result: 'Succeeded', - icon: 'checkmark', - color: 'success', - } - } - } - - get report(): BackupReport { - return this.context.data.report - } - - get timestamp(): string { - return this.context.data.timestamp - } -} diff --git a/web/projects/ui/src/app/apps/ui/modals/backup-report/backup-report.module.ts b/web/projects/ui/src/app/apps/ui/modals/backup-report/backup-report.module.ts deleted file mode 100644 index a41a63e53..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/backup-report/backup-report.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { BackupReportComponent } from './backup-report.component' - -@NgModule({ - declarations: [BackupReportComponent], - imports: [CommonModule, IonicModule], - exports: [BackupReportComponent], -}) -export class BackupReportPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/modals/form/form.module.ts b/web/projects/ui/src/app/apps/ui/modals/form/form.module.ts deleted file mode 100644 index 4b7009137..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/form/form.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { ReactiveFormsModule } from '@angular/forms' -import { RouterModule } from '@angular/router' -import { TuiValueChangesModule } from '@taiga-ui/cdk' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TuiModeModule } from '@taiga-ui/core' -import { FormModule } from 'src/app/common/form/form.module' -import { FormPage } from './form.page' - -@NgModule({ - imports: [ - CommonModule, - ReactiveFormsModule, - RouterModule, - TuiValueChangesModule, - TuiButtonModule, - TuiModeModule, - FormModule, - ], - declarations: [FormPage], - exports: [FormPage], -}) -export class FormPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/modals/form/form.page.html b/web/projects/ui/src/app/apps/ui/modals/form/form.page.html deleted file mode 100644 index 3bd7567a9..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/form/form.page.html +++ /dev/null @@ -1,32 +0,0 @@ -
- - -
diff --git a/web/projects/ui/src/app/apps/ui/modals/form/form.page.scss b/web/projects/ui/src/app/apps/ui/modals/form/form.page.scss deleted file mode 100644 index fc2a2b19d..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/form/form.page.scss +++ /dev/null @@ -1,12 +0,0 @@ -footer { - position: sticky; - bottom: 0; - z-index: 10; - display: flex; - justify-content: flex-end; - padding: 1rem 0; - margin: 1rem 0 -1rem; - gap: 1rem; - background: var(--tui-elevation-01); - border-top: 1px solid var(--tui-base-02); -} diff --git a/web/projects/ui/src/app/apps/ui/modals/form/form.page.ts b/web/projects/ui/src/app/apps/ui/modals/form/form.page.ts deleted file mode 100644 index f7fcb1def..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/form/form.page.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - Input, - OnInit, -} from '@angular/core' -import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' -import { tuiMarkControlAsTouchedAndValidate } from '@taiga-ui/cdk' -import { TuiDialogFormService } from '@taiga-ui/kit' -import { FormGroup } from '@angular/forms' -import { compare, Operation } from 'fast-json-patch' -import { InvalidService } from 'src/app/common/form/invalid.service' -import { FormService } from 'src/app/services/form.service' - -export interface ActionButton { - text: string - handler?: (value: T) => Promise | void - link?: string -} - -export interface FormContext { - spec: InputSpec - buttons: ActionButton[] - value?: T - patch?: Operation[] -} - -@Component({ - selector: 'form-page', - templateUrl: './form.page.html', - styleUrls: ['./form.page.scss'], - providers: [InvalidService], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class FormPage> implements OnInit { - private readonly dialogFormService = inject(TuiDialogFormService) - private readonly formService = inject(FormService) - private readonly invalidService = inject(InvalidService) - private readonly context = inject>>( - POLYMORPHEUS_CONTEXT, - { optional: true }, - ) - - @Input() spec = this.context?.data.spec || {} - @Input() buttons = this.context?.data.buttons || [] - @Input() patch = this.context?.data.patch || [] - @Input() value?: T = this.context?.data.value - - form = new FormGroup({}) - - ngOnInit() { - this.dialogFormService.markAsPristine() - this.form = this.formService.createForm(this.spec, this.value) - this.process(this.patch) - } - - onReset() { - const { value } = this.form - - this.form = this.formService.createForm(this.spec) - this.process(compare(this.form.value, value)) - tuiMarkControlAsTouchedAndValidate(this.form) - this.markAsDirty() - } - - async onClick(handler: Required>['handler']) { - tuiMarkControlAsTouchedAndValidate(this.form) - this.invalidService.scrollIntoView() - - if (this.form.valid && (await handler(this.form.value as T))) { - this.close() - } - } - - markAsDirty() { - this.dialogFormService.markAsDirty() - } - - close() { - this.context?.$implicit.complete() - } - - private process(patch: Operation[]) { - patch.forEach(({ op, path }) => { - const control = this.form.get(path.substring(1).split('/')) - - if (!control || !control.parent) return - - if (op !== 'remove') { - control.markAsDirty() - control.markAsTouched() - } - - control.parent.markAsDirty() - control.parent.markAsTouched() - }) - } -} diff --git a/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.component.html b/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.component.html deleted file mode 100644 index daf261009..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.component.html +++ /dev/null @@ -1,40 +0,0 @@ -

{{ options.message }}

-

{{ options.warning }}

-
- - {{ options.label }} - * - - -
- - -
-
- - - - diff --git a/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.component.scss b/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.component.scss deleted file mode 100644 index d95d85925..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.component.scss +++ /dev/null @@ -1,13 +0,0 @@ -.warning { - color: var(--tui-warning-fill); -} - -.button { - pointer-events: auto; - margin-left: 0.25rem; -} - -.masked { - font-family: text-security-disc; - -webkit-text-security: disc; -} diff --git a/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.component.ts b/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.component.ts deleted file mode 100644 index 9842afe02..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' -import { - POLYMORPHEUS_CONTEXT, - PolymorpheusComponent, -} from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' - -@Component({ - selector: 'prompt', - templateUrl: 'prompt.component.html', - styleUrls: ['prompt.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class PromptComponent { - masked = this.options.useMask - value = this.options.initialValue || '' - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext, - ) {} - - get options(): PromptOptions { - return this.context.data - } - - cancel() { - this.context.$implicit.complete() - } - - submit(value: string) { - if (value || !this.options.required) { - this.context.$implicit.next(value) - } - } -} - -export const PROMPT = new PolymorpheusComponent(PromptComponent) - -export interface PromptOptions { - message: string - label?: string - warning?: string - buttonText?: string - placeholder?: string - required?: boolean - useMask?: boolean - initialValue?: string | null -} diff --git a/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.module.ts b/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.module.ts deleted file mode 100644 index d872cb565..000000000 --- a/web/projects/ui/src/app/apps/ui/modals/prompt/prompt.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { FormsModule } from '@angular/forms' -import { TuiTextfieldControllerModule } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TuiInputModule } from '@taiga-ui/kit' -import { TuiAutoFocusModule } from '@taiga-ui/cdk' -import { PromptComponent } from './prompt.component' - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - TuiInputModule, - TuiButtonModule, - TuiTextfieldControllerModule, - TuiAutoFocusModule, - ], - declarations: [PromptComponent], - exports: [PromptComponent], -}) -export class PromptModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/backups.module.ts b/web/projects/ui/src/app/apps/ui/pages/backups/backups.module.ts deleted file mode 100644 index bcb252335..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/backups.module.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NgModule } from '@angular/core' -import { Routes, RouterModule } from '@angular/router' - -const routes: Routes = [ - { - path: '', - loadChildren: () => - import('./pages/backups/backups.module').then(m => m.BackupsPageModule), - }, - { - path: 'jobs', - loadChildren: () => - import('./pages/backup-jobs/backup-jobs.module').then( - m => m.BackupJobsPageModule, - ), - }, - { - path: 'targets', - loadChildren: () => - import('./pages/backup-targets/backup-targets.module').then( - m => m.BackupTargetsPageModule, - ), - }, - { - path: 'history', - loadChildren: () => - import('./pages/backup-history/backup-history.module').then( - m => m.BackupHistoryPageModule, - ), - }, -] - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class BackupsModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/components/backing-up/backing-up.component.html b/web/projects/ui/src/app/apps/ui/pages/backups/components/backing-up/backing-up.component.html deleted file mode 100644 index 153de04c0..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/components/backing-up/backing-up.component.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - Backup Progress - - - - - - - - - - - - - - {{ pkg.value.manifest.title }} - - - -   - Complete - - - - - - - - Backing up - - - - Waiting... - - - - - - - - - - diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/components/backing-up/backing-up.component.ts b/web/projects/ui/src/app/apps/ui/pages/backups/components/backing-up/backing-up.component.ts deleted file mode 100644 index 4e297019e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/components/backing-up/backing-up.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Pipe, - PipeTransform, -} from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { take, Observable } from 'rxjs' -import { - DataModel, - PackageMainStatus, -} from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'backing-up', - templateUrl: './backing-up.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class BackingUpComponent { - readonly pkgs$ = this.patch.watch$('package-data').pipe(take(1)) - readonly backupProgress$ = this.patch.watch$( - 'server-info', - 'status-info', - 'current-backup', - 'backup-progress', - ) - - constructor(private readonly patch: PatchDB) {} -} - -@Pipe({ - name: 'pkgMainStatus', -}) -export class PkgMainStatusPipe implements PipeTransform { - transform(pkgId: string): Observable { - return this.patch.watch$( - 'package-data', - pkgId, - 'installed', - 'status', - 'main', - 'status', - ) - } - - constructor(private readonly patch: PatchDB) {} -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/directives/backup-create.directive.ts b/web/projects/ui/src/app/apps/ui/pages/backups/directives/backup-create.directive.ts deleted file mode 100644 index 91769e709..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/directives/backup-create.directive.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Directive, HostListener } from '@angular/core' -import { LoadingService } from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { BackupTarget } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { TargetSelectPage } from '../modals/target-select/target-select.page' -import { BackupSelectPage } from '../modals/backup-select/backup-select.page' - -@Directive({ - selector: '[backupCreate]', -}) -export class BackupCreateDirective { - constructor( - private readonly loader: LoadingService, - private readonly dialogs: TuiDialogService, - private readonly embassyApi: ApiService, - ) {} - - @HostListener('click') - onClick() { - this.presentModalTarget() - } - - presentModalTarget() { - this.dialogs - .open(new PolymorpheusComponent(TargetSelectPage), { - label: 'Select Backup Target', - data: { type: 'create' }, - }) - .subscribe(({ id }) => { - this.presentModalSelect(id) - }) - } - - private presentModalSelect(targetId: string) { - this.dialogs - .open(new PolymorpheusComponent(BackupSelectPage), { - label: 'Select Services to Back Up', - data: { btnText: 'Create Backup' }, - }) - .subscribe(pkgIds => { - this.createBackup(targetId, pkgIds) - }) - } - - private async createBackup( - targetId: string, - pkgIds: string[], - ): Promise { - const loader = this.loader.open('Beginning backup...').subscribe() - - await this.embassyApi - .createBackup({ - 'target-id': targetId, - 'package-ids': pkgIds, - }) - .finally(() => loader.unsubscribe()) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/directives/backup-restore.directive.ts b/web/projects/ui/src/app/apps/ui/pages/backups/directives/backup-restore.directive.ts deleted file mode 100644 index b762c6fe0..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/directives/backup-restore.directive.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Directive, HostListener } from '@angular/core' -import { NavController } from '@ionic/angular' -import { TuiDialogService } from '@taiga-ui/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { BackupInfo, BackupTarget } from 'src/app/services/api/api.types' -import * as argon2 from '@start9labs/argon2' -import { TargetSelectPage } from '../modals/target-select/target-select.page' -import { - RecoverData, - RecoverSelectPage, -} from '../modals/recover-select/recover-select.page' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { - PROMPT, - PromptOptions, -} from 'src/app/apps/ui/modals/prompt/prompt.component' -import { - catchError, - EMPTY, - exhaustMap, - map, - Observable, - of, - switchMap, - take, - tap, -} from 'rxjs' - -@Directive({ - selector: '[backupRestore]', -}) -export class BackupRestoreDirective { - constructor( - private readonly errorService: ErrorService, - private readonly dialogs: TuiDialogService, - private readonly navCtrl: NavController, - private readonly embassyApi: ApiService, - private readonly loader: LoadingService, - ) {} - - @HostListener('click') onClick() { - this.presentModalTarget() - } - - async presentModalTarget() { - this.dialogs - .open(new PolymorpheusComponent(TargetSelectPage), { - label: 'Select Backup Source', - data: { type: 'restore' }, - }) - .subscribe(data => { - this.presentModalPassword(data) - }) - } - - presentModalPassword(target: BackupTarget) { - const data: PromptOptions = { - message: - 'Enter the master password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.', - label: 'Master Password', - placeholder: 'Enter master password', - useMask: true, - } - - this.dialogs - .open(PROMPT, { - label: 'Password Required', - data, - }) - .pipe( - exhaustMap(password => - this.getRecoverData( - target.id, - password, - target['embassy-os']?.['password-hash'] || '', - ), - ), - take(1), - switchMap(data => this.presentModalSelect(data)), - ) - .subscribe(() => { - this.navCtrl.navigateRoot('/services') - }) - } - - private getRecoverData( - targetId: string, - password: string, - hash: string, - ): Observable { - return of(password).pipe( - tap(() => argon2.verify(hash, password)), - switchMap(() => this.getBackupInfo(targetId, password)), - catchError(e => { - this.errorService.handleError(e) - - return EMPTY - }), - map(backupInfo => ({ targetId, password, backupInfo })), - ) - } - - private async getBackupInfo( - targetId: string, - password: string, - ): Promise { - const loader = this.loader.open('Decrypting drive...').subscribe() - - return this.embassyApi - .getBackupInfo({ - 'target-id': targetId, - password, - }) - .finally(() => loader.unsubscribe()) - } - - private presentModalSelect(data: RecoverData): Observable { - return this.dialogs.open(new PolymorpheusComponent(RecoverSelectPage), { - label: 'Select Services to Restore', - data, - }) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.module.ts b/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.module.ts deleted file mode 100644 index 4d5d6dbcd..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { FormsModule } from '@angular/forms' -import { TuiGroupModule } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TuiCheckboxBlockModule } from '@taiga-ui/kit' -import { BackupSelectPage } from './backup-select.page' - -@NgModule({ - declarations: [BackupSelectPage], - imports: [ - CommonModule, - FormsModule, - TuiButtonModule, - TuiGroupModule, - TuiCheckboxBlockModule, - ], - exports: [BackupSelectPage], -}) -export class BackupSelectPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.page.html b/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.page.html deleted file mode 100644 index f79a9d0f8..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.page.html +++ /dev/null @@ -1,31 +0,0 @@ -
- -
- - {{ pkg.title }} -
-
-
- - -

No services installed!

-
- -
- - -
diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.page.scss b/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.page.scss deleted file mode 100644 index 89ba0a7aa..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.page.scss +++ /dev/null @@ -1,25 +0,0 @@ -.center { - display: flex; - align-items: center; - justify-content: center; -} - -.pkgs { - width: 100%; - margin-top: 24px; -} - -.label { - display: flex; - align-items: center; - gap: 16px; -} - -.icon { - width: 40px; - height: 40px; -} - -ion-item { - --background: transparent; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.page.ts b/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.page.ts deleted file mode 100644 index f21a5ca7f..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/backup-select/backup-select.page.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Component, Inject, Input } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { firstValueFrom, map } from 'rxjs' -import { DataModel, PackageState } from 'src/app/services/patch-db/data-model' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' - -@Component({ - selector: 'backup-select', - templateUrl: './backup-select.page.html', - styleUrls: ['./backup-select.page.scss'], -}) -export class BackupSelectPage { - @Input() selectedIds: string[] = [] - - hasSelection = false - pkgs: { - id: string - title: string - icon: string - disabled: boolean - checked: boolean - }[] = [] - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext, - private readonly patch: PatchDB, - ) {} - - get btnText(): string { - return this.context.data.btnText - } - - async ngOnInit() { - this.pkgs = await firstValueFrom( - this.patch.watch$('package-data').pipe( - map(pkgs => { - return Object.values(pkgs) - .map(pkg => { - const { id, title } = pkg.manifest - return { - id, - title, - icon: pkg.icon, - disabled: pkg.state !== PackageState.Installed, - checked: this.selectedIds.includes(id), - } - }) - .sort((a, b) => - b.title.toLowerCase() > a.title.toLowerCase() ? -1 : 1, - ) - }), - ), - ) - } - - done() { - this.context.completeWith(this.pkgs.filter(p => p.checked).map(p => p.id)) - } - - handleChange() { - this.hasSelection = this.pkgs.some(p => p.checked) - } - - toggleSelectAll() { - this.pkgs.forEach(pkg => (pkg.checked = !this.hasSelection)) - this.hasSelection = !this.hasSelection - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.module.ts b/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.module.ts deleted file mode 100644 index ab1eabf6b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.module.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { FormsModule } from '@angular/forms' -import { TuiGroupModule } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TuiCheckboxBlockModule } from '@taiga-ui/kit' -import { RecoverSelectPage } from './recover-select.page' -import { ToOptionsPipe } from './to-options.pipe' - -@NgModule({ - declarations: [RecoverSelectPage, ToOptionsPipe], - imports: [ - CommonModule, - FormsModule, - TuiButtonModule, - TuiGroupModule, - TuiCheckboxBlockModule, - ], - exports: [RecoverSelectPage], -}) -export class RecoverSelectPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.page.html b/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.page.html deleted file mode 100644 index 8fd2e77ce..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.page.html +++ /dev/null @@ -1,36 +0,0 @@ - -
- -
- {{ option.title }} -
Version {{ option.version }}
-
Backup made: {{ option.timestamp | date : 'medium' }}
-
- Ready to restore -
-
- Unavailable. {{ option.title }} is already installed. -
-
- Unavailable. Backup was made on a newer version of StartOS. -
-
-
-
- - -
diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.page.scss b/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.page.scss deleted file mode 100644 index 4897866d3..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.page.scss +++ /dev/null @@ -1,31 +0,0 @@ -.items { - width: 100%; - margin: 12px 0 24px; -} - -.label { - padding: 8px 0; - font-size: 14px; -} - -.title { - font-size: 16px; - margin-bottom: 4px; - display: block; -} - -.success { - color: var(--tui-success-fill); -} - -.warning { - color: var(--tui-warning-fill); -} - -.danger { - color: var(--tui-error-fill); -} - -.button { - float: right; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.page.ts b/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.page.ts deleted file mode 100644 index 5052d9eb9..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/recover-select.page.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Component, Inject } from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' -import { BackupInfo } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { PatchDB } from 'patch-db-client' -import { AppRecoverOption } from './to-options.pipe' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { take } from 'rxjs' - -export interface RecoverData { - targetId: string - backupInfo: BackupInfo - password: string -} - -@Component({ - selector: 'recover-select', - templateUrl: './recover-select.page.html', - styleUrls: ['./recover-select.page.scss'], -}) -export class RecoverSelectPage { - readonly packageData$ = this.patch.watch$('package-data').pipe(take(1)) - - hasSelection = false - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly embassyApi: ApiService, - private readonly patch: PatchDB, - ) {} - - get backupInfo(): BackupInfo { - return this.context.data.backupInfo - } - - handleChange(options: AppRecoverOption[]) { - this.hasSelection = options.some(o => o.checked) - } - - async restore(options: AppRecoverOption[]): Promise { - const ids = options.filter(({ checked }) => !!checked).map(({ id }) => id) - const loader = this.loader.open('Initializing...').subscribe() - - try { - await this.embassyApi.restorePackages({ - ids, - 'target-id': this.context.data.targetId, - password: this.context.data.password, - }) - - this.context.completeWith(undefined) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/to-options.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/to-options.pipe.ts deleted file mode 100644 index 59a5644bb..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/recover-select/to-options.pipe.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { Emver } from '@start9labs/shared' -import { map, Observable } from 'rxjs' -import { PackageBackupInfo } from 'src/app/services/api/api.types' -import { ConfigService } from 'src/app/services/config.service' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' - -export interface AppRecoverOption extends PackageBackupInfo { - id: string - checked: boolean - installed: boolean - 'newer-eos': boolean -} - -@Pipe({ - name: 'toOptions', -}) -export class ToOptionsPipe implements PipeTransform { - constructor( - private readonly config: ConfigService, - private readonly emver: Emver, - ) {} - - transform( - packageData$: Observable>, - packageBackups: Record = {}, - ): Observable { - return packageData$.pipe( - map(packageData => - Object.keys(packageBackups) - .map(id => ({ - ...packageBackups[id], - id, - installed: !!packageData[id], - checked: false, - 'newer-eos': this.compare(packageBackups[id]['os-version']), - })) - .sort((a, b) => - b.title.toLowerCase() > a.title.toLowerCase() ? -1 : 1, - ), - ), - ) - } - - private compare(version: string): boolean { - // checks to see if backup was made on a newer version of eOS - return this.emver.compare(version, this.config.version) === 1 - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.module.ts b/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.module.ts deleted file mode 100644 index 6ea234db5..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TargetSelectPage, TargetStatusComponent } from './target-select.page' -import { TargetPipesModule } from '../../pipes/target-pipes.module' -import { TextSpinnerComponentModule } from '@start9labs/shared' - -@NgModule({ - declarations: [TargetSelectPage, TargetStatusComponent], - imports: [ - CommonModule, - IonicModule, - TargetPipesModule, - TextSpinnerComponentModule, - TuiButtonModule, - ], - exports: [TargetSelectPage], -}) -export class TargetSelectPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.page.html b/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.page.html deleted file mode 100644 index 8aa08a50a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.page.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - Saved Targets - - - - -

{{ displayInfo.name }}

- -

{{ displayInfo.description }}

-

{{ displayInfo.path }}

-
-
-
- -
-

No saved targets

- -
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.page.scss b/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.page.scss deleted file mode 100644 index bfffad405..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.page.scss +++ /dev/null @@ -1,3 +0,0 @@ -ion-item { - --background: transparent; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.page.ts b/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.page.ts deleted file mode 100644 index 8a2ca1733..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-select.page.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Inject, - Input, -} from '@angular/core' -import { NavController } from '@ionic/angular' -import { BehaviorSubject } from 'rxjs' -import { BackupTarget } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ErrorService } from '@start9labs/shared' -import { BackupType } from '../../pages/backup-targets/backup-targets.page' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' - -@Component({ - selector: 'target-select', - templateUrl: './target-select.page.html', - styleUrls: ['./target-select.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class TargetSelectPage { - targets: BackupTarget[] = [] - - loading$ = new BehaviorSubject(true) - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext< - BackupTarget, - { type: BackupType } - >, - private readonly navCtrl: NavController, - private readonly api: ApiService, - private readonly errorService: ErrorService, - ) {} - - get type(): BackupType { - return this.context.data.type - } - - async ngOnInit() { - await this.getTargets() - } - - select(target: BackupTarget): void { - this.context.completeWith(target) - } - - goToTargets() { - this.context.$implicit.complete() - this.navCtrl.navigateForward(`/backups/targets`) - } - - async refresh() { - await this.getTargets() - } - - private async getTargets(): Promise { - this.loading$.next(true) - try { - this.targets = (await this.api.getBackupTargets({})).saved - } catch (e: any) { - this.errorService.handleError(e) - } finally { - this.loading$.next(false) - } - } -} - -@Component({ - selector: 'target-status', - templateUrl: './target-status.component.html', - styleUrls: ['./target-select.page.scss'], -}) -export class TargetStatusComponent { - @Input({ required: true }) type!: BackupType - @Input({ required: true }) target!: BackupTarget -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-status.component.html b/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-status.component.html deleted file mode 100644 index fb6942e86..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/modals/target-select/target-status.component.html +++ /dev/null @@ -1,30 +0,0 @@ -
-

- - Unable to connect -

- - -

- - {{ - (target | hasValidBackup) - ? 'Available, contains existing backup' - : 'Available for fresh backup' - }} -

- - -

- - Embassy backup detected -

- -

- - No Embassy backup -

-
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.module.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.module.ts deleted file mode 100644 index f620cd13e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.module.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { - BackupHistoryPage, - DurationPipe, - HasErrorPipe, -} from './backup-history.page' -import { TargetPipesModule } from '../../pipes/target-pipes.module' -import { BackupReportPageModule } from 'src/app/apps/ui/modals/backup-report/backup-report.module' - -const routes: Routes = [ - { - path: '', - component: BackupHistoryPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - TargetPipesModule, - BackupReportPageModule, - RouterModule.forChild(routes), - ], - declarations: [BackupHistoryPage, DurationPipe, HasErrorPipe], - exports: [DurationPipe, HasErrorPipe], -}) -export class BackupHistoryPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.page.html b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.page.html deleted file mode 100644 index 2e4801e5e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.page.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - Backup History - - - - - - Past Events - - Delete Selected - - -
- - - -
- -
- Started At -
- Duration - Result - Job - Target -
- - - - - - - - - - - - - -
- -
- {{ run['started-at'] | date : 'medium' }} -
- - {{ run['started-at']| duration : run['completed-at'] }} Minutes - - - - - - - Report - - {{ run.job.name || 'No job' }} - - -   {{ run.job.target.name }} - -
-
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.page.scss b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.page.scss deleted file mode 100644 index 05b3f2393..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.page.scss +++ /dev/null @@ -1,3 +0,0 @@ -.highlighted { - background-color: var(--ion-color-medium-shade); -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.page.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.page.ts deleted file mode 100644 index 0cec0874d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-history/backup-history.page.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Component } from '@angular/core' -import { Pipe, PipeTransform } from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { BackupReport, BackupRun } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { BehaviorSubject } from 'rxjs' -import { BackupReportComponent } from '../../../../modals/backup-report/backup-report.component' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' - -@Component({ - selector: 'backup-history', - templateUrl: './backup-history.page.html', - styleUrls: ['./backup-history.page.scss'], -}) -export class BackupHistoryPage { - selected: Record = {} - runs: BackupRun[] = [] - loading$ = new BehaviorSubject(true) - - constructor( - private readonly dialogs: TuiDialogService, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly api: ApiService, - ) {} - - async ngOnInit() { - try { - this.runs = await this.api.getBackupRuns({}) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - this.loading$.next(false) - } - } - - get empty() { - return this.count === 0 - } - - get count() { - return Object.keys(this.selected).length - } - - presentModalReport(run: BackupRun) { - this.dialogs - .open(new PolymorpheusComponent(BackupReportComponent), { - label: 'Backup Report', - data: { - report: run.report, - timestamp: run['completed-at'], - }, - }) - .subscribe() - } - - async toggleChecked(id: string) { - if (this.selected[id]) { - delete this.selected[id] - } else { - this.selected[id] = true - } - } - - async toggleAll(runs: BackupRun[]) { - if (this.empty) { - runs.forEach(r => (this.selected[r.id] = true)) - } else { - this.selected = {} - } - } - - async deleteSelected(): Promise { - const ids = Object.keys(this.selected) - const loader = this.loader.open('Deleting...').subscribe() - - try { - await this.api.deleteBackupRuns({ ids }) - this.selected = {} - this.runs = this.runs.filter(r => !ids.includes(r.id)) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} - -@Pipe({ - name: 'duration', -}) -export class DurationPipe implements PipeTransform { - transform(start: string, finish: string): number { - const diffMs = new Date(finish).valueOf() - new Date(start).valueOf() - return diffMs / 100 - } -} - -@Pipe({ - name: 'hasError', -}) -export class HasErrorPipe implements PipeTransform { - transform(report: BackupReport): boolean { - const osErr = !!report.server.error - const pkgErr = !!Object.values(report.packages).find(pkg => pkg.error) - return osErr || pkgErr - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.module.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.module.ts deleted file mode 100644 index f5c833049..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.module.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { FormsModule } from '@angular/forms' -import { RouterModule, Routes } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { TuiNotificationModule, TuiWrapperModule } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TuiInputModule, TuiToggleModule } from '@taiga-ui/kit' -import { BackupJobsPage } from './backup-jobs.page' -import { EditJobComponent } from './edit-job/edit-job.component' -import { ToHumanCronPipe } from './pipes' -import { TargetSelectPageModule } from '../../modals/target-select/target-select.module' -import { TargetPipesModule } from '../../pipes/target-pipes.module' - -const routes: Routes = [ - { - path: '', - component: BackupJobsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - FormsModule, - TargetSelectPageModule, - TargetPipesModule, - TuiNotificationModule, - TuiButtonModule, - TuiInputModule, - TuiToggleModule, - TuiWrapperModule, - ], - declarations: [BackupJobsPage, ToHumanCronPipe, EditJobComponent], - exports: [ToHumanCronPipe], -}) -export class BackupJobsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.page.html b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.page.html deleted file mode 100644 index 871fc55eb..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.page.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - Backup Jobs - - - - -
- - Scheduling automatic backups is an excellent way to ensure your Embassy - data is safely backed up. Your Embassy will issue a notification whenever - one of your scheduled backups succeeds or fails. - View instructions - -
- - - - Saved Jobs - - - Create New Job - - - -
- - - Name - Target - Packages - Schedule - - - - - - - - - - - - - - {{ job.name }} - - -   {{ job.target.name }} - - {{ job['package-ids'].length }} Packages - {{ (job.cron | toHumanCron).message }} - - - - - - - - - - - - - -
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.page.scss b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.page.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.page.ts deleted file mode 100644 index 2399239c0..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/backup-jobs.page.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Component } from '@angular/core' -import { TuiDialogService } from '@taiga-ui/core' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { BehaviorSubject, filter } from 'rxjs' -import { BackupJob } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { EditJobComponent } from './edit-job/edit-job.component' -import { BackupJobBuilder } from './edit-job/job-builder' - -@Component({ - selector: 'backup-jobs', - templateUrl: './backup-jobs.page.html', - styleUrls: ['./backup-jobs.page.scss'], -}) -export class BackupJobsPage { - readonly docsUrl = - 'https://docs.start9.com/latest/user-manual/backups/backup-jobs' - - jobs: BackupJob[] = [] - - loading$ = new BehaviorSubject(true) - - constructor( - private readonly dialogs: TuiDialogService, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly api: ApiService, - ) {} - - async ngOnInit() { - try { - this.jobs = await this.api.getBackupJobs({}) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - this.loading$.next(false) - } - } - - presentModalCreate() { - this.dialogs - .open(new PolymorpheusComponent(EditJobComponent), { - label: 'Create New Job', - data: new BackupJobBuilder({ - name: `Backup Job ${this.jobs.length + 1}`, - }), - }) - .subscribe(job => this.jobs.push(job)) - } - - presentModalUpdate(data: BackupJob) { - this.dialogs - .open(new PolymorpheusComponent(EditJobComponent), { - label: 'Edit Job', - data: new BackupJobBuilder(data), - }) - .subscribe(job => { - data.name = job.name - data.target = job.target - data.cron = job.cron - data['package-ids'] = job['package-ids'] - }) - } - - presentAlertDelete(id: string, index: number) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: 'Delete backup job? This action cannot be undone.', - yes: 'Delete', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => { - this.delete(id, index) - }) - } - - private async delete(id: string, i: number): Promise { - const loader = this.loader.open('Deleting...').subscribe() - - try { - await this.api.removeBackupTarget({ id }) - this.jobs.splice(i, 1) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/edit-job.component.html b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/edit-job.component.html deleted file mode 100644 index 1e599bfb3..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/edit-job.component.html +++ /dev/null @@ -1,47 +0,0 @@ -
- - Job Name - - - - - - - - - Schedule - - - -

- {{ human.message }} -

- -
- Also Execute Now - -
- -
diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/edit-job.component.scss b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/edit-job.component.scss deleted file mode 100644 index 083cd95d8..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/edit-job.component.scss +++ /dev/null @@ -1,34 +0,0 @@ -.button { - width: 100%; - height: var(--tui-height-l); - display: flex; - align-items: center; - justify-content: space-between; - margin: 1rem 0; - padding: 0 1rem; - border-radius: var(--tui-radius-m); - font: var(--tui-font-text-l); - font-weight: bold; -} - -.value { - font: var(--tui-font-text-m); - color: var(--tui-positive); -} - -.toggle { - height: var(--tui-height-l); - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 1rem; - box-shadow: inset 0 0 0 1px var(--tui-base-03); - font: var(--tui-font-text-l); - font-weight: bold; - border-radius: var(--tui-radius-m); -} - -.submit { - float: right; - margin-top: 1rem; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/edit-job.component.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/edit-job.component.ts deleted file mode 100644 index 1553dd541..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/edit-job.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Component, Inject } from '@angular/core' -import { TuiDialogContext, TuiDialogService } from '@taiga-ui/core' -import { - POLYMORPHEUS_CONTEXT, - PolymorpheusComponent, -} from '@tinkoff/ng-polymorpheus' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { BackupJob, BackupTarget } from 'src/app/services/api/api.types' -import { TargetSelectPage } from '../../../modals/target-select/target-select.page' -import { BackupSelectPage } from '../../../modals/backup-select/backup-select.page' -import { BackupJobBuilder } from './job-builder' - -@Component({ - selector: 'edit-job', - templateUrl: './edit-job.component.html', - styleUrls: ['./edit-job.component.scss'], -}) -export class EditJobComponent { - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext, - private readonly dialogs: TuiDialogService, - private readonly loader: LoadingService, - private readonly api: ApiService, - private readonly errorService: ErrorService, - ) {} - - get job() { - return this.context.data - } - - async save() { - const loader = this.loader.open('Saving Job').subscribe() - - try { - const { id } = this.job.job - let job: BackupJob - - if (id) { - job = await this.api.updateBackupJob(this.job.buildUpdate(id)) - } else { - job = await this.api.createBackupJob(this.job.buildCreate()) - } - - this.context.completeWith(job) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - presentModalTarget() { - this.dialogs - .open(new PolymorpheusComponent(TargetSelectPage), { - label: 'Select Backup Target', - data: { type: 'create' }, - }) - .subscribe(target => { - this.job.target = target - }) - } - - presentModalPackages() { - this.dialogs - .open(new PolymorpheusComponent(BackupSelectPage), { - label: 'Select Services to Back Up', - data: { btnText: 'Done' }, - }) - .subscribe(id => { - this.job['package-ids'] = id - }) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/job-builder.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/job-builder.ts deleted file mode 100644 index b84e4d369..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/edit-job/job-builder.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { BackupJob, BackupTarget, RR } from 'src/app/services/api/api.types' - -export class BackupJobBuilder { - name: string - target: BackupTarget - cron: string - 'package-ids': string[] - now = false - - constructor(readonly job: Partial) { - const { name, target, cron } = job - this.name = name || '' - this.target = target || ({} as BackupTarget) - this.cron = cron || '0 2 * * *' - this['package-ids'] = job['package-ids'] || [] - } - - buildCreate(): RR.CreateBackupJobReq { - const { name, target, cron, now } = this - - return { - name, - 'target-id': target.id, - cron, - 'package-ids': this['package-ids'], - now, - } - } - - buildUpdate(id: string): RR.UpdateBackupJobReq { - const { name, target, cron } = this - - return { - id, - name, - 'target-id': target.id, - cron, - 'package-ids': this['package-ids'], - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/pipes.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/pipes.ts deleted file mode 100644 index 0e756aa9a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-jobs/pipes.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import cronstrue from 'cronstrue' - -@Pipe({ - name: 'toHumanCron', -}) -export class ToHumanCronPipe implements PipeTransform { - transform(cron: string): { message: string; color: string } { - const toReturn = { - message: '', - color: 'var(--tui-positive)', - } - - try { - const human = cronstrue.toString(cron, { - verbose: true, - throwExceptionOnParseError: true, - }) - const zero = Number(cron[0]) - const one = Number(cron[1]) - if (Number.isNaN(zero) || Number.isNaN(one)) { - throw new Error( - `${human}. Cannot run cron jobs more than once per hour`, - ) - } - toReturn.message = human - } catch (e) { - toReturn.message = e as string - toReturn.color = 'var(--tui-negative)' - } - - return toReturn - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.module.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.module.ts deleted file mode 100644 index fb507215d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { UnitConversionPipesModule } from '@start9labs/shared' -import { SkeletonListComponentModule } from 'src/app/common/skeleton-list/skeleton-list.component.module' -import { FormPageModule } from 'src/app/apps/ui/modals/form/form.module' -import { BackupTargetsPage } from './backup-targets.page' -import { TuiNotificationModule } from '@taiga-ui/core' - -const routes: Routes = [ - { - path: '', - component: BackupTargetsPage, - }, -] - -@NgModule({ - declarations: [BackupTargetsPage], - imports: [ - CommonModule, - IonicModule, - SkeletonListComponentModule, - UnitConversionPipesModule, - FormPageModule, - RouterModule.forChild(routes), - TuiNotificationModule, - ], -}) -export class BackupTargetsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.page.html b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.page.html deleted file mode 100644 index 655bf28e6..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.page.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - Backup Targets - - - - -
- - Backup targets are physical or virtual locations for storing encrypted - backups. They can be physical drives plugged into your server, shared - folders on your Local Area Network (LAN), or third party clouds such as - Dropbox or Google Drive. - View instructions - -
- - - - - Unknown Physical Drives - - - Refresh - - - -
- - - Make/Model - Label - Capacity - Used - - - - - - - - - - - - - {{ disk.vendor || 'unknown make' }}, {{ disk.model || 'unknown - model' }} - - {{ disk.label }} - {{ disk.capacity | convertBytes }} - - {{ disk.used ? (disk.used | convertBytes) : 'unknown' }} - - - - - Save - - - - -

- To add a new physical backup target, connect the drive and click - refresh. -

-
-
-
- - - - Saved Targets - - - Add Target - - - -
- - - Name - Type - Available - Path - - - - - - - - - - - - - - {{ target.name }} - - -   {{ target.type | titlecase }} - - - - - {{ target.path }} - - - - - - - - - - - - -

No saved backup targets.

-
-
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.page.scss b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.page.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.page.ts deleted file mode 100644 index fb5e193a3..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backup-targets/backup-targets.page.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { Component } from '@angular/core' -import { - BackupTarget, - BackupTargetType, - RR, - UnknownDisk, -} from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { - cifsSpec, - diskBackupTargetSpec, - dropboxSpec, - googleDriveSpec, - remoteBackupTargetSpec, -} from '../../types/target-types' -import { BehaviorSubject, filter } from 'rxjs' -import { TuiDialogService } from '@taiga-ui/core' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { - InputSpec, - unionSelectKey, - unionValueKey, -} from '@start9labs/start-sdk/lib/config/configTypes' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormPage } from 'src/app/apps/ui/modals/form/form.page' -import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' - -type BackupConfig = - | { - type: { - [unionSelectKey]: 'dropbox' | 'google-drive' - [unionValueKey]: RR.AddCloudBackupTargetReq - } - } - | { - type: { - [unionSelectKey]: 'cifs' - [unionValueKey]: RR.AddCifsBackupTargetReq - } - } - -export type BackupType = 'create' | 'restore' - -@Component({ - selector: 'backup-targets', - templateUrl: './backup-targets.page.html', - styleUrls: ['./backup-targets.page.scss'], -}) -export class BackupTargetsPage { - readonly docsUrl = - 'https://docs.start9.com/latest/user-manual/backups/backup-targets' - targets: RR.GetBackupTargetsRes = { - 'unknown-disks': [], - saved: [], - } - - loading$ = new BehaviorSubject(true) - - constructor( - private readonly dialogs: TuiDialogService, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly api: ApiService, - private readonly formDialog: FormDialogService, - ) {} - - ngOnInit() { - this.getTargets() - } - - async presentModalAddPhysical(disk: UnknownDisk, index: number) { - this.formDialog.open(FormPage, { - label: 'New Physical Target', - data: { - spec: await configBuilderToSpec(diskBackupTargetSpec), - value: { - name: disk.label || disk.logicalname, - }, - buttons: [ - { - text: 'Save', - handler: (value: Omit) => - this.add('disk', { - logicalname: disk.logicalname, - ...value, - }).then(disk => { - this.targets['unknown-disks'].splice(index, 1) - this.targets.saved.push(disk) - - return true - }), - }, - ], - }, - }) - } - - async presentModalAddRemote() { - this.formDialog.open(FormPage, { - label: 'New Remote Target', - data: { - spec: await configBuilderToSpec(remoteBackupTargetSpec), - buttons: [ - { - text: 'Save', - handler: ({ type }: BackupConfig) => - this.add( - type[unionSelectKey] === 'cifs' ? 'cifs' : 'cloud', - type[unionValueKey], - ), - }, - ], - }, - }) - } - - async presentModalUpdate(target: BackupTarget) { - let spec: InputSpec - - switch (target.type) { - case 'cifs': - spec = await configBuilderToSpec(cifsSpec) - break - case 'cloud': - spec = await configBuilderToSpec( - target.provider === 'dropbox' ? dropboxSpec : googleDriveSpec, - ) - break - case 'disk': - spec = await configBuilderToSpec(diskBackupTargetSpec) - break - } - - this.formDialog.open(FormPage, { - label: 'Update Target', - data: { - spec, - value: target, - buttons: [ - { - text: 'Save', - handler: ( - value: - | RR.UpdateCifsBackupTargetReq - | RR.UpdateCloudBackupTargetReq - | RR.UpdateDiskBackupTargetReq, - ) => this.update(target.type, { ...value, id: target.id }), - }, - ], - }, - }) - } - - presentAlertDelete(id: string, index: number) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: 'Forget backup target? This actions cannot be undone.', - no: 'Cancel', - yes: 'Delete', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.delete(id, index)) - } - - async delete(id: string, index: number): Promise { - const loader = this.loader.open('Removing...').subscribe() - - try { - await this.api.removeBackupTarget({ id }) - this.targets.saved.splice(index, 1) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - async refresh() { - this.loading$.next(true) - await this.getTargets() - } - - getIcon(type: BackupTargetType) { - switch (type) { - case 'disk': - return 'save-outline' - case 'cifs': - return 'folder-open-outline' - case 'cloud': - return 'cloud-outline' - } - } - - private async getTargets(): Promise { - try { - this.targets = await this.api.getBackupTargets({}) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - this.loading$.next(false) - } - } - - private async add( - type: BackupTargetType, - value: - | RR.AddCifsBackupTargetReq - | RR.AddCloudBackupTargetReq - | RR.AddDiskBackupTargetReq, - ): Promise { - const loader = this.loader.open('Saving target...').subscribe() - - try { - return await this.api.addBackupTarget(type, value) - } finally { - loader.unsubscribe() - } - } - - private async update( - type: BackupTargetType, - value: - | RR.UpdateCifsBackupTargetReq - | RR.UpdateCloudBackupTargetReq - | RR.UpdateDiskBackupTargetReq, - ): Promise { - const loader = this.loader.open('Saving target...').subscribe() - - try { - return await this.api.updateBackupTarget(type, value) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.module.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.module.ts deleted file mode 100644 index c4c53f140..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.module.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { BadgeMenuComponentModule } from 'src/app/common/badge-menu-button/badge-menu.component.module' -import { InsecureWarningComponentModule } from 'src/app/common/insecure-warning/insecure-warning.module' -import { BackupCreateDirective } from '../../directives/backup-create.directive' -import { BackupRestoreDirective } from '../../directives/backup-restore.directive' -import { - BackingUpComponent, - PkgMainStatusPipe, -} from '../../components/backing-up/backing-up.component' -import { BackupSelectPageModule } from '../../modals/backup-select/backup-select.module' -import { RecoverSelectPageModule } from '../../modals/recover-select/recover-select.module' -import { TargetPipesModule } from '../../pipes/target-pipes.module' -import { BackupsPage } from './backups.page' -import { PromptModule } from 'src/app/apps/ui/modals/prompt/prompt.module' - -const routes: Routes = [ - { - path: '', - component: BackupsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - BackupSelectPageModule, - RecoverSelectPageModule, - BadgeMenuComponentModule, - InsecureWarningComponentModule, - TargetPipesModule, - PromptModule, - ], - declarations: [ - BackupsPage, - BackupCreateDirective, - BackupRestoreDirective, - BackingUpComponent, - PkgMainStatusPipe, - ], -}) -export class BackupsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.page.html b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.page.html deleted file mode 100644 index 4dca2133c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.page.html +++ /dev/null @@ -1,112 +0,0 @@ - - - Backups - - - - - - - - - - - Options - - - - -

Create a Backup

-

Create a one-time backup

-
-
- - - -

Restore From Backup

-

Restore services from backup

-
-
- - - -

Jobs

-

Manage backup jobs

-
-
- - - -

Targets

-

Manage backup targets

-
-
- - - -

History

-

View your entire backup history

-
-
- - Upcoming Jobs - -
- - - Scheduled - Job - Target - Packages - - - - - - - - Running - - - {{ upcoming.next | date : 'MMM d, y, h:mm a' }} - - - {{ upcoming.name }} - - -   {{ upcoming.target.name }} - - - {{ upcoming['package-ids'].length }} Packages - - - -

- You have no active or upcoming backup jobs. -

-
-
- - - - - - - - -
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.page.scss b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.page.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.page.ts deleted file mode 100644 index 4b8f2978b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pages/backups/backups.page.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { from, map } from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ConfigService } from 'src/app/services/config.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { CronJob } from 'cron' - -@Component({ - selector: 'backups', - templateUrl: './backups.page.html', - styleUrls: ['./backups.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class BackupsPage { - readonly secure = this.config.isSecure() - readonly current$ = this.patch - .watch$('server-info', 'status-info', 'current-backup', 'job') - .pipe(map(job => job || {})) - readonly upcoming$ = from(this.api.getBackupJobs({})).pipe( - map(jobs => - jobs - .map(job => { - const nextDate = new CronJob(job.cron, () => {}).nextDate() - const next = nextDate.toISO() - const diff = nextDate.diffNow().milliseconds - return { - ...job, - next, - diff, - } - }) - .sort((a, b) => a.diff - b.diff), - ), - ) - - constructor( - private readonly patch: PatchDB, - private readonly config: ConfigService, - private readonly api: ApiService, - ) {} -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pipes/get-display-info.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pipes/get-display-info.pipe.ts deleted file mode 100644 index fe5a927bf..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pipes/get-display-info.pipe.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { BackupTarget } from 'src/app/services/api/api.types' - -@Pipe({ - name: 'getDisplayInfo', -}) -export class GetDisplayInfoPipe implements PipeTransform { - transform(target: BackupTarget): DisplayInfo { - const toReturn: DisplayInfo = { - name: target.name, - path: `Path: ${target.path}`, - description: '', - icon: '', - } - - switch (target.type) { - case 'cifs': - toReturn.description = `Network Folder: ${target.hostname}` - toReturn.icon = 'folder-open-outline' - break - case 'disk': - toReturn.description = `Physical Drive: ${ - target.vendor || 'Unknown Vendor' - }, ${target.model || 'Unknown Model'}` - toReturn.icon = 'save-outline' - break - case 'cloud': - toReturn.description = `Provider: ${target.provider}` - toReturn.icon = 'cloud-outline' - break - } - - return toReturn - } -} - -interface DisplayInfo { - name: string - path: string - description: string - icon: string -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pipes/has-valid-backup.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pipes/has-valid-backup.pipe.ts deleted file mode 100644 index ff1b6237d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pipes/has-valid-backup.pipe.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { BackupTarget } from 'src/app/services/api/api.types' -import { Emver } from '@start9labs/shared' - -@Pipe({ - name: 'hasValidBackup', -}) -export class HasValidBackupPipe implements PipeTransform { - constructor(private readonly emver: Emver) {} - - transform(target: BackupTarget): boolean { - const backup = target['embassy-os'] - return !!backup && this.emver.compare(backup.version, '0.3.0') !== -1 - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/pipes/target-pipes.module.ts b/web/projects/ui/src/app/apps/ui/pages/backups/pipes/target-pipes.module.ts deleted file mode 100644 index 111ad834e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/pipes/target-pipes.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { HasValidBackupPipe } from './has-valid-backup.pipe' -import { GetDisplayInfoPipe } from './get-display-info.pipe' - -@NgModule({ - declarations: [HasValidBackupPipe, GetDisplayInfoPipe], - imports: [CommonModule], - exports: [HasValidBackupPipe, GetDisplayInfoPipe], -}) -export class TargetPipesModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/backups/types/target-types.ts b/web/projects/ui/src/app/apps/ui/pages/backups/types/target-types.ts deleted file mode 100644 index fa129fdef..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/backups/types/target-types.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Config } from '@start9labs/start-sdk/lib/config/builder/config' -import { Value } from '@start9labs/start-sdk/lib/config/builder/value' -import { Variants } from '@start9labs/start-sdk/lib/config/builder/variants' - -export const dropboxSpec = Config.of({ - name: Value.text({ - name: 'Name', - description: 'A friendly name for this Dropbox target', - placeholder: 'My Dropbox', - required: { default: null }, - }), - token: Value.text({ - name: 'Access Token', - description: 'The secret access token for your custom Dropbox app', - required: { default: null }, - masked: true, - }), - path: Value.text({ - name: 'Path', - description: 'The fully qualified path to the backup directory', - placeholder: 'e.g. /Desktop/my-folder', - required: { default: null }, - }), -}) - -export const googleDriveSpec = Config.of({ - name: Value.text({ - name: 'Name', - description: 'A friendly name for this Google Drive target', - placeholder: 'My Google Drive', - required: { default: null }, - }), - path: Value.text({ - name: 'Path', - description: 'The fully qualified path to the backup directory', - placeholder: 'e.g. /Desktop/my-folder', - required: { default: null }, - }), - key: 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 = Config.of({ - name: Value.text({ - name: 'Name', - description: 'A friendly name for this Network Folder', - placeholder: 'My Network Folder', - required: { default: null }, - }), - hostname: Value.text({ - name: 'Hostname', - description: - 'The hostname of your target device on the Local Area Network.', - warning: null, - placeholder: `e.g. 'My Computer' OR 'my-computer.local'`, - required: { default: null }, - patterns: [], - }), - path: Value.text({ - 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).`, - placeholder: 'e.g. my-shared-folder or /Desktop/my-folder', - required: { default: null }, - }), - username: Value.text({ - 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.`, - required: { default: null }, - placeholder: 'My Network Folder', - }), - password: Value.text({ - 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.`, - required: false, - masked: true, - placeholder: 'My Network Folder', - }), -}) - -export const remoteBackupTargetSpec = Config.of({ - type: Value.union( - { - name: 'Target Type', - required: { default: 'dropbox' }, - }, - Variants.of({ - dropbox: { - name: 'Dropbox', - spec: dropboxSpec, - }, - 'google-drive': { - name: 'Google Drive', - spec: googleDriveSpec, - }, - cifs: { - name: 'Network Folder', - spec: cifsSpec, - }, - }), - ), -}) - -export const diskBackupTargetSpec = Config.of({ - name: Value.text({ - name: 'Name', - description: 'A friendly name for this physical target', - placeholder: 'My Physical Target', - required: { default: null }, - }), - path: Value.text({ - name: 'Path', - description: 'The fully qualified path to the backup directory', - placeholder: 'e.g. /Backups/my-folder', - required: { default: null }, - }), -}) diff --git a/web/projects/ui/src/app/apps/ui/pages/home/home.module.ts b/web/projects/ui/src/app/apps/ui/pages/home/home.module.ts deleted file mode 100644 index 8e5ba887d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/home/home.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { HomePage } from './home.page' -import { BadgeMenuComponentModule } from 'src/app/common/badge-menu-button/badge-menu.component.module' -import { WidgetListComponentModule } from 'src/app/common/widget-list/widget-list.component.module' - -const routes: Routes = [ - { - path: '', - component: HomePage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - BadgeMenuComponentModule, - WidgetListComponentModule, - ], - declarations: [HomePage], -}) -export class HomePageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/home/home.page.html b/web/projects/ui/src/app/apps/ui/pages/home/home.page.html deleted file mode 100644 index 1f6300ad7..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/home/home.page.html +++ /dev/null @@ -1,13 +0,0 @@ - - - Home - - - - - - -
- -
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/home/home.page.scss b/web/projects/ui/src/app/apps/ui/pages/home/home.page.scss deleted file mode 100644 index 7efcd5161..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/home/home.page.scss +++ /dev/null @@ -1,9 +0,0 @@ -.padding-top { - padding-top: 2rem; -} - -@media (min-width: 2000px) { - .padding-top { - padding-top: 10rem; - } -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/home/home.page.ts b/web/projects/ui/src/app/apps/ui/pages/home/home.page.ts deleted file mode 100644 index c90e99489..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/home/home.page.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Component } from '@angular/core' - -@Component({ - selector: 'home', - templateUrl: 'home.page.html', - styleUrls: ['home.page.scss'], -}) -export class HomePage {} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-item-toggle.component.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-item-toggle.component.ts deleted file mode 100644 index c96500e82..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-item-toggle.component.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { CommonModule, DOCUMENT } from '@angular/common' -import { - ChangeDetectionStrategy, - Component, - HostListener, - Inject, - Input, - inject, -} from '@angular/core' -import { TuiButtonModule } from '@taiga-ui/core' -import { TuiActiveZoneModule } from '@taiga-ui/cdk' -import { TuiSidebarModule } from '@taiga-ui/addon-mobile' -import { ItemModule, MarketplacePkg } from '@start9labs/marketplace' -import { MarketplaceShowControlsComponent } from '../marketplace-show-preview/components/marketplace-show-controls.component' -import { MarketplaceShowPreviewModule } from '../marketplace-show-preview/marketplace-show-preview.module' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { BehaviorSubject, filter, Observable, shareReplay } from 'rxjs' -import { PatchDB } from 'patch-db-client' -import { SidebarService } from 'src/app/services/sidebar.service' -import { ActivatedRoute } from '@angular/router' - -@Component({ - selector: 'marketplace-item-toggle', - template: ` -
- - - - - -
- `, - styles: [ - ` - .animate { - animation-name: animateIn; - animation-duration: 400ms; - animation-delay: calc(var(--animation-order) * 200ms); - animation-fill-mode: both; - animation-timing-function: ease-in-out; - } - - @keyframes animateIn { - 0% { - opacity: 0; - transform: scale(0.6) translateY(-20px); - } - - 100% { - opacity: 1; - } - } - `, - ], - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [ - CommonModule, - TuiActiveZoneModule, - TuiButtonModule, - TuiSidebarModule, - MarketplaceShowPreviewModule, - ItemModule, - MarketplaceShowControlsComponent, - ], -}) -export class MarketplaceItemToggleComponent { - @Input({ required: true }) - pkg!: MarketplacePkg - - @Input({ required: true }) - index!: number - - constructor( - private readonly patch: PatchDB, - private readonly activatedRoute: ActivatedRoute, - @Inject(DOCUMENT) private readonly document: Document, - ) {} - readonly sidebarService = inject(SidebarService) - localPkg$!: Observable - pkgIdQueryParam = new BehaviorSubject('') - readonly pkgId = this.activatedRoute.queryParamMap.subscribe(params => { - this.pkgIdQueryParam.next(params.get('id')!) - }) - - ngOnChanges() { - this.localPkg$ = this.patch - .watch$('package-data', this.pkg.manifest.id) - .pipe(filter(Boolean), shareReplay({ bufferSize: 1, refCount: true })) - } - - @HostListener('animationend', ['$event.target']) - async onAnimationEnd(_target: EventTarget | null) { - if (this.pkgIdQueryParam.value === this.pkg.manifest.id) { - this.toggle(true) - } - } - - toggle(open: boolean) { - this.sidebarService.toggleState(this.pkg.manifest.id, open) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.component.html b/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.component.html deleted file mode 100644 index e67ee1988..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - Change Registry Icon - - Change Registry - - - diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.component.scss b/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.component.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.component.ts deleted file mode 100644 index cdcda451b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' -import { TuiDialogService } from '@taiga-ui/core' -import { ConfigService } from 'src/app/services/config.service' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { MarketplaceSettingsPage } from '../../marketplace-list/marketplace-settings/marketplace-settings.page' - -@Component({ - selector: 'marketplace-menu', - templateUrl: 'marketplace-menu.component.html', - styleUrls: ['./marketplace-menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MarketplaceMenuComponent { - constructor( - @Inject(TuiDialogService) private readonly dialogs: TuiDialogService, - readonly config: ConfigService, - ) {} - - readonly marketplace = this.config.marketplace - - async presentModalMarketplaceSettings() { - this.dialogs - .open( - new PolymorpheusComponent(MarketplaceSettingsPage), - { - label: 'Change Registry', - }, - ) - .subscribe() - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.module.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.module.ts deleted file mode 100644 index c6963c8f1..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/components/marketplace-menu/marketplace-menu.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NgModule } from '@angular/core' -import { MarketplaceMenuComponent } from './marketplace-menu.component' -import { MenuModule } from '@start9labs/marketplace' -import { TuiButtonModule } from '@taiga-ui/core' - -@NgModule({ - imports: [MenuModule, TuiButtonModule], - exports: [MarketplaceMenuComponent], - declarations: [MarketplaceMenuComponent], -}) -export class MarketplaceMenuModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.module.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.module.ts deleted file mode 100644 index 49580cbd3..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.module.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { RouterModule, Routes } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { ResponsiveColDirective, SharedPipesModule } from '@start9labs/shared' -import { FilterPackagesPipeModule } from '@start9labs/marketplace' -import { MarketplaceMenuModule } from '../components/marketplace-menu/marketplace-menu.module' -import { MarketplaceListPage } from './marketplace-list.page' -import { MarketplaceSettingsPageModule } from './marketplace-settings/marketplace-settings.module' -import { TuiNotificationModule } from '@taiga-ui/core' -import { TuiLetModule } from '@taiga-ui/cdk' -import { MarketplaceItemToggleComponent } from '../components/marketplace-item-toggle.component' -const routes: Routes = [ - { - path: '', - component: MarketplaceListPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - SharedPipesModule, - FilterPackagesPipeModule, - MarketplaceMenuModule, - MarketplaceSettingsPageModule, - TuiNotificationModule, - TuiLetModule, - ResponsiveColDirective, - MarketplaceItemToggleComponent, - ], - declarations: [MarketplaceListPage], - exports: [MarketplaceListPage], -}) -export class MarketplaceListPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.page.html b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.page.html deleted file mode 100644 index c7dae83b8..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.page.html +++ /dev/null @@ -1,112 +0,0 @@ - -
-
- - - -

- Services from this registry are packaged and maintained by the Start9 - team. If you experience an issue or have questions related to a - service from this registry, one of our dedicated support staff will be - happy to assist you. -

-
- -

- Services from this registry are packaged and maintained by members of - the Start9 community. - Install at your own risk - . If you experience an issue or have a question related to a service - in this marketplace, please reach out to the package developer for - assistance. -

-
- -

- Services from this registry are undergoing - beta - testing and may contain bugs. - Install at your own risk - . -

-
- -

- Services from this registry are undergoing - alpha - testing. They are expected to contain bugs and could damage your - system. - Install at your own risk - . -

-
- -

- This is a Custom Registry. Start9 cannot verify the integrity or - functionality of services from this registry, and they could damage - your system. - Install at your own risk - . -

-
-
-
-

- {{ category$ | async | titlecase }} -

-
- -
-
    -
  • - -
  • -
-
-
- -

- Loading - -

-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.page.scss b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.page.scss deleted file mode 100644 index 6a6700581..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.page.scss +++ /dev/null @@ -1,4 +0,0 @@ -.background { - background: url('/assets/img/background.png') no-repeat center center fixed; - z-index: -100; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.page.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.page.ts deleted file mode 100644 index 1402d1c11..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-list.page.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' -import { - AbstractCategoryService, - AbstractMarketplaceService, -} from '@start9labs/marketplace' -import { TuiDialogService } from '@taiga-ui/core' -import { PatchDB } from 'patch-db-client' -import { map } from 'rxjs' -import { ConfigService } from 'src/app/services/config.service' -import { MarketplaceService } from 'src/app/services/marketplace.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { CategoryService } from 'src/app/services/category.service' -import { SidebarService } from 'src/app/services/sidebar.service' -import { MarketplaceSettingsPage } from './marketplace-settings/marketplace-settings.page' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' - -@Component({ - selector: 'marketplace-list', - templateUrl: 'marketplace-list.page.html', - styleUrls: ['./marketplace-list.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MarketplaceListPage { - constructor( - private readonly patch: PatchDB, - @Inject(AbstractMarketplaceService) - private readonly marketplaceService: MarketplaceService, - @Inject(AbstractCategoryService) - private readonly categoryService: CategoryService, - @Inject(TuiDialogService) private readonly dialogs: TuiDialogService, - readonly config: ConfigService, - readonly sidebarService: SidebarService, - ) {} - - readonly packages$ = this.marketplaceService.getSelectedStore$().pipe( - map(({ packages }) => { - this.sidebarService.setMap(packages.map(p => p.manifest.id)) - return packages - }), - ) - readonly localPkgs$ = this.patch.watch$('package-data') - readonly category$ = this.categoryService.getCategory$() - readonly query$ = this.categoryService.getQuery$() - readonly details$ = this.marketplaceService.getSelectedHost$() - readonly marketplace = this.config.marketplace - - async presentModalMarketplaceSettings() { - this.dialogs - .open( - new PolymorpheusComponent(MarketplaceSettingsPage), - { - label: 'Change Registry', - }, - ) - .subscribe() - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.module.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.module.ts deleted file mode 100644 index 0304046c2..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { SharedPipesModule } from '@start9labs/shared' -import { - TuiDataListModule, - TuiHostedDropdownModule, - TuiSvgModule, -} from '@taiga-ui/core' -import { FormPageModule } from 'src/app/apps/ui/modals/form/form.module' -import { MarketplaceSettingsPage } from './marketplace-settings.page' -import { StoreIconComponentModule } from '@start9labs/marketplace' - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - SharedPipesModule, - StoreIconComponentModule, - TuiHostedDropdownModule, - TuiDataListModule, - TuiSvgModule, - FormPageModule, - ], - declarations: [MarketplaceSettingsPage], -}) -export class MarketplaceSettingsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.page.html b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.page.html deleted file mode 100644 index 77dde15e7..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.page.html +++ /dev/null @@ -1,84 +0,0 @@ -
- - Default Registries - - - - - -

{{ s.name }}

-

{{ s.url }}

-
- -
- - Custom Registries - - - - - Add custom registry - - - - - - - - - - -

{{ a.name }}

-

{{ a.url }}

-
- -
- - - - - - -
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.page.scss b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.page.scss deleted file mode 100644 index c0655db59..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.page.scss +++ /dev/null @@ -1,16 +0,0 @@ -ion-item { - --background: transparent; -} - -.host { - display: flex; -} - -.delete { - background: var(--tui-error-bg); - color: var(--tui-error-fill); - - &:focus { - background: var(--tui-error-bg-hover); - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.page.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.page.ts deleted file mode 100644 index d09e7cded..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-list/marketplace-settings/marketplace-settings.page.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' -import { - ErrorService, - LoadingService, - sameUrl, - toUrl, -} from '@start9labs/shared' -import { AbstractMarketplaceService } from '@start9labs/marketplace' -import { ValueSpecObject } from '@start9labs/start-sdk/lib/config/configTypes' -import { TuiDialogService } from '@taiga-ui/core' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { PatchDB } from 'patch-db-client' -import { combineLatest, filter, firstValueFrom, map, Subscription } from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { DataModel, UIStore } from 'src/app/services/patch-db/data-model' -import { MarketplaceService } from 'src/app/services/marketplace.service' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormPage } from 'src/app/apps/ui/modals/form/form.page' -import { ConfigService } from 'src/app/services/config.service' - -@Component({ - selector: 'marketplace-settings', - templateUrl: 'marketplace-settings.page.html', - styleUrls: ['marketplace-settings.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MarketplaceSettingsPage { - constructor( - private readonly api: ApiService, - private readonly loader: LoadingService, - private readonly formDialog: FormDialogService, - private readonly errorService: ErrorService, - @Inject(AbstractMarketplaceService) - private readonly marketplaceService: MarketplaceService, - private readonly patch: PatchDB, - private readonly dialogs: TuiDialogService, - readonly config: ConfigService, - ) {} - - stores$ = combineLatest([ - this.marketplaceService.getKnownHosts$(), - this.marketplaceService.getSelectedHost$(), - ]).pipe( - map(([stores, selected]) => { - const toSlice = stores.map(s => ({ - ...s, - selected: sameUrl(s.url, selected.url), - })) - // 0 and 1 are prod and community - const standard = toSlice.slice(0, 2) - // 2 and beyond are alts - const alt = toSlice.slice(2) - - return { standard, alt } - }), - ) - - async presentModalAdd() { - const { name, spec } = getMarketplaceValueSpec() - - this.formDialog.open(FormPage, { - label: name, - data: { - spec, - buttons: [ - { - text: 'Save for Later', - handler: async (value: { url: string }) => this.saveOnly(value.url), - }, - { - text: 'Save and Connect', - handler: async (value: { url: string }) => - this.saveAndConnect(value.url), - isSubmit: true, - }, - ], - }, - }) - } - - async presentAlertDelete(url: string, name: string = '') { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: `Are you sure you want to delete ${name}?`, - yes: 'Delete', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.delete(url)) - } - - async connect( - url: string, - loader: Subscription = new Subscription(), - ): Promise { - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open('Changing Registry...').subscribe()) - - try { - await this.api.setDbValue(['marketplace', 'selected-url'], url) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async saveOnly(rawUrl: string): Promise { - const loader = this.loader.open('Loading').subscribe() - - try { - const url = new URL(rawUrl).toString() - await this.validateAndSave(url, loader) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private async saveAndConnect(rawUrl: string): Promise { - const loader = this.loader.open('Loading').subscribe() - - try { - const url = new URL(rawUrl).toString() - await this.validateAndSave(url, loader) - await this.connect(url, loader) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private async validateAndSave( - url: string, - loader: Subscription, - ): Promise { - // Error on duplicates - const hosts = await firstValueFrom( - this.patch.watch$('ui', 'marketplace', 'known-hosts'), - ) - const currentUrls = Object.keys(hosts).map(toUrl) - if (currentUrls.includes(url)) throw new Error('marketplace already added') - - // Validate - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open('Validating marketplace...').subscribe()) - - const { name } = await firstValueFrom( - this.marketplaceService.fetchInfo$(url), - ) - - // Save - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open('Saving...').subscribe()) - - await this.api.setDbValue<{ name: string }>( - ['marketplace', 'known-hosts', url], - { name }, - ) - } - - private async delete(url: string): Promise { - const loader = this.loader.open('Deleting...').subscribe() - - const hosts = await firstValueFrom( - this.patch.watch$('ui', 'marketplace', 'known-hosts'), - ) - - const filtered: { [url: string]: UIStore } = Object.keys(hosts) - .filter(key => !sameUrl(key, url)) - .reduce((prev, curr) => { - const name = hosts[curr] - return { - ...prev, - [curr]: name, - } - }, {}) - - try { - await this.api.setDbValue<{ [url: string]: UIStore }>( - ['marketplace', 'known-hosts'], - filtered, - ) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} - -function getMarketplaceValueSpec(): ValueSpecObject { - return { - type: 'object', - name: 'Add Custom Registry', - description: null, - warning: null, - spec: { - url: { - type: 'text', - name: 'URL', - description: 'A fully-qualified URL of the custom registry', - inputmode: 'url', - required: true, - masked: false, - minLength: null, - maxLength: null, - patterns: [ - { - regex: `https?:\/\/[a-zA-Z0-9][a-zA-Z0-9-\.]+[a-zA-Z0-9]\.[^\s]{2,}`, - description: 'Must be a valid URL', - }, - ], - placeholder: 'e.g. https://example.org', - default: null, - warning: null, - disabled: false, - immutable: false, - generate: null, - }, - }, - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/components/marketplace-show-controls.component.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/components/marketplace-show-controls.component.ts deleted file mode 100644 index 8bf8b0548..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/components/marketplace-show-controls.component.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Inject, - inject, - Input, -} from '@angular/core' -import { - AbstractMarketplaceService, - MarketplacePkg, - AboutModule, - AdditionalModule, - DependenciesModule, -} from '@start9labs/marketplace' -import { - Emver, - ErrorService, - isEmptyObject, - LoadingService, - pauseFor, - sameUrl, - EmverPipesModule, - MarkdownPipeModule, - SharedPipesModule, - TextSpinnerComponentModule, -} from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { filter, firstValueFrom, of, Subscription, switchMap } from 'rxjs' -import { - DataModel, - PackageDataEntry, - PackageState, -} from 'src/app/services/patch-db/data-model' -import { ClientStorageService } from 'src/app/services/client-storage.service' -import { MarketplaceService } from 'src/app/services/marketplace.service' -import { hasCurrentDeps } from 'src/app/util/has-deps' -import { PatchDB } from 'patch-db-client' -import { getAllPackages } from 'src/app/util/get-package-data' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { dryUpdate } from 'src/app/util/dry-update' -import { Router } from '@angular/router' -import { SidebarService } from 'src/app/services/sidebar.service' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule } from '@angular/router' - -import { TuiButtonModule } from '@taiga-ui/experimental' - -@Component({ - selector: 'marketplace-show-controls', - template: ` -
- - - - - - - - - - - - - - -
- `, - styles: [ - ` - button { - --tui-padding: 1.5rem; - } - `, - ], - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - CommonModule, - IonicModule, - RouterModule, - TextSpinnerComponentModule, - SharedPipesModule, - EmverPipesModule, - MarkdownPipeModule, - AboutModule, - DependenciesModule, - AdditionalModule, - TuiButtonModule, - ], -}) -export class MarketplaceShowControlsComponent { - @Input() - url?: string - - @Input({ required: true }) - pkg!: MarketplacePkg - - @Input() - localPkg!: PackageDataEntry | null - - readonly showDevTools$ = this.ClientStorageService.showDevTools$ - readonly PackageState = PackageState - private readonly router = inject(Router) - readonly sidebarService = inject(SidebarService) - - constructor( - private readonly dialogs: TuiDialogService, - private readonly ClientStorageService: ClientStorageService, - @Inject(AbstractMarketplaceService) - private readonly marketplaceService: MarketplaceService, - private readonly loader: LoadingService, - private readonly emver: Emver, - private readonly errorService: ErrorService, - private readonly patch: PatchDB, - ) {} - - get localVersion(): string { - return this.localPkg?.manifest.version || '' - } - - async tryInstall() { - this.sidebarService.toggleState(this.pkg.manifest.id, false) - const currentMarketplace = await firstValueFrom( - this.marketplaceService.getSelectedHost$(), - ) - const url = this.url || currentMarketplace.url - - if (!this.localPkg) { - this.alertInstall(url) - } else { - const originalUrl = this.localPkg.installed?.['marketplace-url'] - - if (!sameUrl(url, originalUrl)) { - const proceed = await this.presentAlertDifferentMarketplace( - url, - originalUrl, - ) - if (!proceed) return - } - - const currentDeps = hasCurrentDeps(this.localPkg) - if ( - currentDeps && - this.emver.compare(this.localVersion, this.pkg.manifest.version) !== 0 - ) { - this.dryInstall(url) - } else { - this.install(url) - } - } - } - - async showService() { - this.sidebarService.toggleState(this.pkg.manifest.id, false) - // @TODO code smell - needed to close preview - likely due to sidebar animation - await pauseFor(300) - this.router.navigate(['/services', this.pkg.manifest.id]) - } - - private async presentAlertDifferentMarketplace( - url: string, - originalUrl: string | null | undefined, - ): Promise { - const marketplaces = await firstValueFrom( - this.patch.watch$('ui', 'marketplace'), - ) - - const name: string = marketplaces['known-hosts'][url]?.name || url - - let originalName: string | undefined - if (originalUrl) { - originalName = - marketplaces['known-hosts'][originalUrl]?.name || originalUrl - } - - return new Promise(async resolve => { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content: `This service was originally ${ - originalName ? 'installed from ' + originalName : 'side loaded' - }, but you are currently connected to ${name}. To install from ${name} anyway, click "Continue".`, - yes: 'Continue', - no: 'Cancel', - }, - }) - .subscribe(response => resolve(response)) - }) - } - - private async dryInstall(url: string) { - const breakages = dryUpdate( - this.pkg.manifest, - await getAllPackages(this.patch), - this.emver, - ) - - if (isEmptyObject(breakages)) { - this.install(url) - } else { - const proceed = await this.presentAlertBreakages(breakages) - if (proceed) { - this.install(url) - } - } - } - - private alertInstall(url: string) { - of(this.pkg.manifest.alerts.install) - .pipe( - switchMap(content => - !content - ? of(true) - : this.dialogs.open(TUI_PROMPT, { - label: 'Alert', - size: 's', - data: { - content, - yes: 'Install', - no: 'Cancel', - }, - }), - ), - filter(Boolean), - ) - .subscribe(() => this.install(url)) - } - - private async install(url: string, loader?: Subscription) { - const message = 'Beginning Install...' - - if (loader) { - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open(message).subscribe()) - } else { - loader = this.loader.open(message).subscribe() - } - - const { id, version } = this.pkg.manifest - - try { - await this.marketplaceService.installPackage(id, version, url) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async presentAlertBreakages(breakages: string[]): Promise { - let content: string = - 'As a result of this update, the following services will no longer work properly and may crash:
    ' - const bullets = breakages.map(title => `
  • ${title}
  • `) - content = `${content}${bullets.join('')}
` - - return new Promise(async resolve => { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content, - yes: 'Continue', - no: 'Cancel', - }, - }) - .subscribe(response => resolve(response)) - }) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.component.html b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.component.html deleted file mode 100644 index e9b39e0ce..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.component.html +++ /dev/null @@ -1,47 +0,0 @@ -
- - - - - - - - View more details - -
- -
-
-

Dependencies

-
-
- -
-
-
-
- - -
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.component.scss b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.component.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.component.ts deleted file mode 100644 index 74118a4e6..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.component.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Inject, - inject, - Input, -} from '@angular/core' -import { BehaviorSubject, map } from 'rxjs' -import { - TuiDialogContext, - TuiDialogService, - TuiDurationOptions, - tuiFadeIn, -} from '@taiga-ui/core' -import { tuiPure } from '@taiga-ui/cdk' -import { PolymorpheusContent } from '@tinkoff/ng-polymorpheus' -import { isPlatform } from '@ionic/angular' -import { - AbstractMarketplaceService, - MarketplacePkg, -} from '@start9labs/marketplace' -import { SidebarService } from 'src/app/services/sidebar.service' - -@Component({ - selector: 'marketplace-show-preview', - templateUrl: './marketplace-show-preview.component.html', - styleUrls: ['./marketplace-show-preview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - animations: [tuiFadeIn], -}) -export class MarketplaceShowPreviewComponent { - @Input({ required: true }) - pkg!: MarketplacePkg - - constructor( - @Inject(TuiDialogService) private readonly dialogs: TuiDialogService, - ) {} - - readonly sidebarService = inject(SidebarService) - private readonly marketplaceService = inject(AbstractMarketplaceService) - readonly version$ = new BehaviorSubject('*') - index = 0 - speed = 1000 - isMobile = isPlatform(window, 'ios') || isPlatform(window, 'android') - url$ = this.marketplaceService.getSelectedHost$().pipe(map(({ url }) => url)) - - @tuiPure - getAnimation(duration: number): TuiDurationOptions { - return { value: '', params: { duration } } - } - - presentModalImg(content: PolymorpheusContent) { - this.dialogs - .open(content, { - size: 'l', - }) - .subscribe() - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.module.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.module.ts deleted file mode 100644 index 58bc3bf88..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-show-preview/marketplace-show-preview.module.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { - SharedPipesModule, - TextSpinnerComponentModule, -} from '@start9labs/shared' -import { - AboutModule, - AdditionalModule, - DependenciesModule, - MarketplacePackageHeroComponent, - ReleaseNotesModule, -} from '@start9labs/marketplace' -import { MarketplaceShowPreviewComponent } from './marketplace-show-preview.component' - -import { TuiButtonModule } from '@taiga-ui/experimental' -import { RouterModule } from '@angular/router' - -@NgModule({ - declarations: [MarketplaceShowPreviewComponent], - exports: [MarketplaceShowPreviewComponent], - imports: [ - CommonModule, - SharedPipesModule, - TextSpinnerComponentModule, - RouterModule, - DependenciesModule, - AdditionalModule, - ReleaseNotesModule, - TuiButtonModule, - AboutModule, - MarketplacePackageHeroComponent, - ], -}) -export class MarketplaceShowPreviewModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.component.html b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.component.html deleted file mode 100644 index 15837d03d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.component.html +++ /dev/null @@ -1,32 +0,0 @@ - -
- - Installed - - - Update Available - -
-
- - Removing - - -
-
- - Installing - - {{ progress }} - -
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.component.scss b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.component.scss deleted file mode 100644 index c1ad62772..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -ion-text { - font-weight: bold; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.component.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.component.ts deleted file mode 100644 index 3c4ad7c8a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, Input } from '@angular/core' -import { - PackageDataEntry, - PackageState, -} from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'marketplace-status', - templateUrl: 'marketplace-status.component.html', - styleUrls: ['marketplace-status.component.scss'], -}) -export class MarketplaceStatusComponent { - @Input({ required: true }) version!: string - @Input() localPkg?: PackageDataEntry - - PackageState = PackageState - - get localVersion(): string { - return this.localPkg?.manifest.version || '' - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.module.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.module.ts deleted file mode 100644 index 5f19ea352..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace-status/marketplace-status.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { IonicModule } from '@ionic/angular' -import { EmverPipesModule } from '@start9labs/shared' -import { InstallProgressPipeModule } from 'src/app/common/install-progress/install-progress.module' -import { MarketplaceStatusComponent } from './marketplace-status.component' - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - EmverPipesModule, - InstallProgressPipeModule, - ], - declarations: [MarketplaceStatusComponent], - exports: [MarketplaceStatusComponent], -}) -export class MarketplaceStatusModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace.module.ts b/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace.module.ts deleted file mode 100644 index cbe1a5afb..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/marketplace/marketplace.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' - -const routes: Routes = [ - { - path: '', - pathMatch: 'full', - loadChildren: () => - import('./marketplace-list/marketplace-list.module').then( - m => m.MarketplaceListPageModule, - ), - }, -] - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class MarketplaceModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.module.ts b/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.module.ts deleted file mode 100644 index 2f50677a3..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { SharedPipesModule } from '@start9labs/shared' -import { TuiPromptModule } from '@taiga-ui/kit' -import { NotificationsPage } from './notifications.page' -import { BadgeMenuComponentModule } from 'src/app/common/badge-menu-button/badge-menu.component.module' -import { BackupReportPageModule } from '../../modals/backup-report/backup-report.module' - -const routes: Routes = [ - { - path: '', - component: NotificationsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - BadgeMenuComponentModule, - SharedPipesModule, - BackupReportPageModule, - TuiPromptModule, - ], - declarations: [NotificationsPage], -}) -export class NotificationsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.page.html b/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.page.html deleted file mode 100644 index a09112cd5..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.page.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - Notifications - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -

- Important system alerts and notifications from StartOS will display - here -

-
-
- - - - - - - Delete All - - - - -

- - - {{ $any(packageData[pkgId])?.manifest.title || pkgId }} - - - {{ not.title }} - -

-

{{ truncate(not.message) }}

-

- - View Full Message - -

-

{{ not['created-at'] | date: 'medium' }}

-
- - View Report - - - View Service - - - - -
-
- - - - -
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.page.scss b/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.page.scss deleted file mode 100644 index 0dee98ade..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.page.scss +++ /dev/null @@ -1,11 +0,0 @@ -.notification-message { - margin: 6px 0 8px 0; -} - -.view-message-tag { - margin-bottom: 8px; - font-size: 16px; - a { - cursor: pointer; - } -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.page.ts b/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.page.ts deleted file mode 100644 index a057aaf6d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/notifications/notifications.page.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { Component } from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { PatchDB } from 'patch-db-client' -import { filter, first } from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { - ServerNotifications, - NotificationLevel, - ServerNotification, -} from 'src/app/services/api/api.types' -import { BackupReportComponent } from '../../modals/backup-report/backup-report.component' -import { DataModel } from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'notifications', - templateUrl: 'notifications.page.html', - styleUrls: ['notifications.page.scss'], -}) -export class NotificationsPage { - loading = true - notifications: ServerNotifications = [] - beforeCursor?: number - needInfinite = false - fromToast = !!this.route.snapshot.queryParamMap.get('toast') - readonly perPage = 40 - readonly packageData$ = this.patch.watch$('package-data').pipe(first()) - - constructor( - private readonly embassyApi: ApiService, - private readonly loader: LoadingService, - private readonly dialogs: TuiDialogService, - private readonly errorService: ErrorService, - private readonly route: ActivatedRoute, - private readonly patch: PatchDB, - ) {} - - async ngOnInit() { - this.notifications = await this.getNotifications() - this.loading = false - } - - async doInfinite(e: any) { - const notifications = await this.getNotifications() - this.notifications = this.notifications.concat(notifications) - e.target.complete() - } - - async getNotifications(): Promise { - try { - const notifications = await this.embassyApi.getNotifications({ - before: this.beforeCursor, - limit: this.perPage, - }) - - if (!notifications) return [] - - this.beforeCursor = notifications[notifications.length - 1]?.id - this.needInfinite = notifications.length >= this.perPage - - return notifications - } catch (e: any) { - this.errorService.handleError(e) - } - - return [] - } - - async delete(id: number, index: number): Promise { - const loader = this.loader.open('Deleting...').subscribe() - - try { - // await this.embassyApi.deleteNotification({ id }) - this.notifications.splice(index, 1) - this.beforeCursor = this.notifications[this.notifications.length - 1]?.id - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - presentAlertDeleteAll() { - this.dialogs - .open(TUI_PROMPT, { - label: 'Delete All?', - size: 's', - data: { - content: 'Are you sure you want to delete all notifications?', - yes: 'Delete', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.deleteAll()) - } - - async viewBackupReport(notification: ServerNotification) { - this.dialogs - .open(new PolymorpheusComponent(BackupReportComponent), { - label: 'Backup Report', - data: { - report: notification.data, - timestamp: notification['created-at'], - }, - }) - .subscribe() - } - - viewFullMessage(label: string, message: string) { - this.dialogs.open(message, { label }).subscribe() - } - - truncate(message: string): string { - return message.length <= 240 ? message : '...' + message.substr(-240) - } - - getColor({ level }: ServerNotification): string { - switch (level) { - case NotificationLevel.Info: - return 'primary' - case NotificationLevel.Success: - return 'success' - case NotificationLevel.Warning: - return 'warning' - case NotificationLevel.Error: - return 'danger' - default: - return '' - } - } - - private async deleteAll(): Promise { - const loader = this.loader.open('Deleting...').subscribe() - - try { - // await this.embassyApi.deleteAllNotifications({ - // before: this.notifications[0].id + 1, - // }) - this.notifications = [] - this.beforeCursor = undefined - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/action-success/action-success.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-actions/action-success/action-success.module.ts deleted file mode 100644 index 248dd430e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/action-success/action-success.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { QrCodeModule } from 'ng-qrcode' - -import { ActionSuccessPage } from './action-success.page' - -@NgModule({ - declarations: [ActionSuccessPage], - imports: [CommonModule, IonicModule, QrCodeModule], - exports: [ActionSuccessPage], -}) -export class ActionSuccessPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/action-success/action-success.page.html b/web/projects/ui/src/app/apps/ui/pages/services/app-actions/action-success/action-success.page.html deleted file mode 100644 index ed8babcc0..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/action-success/action-success.page.html +++ /dev/null @@ -1,22 +0,0 @@ -

{{ actionRes.message }}

- -
-
- -
- -

{{ actionRes.value }}

- - {{ actionRes.value }} - - - - -
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/action-success/action-success.page.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-actions/action-success/action-success.page.ts deleted file mode 100644 index f4b390269..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/action-success/action-success.page.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, Inject } from '@angular/core' -import { CopyService } from '@start9labs/shared' -import { TuiDialogContext } from '@taiga-ui/core' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { ActionResponse } from 'src/app/services/api/api.types' - -@Component({ - selector: 'action-success', - templateUrl: './action-success.page.html', -}) -export class ActionSuccessPage { - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext, - readonly copyService: CopyService, - ) {} - - get actionRes(): ActionResponse { - return this.context.data - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions-item.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions-item.component.html deleted file mode 100644 index 71bcc6e40..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions-item.component.html +++ /dev/null @@ -1,7 +0,0 @@ - - - -

{{ action.name }}

-

{{ action.description }}

-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.module.ts deleted file mode 100644 index 6e25ca6c9..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { SharedPipesModule } from '@start9labs/shared' -import { FormPageModule } from 'src/app/apps/ui/modals/form/form.module' -import { ActionSuccessPageModule } from './action-success/action-success.module' -import { - AppActionsPage, - AppActionsItemComponent, - GroupActionsPipe, -} from './app-actions.page' - -const routes: Routes = [ - { - path: '', - component: AppActionsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - SharedPipesModule, - ActionSuccessPageModule, - FormPageModule, - RouterModule.forChild(routes), - ], - declarations: [AppActionsPage, AppActionsItemComponent, GroupActionsPipe], -}) -export class AppActionsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.page.html b/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.page.html deleted file mode 100644 index beb8bb339..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.page.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - Actions - - - - - - - Standard Actions - - - - - Actions for {{ pkg.manifest.title }} -
- -
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.page.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.page.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.page.ts deleted file mode 100644 index f3ee07539..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-actions/app-actions.page.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Input, - Pipe, - PipeTransform, -} from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { NavController } from '@ionic/angular' -import { - isEmptyObject, - getPkgId, - WithId, - ErrorService, - LoadingService, -} from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { PatchDB } from 'patch-db-client' -import { filter, switchMap, timer } from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { - Action, - DataModel, - PackageDataEntry, - PackageState, -} from 'src/app/services/patch-db/data-model' -import { ActionSuccessPage } from './action-success/action-success.page' -import { hasCurrentDeps } from 'src/app/util/has-deps' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormPage } from 'src/app/apps/ui/modals/form/form.page' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { TUI_PROMPT } from '@taiga-ui/kit' - -@Component({ - selector: 'app-actions', - templateUrl: './app-actions.page.html', - styleUrls: ['./app-actions.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppActionsPage { - readonly pkgId = getPkgId(this.route) - readonly pkg$ = this.patch - .watch$('package-data', this.pkgId) - .pipe(filter(pkg => pkg.state === PackageState.Installed)) - - constructor( - private readonly route: ActivatedRoute, - private readonly embassyApi: ApiService, - private readonly dialogs: TuiDialogService, - private readonly errorService: ErrorService, - private readonly loader: LoadingService, - private readonly navCtrl: NavController, - private readonly patch: PatchDB, - private readonly formDialog: FormDialogService, - ) {} - - async handleAction(action: WithId) { - if (action.disabled) { - this.dialogs - .open(action.disabled, { - label: 'Forbidden', - size: 's', - }) - .subscribe() - } else { - if (action['input-spec'] && !isEmptyObject(action['input-spec'])) { - this.formDialog.open(FormPage, { - label: action.name, - data: { - spec: action['input-spec'], - buttons: [ - { - text: 'Execute', - handler: async (value: any) => - this.executeAction(action.id, value), - }, - ], - }, - }) - } else { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: `Are you sure you want to execute action "${ - action.name - }"? ${action.warning || ''}`, - yes: 'Execute', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.executeAction(action.id)) - } - } - } - - async tryUninstall(pkg: PackageDataEntry): Promise { - const { title, alerts, id } = pkg.manifest - - let content = - alerts.uninstall || - `Uninstalling ${title} will permanently delete its data` - - if (hasCurrentDeps(pkg)) { - content = `${content}. Services that depend on ${title} will no longer work properly and may crash` - } - - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content, - yes: 'Uninstall', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.uninstall()) - } - - private async uninstall() { - const loader = this.loader.open(`Beginning uninstall...`).subscribe() - - try { - await this.embassyApi.uninstallPackage({ id: this.pkgId }) - this.embassyApi - .setDbValue(['ack-instructions', this.pkgId], false) - .catch(e => console.error('Failed to mark instructions as unseen', e)) - this.navCtrl.navigateRoot('/services') - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async executeAction( - actionId: string, - input?: object, - ): Promise { - const loader = this.loader.open('Executing action...').subscribe() - - try { - const data = await this.embassyApi.executePackageAction({ - id: this.pkgId, - 'action-id': actionId, - input, - }) - - timer(500) - .pipe( - switchMap(() => - this.dialogs.open(new PolymorpheusComponent(ActionSuccessPage), { - label: 'Execution Complete', - data, - }), - ), - ) - .subscribe() - - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - asIsOrder() { - return 0 - } -} - -interface LocalAction { - name: string - description: string - icon: string -} - -@Component({ - selector: 'app-actions-item', - templateUrl: './app-actions-item.component.html', - styleUrls: ['./app-actions.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppActionsItemComponent { - @Input({ required: true }) action!: LocalAction -} - -@Pipe({ - name: 'groupActions', -}) -export class GroupActionsPipe implements PipeTransform { - transform( - actions: PackageDataEntry['actions'], - ): Array>> | null { - if (!actions) return null - const noGroup = 'noGroup' - const grouped = Object.entries(actions).reduce< - Record[]> - >((groups, [id, action]) => { - const actionWithId = { id, ...action } - const groupKey = action.group || noGroup - if (!groups[groupKey]) { - groups[groupKey] = [actionWithId] - } else { - groups[groupKey].push(actionWithId) - } - return groups - }, {}) - - return Object.values(grouped).map(group => - group.sort((a, b) => a.name.localeCompare(b.name)), - ) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.module.ts deleted file mode 100644 index 26e01e0ed..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { AppCredentialsPage } from './app-credentials.page' -import { - SharedPipesModule, - TextSpinnerComponentModule, -} from '@start9labs/shared' -import { SkeletonListComponentModule } from 'src/app/common/skeleton-list/skeleton-list.component.module' - -const routes: Routes = [ - { - path: '', - component: AppCredentialsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - SharedPipesModule, - TextSpinnerComponentModule, - SkeletonListComponentModule, - ], - declarations: [AppCredentialsPage], -}) -export class AppCredentialsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.page.html b/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.page.html deleted file mode 100644 index f2f9ecb18..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.page.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - Credentials - - - - Refresh - - - - - - - - - - - - - - -

No credentials

-
-
- - - - - -

{{ cred.key }}

-

- {{ unmasked[cred.key] ? cred.value : mask(cred.value) }} -

-
-
- - - - - - -
-
-
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.page.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.page.scss deleted file mode 100644 index 36fb91260..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.page.scss +++ /dev/null @@ -1,4 +0,0 @@ -ion-note { - font-size: 16px; - color: white; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.page.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.page.ts deleted file mode 100644 index b19350897..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-credentials/app-credentials.page.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Component } from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { getPkgId, CopyService, ErrorService } from '@start9labs/shared' -import { mask } from 'src/app/util/mask' - -@Component({ - selector: 'app-credentials', - templateUrl: './app-credentials.page.html', - styleUrls: ['./app-credentials.page.scss'], -}) -export class AppCredentialsPage { - readonly pkgId = getPkgId(this.route) - credentials: Record = {} - unmasked: { [key: string]: boolean } = {} - loading = true - - constructor( - private readonly route: ActivatedRoute, - private readonly embassyApi: ApiService, - private readonly errorService: ErrorService, - readonly copyService: CopyService, - ) {} - - async ngOnInit() { - await this.getCredentials() - } - - async refresh() { - await this.getCredentials() - } - - mask(value: string) { - return mask(value, 64) - } - - toggleMask(key: string) { - this.unmasked[key] = !this.unmasked[key] - } - - private async getCredentials(): Promise { - this.loading = true - try { - this.credentials = await this.embassyApi.getPackageCredentials({ - id: this.pkgId, - }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - this.loading = false - } - } - - asIsOrder(a: any, b: any) { - return 0 - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.module.ts deleted file mode 100644 index 7d418114b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { AppInterfacePage } from './app-interface.page' -import { InterfaceAddressesComponentModule } from 'src/app/common/interface-addresses/interface-addresses.module' - -const routes: Routes = [ - { - path: '', - component: AppInterfacePage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - InterfaceAddressesComponentModule, - ], - declarations: [AppInterfacePage], -}) -export class AppInterfacePageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.page.html b/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.page.html deleted file mode 100644 index 21f4b6c18..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.page.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - {{ interfaceInfo.name }} - - - - -
- -
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.page.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.page.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.page.ts deleted file mode 100644 index a9d57952a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-interface/app-interface.page.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { PatchDB } from 'patch-db-client' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { getPkgId } from '@start9labs/shared' - -@Component({ - selector: 'app-interface', - templateUrl: './app-interface.page.html', - styleUrls: ['./app-interface.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppInterfacePage { - readonly pkgId = getPkgId(this.route) - readonly interfaceId = this.route.snapshot.paramMap.get('interfaceId')! - - readonly interfaceInfo$ = this.patch.watch$( - 'package-data', - this.pkgId, - 'installed', - 'interfaceInfo', - this.interfaceId, - ) - - constructor( - private readonly route: ActivatedRoute, - private readonly patch: PatchDB, - ) {} -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-icon/app-list-icon.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-icon/app-list-icon.component.html deleted file mode 100644 index 90bda33b9..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-icon/app-list-icon.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - -
-
-
-
- - -
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-icon/app-list-icon.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-icon/app-list-icon.component.scss deleted file mode 100644 index d189732b5..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-icon/app-list-icon.component.scss +++ /dev/null @@ -1,22 +0,0 @@ -.bulb { - position: absolute !important; - top: 9px !important; - height: 14px; - width: 14px; - border-radius: 100%; -} - -.warning-icon { - position: absolute !important; - top: 8px !important; - left: 11px !important; - font-size: 12px; - border-radius: 100%; - padding: 1px; -} - -.spinner { - position: absolute !important; - top: 6px !important; - width: 18px; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-icon/app-list-icon.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-icon/app-list-icon.component.ts deleted file mode 100644 index 2692a91fe..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-icon/app-list-icon.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { ConnectionService } from 'src/app/services/connection.service' -import { PkgInfo } from 'src/app/types/pkg-info' - -@Component({ - selector: 'app-list-icon', - templateUrl: 'app-list-icon.component.html', - styleUrls: ['app-list-icon.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppListIconComponent { - @Input({ required: true }) - pkg!: PkgInfo - - readonly connected$ = this.connectionService.connected$ - - constructor(private readonly connectionService: ConnectionService) {} -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/app-list-pkg.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/app-list-pkg.component.html deleted file mode 100644 index 52c2bf14a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/app-list-pkg.component.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - -

{{ manifest.title }}

-

{{ manifest.version | displayEmver }}

- -
- - - - - - - - -
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/app-list-pkg.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/app-list-pkg.component.scss deleted file mode 100644 index c7a3fb9de..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/app-list-pkg.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -img { - border-radius: 100%; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/app-list-pkg.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/app-list-pkg.component.ts deleted file mode 100644 index dcb792718..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/app-list-pkg.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Inject, - Input, - ViewChild, -} from '@angular/core' -import { LaunchMenuComponent } from './launch-menu/launch-menu.component' -import { PackageMainStatus } from 'src/app/services/patch-db/data-model' -import { PkgInfo } from 'src/app/types/pkg-info' -import { DOCUMENT } from '@angular/common' - -@Component({ - selector: 'app-list-pkg', - templateUrl: 'app-list-pkg.component.html', - styleUrls: ['app-list-pkg.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppListPkgComponent { - @ViewChild('launchMenu') launchMenu!: LaunchMenuComponent - - @Input({ required: true }) - pkg!: PkgInfo - - get status(): PackageMainStatus { - return ( - this.pkg.entry.installed?.status.main.status || PackageMainStatus.Stopped - ) - } - - constructor(@Inject(DOCUMENT) private readonly document: Document) {} - - openPopover(e: Event): void { - e.stopPropagation() - e.preventDefault() - this.launchMenu.event = e - this.launchMenu.isOpen = true - } - - launchUI(address: string, e: Event) { - e.stopPropagation() - e.preventDefault() - this.document.defaultView?.open(address, '_blank', 'noreferrer') - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.component.html deleted file mode 100644 index c54aa4b25..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.component.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -

{{ iface.name }}

-

{{ iface.address }}

-
- -
-
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.component.scss deleted file mode 100644 index 70adf02c0..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -ion-popover { - --min-width: 360px; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.component.ts deleted file mode 100644 index b6f4bfd22..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { DOCUMENT } from '@angular/common' -import { - ChangeDetectionStrategy, - Component, - Inject, - Input, - ViewChild, -} from '@angular/core' -import { InstalledPackageInfo } from 'src/app/services/patch-db/data-model' -import { LaunchableInterface } from '../launchable-interfaces.pipe' - -@Component({ - selector: 'launch-menu', - templateUrl: 'launch-menu.component.html', - styleUrls: ['launch-menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class LaunchMenuComponent { - @ViewChild('popover') popover!: HTMLIonPopoverElement - - @Input({ required: true }) - launchableInterfaces!: LaunchableInterface[] - - set isOpen(open: boolean) { - this.popover.isOpen = open - } - - set event(event: Event) { - this.popover.event = event - } - - constructor(@Inject(DOCUMENT) private readonly document: Document) {} - - launchUI(address: string) { - this.document.defaultView?.open(address, '_blank', 'noreferrer') - this.popover.isOpen = false - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.module.ts deleted file mode 100644 index eba343572..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launch-menu/launch-menu.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { LaunchMenuComponent } from './launch-menu.component' - -@NgModule({ - declarations: [LaunchMenuComponent], - imports: [CommonModule, IonicModule], - exports: [LaunchMenuComponent], -}) -export class LaunchMenuComponentModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launchable-interfaces.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launchable-interfaces.pipe.ts deleted file mode 100644 index 8da2a0319..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list-pkg/launchable-interfaces.pipe.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { ConfigService } from 'src/app/services/config.service' -import { InstalledPackageInfo } from 'src/app/services/patch-db/data-model' - -@Pipe({ - name: 'launchableInterfaces', -}) -export class LaunchableInterfacesPipe implements PipeTransform { - constructor(private readonly config: ConfigService) {} - - transform( - interfaceInfo: InstalledPackageInfo['interfaceInfo'], - ): LaunchableInterface[] { - return Object.values(interfaceInfo) - .filter(info => info.type === 'ui') - .map(info => ({ - name: info.name, - address: this.config.launchableAddress(info), - })) - } -} - -export type LaunchableInterface = { - name: string - address: string -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.module.ts deleted file mode 100644 index 87b65a33c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.module.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { AppListPage } from './app-list.page' -import { - EmverPipesModule, - ResponsiveColDirective, - TextSpinnerComponentModule, - TickerModule, -} from '@start9labs/shared' -import { BadgeMenuComponentModule } from 'src/app/common/badge-menu-button/badge-menu.component.module' -import { WidgetListComponentModule } from 'src/app/common/widget-list/widget-list.component.module' -import { StatusComponentModule } from '../status/status.component.module' -import { AppListIconComponent } from './app-list-icon/app-list-icon.component' -import { AppListPkgComponent } from './app-list-pkg/app-list-pkg.component' -import { PackageInfoPipe } from './package-info.pipe' -import { LaunchMenuComponentModule } from './app-list-pkg/launch-menu/launch-menu.module' -import { LaunchableInterfacesPipe } from './app-list-pkg/launchable-interfaces.pipe' - -const routes: Routes = [ - { - path: '', - component: AppListPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - StatusComponentModule, - EmverPipesModule, - TextSpinnerComponentModule, - IonicModule, - RouterModule.forChild(routes), - BadgeMenuComponentModule, - WidgetListComponentModule, - ResponsiveColDirective, - TickerModule, - LaunchMenuComponentModule, - ], - declarations: [ - AppListPage, - AppListIconComponent, - AppListPkgComponent, - PackageInfoPipe, - LaunchableInterfacesPipe, - ], -}) -export class AppListPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.page.html b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.page.html deleted file mode 100644 index 3bc38f762..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.page.html +++ /dev/null @@ -1,43 +0,0 @@ - - - Installed Services - - - - - - - - - - -
-

Welcome to StartOS

-
- -
- - - - - - - - - - -
- - - - - -
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.page.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.page.scss deleted file mode 100644 index 360689817..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.page.scss +++ /dev/null @@ -1,9 +0,0 @@ -.welcome-header { - padding-bottom: 1rem; - text-align: center; - - h1 { - font-weight: bold; - font-size: 2rem; - } -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.page.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.page.ts deleted file mode 100644 index a16ae01e4..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/app-list.page.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { filter, map, pairwise, startWith } from 'rxjs' -import { DataModel } from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'app-list', - templateUrl: './app-list.page.html', - styleUrls: ['./app-list.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppListPage { - readonly pkgs$ = this.patch.watch$('package-data').pipe( - map(pkgs => Object.values(pkgs)), - startWith([]), - pairwise(), - filter(([prev, next]) => { - const length = next.length - return !length || prev.length !== length - }), - map(([_, pkgs]) => - pkgs.sort((a, b) => - b.manifest.title.toLowerCase() > a.manifest.title.toLowerCase() - ? -1 - : 1, - ), - ), - ) - - constructor(private readonly patch: PatchDB) {} -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-list/package-info.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-list/package-info.pipe.ts deleted file mode 100644 index 96b28172e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-list/package-info.pipe.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { Observable, combineLatest } from 'rxjs' -import { filter, map } from 'rxjs/operators' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { getPackageInfo } from 'src/app/util/get-package-info' -import { PkgInfo } from 'src/app/types/pkg-info' -import { PatchDB } from 'patch-db-client' -import { DepErrorService } from 'src/app/services/dep-error.service' - -@Pipe({ - name: 'packageInfo', -}) -export class PackageInfoPipe implements PipeTransform { - constructor( - private readonly patch: PatchDB, - private readonly depErrorService: DepErrorService, - ) {} - - transform(pkgId: string): Observable { - return combineLatest([ - this.patch.watch$('package-data', pkgId).pipe(filter(Boolean)), - this.depErrorService.getPkgDepErrors$(pkgId), - ]).pipe(map(([pkg, depErrors]) => getPackageInfo(pkg, depErrors))) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.module.ts deleted file mode 100644 index cbaa4b59a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { AppLogsPage } from './app-logs.page' -import { LogsComponentModule } from 'src/app/common/logs/logs.component.module' - -const routes: Routes = [ - { - path: '', - component: AppLogsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - LogsComponentModule, - ], - declarations: [AppLogsPage], -}) -export class AppLogsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.page.html b/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.page.html deleted file mode 100644 index 4617646f5..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.page.html +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.page.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.page.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.page.ts deleted file mode 100644 index 103d0bc0e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-logs/app-logs.page.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Component } from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { getPkgId } from '@start9labs/shared' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { RR } from 'src/app/services/api/api.types' - -@Component({ - selector: 'app-logs', - templateUrl: './app-logs.page.html', - styleUrls: ['./app-logs.page.scss'], -}) -export class AppLogsPage { - readonly pkgId = getPkgId(this.route) - - constructor( - private readonly route: ActivatedRoute, - private readonly embassyApi: ApiService, - ) {} - - followLogs() { - return async (params: RR.FollowServerLogsReq) => { - return this.embassyApi.followPackageLogs({ - id: this.pkgId, - ...params, - }) - } - } - - fetchLogs() { - return async (params: RR.GetServerLogsReq) => { - return this.embassyApi.getPackageLogs({ - id: this.pkgId, - ...params, - }) - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/app-show.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/app-show.module.ts deleted file mode 100644 index 43c95b522..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/app-show.module.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { AppShowPage } from './app-show.page' -import { - EmverPipesModule, - ResponsiveColDirective, - SharedPipesModule, -} from '@start9labs/shared' -import { StatusComponentModule } from '../status/status.component.module' -import { AppConfigPageModule } from './modals/app-config/app-config.module' -import { AppShowHeaderComponent } from './components/app-show-header/app-show-header.component' -import { AppShowProgressComponent } from './components/app-show-progress/app-show-progress.component' -import { AppShowStatusComponent } from './components/app-show-status/app-show-status.component' -import { AppShowDependenciesComponent } from './components/app-show-dependencies/app-show-dependencies.component' -import { AppShowMenuComponent } from './components/app-show-menu/app-show-menu.component' -import { - AppShowInterfacesComponent, - InterfaceInfoPipe, -} from './components/app-show-interfaces/app-show-interfaces.component' -import { AppShowHealthChecksComponent } from './components/app-show-health-checks/app-show-health-checks.component' -import { AppShowAdditionalComponent } from './components/app-show-additional/app-show-additional.component' -import { HealthColorPipe } from './pipes/health-color.pipe' -import { ProgressDataPipe } from './pipes/progress-data.pipe' -import { InsecureWarningComponentModule } from 'src/app/common/insecure-warning/insecure-warning.module' -import { LaunchMenuComponentModule } from '../app-list/app-list-pkg/launch-menu/launch-menu.module' - -const routes: Routes = [ - { - path: '', - component: AppShowPage, - }, -] - -@NgModule({ - declarations: [ - AppShowPage, - HealthColorPipe, - ProgressDataPipe, - AppShowHeaderComponent, - AppShowProgressComponent, - AppShowStatusComponent, - AppShowDependenciesComponent, - AppShowMenuComponent, - AppShowInterfacesComponent, - AppShowHealthChecksComponent, - AppShowAdditionalComponent, - InterfaceInfoPipe, - ], - imports: [ - CommonModule, - StatusComponentModule, - IonicModule, - RouterModule.forChild(routes), - AppConfigPageModule, - EmverPipesModule, - ResponsiveColDirective, - SharedPipesModule, - InsecureWarningComponentModule, - LaunchMenuComponentModule, - ], - exports: [InterfaceInfoPipe], -}) -export class AppShowPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/app-show.page.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/app-show.page.html deleted file mode 100644 index 0f23cb8e9..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/app-show.page.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/app-show.page.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/app-show.page.ts deleted file mode 100644 index 6952a1c9f..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/app-show.page.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { NavController } from '@ionic/angular' -import { PatchDB } from 'patch-db-client' -import { - DataModel, - PackageDataEntry, - PackageState, - InstalledPackageInfo, -} from 'src/app/services/patch-db/data-model' -import { - PackageStatus, - PrimaryStatus, - renderPkgStatus, -} from 'src/app/services/pkg-status-rendering.service' -import { map, tap } from 'rxjs/operators' -import { ActivatedRoute, NavigationExtras } from '@angular/router' -import { getPkgId } from '@start9labs/shared' -import { DependentInfo } from 'src/app/types/dependent-info' -import { - DepErrorService, - DependencyErrorType, - PkgDependencyErrors, -} from 'src/app/services/dep-error.service' -import { Observable, combineLatest } from 'rxjs' -import { Manifest } from '@start9labs/marketplace' -import { - AppConfigPage, - PackageConfigData, -} from './modals/app-config/app-config.page' -import { FormDialogService } from 'src/app/services/form-dialog.service' - -export interface DependencyInfo { - id: string - title: string - icon: string - version: string - errorText: string - actionText: string - action: () => any -} - -const STATES = [ - PackageState.Installing, - PackageState.Updating, - PackageState.Restoring, -] - -@Component({ - selector: 'app-show', - templateUrl: './app-show.page.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppShowPage { - readonly pkgId = getPkgId(this.route) - - readonly pkgPlus$ = combineLatest([ - this.patch.watch$('package-data', this.pkgId), - this.depErrorService.getPkgDepErrors$(this.pkgId), - ]).pipe( - tap(([pkg, _]) => { - // if package disappears, navigate to list page - if (!pkg) this.navCtrl.navigateRoot('/services') - }), - map(([pkg, depErrors]) => { - return { - pkg, - dependencies: this.getDepInfo(pkg, depErrors), - status: renderPkgStatus(pkg, depErrors), - } - }), - ) - - constructor( - private readonly route: ActivatedRoute, - private readonly navCtrl: NavController, - private readonly patch: PatchDB, - private readonly depErrorService: DepErrorService, - private readonly formDialog: FormDialogService, - ) {} - - isInstalled({ state }: PackageDataEntry): boolean { - return state === PackageState.Installed - } - - isRunning({ primary }: PackageStatus): boolean { - return primary === PrimaryStatus.Running - } - - isBackingUp({ primary }: PackageStatus): boolean { - return primary === PrimaryStatus.BackingUp - } - - showProgress({ state }: PackageDataEntry): boolean { - return STATES.includes(state) - } - - private getDepInfo( - pkg: PackageDataEntry, - depErrors: PkgDependencyErrors, - ): DependencyInfo[] { - const pkgInstalled = pkg.installed - - if (!pkgInstalled) return [] - - const pkgManifest = pkg.manifest - - return Object.keys(pkgInstalled['current-dependencies']) - .filter(depId => !!pkg.manifest.dependencies[depId]) - .map(depId => - this.getDepValues(pkgInstalled, pkgManifest, depId, depErrors), - ) - } - - private getDepValues( - pkgInstalled: InstalledPackageInfo, - pkgManifest: Manifest, - depId: string, - depErrors: PkgDependencyErrors, - ): DependencyInfo { - const { errorText, fixText, fixAction } = this.getDepErrors( - pkgManifest, - depId, - depErrors, - ) - - const depInfo = pkgInstalled['dependency-info'][depId] - - return { - id: depId, - version: pkgManifest.dependencies[depId].version, // do we want this version range? - title: depInfo?.title || depId, - icon: depInfo?.icon || '', - errorText: errorText - ? `${errorText}. ${pkgManifest.title} will not work as expected.` - : '', - actionText: fixText || 'View', - action: - fixAction || (() => this.navCtrl.navigateForward(`/services/${depId}`)), - } - } - - private getDepErrors( - pkgManifest: Manifest, - depId: string, - depErrors: PkgDependencyErrors, - ) { - const depError = (depErrors[pkgManifest.id] as any)?.[depId] // @TODO fix - - let errorText: string | null = null - let fixText: string | null = null - let fixAction: (() => any) | null = null - - if (depError) { - if (depError.type === DependencyErrorType.NotInstalled) { - errorText = 'Not installed' - fixText = 'Install' - fixAction = () => this.fixDep(pkgManifest, 'install', depId) - } else if (depError.type === DependencyErrorType.IncorrectVersion) { - errorText = 'Incorrect version' - fixText = 'Update' - fixAction = () => this.fixDep(pkgManifest, 'update', depId) - } else if (depError.type === DependencyErrorType.ConfigUnsatisfied) { - errorText = 'Config not satisfied' - fixText = 'Auto config' - fixAction = () => this.fixDep(pkgManifest, 'configure', depId) - } else if (depError.type === DependencyErrorType.NotRunning) { - errorText = 'Not running' - fixText = 'Start' - } else if (depError.type === DependencyErrorType.HealthChecksFailed) { - errorText = 'Required health check not passing' - } else if (depError.type === DependencyErrorType.Transitive) { - errorText = 'Dependency has a dependency issue' - } - } - - return { - errorText, - fixText, - fixAction, - } - } - - private async fixDep( - pkgManifest: Manifest, - action: 'install' | 'update' | 'configure', - id: string, - ): Promise { - switch (action) { - case 'install': - case 'update': - return this.installDep(pkgManifest, id) - case 'configure': - return this.configureDep(pkgManifest, id) - } - } - - private async installDep(manifest: Manifest, depId: string): Promise { - const version = manifest.dependencies[depId].version - - const dependentInfo: DependentInfo = { - id: manifest.id, - title: manifest.title, - version, - } - const navigationExtras: NavigationExtras = { - state: { dependentInfo }, - } - - await this.navCtrl.navigateForward( - `/marketplace/${depId}`, - navigationExtras, - ) - } - - private async configureDep( - manifest: Manifest, - dependencyId: string, - ): Promise { - const dependentInfo: DependentInfo = { - id: manifest.id, - title: manifest.title, - } - - return this.formDialog.open(AppConfigPage, { - label: 'Config', - data: { - pkgId: dependencyId, - dependentInfo, - }, - }) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-additional/app-show-additional.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-additional/app-show-additional.component.html deleted file mode 100644 index e337dd202..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-additional/app-show-additional.component.html +++ /dev/null @@ -1,109 +0,0 @@ -Additional Info - - - - - - -

Installed

-

{{ pkg.installed?.['installed-at'] | date : 'medium' }}

-
-
- - -

Git Hash

-

{{ gitHash }}

-
- -
- - - -

Git Hash

-

Unknown

-
-
-
- - -

License

-

{{ manifest.license }}

-
- -
- - -

Website

-

{{ manifest['marketing-site'] || 'Not provided' }}

-
- -
-
-
- - - - -

Source Repository

-

{{ manifest['upstream-repo'] }}

-
- -
- - -

Wrapper Repository

-

{{ manifest['wrapper-repo'] }}

-
- -
- - -

Support Site

-

{{ manifest['support-site'] || 'Not provided' }}

-
- -
- - -

Donation Link

-

{{ manifest['donation-url'] || 'Not provided' }}

-
- -
-
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-additional/app-show-additional.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-additional/app-show-additional.component.ts deleted file mode 100644 index bd35ee3ae..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-additional/app-show-additional.component.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { CopyService, MarkdownComponent } from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { from } from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'app-show-additional', - templateUrl: 'app-show-additional.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppShowAdditionalComponent { - @Input({ required: true }) - pkg!: PackageDataEntry - - constructor( - readonly copyService: CopyService, - private readonly dialogs: TuiDialogService, - private readonly api: ApiService, - ) {} - - presentModalLicense() { - const { id, version } = this.pkg.manifest - - this.dialogs - .open(new PolymorpheusComponent(MarkdownComponent), { - label: 'License', - size: 'l', - data: { - content: from( - this.api.getStatic( - `/public/package-data/${id}/${version}/LICENSE.md`, - ), - ), - }, - }) - .subscribe() - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-dependencies/app-show-dependencies.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-dependencies/app-show-dependencies.component.html deleted file mode 100644 index e9f7b97d7..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-dependencies/app-show-dependencies.component.html +++ /dev/null @@ -1,29 +0,0 @@ -Dependencies - - - - - - -

- - {{ dep.title }} -

-

{{ dep.version | displayEmver }}

-

- - {{ dep.errorText || 'satisfied' }} - -

-
- - {{ dep.actionText }} - - -
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-dependencies/app-show-dependencies.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-dependencies/app-show-dependencies.component.scss deleted file mode 100644 index b1f9a952f..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-dependencies/app-show-dependencies.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -.icon { - padding-right: 4px; -} - -img { - border-radius: 100%; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-dependencies/app-show-dependencies.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-dependencies/app-show-dependencies.component.ts deleted file mode 100644 index 3a2fee53b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-dependencies/app-show-dependencies.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { DependencyInfo } from '../../app-show.page' - -@Component({ - selector: 'app-show-dependencies', - templateUrl: './app-show-dependencies.component.html', - styleUrls: ['./app-show-dependencies.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppShowDependenciesComponent { - @Input() - dependencies: DependencyInfo[] = [] -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-header/app-show-header.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-header/app-show-header.component.html deleted file mode 100644 index ff3602986..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-header/app-show-header.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - -
- - -

- {{ pkg.manifest.title }} -

-

{{ pkg.manifest.version | displayEmver }}

-
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-header/app-show-header.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-header/app-show-header.component.scss deleted file mode 100644 index 49557dd5b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-header/app-show-header.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -.less-large { - font-size: 18px !important; -} - -.header { - display: flex; -} - -.logo { - height: 54px; - width: 54px; - margin: 0 16px; -} - -img { - border-radius: 100%; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-header/app-show-header.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-header/app-show-header.component.ts deleted file mode 100644 index 0d37b9af1..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-header/app-show-header.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'app-show-header', - templateUrl: './app-show-header.component.html', - styleUrls: ['./app-show-header.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppShowHeaderComponent { - @Input({ required: true }) - pkg!: PackageDataEntry -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-health-checks/app-show-health-checks.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-health-checks/app-show-health-checks.component.html deleted file mode 100644 index 209056834..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-health-checks/app-show-health-checks.component.html +++ /dev/null @@ -1,82 +0,0 @@ - - Health Checks - - - - - - - - - - -

- {{ check.name }} -

- -

- {{ result | titlecase }} - ... - - {{ $any(check).error }} - - - {{ $any(check).message }} - - - : - {{ $any(check).message }} - -

-
-
-
- - - - -

- {{ check.name }} -

-

Awaiting result...

-
-
-
-
- - - - - - - - - - - - -
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-health-checks/app-show-health-checks.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-health-checks/app-show-health-checks.component.scss deleted file mode 100644 index db58b129d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-health-checks/app-show-health-checks.component.scss +++ /dev/null @@ -1,24 +0,0 @@ -.icon-spinner { - height: 20px; - width: 20px; -} - -.avatar { - width: 20px; - height: 20px; - border-radius: 0; -} - -.label { - width: 100px; - margin-bottom: 10px; -} - -.description { - width: 150px; - margin-bottom: 10px; -} - -.bold { - font-weight: bold; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-health-checks/app-show-health-checks.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-health-checks/app-show-health-checks.component.ts deleted file mode 100644 index f2b5e96ce..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-health-checks/app-show-health-checks.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { map } from 'rxjs' -import { ConnectionService } from 'src/app/services/connection.service' -import { DataModel, HealthResult } from 'src/app/services/patch-db/data-model' -import { isEmptyObject } from '@start9labs/shared' - -@Component({ - selector: 'app-show-health-checks', - templateUrl: './app-show-health-checks.component.html', - styleUrls: ['./app-show-health-checks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppShowHealthChecksComponent { - @Input({ required: true }) pkgId!: string - - readonly connected$ = this.connectionService.connected$ - - get healthChecks$() { - return this.patch - .watch$('package-data', this.pkgId, 'installed', 'status', 'main') - .pipe( - map(main => { - if (main.status !== 'running' || isEmptyObject(main.health)) - return null - return Object.values(main.health) - }), - ) - } - - constructor( - private readonly connectionService: ConnectionService, - private readonly patch: PatchDB, - ) {} - - isLoading(result: HealthResult): boolean { - return result === HealthResult.Starting || result === HealthResult.Loading - } - - isReady(result: HealthResult): boolean { - return result !== HealthResult.Failure && result !== HealthResult.Loading - } - - asIsOrder() { - return 0 - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-interfaces/app-show-interfaces.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-interfaces/app-show-interfaces.component.html deleted file mode 100644 index 737b5bbc1..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-interfaces/app-show-interfaces.component.html +++ /dev/null @@ -1,25 +0,0 @@ -Interfaces - - - -

{{ info.name }}

-

{{ info.description }}

-

- {{ info.typeDetail }} -

-
- - - -
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-interfaces/app-show-interfaces.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-interfaces/app-show-interfaces.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-interfaces/app-show-interfaces.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-interfaces/app-show-interfaces.component.ts deleted file mode 100644 index d248a0a3a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-interfaces/app-show-interfaces.component.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { DOCUMENT } from '@angular/common' -import { - ChangeDetectionStrategy, - Component, - Inject, - Input, -} from '@angular/core' -import { ConfigService } from 'src/app/services/config.service' -import { - InstalledPackageInfo, - InterfaceInfo, -} from 'src/app/services/patch-db/data-model' -import { Pipe, PipeTransform } from '@angular/core' - -@Component({ - selector: 'app-show-interfaces', - templateUrl: './app-show-interfaces.component.html', - styleUrls: ['./app-show-interfaces.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppShowInterfacesComponent { - @Input({ required: true }) - pkg!: InstalledPackageInfo - - constructor( - private readonly config: ConfigService, - @Inject(DOCUMENT) private readonly document: Document, - ) {} - - launchUI(info: InterfaceInfo, e: Event) { - e.stopPropagation() - e.preventDefault() - this.document.defaultView?.open( - this.config.launchableAddress(info), - '_blank', - 'noreferrer', - ) - } -} - -@Pipe({ - name: 'interfaceInfo', -}) -export class InterfaceInfoPipe implements PipeTransform { - transform(info: InstalledPackageInfo['interfaceInfo']) { - return Object.entries(info).map(([id, val]) => { - let color: string - let icon: string - let typeDetail: string - - switch (val.type) { - case 'ui': - color = 'primary' - icon = 'desktop-outline' - typeDetail = 'User Interface (UI)' - break - case 'p2p': - color = 'secondary' - icon = 'people-outline' - typeDetail = 'Peer-To-Peer Interface (P2P)' - break - case 'api': - color = 'tertiary' - icon = 'terminal-outline' - typeDetail = 'Application Program Interface (API)' - break - case 'other': - color = 'dark' - icon = 'cube-outline' - typeDetail = 'Unknown Interface Type' - break - } - - return { - ...val, - id, - color, - icon, - typeDetail, - } - }) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-menu/app-show-menu.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-menu/app-show-menu.component.html deleted file mode 100644 index c57070d8c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-menu/app-show-menu.component.html +++ /dev/null @@ -1,96 +0,0 @@ -Menu - - - - - -

Instructions

-

Understand how to use {{ pkg.manifest.title }}

-
-
- - - - - -

Config

-

Customize {{ pkg.manifest.title }}

-
-
- - - - - -

Credentials

-

Password, keys, or other credentials of interest

-
-
- - - - - -

Actions

-

Uninstall and other commands specific to {{ pkg.manifest.title }}

-
-
- - - - - -

Outbound Proxy

-

Proxy all outbound traffic from {{ pkg.manifest.title }}

-

- - {{ - !proxy.value - ? 'None' - : proxy.value === 'primary' - ? 'System Primary' - : proxy.value === 'mirror' - ? 'Mirror P2P' - : proxy.value.proxyId - }} - -

-
-
- - - - - -

Logs

-

Raw, unfiltered logs

-
-
- - - - - -

Marketplace Listing

-

View service in the marketplace

-
-
- - - - -

Marketplace Listing

-

This package was not installed from the marketplace

-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-menu/app-show-menu.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-menu/app-show-menu.component.scss deleted file mode 100644 index f31b84ed5..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-menu/app-show-menu.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -.highlighted { - * { - color: var(--ion-color-dark); - font-weight: bold; - } -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-menu/app-show-menu.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-menu/app-show-menu.component.ts deleted file mode 100644 index 237429bd2..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-menu/app-show-menu.component.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { TuiDialogService } from '@taiga-ui/core' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { MarkdownComponent } from '@start9labs/shared' -import { from, map } from 'rxjs' -import { PatchDB } from 'patch-db-client' -import { NavController } from '@ionic/angular' -import { ActivatedRoute, Params } from '@angular/router' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { ProxyService } from 'src/app/services/proxy.service' -import { - AppConfigPage, - PackageConfigData, -} from '../../modals/app-config/app-config.page' - -@Component({ - selector: 'app-show-menu', - templateUrl: './app-show-menu.component.html', - styleUrls: ['./app-show-menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppShowMenuComponent { - @Input({ required: true }) - pkg!: PackageDataEntry - - get highlighted$() { - return this.patch - .watch$('ui', 'ack-instructions', this.pkg.manifest.id) - .pipe(map(seen => !seen)) - } - - constructor( - private readonly route: ActivatedRoute, - private readonly navCtrl: NavController, - private readonly dialogs: TuiDialogService, - private readonly formDialog: FormDialogService, - private readonly api: ApiService, - readonly patch: PatchDB, - private readonly proxyService: ProxyService, - ) {} - - async presentModalInstructions() { - const { id, version } = this.pkg.manifest - - this.api - .setDbValue(['ack-instructions', id], true) - .catch(e => console.error('Failed to mark instructions as seen', e)) - - this.dialogs - .open(new PolymorpheusComponent(MarkdownComponent), { - label: 'Instructions', - size: 'l', - data: { - content: from( - this.api.getStatic( - `/public/package-data/${id}/${version}/INSTRUCTIONS.md`, - ), - ), - }, - }) - .subscribe() - } - - openConfig() { - this.formDialog.open(AppConfigPage, { - label: `${this.pkg.manifest.title} configuration`, - data: { pkgId: this.pkg.manifest.id }, - }) - } - - setOutboundProxy() { - this.proxyService.presentModalSetOutboundProxy({ - packageId: this.pkg.manifest.id, - outboundProxy: this.pkg.installed!.outboundProxy, - hasP2P: Object.values(this.pkg.installed!.interfaceInfo).some( - i => i.type === 'p2p', - ), - }) - } - - navigate(path: string, qp?: Params) { - return this.navCtrl.navigateForward([path], { - relativeTo: this.route, - queryParams: qp, - }) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-progress/app-show-progress.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-progress/app-show-progress.component.html deleted file mode 100644 index 3134cee5e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-progress/app-show-progress.component.html +++ /dev/null @@ -1,20 +0,0 @@ -

Downloading: {{ progressData.downloadProgress }}%

- - -

Validating: {{ progressData.validateProgress }}%

- - -

Unpacking: {{ progressData.unpackProgress }}%

- diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-progress/app-show-progress.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-progress/app-show-progress.component.scss deleted file mode 100644 index 7ffd8ed70..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-progress/app-show-progress.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -:host { - display: block; - padding: 16px; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-progress/app-show-progress.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-progress/app-show-progress.component.ts deleted file mode 100644 index f902d42f0..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-progress/app-show-progress.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { - InstallProgress, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { ProgressData } from 'src/app/types/progress-data' - -@Component({ - selector: 'app-show-progress', - templateUrl: './app-show-progress.component.html', - styleUrls: ['./app-show-progress.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppShowProgressComponent { - @Input({ required: true }) - pkg!: PackageDataEntry - - @Input({ required: true }) - progressData!: ProgressData - - get unpackingBuffer(): number { - return this.progressData.validateProgress === 100 && - !this.progressData.unpackProgress - ? 0 - : 1 - } - - get validationBuffer(): number { - return this.progressData.downloadProgress === 100 && - !this.progressData.validateProgress - ? 0 - : 1 - } - - getColor(action: keyof InstallProgress): string { - return this.pkg['install-progress']?.[action] ? 'success' : 'secondary' - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-status/app-show-status.component.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-status/app-show-status.component.html deleted file mode 100644 index e8cc07e66..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-status/app-show-status.component.html +++ /dev/null @@ -1,59 +0,0 @@ -Status - - - - - - - - - - - - - Stop - - - - - Restart - - - - - - Start - - - - - Configure - - - - - diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-status/app-show-status.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-status/app-show-status.component.scss deleted file mode 100644 index d6b8b47fb..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-status/app-show-status.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -.label { - overflow: visible; -} - -.action-button { - margin: 12px 20px 10px 0; - min-height: 42px; - min-width: 140px; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-status/app-show-status.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-status/app-show-status.component.ts deleted file mode 100644 index 164edd6f4..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/components/app-show-status/app-show-status.component.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Input, - ViewChild, -} from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { PatchDB } from 'patch-db-client' -import { filter } from 'rxjs' -import { - PackageStatus, - PrimaryRendering, - PrimaryStatus, - StatusRendering, -} from 'src/app/services/pkg-status-rendering.service' -import { - DataModel, - InterfaceInfo, - PackageDataEntry, - PackageState, -} from 'src/app/services/patch-db/data-model' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { - AppConfigPage, - PackageConfigData, -} from '../../modals/app-config/app-config.page' -import { hasCurrentDeps } from 'src/app/util/has-deps' -import { ConnectionService } from 'src/app/services/connection.service' -import { LaunchMenuComponent } from '../../../app-list/app-list-pkg/launch-menu/launch-menu.component' - -@Component({ - selector: 'app-show-status', - templateUrl: './app-show-status.component.html', - styleUrls: ['./app-show-status.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppShowStatusComponent { - @ViewChild('launchMenu') launchMenu!: LaunchMenuComponent - - @Input({ required: true }) - pkg!: PackageDataEntry - - @Input({ required: true }) - status!: PackageStatus - - PR = PrimaryRendering - - readonly connected$ = this.connectionService.connected$ - - constructor( - private readonly dialogs: TuiDialogService, - private readonly errorService: ErrorService, - private readonly loader: LoadingService, - private readonly embassyApi: ApiService, - private readonly formDialog: FormDialogService, - private readonly connectionService: ConnectionService, - private readonly patch: PatchDB, - ) {} - - private get id(): string { - return this.pkg.manifest.id - } - - get interfaceInfo(): Record { - return this.pkg.installed!['interfaceInfo'] - } - - get isConfigured(): boolean { - return this.pkg.installed!.status.configured - } - - get isInstalled(): boolean { - return this.pkg.state === PackageState.Installed - } - - get isRunning(): boolean { - return this.status.primary === PrimaryStatus.Running - } - - get canStop(): boolean { - return [ - PrimaryStatus.Running, - PrimaryStatus.Starting, - PrimaryStatus.Restarting, - ].includes(this.status.primary as PrimaryStatus) - } - - get isStopped(): boolean { - return this.status.primary === PrimaryStatus.Stopped - } - - get rendering(): StatusRendering { - return PrimaryRendering[this.status.primary] - } - - presentModalConfig(): void { - this.formDialog.open(AppConfigPage, { - label: `${this.pkg.manifest.title} configuration`, - data: { pkgId: this.id }, - }) - } - - async tryStart(): Promise { - if (this.status.dependency === 'warning') { - const depErrMsg = `${this.pkg.manifest.title} has unmet dependencies. It will not work as expected.` - const proceed = await this.presentAlertStart(depErrMsg) - - if (!proceed) return - } - - const alertMsg = this.pkg.manifest.alerts.start - - if (alertMsg) { - const proceed = await this.presentAlertStart(alertMsg) - - if (!proceed) return - } - - this.start() - } - - async tryStop(): Promise { - const { title, alerts } = this.pkg.manifest - - let content = alerts.stop || '' - if (hasCurrentDeps(this.pkg)) { - const depMessage = `Services that depend on ${title} will no longer work properly and may crash` - content = content ? `${content}.\n\n${depMessage}` : depMessage - } - - if (content) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content, - yes: 'Stop', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.stop()) - } else { - this.stop() - } - } - - async tryRestart(): Promise { - if (hasCurrentDeps(this.pkg)) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content: `Services that depend on ${this.pkg.manifest} may temporarily experiences issues`, - yes: 'Restart', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.restart()) - } else { - this.restart() - } - } - - private async start(): Promise { - const loader = this.loader.open(`Starting...`).subscribe() - - try { - await this.embassyApi.startPackage({ id: this.id }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async stop(): Promise { - const loader = this.loader.open(`Stopping...`).subscribe() - - try { - await this.embassyApi.stopPackage({ id: this.id }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async restart(): Promise { - const loader = this.loader.open(`Restarting...`).subscribe() - - try { - await this.embassyApi.restartPackage({ id: this.id }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - private async presentAlertStart(content: string): Promise { - return new Promise(async resolve => { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content, - yes: 'Continue', - no: 'Cancel', - }, - }) - .subscribe(response => resolve(response)) - }) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config-dep.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config-dep.component.ts deleted file mode 100644 index a895ac222..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config-dep.component.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Input, - OnChanges, -} from '@angular/core' -import { compare, getValueByPointer, Operation } from 'fast-json-patch' -import { isObject } from '@start9labs/shared' -import { tuiIsNumber } from '@taiga-ui/cdk' - -@Component({ - selector: 'app-config-dep', - template: ` - -

- {{ package }} -

- The following modifications have been made to {{ package }} to satisfy - {{ dep }}: -
    -
  • -
- To accept these modifications, click "Save". -
- `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppConfigDepComponent implements OnChanges { - @Input() - package = '' - - @Input() - dep = '' - - @Input() - original: object = {} - - @Input() - value: object = {} - - diff: string[] = [] - - ngOnChanges() { - this.diff = compare(this.original, this.value).map( - op => `${this.getPath(op)}: ${this.getMessage(op)}`, - ) - } - - private getPath(operation: Operation): string { - const path = operation.path - .substring(1) - .split('/') - .map(node => { - const num = Number(node) - return isNaN(num) ? node : num - }) - - if (tuiIsNumber(path[path.length - 1])) { - path.pop() - } - - return path.join(' → ') - } - - private getMessage(operation: Operation): string { - switch (operation.op) { - case 'add': - return `Added ${this.getNewValue(operation.value)}` - case 'remove': - return `Removed ${this.getOldValue(operation.path)}` - case 'replace': - return `Changed from ${this.getOldValue( - operation.path, - )} to ${this.getNewValue(operation.value)}` - default: - return `Unknown operation` - } - } - - private getOldValue(path: any): string { - const val = getValueByPointer(this.original, path) - if (['string', 'number', 'boolean'].includes(typeof val)) { - return val - } else if (isObject(val)) { - return 'entry' - } else { - return 'list' - } - } - - private getNewValue(val: any): string { - if (['string', 'number', 'boolean'].includes(typeof val)) { - return val - } else if (isObject(val)) { - return 'new entry' - } else { - return 'new list' - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.module.ts deleted file mode 100644 index 3279dd8bb..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { ReactiveFormsModule } from '@angular/forms' -import { - TuiLoaderModule, - TuiModeModule, - TuiNotificationModule, -} from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { FormPageModule } from 'src/app/apps/ui/modals/form/form.module' - -import { AppConfigPage } from './app-config.page' -import { AppConfigDepComponent } from './app-config-dep.component' - -@NgModule({ - imports: [ - CommonModule, - ReactiveFormsModule, - FormPageModule, - TuiLoaderModule, - TuiNotificationModule, - TuiButtonModule, - TuiModeModule, - ], - declarations: [AppConfigPage, AppConfigDepComponent], - exports: [AppConfigPage], -}) -export class AppConfigPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.page.html b/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.page.html deleted file mode 100644 index bf400b392..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.page.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - {{ pkg.manifest.title }} has been automatically configured with - recommended defaults. Make whatever changes you want, then click "Save". - - - - - - - - No config options for {{ pkg.manifest.title }} {{ pkg.manifest.version }}. - - - - - - - - - - -
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.page.scss b/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.page.scss deleted file mode 100644 index d29fc1ffa..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.page.scss +++ /dev/null @@ -1,8 +0,0 @@ -.notification { - font-size: 1rem; - margin-bottom: 1rem; -} - -.reset { - margin-right: auto; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.page.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.page.ts deleted file mode 100644 index c901d96d7..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/modals/app-config/app-config.page.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { Component, Inject } from '@angular/core' -import { endWith, firstValueFrom, Subscription } from 'rxjs' -import { TuiDialogContext, TuiDialogService } from '@taiga-ui/core' -import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { - ErrorService, - getErrorMessage, - isEmptyObject, - LoadingService, -} from '@start9labs/shared' -import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { PatchDB } from 'patch-db-client' -import { compare, Operation } from 'fast-json-patch' -import { hasCurrentDeps } from 'src/app/util/has-deps' -import { getAllPackages, getPackage } from 'src/app/util/get-package-data' -import { Breakages } from 'src/app/services/api/api.types' -import { InvalidService } from 'src/app/common/form/invalid.service' -import { DependentInfo } from 'src/app/types/dependent-info' -import { ActionButton } from 'src/app/apps/ui/modals/form/form.page' - -export interface PackageConfigData { - readonly pkgId: string - readonly dependentInfo?: DependentInfo -} - -@Component({ - selector: 'app-config', - templateUrl: './app-config.page.html', - styleUrls: ['./app-config.page.scss'], - providers: [InvalidService], -}) -export class AppConfigPage { - readonly pkgId = this.context.data.pkgId - readonly dependentInfo = this.context.data.dependentInfo - - loadingError = '' - loadingText = this.dependentInfo - ? `Setting properties to accommodate ${this.dependentInfo.title}` - : 'Loading Config' - - pkg?: PackageDataEntry - spec: InputSpec = {} - patch: Operation[] = [] - buttons: ActionButton[] = [ - { - text: 'Save', - handler: value => this.save(value), - }, - ] - - original: object | null = null - value: object | null = null - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext, - private readonly dialogs: TuiDialogService, - private readonly errorService: ErrorService, - private readonly loader: LoadingService, - private readonly embassyApi: ApiService, - private readonly patchDb: PatchDB, - ) {} - - async ngOnInit() { - try { - this.pkg = await getPackage(this.patchDb, this.pkgId) - - if (!this.pkg) { - this.loadingError = 'This service does not exist' - - return - } - - if (this.dependentInfo) { - const depConfig = await this.embassyApi.dryConfigureDependency({ - 'dependency-id': this.pkgId, - 'dependent-id': this.dependentInfo.id, - }) - - this.original = depConfig['old-config'] - this.value = depConfig['new-config'] || this.original - this.spec = depConfig.spec - this.patch = compare(this.original, this.value) - } else { - const { config, spec } = await this.embassyApi.getPackageConfig({ - id: this.pkgId, - }) - - this.original = config - this.value = config - this.spec = spec - } - } catch (e: any) { - this.loadingError = getErrorMessage(e) - } finally { - this.loadingText = '' - } - } - - private async save(config: any) { - const loader = new Subscription() - - try { - await this.uploadFiles(config, loader) - - if (hasCurrentDeps(this.pkg!)) { - await this.configureDeps(config, loader) - } else { - await this.configure(config, loader) - } - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async uploadFiles(config: Record, loader: Subscription) { - loader.unsubscribe() - loader.closed = false - - // TODO: Could be nested files - const keys = Object.keys(config).filter(key => config[key] instanceof File) - const message = `Uploading File${keys.length > 1 ? 's' : ''}...` - - if (!keys.length) return - - loader.add(this.loader.open(message).subscribe()) - - const hashes = await Promise.all( - keys.map(key => this.embassyApi.uploadFile(config[key])), - ) - keys.forEach((key, i) => (config[key] = hashes[i])) - } - - private async configureDeps( - config: Record, - loader: Subscription, - ) { - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open('Checking dependent services...').subscribe()) - - const breakages = await this.embassyApi.drySetPackageConfig({ - id: this.pkgId, - config, - }) - - loader.unsubscribe() - loader.closed = false - - if (isEmptyObject(breakages) || (await this.approveBreakages(breakages))) { - await this.configure(config, loader) - } - } - - private async configure(config: Record, loader: Subscription) { - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open('Saving...').subscribe()) - - await this.embassyApi.setPackageConfig({ id: this.pkgId, config }) - this.context.$implicit.complete() - } - - private async approveBreakages(breakages: Breakages): Promise { - const packages = await getAllPackages(this.patchDb) - const message = - 'As a result of this change, the following services will no longer work properly and may crash:
    ' - const content = `${message}${Object.keys(breakages).map( - id => `
  • ${packages[id].manifest.title}
  • `, - )}
` - const data: TuiPromptData = { content, yes: 'Continue', no: 'Cancel' } - - return firstValueFrom( - this.dialogs.open(TUI_PROMPT, { data }).pipe(endWith(false)), - ) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/pipes/health-color.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/pipes/health-color.pipe.ts deleted file mode 100644 index a274aa8c0..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/pipes/health-color.pipe.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { HealthResult } from 'src/app/services/patch-db/data-model' - -@Pipe({ - name: 'healthColor', -}) -export class HealthColorPipe implements PipeTransform { - transform(val: HealthResult): string { - switch (val) { - case HealthResult.Success: - return 'success' - case HealthResult.Failure: - return 'warning' - case HealthResult.Disabled: - return 'dark' - case HealthResult.Starting: - case HealthResult.Loading: - return 'primary' - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/app-show/pipes/progress-data.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/services/app-show/pipes/progress-data.pipe.ts deleted file mode 100644 index 1e5397648..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/app-show/pipes/progress-data.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' -import { ProgressData } from 'src/app/types/progress-data' -import { packageLoadingProgress } from 'src/app/util/package-loading-progress' - -@Pipe({ - name: 'progressData', -}) -export class ProgressDataPipe implements PipeTransform { - transform(pkg: PackageDataEntry): ProgressData | null { - return packageLoadingProgress(pkg['install-progress']) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/services.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/services.module.ts deleted file mode 100644 index 6f2b8c9ff..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/services.module.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { NgModule } from '@angular/core' -import { Routes, RouterModule } from '@angular/router' - -const routes: Routes = [ - { - path: '', - redirectTo: 'list', - pathMatch: 'full', - }, - { - path: 'list', - loadChildren: () => - import('./app-list/app-list.module').then(m => m.AppListPageModule), - }, - { - path: ':pkgId', - loadChildren: () => - import('./app-show/app-show.module').then(m => m.AppShowPageModule), - }, - { - path: ':pkgId/actions', - loadChildren: () => - import('./app-actions/app-actions.module').then( - m => m.AppActionsPageModule, - ), - }, - { - path: ':pkgId/logs', - loadChildren: () => - import('./app-logs/app-logs.module').then(m => m.AppLogsPageModule), - }, - { - path: ':pkgId/credentials', - loadChildren: () => - import('./app-credentials/app-credentials.module').then( - m => m.AppCredentialsPageModule, - ), - }, - { - path: ':pkgId/interfaces/:interfaceId', - loadChildren: () => - import('./app-interface/app-interface.module').then( - m => m.AppInterfacePageModule, - ), - }, -] - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class ServicesModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.html b/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.html deleted file mode 100644 index fd265fd96..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.html +++ /dev/null @@ -1,30 +0,0 @@ -

- {{ (connected$ | async) ? rendering.display : 'Unknown' }} - - - this may take a while - - - - - {{ progress }} - - - - -

diff --git a/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.module.ts b/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.module.ts deleted file mode 100644 index d70f22c4c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { UnitConversionPipesModule } from '@start9labs/shared' -import { InstallProgressPipeModule } from 'src/app/common/install-progress/install-progress.module' -import { StatusComponent } from './status.component' - -@NgModule({ - declarations: [StatusComponent], - imports: [ - CommonModule, - IonicModule, - UnitConversionPipesModule, - InstallProgressPipeModule, - ], - exports: [StatusComponent], -}) -export class StatusComponentModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.scss b/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.ts b/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.ts deleted file mode 100644 index 156dc9e6c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/services/status/status.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Component, Input } from '@angular/core' -import { ConnectionService } from 'src/app/services/connection.service' -import { InstallProgress } from 'src/app/services/patch-db/data-model' -import { - PrimaryRendering, - PrimaryStatus, - StatusRendering, -} from 'src/app/services/pkg-status-rendering.service' - -@Component({ - selector: 'status', - templateUrl: './status.component.html', - styleUrls: ['./status.component.scss'], -}) -export class StatusComponent { - PS = PrimaryStatus - PR = PrimaryRendering - - @Input({ required: true }) rendering!: StatusRendering - @Input() size?: string - @Input() style?: string = 'regular' - @Input() weight?: string = 'normal' - @Input() installProgress?: InstallProgress - @Input() sigtermTimeout?: string | null = null - - readonly connected$ = this.connectionService.connected$ - - constructor(private readonly connectionService: ConnectionService) {} -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/domains/domain.const.ts b/web/projects/ui/src/app/apps/ui/pages/system/domains/domain.const.ts deleted file mode 100644 index 2d93ab758..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/domains/domain.const.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Config } from '@start9labs/start-sdk/lib/config/builder/config' -import { Value } from '@start9labs/start-sdk/lib/config/builder/value' -import { Variants } from '@start9labs/start-sdk/lib/config/builder/variants' -import { Proxy } from 'src/app/services/patch-db/data-model' -import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' - -const auth = Config.of({ - username: Value.text({ - name: 'Username', - required: { default: null }, - }), - password: Value.text({ - name: 'Password', - required: { default: null }, - masked: true, - }), -}) - -function getStrategyUnion(proxies: Proxy[]) { - const inboundProxies = proxies - .filter(p => p.type === 'inbound-outbound') - .reduce((prev, curr) => { - return { - [curr.id]: curr.name, - ...prev, - } - }, {}) - - return Value.union( - { - name: 'Networking Strategy', - required: { default: null }, - description: `
Local
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 -
Proxy
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) or paying service provider such as Static Wire -`, - }, - Variants.of({ - local: { - name: 'Local', - spec: Config.of({ - ipStrategy: Value.select({ - name: 'IP Strategy', - description: `
IPv6 Only (recommended)
Requirements:
  1. ISP IPv6 support
  2. OpenWRT (recommended) or Linksys router
Pros: Ready for IPv6 Internet. Enhanced privacy. Run multiple clearnet servers from the same network -Cons: Interfaces using this domain will only be accessible to people whose ISP supports IPv6 -
IPv6 and IPv4
Pros: Ready for IPv6 Internet. Accessible by anyone -Cons: Less private, as IPv4 addresses are closely correlated with geographic areas. Cannot run multiple clearnet servers from the same network -
IPv4 Only
Pros: Accessible by anyone -Cons: Less private, as IPv4 addresses are closely correlated with geographic areas. Cannot run multiple clearnet servers from the same network -`, - required: { default: 'ipv6' }, - values: { - ipv6: 'IPv6 Only', - ipv4: 'IPv4 Only', - dualstack: 'IPv6 and IPv4', - }, - }), - }), - }, - proxy: { - name: 'Proxy', - spec: Config.of({ - proxyStrategy: Value.union( - { - name: 'Proxy Strategy', - required: { default: 'primary' }, - description: `
Primary
Use the Primary Inbound proxy from your proxy settings. If you do not have any inbound proxies, no proxy will be used -
Other
Use a specific proxy from your proxy settings -`, - }, - Variants.of({ - primary: { - name: 'Primary', - spec: Config.of({}), - }, - other: { - name: 'Specific', - spec: Config.of({ - proxyId: Value.select({ - name: 'Select Proxy', - required: { default: null }, - values: inboundProxies, - }), - }), - }, - }), - ), - }), - }, - }), - ) -} - -export async function getStart9ToSpec(proxies: Proxy[]) { - return configBuilderToSpec( - Config.of({ - strategy: getStrategyUnion(proxies), - }), - ) -} - -export async function getCustomSpec(proxies: Proxy[]) { - return configBuilderToSpec( - Config.of({ - hostname: Value.text({ - name: 'Hostname', - required: { default: null }, - placeholder: 'yourdomain.com', - }), - provider: Value.union( - { - name: 'Dynamic DNS Provider', - required: { default: 'start9' }, - }, - Variants.of({ - start9: { - name: 'Start9', - spec: Config.of({}), - }, - njalla: { - name: 'Njalla', - spec: auth, - }, - duckdns: { - name: 'Duck DNS', - spec: auth, - }, - dyn: { - name: 'DynDNS', - spec: auth, - }, - easydns: { - name: 'easyDNS', - spec: auth, - }, - zoneedit: { - name: 'Zoneedit', - spec: auth, - }, - googledomains: { - name: 'Google Domains (IPv4 or IPv6)', - spec: auth, - }, - namecheap: { - name: 'Namecheap (IPv4 only)', - spec: auth, - }, - }), - ), - strategy: getStrategyUnion(proxies), - }), - ) -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.module.ts deleted file mode 100644 index 39af70e92..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { DomainsPage } from './domains.page' -import { TuiNotificationModule } from '@taiga-ui/core' -import { SharedPipesModule } from '@start9labs/shared' - -const routes: Routes = [ - { - path: '', - component: DomainsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - TuiNotificationModule, - RouterModule.forChild(routes), - SharedPipesModule, - ], - declarations: [DomainsPage], -}) -export class DomainsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.page.html b/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.page.html deleted file mode 100644 index a14af00d1..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.page.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - Domains - - - - -
- - Adding domains permits accessing your server and services over clearnet. - View instructions - -
- - - - Start9.to - - - Claim - - - -
- - - Domain - Added - DDNS Provider - Network Strategy - Used By - - - - {{ start9To.value }} - {{ start9To.createdAt| date: 'short' }} - Start9 - - {{ $any(start9To.networkStrategy).ipStrategy || - $any(start9To.networkStrategy).proxyId || 'Primary Proxy' }} - - - - {{ qty }} Interfaces - - - N/A - - - - - - - - - - - -
- - - Custom Domains - - - Add Domain - - - -
- - - Domain - Added - DDNS Provider - Network Strategy - Used By - - - - {{ domain.value }} - {{ domain.createdAt| date: 'short' }} - {{ domain.provider }} - - {{ $any(domain.networkStrategy).ipStrategy || - $any(domain.networkStrategy).proxyId || 'Primary Proxy' }} - - - - {{ qty }} Interfaces - - - N/A - - - - - - - - - - - -
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.page.ts deleted file mode 100644 index 605fb8a2e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/domains/domains.page.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { Component } from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { filter, firstValueFrom, map } from 'rxjs' -import { PatchDB } from 'patch-db-client' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { DataModel, Domain } from 'src/app/services/patch-db/data-model' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormContext, FormPage } from '../../../modals/form/form.page' -import { getCustomSpec, getStart9ToSpec } from './domain.const' - -@Component({ - selector: 'domains', - templateUrl: 'domains.page.html', - styleUrls: ['domains.page.scss'], -}) -export class DomainsPage { - readonly docsUrl = 'https://docs.start9.com/latest/user-manual/domains' - - readonly domains$ = this.patch.watch$('server-info', 'network').pipe( - map(network => { - const start9ToSubdomain = network.start9ToSubdomain - const start9To = !start9ToSubdomain - ? null - : { - ...start9ToSubdomain, - value: `${start9ToSubdomain.value}.start9.to`, - provider: 'Start9', - } - - return { start9To, custom: network.domains } - }), - ) - - constructor( - private readonly errorService: ErrorService, - private readonly dialogs: TuiDialogService, - private readonly api: ApiService, - private readonly loader: LoadingService, - private readonly formDialog: FormDialogService, - private readonly patch: PatchDB, - ) {} - - async presentModalAdd() { - const proxies = await firstValueFrom( - this.patch.watch$('server-info', 'network', 'proxies'), - ) - - const options: Partial>> = { - label: 'Custom Domain', - data: { - spec: await getCustomSpec(proxies), - buttons: [ - { - text: 'Manage proxies', - link: '/system/proxies', - }, - { - text: 'Save', - handler: async value => this.save(value), - }, - ], - }, - } - this.formDialog.open(FormPage, options) - } - - async presentModalClaimStart9To() { - const proxies = await firstValueFrom( - this.patch.watch$('server-info', 'network', 'proxies'), - ) - - const options: Partial>> = { - label: 'start9.to', - data: { - spec: await getStart9ToSpec(proxies), - buttons: [ - { - text: 'Manage proxies', - link: '/system/proxies', - }, - { - text: 'Save', - handler: async value => this.claimStart9ToDomain(value), - }, - ], - }, - } - this.formDialog.open(FormPage, options) - } - - presentAlertDelete(hostname: string) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: 'Delete domain?', - yes: 'Delete', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.delete(hostname)) - } - - presentAlertDeleteStart9To() { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: 'Delete start9.to domain?', - yes: 'Delete', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.deleteStart9ToDomain()) - } - - presentAlertUsedBy(domain: string, usedBy: Domain['usedBy']) { - this.dialogs - .open( - `${domain} is currently being used by:
    ${usedBy.map(u => - u.interfaces.map(i => `
  • ${u.service.title} - ${i.title}
  • `), - )}
`, - { - label: 'Used by', - size: 's', - }, - ) - .subscribe() - } - - private async claimStart9ToDomain(value: any): Promise { - const loader = this.loader.open('Saving...').subscribe() - - const strategy = value.strategy.unionValueKey - - const networkStrategy = - value.strategy.unionSelectKey === 'local' - ? { ipStrategy: strategy.ipStrategy } - : { - proxyId: - strategy.proxyStrategy.unionSelectKey === 'primary' - ? null - : strategy.proxyStrategy.unionValueKey.proxyId, - } - - try { - await this.api.claimStart9ToDomain({ networkStrategy }) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private async save(value: any): Promise { - const loader = this.loader.open('Saving...').subscribe() - - const providerName = value.provider.unionSelectKey - - const strategy = value.strategy.unionValueKey - - const networkStrategy = - value.strategy.unionSelectKey === 'local' - ? { ipStrategy: strategy.ipStrategy } - : { - proxyId: - strategy.proxyStrategy.unionSelectKey === 'primary' - ? null - : strategy.proxyStrategy.unionValueKey.proxyId, - } - - try { - await this.api.addDomain({ - hostname: value.hostname, - provider: { - name: providerName, - username: - providerName === 'start9' - ? null - : value.provider.unionValueKey.username, - password: - providerName === 'start9' - ? null - : value.provider.unionValueKey.password, - }, - networkStrategy, - }) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private async delete(hostname: string): Promise { - const loader = this.loader.open('Deleting...').subscribe() - - try { - await this.api.deleteDomain({ hostname }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async deleteStart9ToDomain(): Promise { - const loader = this.loader.open('Deleting...').subscribe() - - try { - await this.api.deleteStart9ToDomain({}) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/email/email.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/email/email.module.ts deleted file mode 100644 index f86ad248a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/email/email.module.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { TuiNotificationModule } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { EmailPage } from './email.page' -import { Routes, RouterModule } from '@angular/router' -import { FormsModule, ReactiveFormsModule } from '@angular/forms' -import { FormModule } from 'src/app/common/form/form.module' -import { TuiInputModule } from '@taiga-ui/kit' - -const routes: Routes = [ - { - path: '', - component: EmailPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - TuiButtonModule, - FormModule, - FormsModule, - ReactiveFormsModule, - TuiInputModule, - TuiNotificationModule, - RouterModule.forChild(routes), - ], - declarations: [EmailPage], -}) -export class EmailPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/email/email.page.html b/web/projects/ui/src/app/apps/ui/pages/system/email/email.page.html deleted file mode 100644 index 6159b8cfe..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/email/email.page.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - Email - - - - -
- - Adding SMTP credentials to StartOS enables StartOS and some services to - send you emails. - - View instructions - - - -
- SMTP Credentials - -
- -
-
-
- Test Email - - Firstname Lastname <email@example.com> - - -
- -
-
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/system/email/email.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/email/email.page.scss deleted file mode 100644 index 4cceda182..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/email/email.page.scss +++ /dev/null @@ -1,11 +0,0 @@ -ion-item-divider { - text-transform: unset; - padding-bottom: 12px; - padding-left: 0; -} - -ion-item-group { - background-color: #1e2024; - border: 1px solid #717171; - border-radius: 6px; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/email/email.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/email/email.page.ts deleted file mode 100644 index fb8ae298e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/email/email.page.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Component } from '@angular/core' -import { UntypedFormGroup } from '@angular/forms' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes' -import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants' -import { TuiDialogService } from '@taiga-ui/core' -import { PatchDB } from 'patch-db-client' -import { switchMap } from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { FormService } from 'src/app/services/form.service' -import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' - -@Component({ - selector: 'email', - templateUrl: './email.page.html', - styleUrls: ['./email.page.scss'], -}) -export class EmailPage { - spec: Promise = configBuilderToSpec(customSmtp) - testAddress = '' - readonly form$ = this.patch - .watch$('server-info', 'smtp') - .pipe( - switchMap(async value => - this.formService.createForm(await this.spec, value), - ), - ) - - constructor( - private readonly dialogs: TuiDialogService, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly patch: PatchDB, - private readonly api: ApiService, - private readonly formService: FormService, - ) {} - - async save(value: unknown): Promise { - const loader = this.loader.open('Saving...').subscribe() - - try { - await this.api.configureEmail(customSmtp.validator.unsafeCast(value)) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - async sendTestEmail(form: UntypedFormGroup) { - const loader = this.loader.open('Sending...').subscribe() - - try { - await this.api.testEmail({ - to: this.testAddress, - ...form.value, - }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - - this.dialogs - .open( - `A test email has been sent to ${this.testAddress}.

Check your spam folder and mark as not spam`, - { - label: 'Success', - size: 's', - }, - ) - .subscribe() - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.module.ts deleted file mode 100644 index 436a9ed06..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { EmverPipesModule } from '@start9labs/shared' -import { TuiCheckboxLabeledModule, TuiPromptModule } from '@taiga-ui/kit' -import { ExperimentalFeaturesPage } from './experimental-features.page' -import { FormsModule } from '@angular/forms' - -const routes: Routes = [ - { - path: '', - component: ExperimentalFeaturesPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - TuiPromptModule, - RouterModule.forChild(routes), - EmverPipesModule, - TuiCheckboxLabeledModule, - FormsModule, - ], - declarations: [ExperimentalFeaturesPage], -}) -export class ExperimentalFeaturesPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.page.html b/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.page.html deleted file mode 100644 index 00ed011f6..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.page.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - Experimental Features - - - - - - - - -

Reset Tor

-

- Resetting the Tor daemon on your server may resolve Tor connectivity - issues. -

-
-
- - - -

{{ server.zram ? 'Disable' : 'Enable' }} zram

-

- Zram creates compressed swap in memory, resulting in faster I/O for - low RAM devices -

-
-
-
-
- - -

- You are currently connected over Tor. If you reset the Tor daemon, you will - lose connectivity until it comes back online. -

-

Reset Tor?

-

- Optionally wipe state to forcibly acquire new guard nodes. It is recommended - to try without wiping state first. -

- - Wipe state - -
diff --git a/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.page.ts deleted file mode 100644 index 2861ca199..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/experimental-features/experimental-features.page.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - TemplateRef, - ViewChild, -} from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ConfigService } from 'src/app/services/config.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { TuiAlertService, TuiDialogService } from '@taiga-ui/core' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { filter } from 'rxjs' - -@Component({ - selector: 'experimental-features', - templateUrl: './experimental-features.page.html', - styleUrls: ['./experimental-features.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ExperimentalFeaturesPage { - readonly server$ = this.patch.watch$('server-info') - - @ViewChild('tor') - template?: TemplateRef - - wipe = false - - constructor( - private readonly alerts: TuiAlertService, - private readonly patch: PatchDB, - private readonly config: ConfigService, - private readonly dialogs: TuiDialogService, - private readonly loader: LoadingService, - private readonly api: ApiService, - private readonly errorService: ErrorService, - ) {} - - get isTor(): boolean { - return this.config.isTor() - } - - async presentAlertResetTor() { - this.wipe = false - this.dialogs - .open(TUI_PROMPT, { - label: this.isTor ? 'Warning' : 'Confirm', - data: { - content: this.template, - yes: 'Reset', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.resetTor(this.wipe)) - } - - presentAlertZram(enabled: boolean) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - data: { - content: enabled - ? 'Are you sure you want to disable zram? It provides significant performance benefits on low RAM devices.' - : 'Enable zram? It will only make a difference on lower RAM devices.', - yes: enabled ? 'Disable' : 'Enable', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.toggleZram(enabled)) - } - - private async resetTor(wipeState: boolean) { - const loader = this.loader.open('Resetting Tor...').subscribe() - - try { - await this.api.resetTor({ - 'wipe-state': wipeState, - reason: 'User triggered', - }) - this.alerts.open('Tor reset in progress').subscribe() - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async toggleZram(enabled: boolean) { - const loader = this.loader - .open(enabled ? 'Disabling zram...' : 'Enabling zram...') - .subscribe() - - try { - await this.api.toggleZram({ enable: !enabled }) - this.alerts.open(`Zram ${enabled ? 'disabled' : 'enabled'}`).subscribe() - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.module.ts deleted file mode 100644 index c1b88603d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { KernelLogsPage } from './kernel-logs.page' -import { LogsComponentModule } from 'src/app/common/logs/logs.component.module' - -const routes: Routes = [ - { - path: '', - component: KernelLogsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - LogsComponentModule, - ], - declarations: [KernelLogsPage], -}) -export class KernelLogsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.page.html b/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.page.html deleted file mode 100644 index e744ec68f..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.page.html +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.page.ts deleted file mode 100644 index 42118d02c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/kernel-logs/kernel-logs.page.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component } from '@angular/core' -import { RR } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' - -@Component({ - selector: 'kernel-logs', - templateUrl: './kernel-logs.page.html', - styleUrls: ['./kernel-logs.page.scss'], -}) -export class KernelLogsPage { - constructor(private readonly embassyApi: ApiService) {} - - followLogs() { - return async (params: RR.FollowServerLogsReq) => { - return this.embassyApi.followKernelLogs(params) - } - } - - fetchLogs() { - return async (params: RR.GetServerLogsReq) => { - return this.embassyApi.getKernelLogs(params) - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.module.ts deleted file mode 100644 index a18c8cbbe..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.module.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { FormsModule } from '@angular/forms' -import { RouterModule, Routes } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { - TuiDataListModule, - TuiHostedDropdownModule, - TuiNotificationModule, - TuiSvgModule, - TuiWrapperModule, -} from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TuiBadgeModule, TuiInputModule, TuiToggleModule } from '@taiga-ui/kit' -import { ProxiesPage } from './proxies.page' - -const routes: Routes = [ - { - path: '', - component: ProxiesPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - FormsModule, - TuiNotificationModule, - TuiButtonModule, - TuiInputModule, - TuiToggleModule, - TuiWrapperModule, - TuiBadgeModule, - TuiSvgModule, - TuiHostedDropdownModule, - TuiDataListModule, - ], - declarations: [ProxiesPage], -}) -export class ProxiesPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.page.html b/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.page.html deleted file mode 100644 index 2e15ce50f..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.page.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - Proxies - - - - -
- - Currently, StartOS only supports Wireguard proxies, which can be used for: -
    -
  1. - Proxying - outbound - traffic to mask your home/business IP from other servers accessed by - your server/services -
  2. -
  3. - Proxying - inbound - traffic to mask your home/business IP from anyone accessing your - server/services over clearnet -
  4. -
  5. - Creating a Virtual Local Area Network (VLAN) to enable private, remote - VPN access to your server/services -
  6. -
- View instructions -
-
- - - - Proxies - - - Add Proxy - - - -
- - - Name - Created - Type - Primary - Used By - - - - {{ proxy.name }} - {{ proxy.createdAt| date: 'short' }} - {{ proxy.type }} - - - - - - - {{ usedBy.domains.length + usedBy.services.length }} Connections - - - N/A - - - - - - - - - - - - - - - - - - - - - -
-
-
diff --git a/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.page.ts deleted file mode 100644 index 6ba862396..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/proxies/proxies.page.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { Component, ViewChild } from '@angular/core' -import { - TuiDialogOptions, - TuiDialogService, - TuiHostedDropdownComponent, -} from '@taiga-ui/core' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { filter } from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { PatchDB } from 'patch-db-client' -import { DataModel, Proxy } from 'src/app/services/patch-db/data-model' -import { FormContext, FormPage } from '../../../modals/form/form.page' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { Config } from '@start9labs/start-sdk/lib/config/builder/config' -import { Value } from '@start9labs/start-sdk/lib/config/builder/value' - -@Component({ - selector: 'proxies', - templateUrl: './proxies.page.html', - styleUrls: ['./proxies.page.scss'], -}) -export class ProxiesPage { - @ViewChild(TuiHostedDropdownComponent) - menuComponent?: TuiHostedDropdownComponent - - menuOpen = false - - readonly docsUrl = 'https://docs.start9.com/latest/user-manual/vpns/' - - readonly proxies$ = this.patch.watch$('server-info', 'network', 'proxies') - - constructor( - private readonly dialogs: TuiDialogService, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly api: ApiService, - private readonly patch: PatchDB, - private readonly formDialog: FormDialogService, - ) {} - - async presentModalAdd() { - const options: Partial>> = { - label: 'Add Proxy', - data: { - spec: await wireguardSpec.build({} as any), - buttons: [ - { - text: 'Save', - handler: value => this.save(value).then(() => true), - }, - ], - }, - } - this.formDialog.open(FormPage, options) - } - - async presentModalRename(proxy: Proxy) { - const options: Partial>> = { - label: `Rename ${proxy.name}`, - data: { - spec: { - name: await Value.text({ - name: 'Name', - required: { default: proxy.name }, - }).build({} as any), - }, - buttons: [ - { - text: 'Save', - handler: value => this.update(value).then(() => true), - }, - ], - }, - } - this.formDialog.open(FormPage, options) - } - - presentAlertDelete(id: string) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: 'Delete proxy? This action cannot be undone.', - yes: 'Delete', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => { - this.delete(id) - }) - } - - presentAlertUsedBy(name: string, usedBy: Proxy['usedBy']) { - let message = `Proxy "${name}" is currently used by:` - if (usedBy.domains.length) { - message = `${message}

Domains (inbound)

    ${usedBy.domains.map( - d => `
  • ${d}
  • `, - )}
` - } - if (usedBy.services.length) { - message = `${message}

Services (outbound)

${usedBy.services.map( - s => `
  • ${s.title}
  • `, - )}` - } - - this.dialogs - .open(message, { - label: 'Used by', - size: 's', - }) - .subscribe() - } - - private async save(value: WireguardSpec): Promise { - const loader = this.loader.open('Saving...').subscribe() - - try { - await this.api.addProxy({ - name: value.name, - config: value.config?.filePath || '', - }) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - async update( - value: Partial<{ - name: string - primaryInbound: true - primaryOutbound: true - }>, - ): Promise { - const loader = this.loader.open('Saving...').subscribe() - - try { - await this.api.updateProxy(value) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private async delete(id: string): Promise { - const loader = this.loader.open('Deleting...').subscribe() - - try { - await this.api.deleteProxy({ id }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} - -const wireguardSpec = Config.of({ - name: Value.text({ - name: 'Name', - description: 'A friendly name to help you remember and identify this proxy', - required: { default: null }, - }), - config: Value.file({ - name: 'Wiregaurd Config', - required: { default: null }, - extensions: ['.conf'], - }), -}) - -type WireguardSpec = typeof wireguardSpec.validator._TYPE diff --git a/web/projects/ui/src/app/apps/ui/pages/system/router/router.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/router/router.module.ts deleted file mode 100644 index 1f745495b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/router/router.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { RouterPage } from './router.page' -import { PrimaryIpPipeModule } from 'src/app/common/primary-ip/primary-ip.module' -import { FormsModule } from '@angular/forms' - -const routes: Routes = [ - { - path: '', - component: RouterPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - PrimaryIpPipeModule, - FormsModule, - ], - declarations: [RouterPage], -}) -export class RouterPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/router/router.page.html b/web/projects/ui/src/app/apps/ui/pages/system/router/router.page.html deleted file mode 100644 index a25127a9a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/router/router.page.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - Port Forwards - - - - -
    - - - - -

    - UPnP Disabled -

    -

    - Below are a list of ports that must be - manually - forwarded in your router in order to enable clearnet access. -
    -
    - Alternatively, you can enable UPnP in your router for automatic - configuration. - - View instructions - -

    -
    - - -

    - UPnP Enabled! -

    -

    - The ports below have been - automatically - forwarded in your router. -
    -
    - If you are running multiple servers, you may want to override - specific ports to suite your needs. - - View instructions - -

    -
    -
    -
    - - - - - Port - - - Target - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    {{ ip }}:{{ pf.target }}

    -
    - - - -
    -
    -
    -
    -
    -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/system/router/router.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/router/router.page.scss deleted file mode 100644 index 50f21298d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/router/router.page.scss +++ /dev/null @@ -1,26 +0,0 @@ -ion-item-divider { - padding-bottom: 8px; - padding-left: 0px; -} - -ion-item-group { - background-color: #1e2024; - border: 1px solid #717171; - border-radius: 6px; -} - -ion-item { - --inner-padding-end: 0; -} - -ion-buttons { - margin-left: 0; - margin-right: 8px; - ion-button::part(native) { - padding: 0 2px; - } -} - -.larger-icon { - font-size: 20px; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/system/router/router.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/router/router.page.ts deleted file mode 100644 index c02027ab1..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/router/router.page.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { DataModel, PortForward } from 'src/app/services/patch-db/data-model' -import { LoadingService, CopyService, ErrorService } from '@start9labs/shared' -import { ApiService } from 'src/app/services/api/embassy-api.service' - -@Component({ - selector: 'router', - templateUrl: './router.page.html', - styleUrls: ['./router.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class RouterPage { - readonly server$ = this.patch.watch$('server-info') - editing: Record = {} - overrides: Record = {} - - constructor( - readonly copyService: CopyService, - private readonly patch: PatchDB, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly api: ApiService, - ) {} - - async editPort(pf: PortForward) { - this.editing[pf.target] = !this.editing[pf.target] - this.overrides[pf.target] = pf.override || pf.assigned - } - - async saveOverride(pf: PortForward) { - const loader = this.loader.open('Saving...').subscribe() - - try { - await this.api.overridePortForward({ - target: pf.target, - port: this.overrides[pf.target], - }) - delete this.editing[pf.target] - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.module.ts deleted file mode 100644 index 4b14f215c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { ServerLogsPage } from './server-logs.page' -import { LogsComponentModule } from 'src/app/common/logs/logs.component.module' - -const routes: Routes = [ - { - path: '', - component: ServerLogsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - LogsComponentModule, - ], - declarations: [ServerLogsPage], -}) -export class ServerLogsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.page.html b/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.page.html deleted file mode 100644 index c41ab6461..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.page.html +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.page.ts deleted file mode 100644 index 5fa903876..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-logs/server-logs.page.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component } from '@angular/core' -import { RR } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' - -@Component({ - selector: 'server-logs', - templateUrl: './server-logs.page.html', - styleUrls: ['./server-logs.page.scss'], -}) -export class ServerLogsPage { - constructor(private readonly embassyApi: ApiService) {} - - followLogs() { - return async (params: RR.FollowServerLogsReq) => { - return this.embassyApi.followServerLogs(params) - } - } - - fetchLogs() { - return async (params: RR.GetServerLogsReq) => { - return this.embassyApi.getServerLogs(params) - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.module.ts deleted file mode 100644 index 4b6d12b89..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { ServerMetricsPage } from './server-metrics.page' -import { SkeletonListComponentModule } from 'src/app/common/skeleton-list/skeleton-list.component.module' -import { SharedPipesModule } from '@start9labs/shared' - -const routes: Routes = [ - { - path: '', - component: ServerMetricsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - SkeletonListComponentModule, - SharedPipesModule, - ], - declarations: [ServerMetricsPage], -}) -export class ServerMetricsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.page.html b/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.page.html deleted file mode 100644 index 58be4b196..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.page.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - Monitor - - - - - -

    - Websocket Failed. Reconnecting -

    - - - -

    Current Time (UTC)

    -

    - {{ serverData[0].value | date : 'MMMM d, y, h:mm:ss a' : 'UTC' }} -

    -

    - - NTP not synced, time could be wrong - -

    -
    -
    - - - -

    Uptime

    -

    - {{ uptime.days }} - Days, - {{ uptime.hours }} - Hours, - {{ uptime.minutes }} - Minutes, - {{ uptime.seconds }} - Seconds -

    -
    -
    - - - - General - - Temperature - - - {{ general.temperature.value }} °C - - N/A - - - - - - Memory - - Percentage Used - - {{ memory['percentage-used'].value }} % - - - - Total - - {{ memory.total.value }} MiB - - - - Used - - {{ memory.used.value }} MiB - - - - Available - {{ memory.available.value }} MiB - - - zram Used - {{ memory['zram-used'].value }} MiB - - - zram Total - {{ memory['zram-total'].value }} MiB - - - zram Available - - {{ memory['zram-available'].value }} MiB - - - - - - CPU - - Percentage Used - {{ cpu['percentage-used'].value }} % - - - User Space - - {{ cpu['user-space'].value }} % - - - - Kernel Space - - {{ cpu['kernel-space'].value }} % - - - - Idle - {{ cpu.idle.value }} % - - - I/O Wait - {{ cpu.wait.value }} % - - - - - Disk - - Percentage Used - {{ disk['percentage-used'].value }} % - - - Capacity - - {{ disk.capacity.value }} GB - - - - Used - - {{ disk.used.value }} GB - - - - Available - {{ disk.available.value }} GB - - - -
    - - - - -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.page.scss deleted file mode 100644 index 17a33cb9c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.page.scss +++ /dev/null @@ -1,3 +0,0 @@ -ion-note { - font-size: medium; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.page.ts deleted file mode 100644 index 5209ee91e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-metrics/server-metrics.page.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Component } from '@angular/core' -import { Metrics } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { TimeService } from 'src/app/services/time-service' -import { - catchError, - combineLatest, - filter, - from, - Observable, - startWith, - switchMap, -} from 'rxjs' -import { ConnectionService } from 'src/app/services/connection.service' - -@Component({ - selector: 'server-metrics', - templateUrl: './server-metrics.page.html', - styleUrls: ['./server-metrics.page.scss'], -}) -export class ServerMetricsPage { - websocketFail = false - - readonly serverData$ = this.getServerData$() - - constructor( - private readonly api: ApiService, - private readonly timeService: TimeService, - private readonly connectionService: ConnectionService, - ) {} - - private getServerData$(): Observable< - [ - { - value: number - synced: boolean - }, - { - days: number - hours: number - minutes: number - seconds: number - }, - Metrics, - ] - > { - return combineLatest([ - this.timeService.now$, - this.timeService.uptime$, - this.getMetrics$(), - ]).pipe( - catchError(() => { - this.websocketFail = true - return this.connectionService.connected$.pipe( - filter(Boolean), - switchMap(() => this.getServerData$()), - ) - }), - ) - } - - private getMetrics$(): Observable { - return from(this.api.getServerMetrics({})).pipe( - switchMap(({ metrics, guid }) => - this.api - .openMetricsWebsocket$({ - url: `/rpc/${guid}`, - openObserver: { - next: () => (this.websocketFail = false), - }, - }) - .pipe(startWith(metrics)), - ), - ) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-show/backup-color.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-show/backup-color.pipe.ts deleted file mode 100644 index 461afa03e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-show/backup-color.pipe.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' - -@Pipe({ - name: 'backupColor', -}) -export class BackupColorPipe implements PipeTransform { - transform(lastBackup: string | null): 'success' | 'warning' | 'danger' { - if (!lastBackup) return 'danger' - - const currentDate = new Date().valueOf() - const backupDate = new Date(lastBackup).valueOf() - const diff = currentDate - backupDate - const week = 604800000 - - if (diff <= week) { - return 'success' - } else if (diff > week && diff <= week * 2) { - return 'warning' - } else { - return 'danger' - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.html b/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.html deleted file mode 100644 index e2eaebcfb..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.html +++ /dev/null @@ -1,13 +0,0 @@ -

    StartOS {{ versions[0].version }}

    -

    Release Notes

    - - - -

    {{ v.version }}

    -
    -
    -
    - - diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.module.ts deleted file mode 100644 index 95c7c02d2..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.module.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { MarkdownPipeModule, SafeLinksDirective } from '@start9labs/shared' -import { TuiScrollbarModule } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TuiAutoFocusModule } from '@taiga-ui/cdk' -import { NgDompurifyModule } from '@tinkoff/ng-dompurify' -import { OSUpdatePage } from './os-update.page' - -@NgModule({ - declarations: [OSUpdatePage], - imports: [ - CommonModule, - MarkdownPipeModule, - TuiButtonModule, - TuiAutoFocusModule, - TuiScrollbarModule, - SafeLinksDirective, - NgDompurifyModule, - ], - exports: [OSUpdatePage], -}) -export class OSUpdatePageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.scss deleted file mode 100644 index d2f78caf7..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.scss +++ /dev/null @@ -1,24 +0,0 @@ -.title { - margin-top: 0; - font-weight: bold; -} - -.subtitle { - color: var(--tui-text-02); - font-weight: normal; -} - -.scrollbar { - margin: 24px 0; - max-height: 50vh; -} - -.version { - box-shadow: 0 1px var(--tui-base-02); - margin: 0 24px 0 0; - padding: 6px 0; -} - -.button { - float: right; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.ts deleted file mode 100644 index d87a3856e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-show/os-update/os-update.page.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { EOSService } from 'src/app/services/eos.service' - -@Component({ - selector: 'os-update', - templateUrl: './os-update.page.html', - styleUrls: ['./os-update.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class OSUpdatePage { - versions: { version: string; notes: string }[] = [] - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) private readonly context: TuiDialogContext, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly embassyApi: ApiService, - private readonly eosService: EOSService, - ) {} - - ngOnInit() { - const releaseNotes = this.eosService.eos?.['release-notes']! - - this.versions = Object.keys(releaseNotes) - .sort() - .reverse() - .map(version => ({ - version, - notes: releaseNotes[version], - })) - } - - async updateEOS() { - const loader = this.loader.open('Beginning update...').subscribe() - - try { - await this.embassyApi.updateServer() - this.context.$implicit.complete() - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.module.ts deleted file mode 100644 index 18799d6b5..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.module.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { ServerShowPage } from './server-show.page' -import { FormsModule } from '@angular/forms' -import { TextSpinnerComponentModule } from '@start9labs/shared' -import { BadgeMenuComponentModule } from 'src/app/common/badge-menu-button/badge-menu.component.module' -import { InsecureWarningComponentModule } from 'src/app/common/insecure-warning/insecure-warning.module' -import { OSUpdatePageModule } from './os-update/os-update.page.module' -import { PromptModule } from 'src/app/apps/ui/modals/prompt/prompt.module' -import { ThemeSwitcherModule } from '../theme-switcher/theme-switcher.module' -import { BackupColorPipe } from './backup-color.pipe' - -const routes: Routes = [ - { - path: '', - component: ServerShowPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - IonicModule, - TextSpinnerComponentModule, - BadgeMenuComponentModule, - OSUpdatePageModule, - ThemeSwitcherModule, - InsecureWarningComponentModule, - PromptModule, - RouterModule.forChild(routes), - ], - declarations: [ServerShowPage, BackupColorPipe], -}) -export class ServerShowPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.page.html b/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.page.html deleted file mode 100644 index a508924f5..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.page.html +++ /dev/null @@ -1,124 +0,0 @@ - - - System - - - - - - - - - - - - - - - - - -

    Clock sync failure

    -

    - This will cause connectivity issues. Refer to the StartOS docs to - resolve the issue. -

    -
    - - Open Docs - - -
    - - - - -

    Http detected

    -

    - Tor is faster over https. - - Download and trust your server's Root CA - - , then switch to https. -

    -
    - - Open Https - - -
    - -
    - - - {{ cat.key }} - - - - - - - -

    {{ button.title }}

    -

    {{ button.description }}

    - - -

    - - Update Complete. Restart to apply changes - - - - - - Update Available - - - - - - Check for updates - - - -

    - -

    - - {{ !server.network.outboundProxy ? 'None' : - server.network.outboundProxy === 'primary' ? 'System Primary' : - server.network.outboundProxy.proxyId }} - -

    -
    -
    -
    -
    -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.page.scss deleted file mode 100644 index 84f709c07..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.page.scss +++ /dev/null @@ -1,15 +0,0 @@ -ion-item-divider { - text-transform: unset; - padding-bottom: 12px; - padding-left: 0; -} - -ion-item-group { - background-color: #1e2024; - border: 1px solid #717171; - border-radius: 6px; -} - -ion-item { - --background: #1e2024; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.page.ts deleted file mode 100644 index bd241d64f..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-show/server-show.page.ts +++ /dev/null @@ -1,691 +0,0 @@ -import { Component, Inject } from '@angular/core' -import { NavController } from '@ionic/angular' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ActivatedRoute } from '@angular/router' -import { PatchDB } from 'patch-db-client' -import { filter, Observable, of, switchMap, take } from 'rxjs' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { EOSService } from 'src/app/services/eos.service' -import { ClientStorageService } from 'src/app/services/client-storage.service' -import { OSUpdatePage } from './os-update/os-update.page' -import { getAllPackages } from 'src/app/util/get-package-data' -import { AuthService } from 'src/app/services/auth.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormPage } from '../../../modals/form/form.page' -import { Config } from '@start9labs/start-sdk/lib/config/builder/config' -import { Value } from '@start9labs/start-sdk/lib/config/builder/value' -import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' -import { ConfigService } from 'src/app/services/config.service' -import { TuiAlertService, TuiDialogService } from '@taiga-ui/core' -import { PROMPT } from 'src/app/apps/ui/modals/prompt/prompt.component' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { WINDOW } from '@ng-web-apis/common' -import { getServerInfo } from 'src/app/util/get-server-info' -import * as argon2 from '@start9labs/argon2' -import { ProxyService } from 'src/app/services/proxy.service' - -@Component({ - selector: 'server-show', - templateUrl: 'server-show.page.html', - styleUrls: ['server-show.page.scss'], -}) -export class ServerShowPage { - manageClicks = 0 - powerClicks = 0 - - readonly server$ = this.patch.watch$('server-info') - readonly showUpdate$ = this.eosService.showUpdate$ - readonly showDiskRepair$ = this.clientStorageService.showDiskRepair$ - - readonly isTorHttp = this.config.isTorHttp() - - constructor( - private readonly dialogs: TuiDialogService, - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly api: ApiService, - private readonly navCtrl: NavController, - private readonly route: ActivatedRoute, - private readonly patch: PatchDB, - private readonly eosService: EOSService, - private readonly clientStorageService: ClientStorageService, - private readonly authService: AuthService, - private readonly alerts: TuiAlertService, - private readonly config: ConfigService, - private readonly formDialog: FormDialogService, - private readonly proxyService: ProxyService, - @Inject(WINDOW) private readonly windowRef: Window, - ) {} - - addClick(title: string) { - switch (title) { - case 'Security': - this.addSecurityClick() - break - case 'Power': - this.addPowerClick() - break - default: - return - } - } - - private async setBrowserTab(): Promise { - this.patch - .watch$('ui', 'name') - .pipe( - switchMap(initialValue => - this.dialogs.open(PROMPT, { - label: 'Browser Tab Title', - data: { - message: `This value will be displayed as the title of your browser tab.`, - label: 'Device Name', - placeholder: 'StartOS', - required: false, - buttonText: 'Save', - initialValue, - }, - }), - ), - take(1), - ) - .subscribe(name => this.setName(name || null)) - } - - private updateEos() { - this.dialogs.open(new PolymorpheusComponent(OSUpdatePage)).subscribe() - } - - private presentAlertResetPassword() { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content: - 'You will still need your current password to decrypt existing backups!', - yes: 'Continue', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.presentModalResetPassword()) - } - - async presentModalResetPassword(): Promise { - this.formDialog.open(FormPage, { - label: 'Change Master Password', - data: { - spec: await configBuilderToSpec(passwordSpec), - buttons: [ - { - text: 'Save', - handler: (value: PasswordSpec) => this.resetPassword(value), - }, - ], - }, - }) - } - - private async resetPassword(value: PasswordSpec): Promise { - let err = '' - - if (value.newPassword1 !== value.newPassword2) { - err = 'New passwords do not match' - } else if (value.newPassword1.length < 12) { - err = 'New password must be 12 characters or greater' - } else if (value.newPassword1.length > 64) { - err = 'New password must be less than 65 characters' - } - - // confirm current password is correct - const { 'password-hash': passwordHash } = await getServerInfo(this.patch) - try { - argon2.verify(passwordHash, value.currentPassword) - } catch (e) { - err = 'Current password is invalid' - } - - if (err) { - this.errorService.handleError(err) - return false - } - - const loader = this.loader.open('Saving...').subscribe() - - try { - await this.api.resetPassword({ - 'old-password': value.currentPassword, - 'new-password': value.newPassword1, - }) - - this.alerts.open('Password changed!').subscribe() - - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private presentAlertLogout() { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: 'Are you sure you want to log out?', - yes: 'Logout', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.logout()) - } - - private presentAlertRestart() { - this.dialogs - .open(TUI_PROMPT, { - label: 'Restart', - size: 's', - data: { - content: - 'Are you sure you want to restart your server? It can take several minutes to come back online.', - yes: 'Restart', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.restart()) - } - - private presentAlertShutdown() { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content: - 'Are you sure you want to power down your server? This can take several minutes, and your server will not come back online automatically. To power on again, You will need to physically unplug your server and plug it back in', - yes: 'Shutdown', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.shutdown()) - } - - private async presentAlertSystemRebuild() { - const localPkgs = await getAllPackages(this.patch) - const minutes = Object.keys(localPkgs).length * 2 - - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content: `This action will tear down all service containers and rebuild them from scratch. No data will be deleted. This action is useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues. It may take up to ${minutes} minutes to complete. During this time, you will lose all connectivity to your server.`, - yes: 'Rebuild', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.systemRebuild()) - } - - private presentAlertRepairDisk() { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content: `This action should only be executed if directed by a Start9 support specialist. We recommend backing up your device before preforming this action.

    If anything happens to the device during the reboot, such as losing power or unplugging the drive, the filesystem will be in an unrecoverable state. Please proceed with caution.

    `, - yes: 'Rebuild', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.systemRebuild()) - } - - async launchHttps() { - const info = await getServerInfo(this.patch) - this.windowRef.open(`https://${info.ui.torHostname}`, '_self') - } - - private async setName(value: string | null): Promise { - const loader = this.loader.open('Saving...').subscribe() - - try { - await this.api.setDbValue(['name'], value) - } finally { - loader.unsubscribe() - } - } - - // should wipe cache independent of actual BE logout - private logout() { - this.api.logout({}).catch(e => console.error('Failed to log out', e)) - this.authService.setUnverified() - } - - private async restart() { - const action = 'Restart' - const loader = this.loader.open(`Beginning ${action}...`).subscribe() - - try { - await this.api.restartServer({}) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async shutdown() { - const action = 'Shutdown' - const loader = this.loader.open(`Beginning ${action}...`).subscribe() - - try { - await this.api.shutdownServer({}) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async systemRebuild() { - const action = 'System Rebuild' - const loader = this.loader.open(`Beginning ${action}...`).subscribe() - - try { - await this.api.systemRebuild({}) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async checkForEosUpdate(): Promise { - const loader = this.loader.open('Checking for updates').subscribe() - - try { - await this.eosService.loadEos() - - loader.unsubscribe() - - if (this.eosService.updateAvailable$.value) { - this.updateEos() - } else { - this.presentAlertLatest() - } - } catch (e: any) { - loader.unsubscribe() - this.errorService.handleError(e) - } - } - - private presentAlertLatest() { - this.dialogs - .open('You are on the latest version of StartOS.', { - label: 'Up to date!', - size: 's', - }) - .subscribe() - } - - settings: ServerSettings = { - General: [ - { - title: 'About', - description: 'Basic information about your server', - icon: 'information-circle-outline', - action: () => - this.navCtrl.navigateForward(['specs'], { relativeTo: this.route }), - detail: true, - disabled$: of(false), - }, - { - title: 'Software Update', - description: 'Get the latest version of StartOS', - icon: 'cloud-download-outline', - action: () => - this.eosService.updateAvailable$.getValue() - ? this.updateEos() - : this.checkForEosUpdate(), - detail: false, - disabled$: this.eosService.updatingOrBackingUp$, - }, - { - title: 'Email', - description: - 'Connect to an external SMTP server to send yourself emails', - icon: 'mail-outline', - action: () => - this.navCtrl.navigateForward(['email'], { relativeTo: this.route }), - detail: true, - disabled$: of(false), - }, - { - title: 'Sideload a Service', - description: `Manually install a service`, - icon: 'push-outline', - action: () => - this.navCtrl.navigateForward(['sideload'], { - relativeTo: this.route, - }), - detail: true, - disabled$: of(false), - }, - { - title: 'Change Master Password', - description: `Change your StartOS master password`, - icon: 'key-outline', - action: () => this.presentAlertResetPassword(), - detail: false, - disabled$: of(false), - }, - { - title: 'Experimental Features', - description: 'Try out new and potentially unstable new features', - icon: 'flask-outline', - action: () => - this.navCtrl.navigateForward(['experimental-features'], { - relativeTo: this.route, - }), - detail: true, - disabled$: of(false), - }, - ], - Network: [ - { - title: 'Domains', - description: 'Manage domains for clearnet connectivity', - icon: 'globe-outline', - action: () => - this.navCtrl.navigateForward(['domains'], { relativeTo: this.route }), - detail: true, - disabled$: of(false), - }, - { - title: 'Proxies', - description: 'Manage proxies for inbound and outbound connections', - icon: 'shuffle-outline', - action: () => - this.navCtrl.navigateForward(['proxies'], { relativeTo: this.route }), - detail: true, - disabled$: of(false), - }, - { - title: 'Router Config', - description: 'Connect or configure your router for clearnet', - icon: 'radio-outline', - action: () => - this.navCtrl.navigateForward(['router-config'], { - relativeTo: this.route, - }), - detail: true, - disabled$: of(false), - }, - { - title: 'WiFi', - description: 'Add or remove WiFi networks', - icon: 'wifi', - action: () => - this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }), - detail: true, - disabled$: of(false), - }, - ], - 'User Interface': [ - { - title: 'Browser Tab Title', - description: `Customize the display name of your browser tab`, - icon: 'pricetag-outline', - action: () => this.setBrowserTab(), - detail: false, - disabled$: of(false), - }, - { - title: 'Web Addresses', - description: 'View and manage web addresses for accessing this UI', - icon: 'desktop-outline', - action: () => - this.navCtrl.navigateForward(['interfaces', 'ui'], { - relativeTo: this.route, - }), - detail: true, - disabled$: of(false), - }, - ], - 'Privacy and Security': [ - { - title: 'Outbound Proxy', - description: 'Proxy outbound traffic from the StartOS main process', - icon: 'shield-outline', - action: () => this.proxyService.presentModalSetOutboundProxy(), - detail: false, - disabled$: of(false), - }, - { - title: 'SSH', - description: - 'Manage your SSH keys to access your server from the command line', - icon: 'terminal-outline', - action: () => - this.navCtrl.navigateForward(['ssh'], { relativeTo: this.route }), - detail: true, - disabled$: of(false), - }, - { - title: 'Active Sessions', - description: 'View and manage device access', - icon: 'stopwatch-outline', - action: () => - this.navCtrl.navigateForward(['sessions'], { - relativeTo: this.route, - }), - detail: true, - disabled$: of(false), - }, - ], - Logs: [ - { - title: 'Activity Monitor', - description: 'CPU, disk, memory, and other useful metrics', - icon: 'pulse', - action: () => - this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }), - detail: true, - disabled$: of(false), - }, - { - title: 'OS Logs', - description: 'Raw, unfiltered operating system logs', - icon: 'receipt-outline', - action: () => - this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }), - detail: true, - disabled$: of(false), - }, - { - title: 'Kernel Logs', - description: - 'Diagnostic log stream for device drivers and other kernel processes', - icon: 'receipt-outline', - action: () => - this.navCtrl.navigateForward(['kernel-logs'], { - relativeTo: this.route, - }), - detail: true, - disabled$: of(false), - }, - { - title: 'Tor Logs', - description: 'Diagnostic log stream for the Tor daemon on StartOS', - icon: 'receipt-outline', - action: () => - this.navCtrl.navigateForward(['tor-logs'], { - relativeTo: this.route, - }), - detail: true, - disabled$: of(false), - }, - ], - Support: [ - { - title: 'User Manual', - description: 'Discover what StartOS can do', - icon: 'map-outline', - action: () => - this.windowRef.open( - 'https://docs.start9.com/0.3.5.x/user-manual', - '_blank', - 'noreferrer', - ), - detail: true, - disabled$: of(false), - }, - { - title: 'Contact Support', - description: 'Get help from the Start9 team and community', - icon: 'chatbubbles-outline', - action: () => - this.windowRef.open( - 'https://start9.com/contact', - '_blank', - 'noreferrer', - ), - detail: true, - disabled$: of(false), - }, - { - title: 'Donate to Start9', - description: `Support StartOS development`, - icon: 'logo-bitcoin', - action: () => - this.windowRef.open( - 'https://donate.start9.com', - '_blank', - 'noreferrer', - ), - detail: true, - disabled$: of(false), - }, - ], - Power: [ - { - title: 'Log Out', - description: '', - icon: 'log-out-outline', - action: () => this.presentAlertLogout(), - detail: false, - disabled$: of(false), - }, - { - title: 'Restart', - description: '', - icon: 'reload', - action: () => this.presentAlertRestart(), - detail: false, - disabled$: of(false), - }, - { - title: 'Shutdown', - description: '', - icon: 'power', - action: () => this.presentAlertShutdown(), - detail: false, - disabled$: of(false), - }, - { - title: 'System Rebuild', - description: '', - icon: 'construct-outline', - action: () => this.presentAlertSystemRebuild(), - detail: false, - disabled$: of(false), - }, - { - title: 'Repair Disk', - description: '', - icon: 'medkit-outline', - action: () => this.presentAlertRepairDisk(), - detail: false, - disabled$: of(false), - }, - ], - } - - private addSecurityClick() { - this.manageClicks++ - - if (this.manageClicks === 5) { - this.manageClicks = 0 - this.alerts - .open( - this.clientStorageService.toggleShowDevTools() - ? 'Dev tools unlocked' - : 'Dev tools hidden', - ) - .subscribe() - } - } - - private addPowerClick() { - this.powerClicks++ - if (this.powerClicks === 5) { - this.powerClicks = 0 - this.clientStorageService.toggleShowDiskRepair() - } - } - - asIsOrder() { - return 0 - } -} - -interface ServerSettings { - [key: string]: SettingBtn[] -} - -interface SettingBtn { - title: string - description: string - icon: string - action: Function - detail: boolean - disabled$: Observable -} - -export const passwordSpec = Config.of({ - currentPassword: Value.text({ - name: 'Current Password', - required: { - default: null, - }, - masked: true, - }), - newPassword1: Value.text({ - name: 'New Password', - required: { - default: null, - }, - masked: true, - }), - newPassword2: Value.text({ - name: 'Retype New Password', - required: { - default: null, - }, - masked: true, - }), -}) - -type PasswordSpec = typeof passwordSpec.validator._TYPE diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.module.ts deleted file mode 100644 index 2393527ac..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { ServerSpecsPage } from './server-specs.page' -import { EmverPipesModule } from '@start9labs/shared' -import { TuiLetModule } from '@taiga-ui/cdk' - -const routes: Routes = [ - { - path: '', - component: ServerSpecsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - EmverPipesModule, - TuiLetModule, - ], - declarations: [ServerSpecsPage], -}) -export class ServerSpecsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.page.html b/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.page.html deleted file mode 100644 index 0c3b24f92..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.page.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - About - - - - - - StartOS Info - - -

    Version

    -

    {{ server.version | displayEmver }}

    -
    -
    - - -

    Git Hash

    -

    {{ gitHash }}

    -
    - - - -
    - - Device Credentials - - - -

    CA fingerprint

    -

    {{ server['ca-fingerprint'] }}

    -
    - - - -
    -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.page.scss deleted file mode 100644 index 61ead3b94..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.page.scss +++ /dev/null @@ -1,3 +0,0 @@ -p { - font-family: 'Courier New'; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.page.ts deleted file mode 100644 index 7e836bc4c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/server-specs/server-specs.page.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { ConfigService } from 'src/app/services/config.service' -import { CopyService } from '@start9labs/shared' -import { DataModel } from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'server-specs', - templateUrl: './server-specs.page.html', - styleUrls: ['./server-specs.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ServerSpecsPage { - readonly server$ = this.patch.watch$('server-info') - - constructor( - readonly copyService: CopyService, - private readonly patch: PatchDB, - private readonly config: ConfigService, - ) {} - - get gitHash(): string { - return this.config.gitHash - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.module.ts deleted file mode 100644 index 444321c36..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { PlatformInfoPipe, SessionsPage } from './sessions.page' -import { SharedPipesModule } from '@start9labs/shared' -import { TuiLetModule } from '@taiga-ui/cdk' - -const routes: Routes = [ - { - path: '', - component: SessionsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - SharedPipesModule, - TuiLetModule, - ], - declarations: [SessionsPage, PlatformInfoPipe], -}) -export class SessionsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.page.html b/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.page.html deleted file mode 100644 index d8579c6fa..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.page.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - Active Sessions - - - - - - - Current Session -
    - - - User Agent - Platform - Last Active - - - - - {{ currentSession['user-agent'] }} - - - -   {{ info.name }} - - - - {{ currentSession['last-active']| date: 'medium' }} - - - - - - - - - - - - -
    - - - - - Other Sessions - - Terminate Selected - - - -
    - - - -
    - -
    - User Agent -
    - Platform - Last Active -
    - - - - -
    - -
    - {{ session['user-agent'] }} -
    - - - -   {{ info.name }} - - - - {{ session['last-active']| date: 'medium' }} - -
    -

    - You are not logged in anywhere else -

    -
    - - - - - - - - -
    -
    -
    -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.page.scss deleted file mode 100644 index 05b3f2393..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.page.scss +++ /dev/null @@ -1,3 +0,0 @@ -.highlighted { - background-color: var(--ion-color-medium-shade); -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.page.ts deleted file mode 100644 index 607d4ce29..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/sessions/sessions.page.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Component } from '@angular/core' -import { Pipe, PipeTransform } from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { PlatformType, Session } from 'src/app/services/api/api.types' -import { Observable, Subject, from, map, merge, shareReplay } from 'rxjs' - -@Component({ - selector: 'sessions', - templateUrl: 'sessions.page.html', - styleUrls: ['sessions.page.scss'], -}) -export class SessionsPage { - private readonly sessions$ = from(this.api.getSessions({})) - private readonly localOther$ = new Subject() - private readonly remoteOther$: Observable = - this.sessions$.pipe( - map(s => - Object.entries(s.sessions) - .filter(([id, _]) => id !== s.current) - .map(([id, session]) => ({ - id, - ...session, - })) - .sort( - (a, b) => - new Date(b['last-active']).valueOf() - - new Date(a['last-active']).valueOf(), - ), - ), - ) - - readonly currentSession$ = this.sessions$.pipe( - map(s => s.sessions[s.current]), - shareReplay(), - ) - - readonly otherSessions$ = merge(this.localOther$, this.remoteOther$) - - selected: Record = {} - - constructor( - private readonly loader: LoadingService, - private readonly errorService: ErrorService, - private readonly api: ApiService, - ) {} - - get empty() { - return this.count === 0 - } - - get count() { - return Object.keys(this.selected).length - } - - async toggleChecked(id: string) { - if (this.selected[id]) { - delete this.selected[id] - } else { - this.selected[id] = true - } - } - - async toggleAll(otherSessions: SessionWithId[]) { - if (this.empty) { - otherSessions.forEach(s => (this.selected[s.id] = true)) - } else { - this.selected = {} - } - } - - async kill(otherSessions: SessionWithId[]): Promise { - const ids = Object.keys(this.selected) - - const loader = this.loader - .open(`Terminating session${ids.length > 1 ? 's' : ''}...`) - .subscribe() - - try { - await this.api.killSessions({ ids }) - this.selected = {} - this.localOther$.next(otherSessions.filter(s => !ids.includes(s.id))) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} - -interface SessionWithId extends Session { - id: string -} - -@Pipe({ - name: 'platformInfo', -}) -export class PlatformInfoPipe implements PipeTransform { - transform(platforms: PlatformType[]): { name: string; icon: string } { - const info = { - name: '', - icon: 'phone-portrait-outline', - } - - if (platforms.includes('cli')) { - info.name = 'CLI' - info.icon = 'terminal-outline' - } else if (platforms.includes('desktop')) { - info.name = 'Desktop/Laptop' - info.icon = 'desktop-outline' - } else if (platforms.includes('android')) { - info.name = 'Android Device' - } else if (platforms.includes('iphone')) { - info.name = 'iPhone' - } else if (platforms.includes('ipad')) { - info.name = 'iPad' - } else if (platforms.includes('ios')) { - info.name = 'iOS Device' - } else { - info.name = 'Unknown Device' - } - - return info - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/sideload/dnd.directive.ts b/web/projects/ui/src/app/apps/ui/pages/system/sideload/dnd.directive.ts deleted file mode 100644 index 35daf09ef..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/sideload/dnd.directive.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - Directive, - ElementRef, - EventEmitter, - HostBinding, - HostListener, - Output, -} from '@angular/core' -import { DomSanitizer } from '@angular/platform-browser' - -@Directive({ - selector: '[appDnd]', -}) -export class DragNDropDirective { - @Output() onFileDropped: EventEmitter = new EventEmitter() - - @HostBinding('style.background') private background = 'rgba(24, 24, 24, 0.5)' - - constructor(el: ElementRef, private sanitizer: DomSanitizer) {} - - @HostListener('dragover', ['$event']) public onDragOver(evt: DragEvent) { - evt.preventDefault() - evt.stopPropagation() - this.background = '#6a937b3c' - } - - @HostListener('dragleave', ['$event']) public onDragLeave(evt: DragEvent) { - evt.preventDefault() - evt.stopPropagation() - this.background = 'rgba(24, 24, 24, 0.5)' - } - - @HostListener('drop', ['$event']) public onDrop(evt: DragEvent) { - evt.preventDefault() - evt.stopPropagation() - this.background = ' rgba(24, 24, 24, 0.5)' - this.onFileDropped.emit(evt) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.module.ts deleted file mode 100644 index a38c4c7b9..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.module.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { SideloadPage } from './sideload.page' -import { RouterModule, Routes } from '@angular/router' -import { EmverPipesModule, SharedPipesModule } from '@start9labs/shared' -import { DragNDropDirective } from './dnd.directive' -import { - AboutModule, - AdditionalModule, - DependenciesModule, - MarketplacePackageHeroComponent, -} from '@start9labs/marketplace' -// TODO: Find a way to not tie two routes together -import { MarketplaceShowControlsComponent } from '../../marketplace/marketplace-show-preview/components/marketplace-show-controls.component' - -const routes: Routes = [ - { - path: '', - component: SideloadPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - SharedPipesModule, - EmverPipesModule, - AboutModule, - AdditionalModule, - MarketplaceShowControlsComponent, - DependenciesModule, - MarketplacePackageHeroComponent, - ], - declarations: [SideloadPage, DragNDropDirective], -}) -export class SideloadPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.page.html b/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.page.html deleted file mode 100644 index 3ff228fbd..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.page.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - Sideload Service - - - - - -
    -

    - - Invalid package file -

    - Try again -
    - - - - -
    -
    - - - -
    -
    - - - - -
    -
    -

    Dependencies

    -
    -
    - -
    -
    -
    -
    - -
    -
    - - -
    - -

    Upload .s9pk package file

    -

    - - Tip: switch to LAN for faster uploads. - -

    - - - - -
    -
    -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.page.scss deleted file mode 100644 index 3155e4585..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.page.scss +++ /dev/null @@ -1,44 +0,0 @@ -.inline { - vertical-align: initial; -} - -.drop-area { - display: flex; - background-color: rgba(24, 24, 24, 0.5); - flex-direction: column; - justify-content: center; - align-items: center; - border-style: dashed; - border-width: 2px; - border-color: var(--ion-color-dark); - color: var(--ion-color-dark); - border-radius: 5px; - margin: 20px; - padding: 30px; - min-height: 600px; - - &_filled { - display: flex; - background-color: rgba(24, 24, 24, 0.5); - flex-direction: column; - justify-content: center; - align-items: center; - border-style: solid; - border-width: 2px; - border-color: var(--ion-color-dark); - color: var(--ion-color-dark); - border-radius: 5px; - margin: 60px; - padding: 30px; - min-height: 600px; - min-width: 400px; - } - - &_mobile { - border-width: 0px !important; - } - - ion-input { - color: var(--ion-color-dark); - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.page.ts deleted file mode 100644 index f60b62ca9..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/sideload/sideload.page.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { Component } from '@angular/core' -import { isPlatform, NavController } from '@ionic/angular' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { Manifest, MarketplacePkg } from '@start9labs/marketplace' -import { ConfigService } from 'src/app/services/config.service' -import { ErrorService, LoadingService } from '@start9labs/shared' -import cbor from 'cbor' - -interface Positions { - [key: string]: [bigint, bigint] // [position, length] -} - -const MAGIC = new Uint8Array([59, 59]) -const VERSION = new Uint8Array([1]) - -@Component({ - selector: 'sideload', - templateUrl: './sideload.page.html', - styleUrls: ['./sideload.page.scss'], -}) -export class SideloadPage { - isMobile = isPlatform(window, 'ios') || isPlatform(window, 'android') - pkgData?: { - pkg: MarketplacePkg - file: File - } - onTor = this.config.isTor() - invalid = false - - constructor( - private readonly loader: LoadingService, - private readonly api: ApiService, - private readonly navCtrl: NavController, - private readonly errorService: ErrorService, - private readonly config: ConfigService, - ) {} - - handleFileDrop(e: any) { - const files = e.dataTransfer.files - this.setFile(files) - } - - handleFileInput(e: any) { - const files = e.target.files - this.setFile(files) - } - - clear() { - this.pkgData = undefined - this.invalid = false - } - - async handleUpload() { - if (!this.pkgData) return - const loader = this.loader.open('Uploading package').subscribe() - const { pkg, file } = this.pkgData - - try { - const guid = await this.api.sideloadPackage({ - manifest: pkg.manifest, - icon: pkg.icon, - size: file.size, - }) - this.api.uploadPackage(guid, file!).catch(e => console.error(e)) - - this.navCtrl.navigateRoot('/services') - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - this.clear() - } - } - - private async setFile(files?: File[]) { - if (!files || !files.length) return - const file = files[0] - if (!file) return - - await this.validateS9pk(file) - } - - private async validateS9pk(file: File) { - const magic = new Uint8Array(await blobToBuffer(file.slice(0, 2))) - const version = new Uint8Array(await blobToBuffer(file.slice(2, 3))) - if (compare(magic, MAGIC) && compare(version, VERSION)) { - this.pkgData = { - pkg: await this.parseS9pk(file), - file, - } - } else { - this.invalid = true - } - } - - private async parseS9pk(file: File): Promise { - const positions: Positions = {} - // magic=2bytes, version=1bytes, pubkey=32bytes, signature=64bytes, toc_length=4bytes = 103byte is starting point - let start = 103 - let end = start + 1 // 104 - const tocLength = new DataView( - await blobToBuffer(file.slice(99, 103) ?? new Blob()), - ).getUint32(0, false) - await getPositions(start, end, file, positions, tocLength as any) - - const manifest = await this.getAsset(positions, file, 'manifest') - const [icon] = await Promise.all([ - this.getIcon(positions, file, manifest), - // this.getAsset(positions, file, 'license'), - // this.getAsset(positions, file, 'instructions'), - ]) - - return { - manifest, - icon, - license: '', - instructions: '', - categories: [], - versions: [], - 'dependency-metadata': {}, - 'published-at': '', - } - } - - private async getAsset( - positions: Positions, - file: Blob, - asset: 'manifest' | 'license' | 'instructions', - ): Promise { - const data = await blobToBuffer( - file.slice( - Number(positions[asset][0]), - Number(positions[asset][0]) + Number(positions[asset][1]), - ), - ) - return cbor.decode(data, true) - } - - private async getIcon( - positions: Positions, - file: Blob, - manifest: Manifest, - ): Promise { - const contentType = `image/${manifest.assets.icon.split('.').pop()}` - const data = file.slice( - Number(positions['icon'][0]), - Number(positions['icon'][0]) + Number(positions['icon'][1]), - contentType, - ) - return blobToDataURL(data) - } -} - -async function getPositions( - initialStart: number, - initialEnd: number, - file: Blob, - positions: Positions, - tocLength: number, -) { - let start = initialStart - let end = initialEnd - const titleLength = new Uint8Array( - await blobToBuffer(file.slice(start, end)), - )[0] - const tocTitle = await file.slice(end, end + titleLength).text() - start = end + titleLength - end = start + 8 - const chapterPosition = new DataView( - await blobToBuffer(file.slice(start, end)), - ).getBigUint64(0, false) - start = end - end = start + 8 - const chapterLength = new DataView( - await blobToBuffer(file.slice(start, end)), - ).getBigUint64(0, false) - - positions[tocTitle] = [chapterPosition, chapterLength] - start = end - end = start + 1 - if (end <= tocLength + (initialStart - 1)) { - await getPositions(start, end, file, positions, tocLength) - } -} - -async function readBlobAsDataURL( - f: Blob | File, -): Promise { - const reader = new FileReader() - return new Promise((resolve, reject) => { - reader.onloadend = () => { - resolve(reader.result) - } - reader.readAsDataURL(f) - reader.onerror = _ => reject(new Error('error reading blob')) - }) -} -async function blobToDataURL(data: Blob | File): Promise { - const res = await readBlobAsDataURL(data) - if (res instanceof ArrayBuffer) { - throw new Error('readBlobAsDataURL response should not be an array buffer') - } - if (res == null) { - throw new Error('readBlobAsDataURL response should not be null') - } - if (typeof res === 'string') return res - throw new Error('no possible blob to data url resolution found') -} - -async function blobToBuffer(data: Blob | File): Promise { - const res = await readBlobToArrayBuffer(data) - if (res instanceof String) { - throw new Error('readBlobToArrayBuffer response should not be a string') - } - if (res == null) { - throw new Error('readBlobToArrayBuffer response should not be null') - } - if (res instanceof ArrayBuffer) return res - throw new Error('no possible blob to array buffer resolution found') -} - -async function readBlobToArrayBuffer( - f: Blob | File, -): Promise { - const reader = new FileReader() - return new Promise((resolve, reject) => { - reader.onloadend = () => { - resolve(reader.result) - } - reader.readAsArrayBuffer(f) - reader.onerror = _ => reject(new Error('error reading blob')) - }) -} - -function compare(a: Uint8Array, b: Uint8Array) { - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false - } - return true -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.module.ts deleted file mode 100644 index 84114149a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { SharedPipesModule } from '@start9labs/shared' -import { PromptModule } from 'src/app/apps/ui/modals/prompt/prompt.module' -import { SSHKeysPage } from './ssh-keys.page' -import { TuiNotificationModule } from '@taiga-ui/core' - -const routes: Routes = [ - { - path: '', - component: SSHKeysPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - SharedPipesModule, - PromptModule, - TuiNotificationModule, - RouterModule.forChild(routes), - ], - declarations: [SSHKeysPage], -}) -export class SSHKeysPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.page.html b/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.page.html deleted file mode 100644 index ff61301a0..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.page.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - SSH Keys - - - - -
    - - Adding domains to StartOS enables you to access your server and service - interfaces over clearnet. - View instructions - -
    - - - - Saved Keys - - - Add Key - - - -
    - - - Hostname - Created At - Algorithm - Fingerprint - - - - - - - - - - - - - - {{ ssh.hostname }} - - {{ ssh['created-at'] | date: 'medium' }} - - {{ ssh.alg }} - {{ ssh.fingerprint }} - - - - - - - - - - -
    -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.page.ts deleted file mode 100644 index 67c10271b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/ssh-keys/ssh-keys.page.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Component } from '@angular/core' -import { ErrorService, LoadingService } from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { BehaviorSubject, filter, take } from 'rxjs' -import { SSHKey } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { PROMPT } from 'src/app/apps/ui/modals/prompt/prompt.component' -import { TUI_PROMPT } from '@taiga-ui/kit' - -@Component({ - selector: 'ssh-keys', - templateUrl: 'ssh-keys.page.html', - styleUrls: ['ssh-keys.page.scss'], -}) -export class SSHKeysPage { - readonly docsUrl = 'https://docs.start9.com/0.3.5.x/user-manual/ssh' - sshKeys: SSHKey[] = [] - loading$ = new BehaviorSubject(true) - - constructor( - private readonly loader: LoadingService, - private readonly dialogs: TuiDialogService, - private readonly errorService: ErrorService, - private readonly embassyApi: ApiService, - ) {} - - async ngOnInit() { - await this.getKeys() - } - - async getKeys(): Promise { - try { - this.sshKeys = await this.embassyApi.getSshKeys({}) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - this.loading$.next(false) - } - } - - async presentModalAdd() { - this.dialogs - .open(PROMPT, { - label: 'SSH Key', - data: { - message: - 'Enter the SSH public key you would like to authorize for root access to your Embassy.', - }, - }) - .pipe(take(1)) - .subscribe(pk => this.add(pk)) - } - - presentAlertDelete(key: SSHKey, i: number) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: 'Delete key? This action cannot be undone.', - yes: 'Delete', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.delete(key, i)) - } - - private async add(pubkey: string): Promise { - const loader = this.loader.open('Saving...').subscribe() - - try { - const key = await this.embassyApi.addSshKey({ key: pubkey }) - this.sshKeys.push(key) - } finally { - loader.unsubscribe() - } - } - - private async delete(key: SSHKey, i: number): Promise { - const loader = this.loader.open('Deleting...').subscribe() - - try { - await this.embassyApi.deleteSshKey({ fingerprint: key.fingerprint }) - this.sshKeys.splice(i, 1) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/system.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/system.module.ts deleted file mode 100644 index b7096753f..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/system.module.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { NgModule } from '@angular/core' -import { Routes, RouterModule } from '@angular/router' - -const routes: Routes = [ - { - path: '', - loadChildren: () => - import('./server-show/server-show.module').then( - m => m.ServerShowPageModule, - ), - }, - { - path: 'interfaces/ui', - loadChildren: () => - import('./ui-details/ui-details.module').then(m => m.UIDetailsPageModule), - }, - { - path: 'router-config', - loadChildren: () => - import('./router/router.module').then(m => m.RouterPageModule), - }, - { - path: 'logs', - loadChildren: () => - import('./server-logs/server-logs.module').then( - m => m.ServerLogsPageModule, - ), - }, - { - path: 'kernel-logs', - loadChildren: () => - import('./kernel-logs/kernel-logs.module').then( - m => m.KernelLogsPageModule, - ), - }, - { - path: 'tor-logs', - loadChildren: () => - import('./tor-logs/tor-logs.module').then(m => m.TorLogsPageModule), - }, - { - path: 'metrics', - loadChildren: () => - import('./server-metrics/server-metrics.module').then( - m => m.ServerMetricsPageModule, - ), - }, - { - path: 'sessions', - loadChildren: () => - import('./sessions/sessions.module').then(m => m.SessionsPageModule), - }, - { - path: 'sideload', - loadChildren: () => - import('./sideload/sideload.module').then(m => m.SideloadPageModule), - }, - { - path: 'specs', - loadChildren: () => - import('./server-specs/server-specs.module').then( - m => m.ServerSpecsPageModule, - ), - }, - { - path: 'domains', - loadChildren: () => - import('./domains/domains.module').then(m => m.DomainsPageModule), - }, - { - path: 'proxies', - loadChildren: () => - import('./proxies/proxies.module').then(m => m.ProxiesPageModule), - }, - { - path: 'ssh', - loadChildren: () => - import('./ssh-keys/ssh-keys.module').then(m => m.SSHKeysPageModule), - }, - { - path: 'wifi', - loadChildren: () => - import('./wifi/wifi.module').then(m => m.WifiPageModule), - }, - { - path: 'experimental-features', - loadChildren: () => - import('./experimental-features/experimental-features.module').then( - m => m.ExperimentalFeaturesPageModule, - ), - }, - { - path: 'email', - loadChildren: () => - import('./email/email.module').then(m => m.EmailPageModule), - }, -] - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class SystemModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/theme-switcher/theme-switcher.component.html b/web/projects/ui/src/app/apps/ui/pages/system/theme-switcher/theme-switcher.component.html deleted file mode 100644 index e475a729a..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/theme-switcher/theme-switcher.component.html +++ /dev/null @@ -1,23 +0,0 @@ - - - -

    Theme

    -

    {{ value }}

    -
    -
    - - - - diff --git a/web/projects/ui/src/app/apps/ui/pages/system/theme-switcher/theme-switcher.component.ts b/web/projects/ui/src/app/apps/ui/pages/system/theme-switcher/theme-switcher.component.ts deleted file mode 100644 index dc8e0e329..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/theme-switcher/theme-switcher.component.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' - -import { ThemeSwitcherService } from 'src/app/services/theme-switcher.service' - -@Component({ - selector: 'theme-switcher', - templateUrl: './theme-switcher.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ThemeSwitcherComponent { - value = this.switcher.value - - open = false - - readonly themes = ['Dark', 'Light'] - - constructor(private readonly switcher: ThemeSwitcherService) {} - - onChange(value: string): void { - this.value = value - this.switcher.next(value) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/theme-switcher/theme-switcher.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/theme-switcher/theme-switcher.module.ts deleted file mode 100644 index 4d817e933..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/theme-switcher/theme-switcher.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from '@angular/core' -import { FormsModule } from '@angular/forms' -import { IonicModule } from '@ionic/angular' -import { TuiDialogModule } from '@taiga-ui/core' -import { TuiRadioListModule } from '@taiga-ui/kit' - -import { ThemeSwitcherComponent } from './theme-switcher.component' - -@NgModule({ - imports: [IonicModule, FormsModule, TuiDialogModule, TuiRadioListModule], - declarations: [ThemeSwitcherComponent], - exports: [ThemeSwitcherComponent], -}) -export class ThemeSwitcherModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.module.ts deleted file mode 100644 index 9e4f29130..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { TorLogsPage } from './tor-logs.page' -import { LogsComponentModule } from 'src/app/common/logs/logs.component.module' - -const routes: Routes = [ - { - path: '', - component: TorLogsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - LogsComponentModule, - ], - declarations: [TorLogsPage], -}) -export class TorLogsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.page.html b/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.page.html deleted file mode 100644 index 43fb6ff18..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.page.html +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.page.ts deleted file mode 100644 index 4fc304715..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/tor-logs/tor-logs.page.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component } from '@angular/core' -import { RR } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' - -@Component({ - selector: 'tor-logs', - templateUrl: './tor-logs.page.html', - styleUrls: ['./tor-logs.page.scss'], -}) -export class TorLogsPage { - constructor(private readonly api: ApiService) {} - - followLogs() { - return async (params: RR.FollowServerLogsReq) => { - return this.api.followTorLogs(params) - } - } - - fetchLogs() { - return async (params: RR.GetServerLogsReq) => { - return this.api.getTorLogs(params) - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.module.ts deleted file mode 100644 index c347dd42b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { UIDetailsPage } from './ui-details.page' -import { InterfaceAddressesComponentModule } from 'src/app/common/interface-addresses/interface-addresses.module' - -const routes: Routes = [ - { - path: '', - component: UIDetailsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - InterfaceAddressesComponentModule, - ], - declarations: [UIDetailsPage], -}) -export class UIDetailsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.page.html b/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.page.html deleted file mode 100644 index 0b7f18ccb..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.page.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - StartOS UI - - - - -
    - -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.page.ts deleted file mode 100644 index 51c1297a6..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/ui-details/ui-details.page.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { DataModel } from 'src/app/services/patch-db/data-model' - -@Component({ - selector: 'ui-details', - templateUrl: './ui-details.page.html', - styleUrls: ['./ui-details.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class UIDetailsPage { - readonly ui$ = this.patch.watch$('server-info', 'ui') - - constructor(private readonly patch: PatchDB) {} -} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.module.ts b/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.module.ts deleted file mode 100644 index 92323a129..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { SharedPipesModule } from '@start9labs/shared' -import { TuiLetModule } from '@taiga-ui/cdk' -import { FormPageModule } from 'src/app/apps/ui/modals/form/form.module' -import { WifiPage, ToWifiIconPipe } from './wifi.page' - -const routes: Routes = [ - { - path: '', - component: WifiPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - SharedPipesModule, - TuiLetModule, - FormPageModule, - RouterModule.forChild(routes), - ], - declarations: [WifiPage, ToWifiIconPipe], -}) -export class WifiPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.page.html b/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.page.html deleted file mode 100644 index 5680a3696..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.page.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - WiFi Settings - - - - -
    - - - -

    - Adding WiFi credentials to StartOS allows you to remove the Ethernet - cable and move the device anywhere you want. StartOS will - automatically connect to available networks. - - View instructions - -

    -
    -
    - - Wi-Fi - - -
    - - - - - - Known Networks - - - - -

    {{ ssid.key }}

    -

    - - Connected -

    -
    -
    - - - - -
    - - - - - Connect - - - Forget this network - - - - -
    -
    - - Other Networks - - - - {{ avWifi.ssid }} -
    - - Connect - - - -
    -
    -
    -
    -
    - - Other... - -
    -
    - - - - Known Networks - - - - - - - - Other Networks - - - - - - - - -
    -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.page.scss b/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.page.scss deleted file mode 100644 index e2b0409c1..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.page.scss +++ /dev/null @@ -1,36 +0,0 @@ -.no-padding { - padding-right: 0; -} - -.skeleton-parts { - ion-button::part(native) { - padding-inline-start: 0; - padding-inline-end: 0; - }; -} - -.connect-button { - font-size: 10px; - font-weight: bold; - margin-right: 12px; -} - -.slot-end { - margin-left: 4px; -} - -ion-item-divider { - text-transform: unset; - padding-bottom: 12px; - padding-left: 0; -} - -ion-item-group { - background-color: #1e2024; - border: 1px solid #717171; - border-radius: 6px; -} - -ion-item { - --background: #1e2024; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.page.ts b/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.page.ts deleted file mode 100644 index 842853177..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/system/wifi/wifi.page.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { Component, Pipe, PipeTransform } from '@angular/core' -import { - TuiAlertService, - TuiDialogOptions, - TuiNotification, -} from '@taiga-ui/core' -import { ToggleCustomEvent } from '@ionic/core' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { AvailableWifi, RR } from 'src/app/services/api/api.types' -import { ErrorService, LoadingService, pauseFor } from '@start9labs/shared' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page' -import { PatchDB } from 'patch-db-client' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { ConnectionService } from 'src/app/services/connection.service' -import { - BehaviorSubject, - catchError, - distinctUntilChanged, - filter, - from, - merge, - Observable, - Subject, - switchMap, - tap, -} from 'rxjs' -import { wifiSpec } from './wifi.const' - -interface WiFiForm { - ssid: string - password: string -} - -@Component({ - selector: 'wifi', - templateUrl: 'wifi.page.html', - styleUrls: ['wifi.page.scss'], -}) -export class WifiPage { - readonly connected$ = this.connectionService.connected$.pipe(filter(Boolean)) - readonly enabled$ = this.patch - .watch$('server-info', 'network', 'wifi', 'enabled') - .pipe( - distinctUntilChanged(), - tap(enabled => { - if (enabled) this.trigger$.next('') - }), - ) - readonly trigger$ = new BehaviorSubject('') - readonly localChanges$ = new Subject() - readonly wifi$ = merge( - this.trigger$.pipe(switchMap(() => this.getWifi$())), - this.localChanges$, - ) - - constructor( - private readonly api: ApiService, - private readonly alerts: TuiAlertService, - private readonly loader: LoadingService, - private readonly formDialog: FormDialogService, - private readonly errorService: ErrorService, - private readonly patch: PatchDB, - private readonly connectionService: ConnectionService, - ) {} - - async toggleWifi(e: ToggleCustomEvent): Promise { - const enable = e.detail.checked - const loader = this.loader - .open(enable ? 'Enabling Wifi' : 'Disabling WiFi') - .subscribe() - - try { - await this.api.enableWifi({ enable }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - async connect(ssid: string): Promise { - const loader = this.loader - .open('Connecting. This could take a while...') - .subscribe() - - try { - await this.api.connectWifi({ ssid }) - await this.confirmWifi(ssid) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - async forget(ssid: string, wifi: RR.GetWifiRes): Promise { - const loader = this.loader.open('Deleting...').subscribe() - - try { - await this.api.deleteWifi({ ssid }) - delete wifi.ssids[ssid] - this.localChanges$.next(wifi) - this.trigger$.next('') - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - async presentModalAdd(network: AvailableWifi) { - if (!network.security.length) { - this.connect(network.ssid) - } else { - const options: Partial>> = { - label: 'Password Needed', - data: { - spec: wifiSpec.spec, - buttons: [ - { - text: 'Connect', - handler: async ({ ssid, password }) => - this.saveAndConnect(ssid, password), - }, - ], - }, - } - this.formDialog.open(FormPage, options) - } - } - - presentModalAddOther(wifi: RR.GetWifiRes) { - const options: Partial>> = { - label: wifiSpec.name, - data: { - spec: wifiSpec.spec, - buttons: [ - { - text: 'Save for Later', - handler: async ({ ssid, password }) => - this.save(ssid, password, wifi), - }, - { - text: 'Save and Connect', - handler: async ({ ssid, password }) => - this.saveAndConnect(ssid, password), - }, - ], - }, - } - this.formDialog.open(FormPage, options) - } - - private getWifi$(): Observable { - return from(this.api.getWifi({}, 10000)).pipe( - catchError((e: any) => { - this.errorService.handleError(e) - return [] - }), - ) - } - - private presentToastSuccess() { - this.alerts - .open('Connection successful!', { - status: TuiNotification.Success, - }) - .subscribe() - } - - private async presentToastFail(): Promise { - this.alerts - .open('Check credentials and try again', { - label: 'Failed to connect', - status: TuiNotification.Warning, - }) - .subscribe() - } - - private async save( - ssid: string, - password: string, - wifi: RR.GetWifiRes, - ): Promise { - const loader = this.loader.open('Saving...').subscribe() - - try { - await this.api.addWifi({ - ssid, - password, - priority: 0, - connect: false, - }) - wifi.ssids[ssid] = 0 - this.localChanges$.next(wifi) - this.trigger$.next('') - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private async saveAndConnect( - ssid: string, - password: string, - ): Promise { - const loader = this.loader - .open('Connecting. This could take a while...') - .subscribe() - - try { - await this.api.addWifi({ - ssid, - password, - priority: 0, - connect: true, - }) - await this.confirmWifi(ssid) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private async confirmWifi(ssid: string): Promise { - const maxAttempts = 5 - let attempts = 0 - - while (true) { - if (attempts > maxAttempts) { - this.presentToastFail() - break - } - - try { - const start = new Date().valueOf() - const newWifi = await this.api.getWifi({}, 10000) - const end = new Date().valueOf() - if (newWifi.connected === ssid) { - this.localChanges$.next(newWifi) - this.presentToastSuccess() - break - } else { - attempts++ - const diff = end - start - // depending on the response time, wait a min of 1000 ms, and a max of 4000 ms in between retries. Both 1000 and 4000 are arbitrary - await pauseFor(Math.max(1000, 4000 - diff)) - } - } catch (e) { - attempts++ - console.warn(e) - } - } - } -} - -@Pipe({ - name: 'toWifiIcon', -}) -export class ToWifiIconPipe implements PipeTransform { - transform(signal: number): string { - if (signal >= 0 && signal < 5) { - return 'assets/img/icons/wifi-0.png' - } else if (signal >= 5 && signal < 50) { - return 'assets/img/icons/wifi-1.png' - } else if (signal >= 50 && signal < 90) { - return 'assets/img/icons/wifi-2.png' - } else { - return 'assets/img/icons/wifi-3.png' - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/updates/filter-updates.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/updates/filter-updates.pipe.ts deleted file mode 100644 index ab2497b5b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/updates/filter-updates.pipe.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { Emver } from '@start9labs/shared' -import { MarketplacePkg } from '@start9labs/marketplace' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' - -@Pipe({ - name: 'filterUpdates', -}) -export class FilterUpdatesPipe implements PipeTransform { - constructor(private readonly emver: Emver) {} - - transform( - pkgs: MarketplacePkg[], - local: Record, - ): MarketplacePkg[] { - return pkgs.filter( - ({ manifest }) => - this.emver.compare( - manifest.version, - local[manifest.id]?.manifest.version, // @TODO this won't work, need old version - ) === 1, - ) - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/updates/install-progress.pipe.ts b/web/projects/ui/src/app/apps/ui/pages/updates/install-progress.pipe.ts deleted file mode 100644 index 9ecf86dc3..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/updates/install-progress.pipe.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { InstallProgress } from 'src/app/services/patch-db/data-model' -import { packageLoadingProgress } from 'src/app/util/package-loading-progress' - -@Pipe({ - name: 'installProgress', -}) -export class InstallProgressPipe implements PipeTransform { - transform(installProgress?: InstallProgress): number { - return packageLoadingProgress(installProgress)?.totalProgress || 0 - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/updates/updates.module.ts b/web/projects/ui/src/app/apps/ui/pages/updates/updates.module.ts deleted file mode 100644 index 28b881b41..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/updates/updates.module.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { - MimeTypePipeModule, - StoreIconComponentModule, -} from '@start9labs/marketplace' -import { - EmverPipesModule, - MarkdownPipeModule, - SafeLinksDirective, - SharedPipesModule, -} from '@start9labs/shared' -import { NgDompurifyModule } from '@tinkoff/ng-dompurify' -import { TuiProgressModule } from '@taiga-ui/kit' -import { BadgeMenuComponentModule } from 'src/app/common/badge-menu-button/badge-menu.component.module' -import { SkeletonListComponentModule } from 'src/app/common/skeleton-list/skeleton-list.component.module' -import { UpdatesPage } from './updates.page' -import { InstallProgressPipe } from './install-progress.pipe' -import { FilterUpdatesPipe } from './filter-updates.pipe' - -const routes: Routes = [ - { - path: '', - component: UpdatesPage, - }, -] - -@NgModule({ - declarations: [UpdatesPage, FilterUpdatesPipe, InstallProgressPipe], - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - BadgeMenuComponentModule, - SharedPipesModule, - SkeletonListComponentModule, - MarkdownPipeModule, - StoreIconComponentModule, - EmverPipesModule, - MimeTypePipeModule, - SafeLinksDirective, - NgDompurifyModule, - TuiProgressModule, - ], - exports: [FilterUpdatesPipe, InstallProgressPipe], -}) -export class UpdatesPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/updates/updates.page.html b/web/projects/ui/src/app/apps/ui/pages/updates/updates.page.html deleted file mode 100644 index 33c3a8a68..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/updates/updates.page.html +++ /dev/null @@ -1,124 +0,0 @@ - - - Updates - - - - - - - - -
    - - -   - - {{ host.name }} - - - - - Request Failed - - - - - -
    - - - - - - -

    {{ pkg.manifest.title }}

    -

    - - - {{ local.manifest.version || '' | displayEmver }} - -   - -   - - {{ pkg.manifest.version | displayEmver }} - -

    -

    - {{ error }} -

    -
    -
    - - - - - - {{ marketplaceService.updateErrors[pkg.manifest.id] ? - 'Retry' : 'Update' }} - - - -
    -
    -
    -
    -
    What's new
    -

    -
    - - View listing - - -
    -
    -
    -
    - - All services are up to date! - -
    -
    - - -
    - -
    -
    -
    -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/updates/updates.page.scss b/web/projects/ui/src/app/apps/ui/pages/updates/updates.page.scss deleted file mode 100644 index 11f135fda..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/updates/updates.page.scss +++ /dev/null @@ -1,26 +0,0 @@ -.item-container { - border-bottom: 1px solid #373737; - padding: 12px 0; -} - -.notes { - margin-left: 20px; - - h5 { - font-weight: 600; - } -} - -ion-item-divider { - --padding-top: 8px; - --padding-bottom: 8px; - --background: var(--ion-color-medium-tint); -} - -ion-item { - --background-hover: none; - - &::part(native) { - cursor: context-menu; - } -} \ No newline at end of file diff --git a/web/projects/ui/src/app/apps/ui/pages/updates/updates.page.ts b/web/projects/ui/src/app/apps/ui/pages/updates/updates.page.ts deleted file mode 100644 index ca0ff1f87..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/updates/updates.page.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Component, Inject } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { MarketplaceService } from 'src/app/services/marketplace.service' -import { - AbstractMarketplaceService, - Manifest, - Marketplace, - StoreIdentity, -} from '@start9labs/marketplace' -import { LoadingService } from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { NavController } from '@ionic/angular' -import { hasCurrentDeps } from 'src/app/util/has-deps' -import { getAllPackages } from 'src/app/util/get-package-data' -import { ConfigService } from 'src/app/services/config.service' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { Emver, isEmptyObject } from '@start9labs/shared' -import { combineLatest, Observable } from 'rxjs' -import { dryUpdate } from 'src/app/util/dry-update' - -interface UpdatesData { - hosts: StoreIdentity[] - marketplace: Marketplace - localPkgs: Record - errors: string[] -} - -@Component({ - selector: 'updates', - templateUrl: 'updates.page.html', - styleUrls: ['updates.page.scss'], -}) -export class UpdatesPage { - readonly data$: Observable = combineLatest({ - hosts: this.marketplaceService.getKnownHosts$(true), - marketplace: this.marketplaceService.getMarketplace$(), - localPkgs: this.patch.watch$('package-data'), - errors: this.marketplaceService.getRequestErrors$(), - }) - - constructor( - @Inject(AbstractMarketplaceService) - readonly marketplaceService: MarketplaceService, - private readonly patch: PatchDB, - private readonly navCtrl: NavController, - private readonly loader: LoadingService, - private readonly dialogs: TuiDialogService, - private readonly emver: Emver, - readonly config: ConfigService, - ) {} - - viewInMarketplace(event: Event, url: string, id: string) { - event.stopPropagation() - - this.navCtrl.navigateForward([`marketplace/${id}`], { - queryParams: { url }, - }) - } - - async tryUpdate( - manifest: Manifest, - url: string, - local: PackageDataEntry, - e: Event, - ): Promise { - e.stopPropagation() - - const { id, version } = manifest - - delete this.marketplaceService.updateErrors[id] - this.marketplaceService.updateQueue[id] = true - - if (hasCurrentDeps(local)) { - this.dryInstall(manifest, url) - } else { - this.install(id, version, url) - } - } - - private async dryInstall(manifest: Manifest, url: string) { - const { id, version, title } = manifest - - const breakages = dryUpdate( - manifest, - await getAllPackages(this.patch), - this.emver, - ) - - if (isEmptyObject(breakages)) { - this.install(id, version, url) - } else { - const proceed = await this.presentAlertBreakages(title, breakages) - if (proceed) { - this.install(id, version, url) - } else { - delete this.marketplaceService.updateQueue[id] - } - } - } - - private async presentAlertBreakages( - title: string, - breakages: string[], - ): Promise { - let content: string = `As a result of updating ${title}, the following services will no longer work properly and may crash:
      ` - const bullets = breakages.map(depTitle => { - return `
    • ${depTitle}
    • ` - }) - content = `${content}${bullets.join('')}
    ` - - return new Promise(async resolve => { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { - content, - yes: 'Continue', - no: 'Cancel', - }, - }) - .subscribe(response => resolve(response)) - }) - } - - private async install(id: string, version: string, url: string) { - try { - await this.marketplaceService.installPackage(id, version, url) - delete this.marketplaceService.updateQueue[id] - } catch (e: any) { - delete this.marketplaceService.updateQueue[id] - this.marketplaceService.updateErrors[id] = e.message - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.component.html b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.component.html deleted file mode 100644 index bef3063ad..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
    - - No additional widgets found -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.component.scss b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.component.scss deleted file mode 100644 index f4c4a3171..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -.wrapper { - display: flex; - flex-direction: column; - gap: 12px; -} - -.button { - background: transparent; - text-align: left; - font-size: 18px; - font-weight: bold; - border: none; - border-radius: 16px; - padding: 16px; - box-shadow: inset 0 0 0 1px var(--tui-base-03); -} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.component.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.component.ts deleted file mode 100644 index 9feb5dee6..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { DataModel, Widget } from 'src/app/services/patch-db/data-model' -import { - POLYMORPHEUS_CONTEXT, - PolymorpheusComponent, -} from '@tinkoff/ng-polymorpheus' -import { TuiDialogContext } from '@taiga-ui/core' -import { BUILT_IN_WIDGETS } from '../widgets' - -@Component({ - selector: 'add-widget', - templateUrl: './add.component.html', - styleUrls: ['./add.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AddWidgetComponent { - readonly context = inject>(POLYMORPHEUS_CONTEXT) - - readonly installed$ = inject(PatchDB).watch$('ui', 'widgets') - - readonly widgets = BUILT_IN_WIDGETS - - readonly filter = (widget: Widget, installed: readonly Widget[]) => - !installed.find(({ id }) => id === widget.id) -} - -export const ADD_WIDGET = new PolymorpheusComponent< - AddWidgetComponent, - TuiDialogContext ->(AddWidgetComponent) diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.module.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.module.ts deleted file mode 100644 index 94db46596..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/add/add.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { TuiFilterPipeModule, TuiForModule } from '@taiga-ui/cdk' - -import { AddWidgetComponent } from './add.component' - -@NgModule({ - imports: [CommonModule, TuiFilterPipeModule, TuiForModule], - declarations: [AddWidgetComponent], - exports: [AddWidgetComponent], -}) -export class AddWidgetModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.component.html b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.component.html deleted file mode 100644 index e45790dc8..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.component.html +++ /dev/null @@ -1 +0,0 @@ -Add to quick launch diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.component.scss b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.component.scss deleted file mode 100644 index d5ef561a9..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.add { - font-size: 13px; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.component.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.component.ts deleted file mode 100644 index 40a238158..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' - -@Component({ - selector: 'widget-favorites', - templateUrl: './favorites.component.html', - styleUrls: ['./favorites.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class FavoritesComponent {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.module.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.module.ts deleted file mode 100644 index 2f60117c3..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/favorites/favorites.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core' -import { FavoritesComponent } from './favorites.component' -import { IonicModule } from '@ionic/angular' - -@NgModule({ - imports: [IonicModule], - declarations: [FavoritesComponent], - exports: [FavoritesComponent], -}) -export class FavoritesModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.component.html b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.component.html deleted file mode 100644 index b10313ac3..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.component.html +++ /dev/null @@ -1,11 +0,0 @@ -

    Service health overview

    - - - {{ labels[index] }}: {{ data[index] }} - - diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.component.scss b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.component.scss deleted file mode 100644 index 8ba371967..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.component.scss +++ /dev/null @@ -1,19 +0,0 @@ -:host { - /* index order must match labels array */ - --tui-chart-0: var(--ion-color-danger-tint); // error - --tui-chart-1: var(--ion-color-success-tint); // healthy - --tui-chart-2: var(--ion-color-warning-tint); // needs attention - --tui-chart-3: var(--ion-color-step-600); // stopped - --tui-chart-4: var(--ion-color-primary-tint); // transitioning -} - -.widget-title { - margin: 0; - font-size: 18px; - font-weight: bold; -} - -.ring-chart { - transform: scale(0.85); - margin: 0.6rem auto; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.component.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.component.ts deleted file mode 100644 index 4a1213d9b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { map } from 'rxjs/operators' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { PrimaryStatus } from 'src/app/services/pkg-status-rendering.service' -import { getPackageInfo } from 'src/app/util/get-package-info' -import { PkgInfo } from 'src/app/types/pkg-info' -import { combineLatest } from 'rxjs' -import { DepErrorService } from 'src/app/services/dep-error.service' - -@Component({ - selector: 'widget-health', - templateUrl: './health.component.html', - styleUrls: ['./health.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class HealthComponent { - readonly labels = [ - 'Error', - 'Healthy', - 'Needs Attention', - 'Stopped', - 'Transitioning', - ] as const - - readonly data$ = combineLatest([ - inject(PatchDB).watch$('package-data'), - inject(DepErrorService).depErrors$, - ]).pipe( - map(([data, depErrors]) => { - const pkgs = Object.values(data).map(pkg => - getPackageInfo(pkg, depErrors[pkg.manifest.id]), - ) - const result = this.labels.reduce>( - (acc, label) => ({ - ...acc, - [label]: this.getCount(label, pkgs), - }), - {}, - ) - - result['Healthy'] = - pkgs.length - - result['Error'] - - result['Needs Attention'] - - result['Stopped'] - - result['Transitioning'] - - return this.labels.map(label => result[label]) - }), - ) - - private getCount(label: string, pkgs: PkgInfo[]): number { - switch (label) { - case 'Error': - return pkgs.filter( - a => a.primaryStatus !== PrimaryStatus.Stopped && a.error, - ).length - case 'Needs Attention': - return pkgs.filter(a => a.warning).length - case 'Stopped': - return pkgs.filter(a => a.primaryStatus === PrimaryStatus.Stopped) - .length - case 'Transitioning': - return pkgs.filter(a => a.transitioning).length - default: - return 0 - } - } -} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.module.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.module.ts deleted file mode 100644 index 31f5f0107..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/health/health.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core' -import { HealthComponent } from './health.component' -import { TuiRingChartModule } from '@taiga-ui/addon-charts' -import { TuiHintModule } from '@taiga-ui/core' -import { CommonModule } from '@angular/common' - -@NgModule({ - imports: [CommonModule, TuiRingChartModule, TuiHintModule], - declarations: [HealthComponent], - exports: [HealthComponent], -}) -export class HealthModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.component.html b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.component.html deleted file mode 100644 index 1d80ce23d..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.component.html +++ /dev/null @@ -1,30 +0,0 @@ -
    -
    - -
    - 30% -
    Storage
    -
    -
    -
    - -
    - 10% -
    CPU
    -
    -
    -
    - -
    - 10% -
    Memory
    -
    -
    -
    - -
    - 50.6⁰C -
    Temp
    -
    -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.component.scss b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.component.scss deleted file mode 100644 index 3af62e77e..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.component.scss +++ /dev/null @@ -1,34 +0,0 @@ -.stats { - display: flex; - align-items: center; - justify-content: space-around; - flex-wrap: wrap; - height: 100%; - text-align: center; - - &_mobile .stat { - width: 50%; - } -} - -.stat { - display: flex; - align-items: center; - justify-content: center; - - :host-context(.wrapper_mobile) & { - width: 50%; - } -} - -.stat-icon { - font-size: 32px; - margin: 12px; -} - -.description { - color: #3a7be0; - text-transform: uppercase; - font-weight: bold; - font-size: 12px; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.component.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.component.ts deleted file mode 100644 index 813fff9f2..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' - -@Component({ - selector: 'widget-metrics', - templateUrl: './metrics.component.html', - styleUrls: ['./metrics.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MetricsComponent {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.module.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.module.ts deleted file mode 100644 index b55748b4c..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/metrics/metrics.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core' -import { IonicModule } from '@ionic/angular' -import { MetricsComponent } from './metrics.component' - -@NgModule({ - imports: [IonicModule], - declarations: [MetricsComponent], - exports: [MetricsComponent], -}) -export class MetricsModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.component.html b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.component.html deleted file mode 100644 index 9bd377f68..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.component.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.component.scss b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.component.scss deleted file mode 100644 index 2fd308dc3..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.component.scss +++ /dev/null @@ -1,13 +0,0 @@ -:host { - border-radius: inherit; -} - -.iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 0; - border-radius: inherit; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.component.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.component.ts deleted file mode 100644 index a7711fd93..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' - -@Component({ - selector: 'widget-network', - templateUrl: './network.component.html', - styleUrls: ['./network.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class NetworkComponent {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.module.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.module.ts deleted file mode 100644 index 7ffb7a1ef..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/network/network.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NgModule } from '@angular/core' -import { NetworkComponent } from './network.component' - -@NgModule({ - imports: [], - declarations: [NetworkComponent], - exports: [NetworkComponent], -}) -export class NetworkModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.component.html b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.component.html deleted file mode 100644 index 3a1e58c95..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.component.html +++ /dev/null @@ -1 +0,0 @@ -System time and uptime diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.component.scss b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.component.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.component.ts deleted file mode 100644 index f669a7363..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' - -@Component({ - selector: 'widget-uptime', - templateUrl: './uptime.component.html', - styleUrls: ['./uptime.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class UptimeComponent {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.module.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.module.ts deleted file mode 100644 index 497e1b0cc..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/uptime/uptime.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NgModule } from '@angular/core' -import { UptimeComponent } from './uptime.component' - -@NgModule({ - imports: [], - declarations: [UptimeComponent], - exports: [UptimeComponent], -}) -export class UptimeModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/widgets.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/widgets.ts deleted file mode 100644 index 8b94a37aa..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/built-in/widgets.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Widget } from 'src/app/services/patch-db/data-model' - -export const BUILT_IN_WIDGETS: readonly Widget[] = [ - { - id: 'favorites', - meta: { - name: 'Favorites', - width: 2, - height: 2, - mobileWidth: 2, - mobileHeight: 2, - }, - }, - { - id: 'health', - meta: { - name: 'Service health overview', - width: 2, - height: 2, - mobileWidth: 2, - mobileHeight: 2, - }, - }, - { - id: 'metrics', - meta: { - name: 'Server metrics', - width: 4, - height: 1, - mobileWidth: 2, - mobileHeight: 2, - }, - }, - { - id: 'network', - meta: { - name: 'Network', - width: 4, - height: 2, - mobileWidth: 2, - mobileHeight: 3, - }, - }, - { - id: 'uptime', - meta: { - name: 'System time and uptime', - width: 2, - height: 2, - mobileWidth: 2, - mobileHeight: 2, - }, - }, -] diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.module.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.module.ts deleted file mode 100644 index de8f92c3f..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.module.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { RouterModule, Routes } from '@angular/router' -import { TuiLoaderModule } from '@taiga-ui/core' -import { TuiTilesModule } from '@taiga-ui/kit' - -import { WidgetsPage } from './widgets.page' -import { AddWidgetModule } from './built-in/add/add.module' -import { FavoritesModule } from './built-in/favorites/favorites.module' -import { HealthModule } from './built-in/health/health.module' -import { MetricsModule } from './built-in/metrics/metrics.module' -import { NetworkModule } from './built-in/network/network.module' -import { UptimeModule } from './built-in/uptime/uptime.module' - -const routes: Routes = [ - { - path: '', - component: WidgetsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - TuiTilesModule, - AddWidgetModule, - FavoritesModule, - HealthModule, - MetricsModule, - NetworkModule, - UptimeModule, - RouterModule.forChild(routes), - TuiLoaderModule, - ], - declarations: [WidgetsPage], - exports: [WidgetsPage], -}) -export class WidgetsPageModule {} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.page.html b/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.page.html deleted file mode 100644 index 4ae2d9430..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.page.html +++ /dev/null @@ -1,55 +0,0 @@ -

    - - - - - {{ edit ? 'Save' : 'Edit'}} - - - - Add - - -

    - - - -
    - -
    -
    - - - - - -
    -
    diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.page.scss b/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.page.scss deleted file mode 100644 index cf27b504b..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.page.scss +++ /dev/null @@ -1,112 +0,0 @@ -:host { - overflow: auto; -} - -.title { - margin-right: auto; -} - -.loader { - width: 24px; - color: var(--ion-color-tertiary); -} - -.heading { - display: flex; - align-items: center; - height: 44px; - font-size: 20px; - margin: 14px 0 -20px; - padding: 0 40px; - - :host.dialog & { - margin: 0 0 -24px; - padding: 0; - font-size: 24px; - } -} - -.button { - width: 50%; - - ion-icon { - margin-right: 8px; - } -} - -.wrapper { - gap: 24px; - grid-auto-rows: 100px; - grid-auto-columns: 1fr; - margin: 40px; - - &_wide { - grid-template-columns: repeat(4, 1fr); - } - - :host.dialog & { - margin: 40px 0; - } -} - -.item { - box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.1); - border-radius: 24px; -} - -.content { - height: 100%; - text-align: center; - background: var(--tui-base-02); - border-radius: 24px; - padding: 24px; - box-sizing: border-box; - overflow: hidden; - box-shadow: 0 3px 5px -2px rgba(0, 0, 0, 0.5); - transition: opacity 0.3s; - - .item_edit & { - opacity: var(--tui-disabled-opacity); - pointer-events: none; - } -} - -.handle { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - cursor: move; - - .item:not(.item_edit) & { - pointer-events: none; - } -} - -.remove, -.settings, -.pending { - position: absolute; - right: 24px; - top: 50%; - width: 24px; - height: 24px; - transform: translateY(-50%); - padding: 10px; - box-sizing: content-box; - border-radius: 100%; - background: rgba(0, 0, 0, 0.5); - pointer-events: none; - opacity: 0; - transition: opacity 0.3s; - - .item_edit & { - pointer-events: auto; - opacity: 1; - } -} - -.settings { - left: 24px; -} diff --git a/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.page.ts b/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.page.ts deleted file mode 100644 index 570071f0f..000000000 --- a/web/projects/ui/src/app/apps/ui/pages/widgets/widgets.page.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Inject, - Input, - Optional, - Type, -} from '@angular/core' -import { TuiDialogContext, TuiDialogService } from '@taiga-ui/core' -import { - POLYMORPHEUS_CONTEXT, - PolymorpheusComponent, -} from '@tinkoff/ng-polymorpheus' -import { PatchDB } from 'patch-db-client' -import { DataModel, Widget } from 'src/app/services/patch-db/data-model' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ADD_WIDGET } from './built-in/add/add.component' -import { FavoritesComponent } from './built-in/favorites/favorites.component' -import { HealthComponent } from './built-in/health/health.component' -import { NetworkComponent } from './built-in/network/network.component' -import { MetricsComponent } from './built-in/metrics/metrics.component' -import { UptimeComponent } from './built-in/uptime/uptime.component' -import { take } from 'rxjs' - -@Component({ - selector: 'widgets', - templateUrl: 'widgets.page.html', - styleUrls: ['widgets.page.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - host: { - '[class.dialog]': 'context', - }, -}) -export class WidgetsPage { - @Input() - wide = false - - edit = false - - order = new Map() - - items: readonly Widget[] = [] - - pending = true - - readonly components: Record> = { - health: HealthComponent, - favorites: FavoritesComponent, - metrics: MetricsComponent, - network: NetworkComponent, - uptime: UptimeComponent, - } - - constructor( - @Optional() - @Inject(POLYMORPHEUS_CONTEXT) - readonly context: TuiDialogContext | null, - private readonly dialog: TuiDialogService, - private readonly patch: PatchDB, - private readonly cdr: ChangeDetectorRef, - private readonly api: ApiService, - ) { - this.patch - .watch$('ui', 'widgets') - .pipe(take(1)) - .subscribe(items => { - this.updateItems(items) - this.pending = false - }) - } - - trackBy(_: number, { id }: Widget) { - return id - } - - toggle() { - if (this.edit) { - this.updateItems(this.getReordered()) - } - - this.edit = !this.edit - } - - add() { - this.dialog.open(ADD_WIDGET, { label: 'Add widget' }).subscribe(widget => { - this.addWidget(widget!) - }) - } - - remove(index: number) { - this.removeWidget(index) - } - - private removeWidget(index: number) { - this.updateItems( - this.getReordered().filter((_, i) => i !== this.order.get(index)), - ) - } - - private addWidget(widget: Widget) { - this.updateItems(this.getReordered().concat(widget)) - } - - private getReordered(): Widget[] { - const items: Widget[] = [] - - Array.from(this.order.entries()).forEach(([index, order]) => { - items[order] = this.items[index] - }) - - return items - } - - private updateItems(items: readonly Widget[]) { - const previous = this.items - - if (!this.pending) { - this.pending = true - this.api - .setDbValue(['widgets'], items) - .catch(() => { - this.updateItems(previous) - }) - .finally(() => { - this.pending = false - this.cdr.markForCheck() - }) - } - - this.items = items - this.order = new Map(items.map((_, index) => [index, index])) - } -} - -export const WIDGETS_COMPONENT = new PolymorpheusComponent(WidgetsPage) diff --git a/web/projects/ui/src/app/apps/ui/ui.module.ts b/web/projects/ui/src/app/apps/ui/ui.module.ts deleted file mode 100644 index 0688c8f0d..000000000 --- a/web/projects/ui/src/app/apps/ui/ui.module.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' - -const ROUTES: Routes = [ - { - redirectTo: 'services', - pathMatch: 'full', - path: '', - }, - { - path: 'home', - loadChildren: () => - import('./pages/home/home.module').then(m => m.HomePageModule), - }, - { - path: 'system', - loadChildren: () => - import('./pages/system/system.module').then(m => m.SystemModule), - }, - { - path: 'updates', - loadChildren: () => - import('./pages/updates/updates.module').then(m => m.UpdatesPageModule), - }, - { - path: 'marketplace', - loadChildren: () => - import('./pages/marketplace/marketplace.module').then( - m => m.MarketplaceModule, - ), - }, - { - path: 'notifications', - loadChildren: () => - import('./pages/notifications/notifications.module').then( - m => m.NotificationsPageModule, - ), - }, - { - path: 'services', - loadChildren: () => - import('./pages/services/services.module').then(m => m.ServicesModule), - }, - { - path: 'backups', - loadChildren: () => - import('./pages/backups/backups.module').then(m => m.BackupsModule), - }, -] - -@NgModule({ - imports: [RouterModule.forChild(ROUTES)], -}) -export class UiModule {} diff --git a/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.html b/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.html deleted file mode 100644 index 46da84a76..000000000 --- a/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - -
    - - {{ unreadCount }} - - -
    diff --git a/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.module.ts b/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.module.ts deleted file mode 100644 index 1ad6c0e93..000000000 --- a/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { BadgeMenuComponent } from './badge-menu.component' - -@NgModule({ - imports: [CommonModule, IonicModule], - declarations: [BadgeMenuComponent], - exports: [BadgeMenuComponent], -}) -export class BadgeMenuComponentModule {} diff --git a/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.scss b/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.scss deleted file mode 100644 index 1e9265e5a..000000000 --- a/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.scss +++ /dev/null @@ -1,33 +0,0 @@ -:host { - display: flex; - align-items: center; - padding-right: 8px; -} - -.sidebar { - display: none; -} - -@media screen and (min-width: 992px) { - .widgets { - display: none; - } - - .sidebar { - display: inline-block; - } -} - -.wrapper { - position: relative; -} - -.md-badge { - background-color: var(--ion-color-danger); - position: absolute; - top: -8px; - left: 56%; - border-radius: 6px; - z-index: 1; - font-size: 80%; -} diff --git a/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.ts b/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.ts deleted file mode 100644 index 6e3406375..000000000 --- a/web/projects/ui/src/app/common/badge-menu-button/badge-menu.component.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' -import { SplitPaneTracker } from 'src/app/services/split-pane.service' -import { PatchDB } from 'patch-db-client' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { TuiDialogService } from '@taiga-ui/core' -import { WIDGETS_COMPONENT } from 'src/app/apps/ui/pages/widgets/widgets.page' -import { WorkspaceConfig } from '@start9labs/shared' -import { - ClientStorageService, - WidgetDrawer, -} from 'src/app/services/client-storage.service' - -const { enableWidgets } = - require('../../../../../../config.json') as WorkspaceConfig - -@Component({ - selector: 'badge-menu-button', - templateUrl: './badge-menu.component.html', - styleUrls: ['./badge-menu.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class BadgeMenuComponent { - readonly unreadCount$ = this.patch.watch$( - 'server-info', - 'unreadNotifications', - 'count', - ) - readonly sidebarOpen$ = this.splitPane.sidebarOpen$ - readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$ - - readonly enableWidgets = enableWidgets - - constructor( - private readonly splitPane: SplitPaneTracker, - private readonly patch: PatchDB, - private readonly dialog: TuiDialogService, - private readonly clientStorageService: ClientStorageService, - ) {} - - onSidebar(drawer: WidgetDrawer) { - this.clientStorageService.updateWidgetDrawer({ - ...drawer, - open: !drawer.open, - }) - } - - onWidgets() { - this.dialog.open(WIDGETS_COMPONENT, { label: 'Widgets' }).subscribe() - } -} diff --git a/web/projects/ui/src/app/common/interface-addresses/interface-addresses-item.component.html b/web/projects/ui/src/app/common/interface-addresses/interface-addresses-item.component.html deleted file mode 100644 index 3b5669a64..000000000 --- a/web/projects/ui/src/app/common/interface-addresses/interface-addresses-item.component.html +++ /dev/null @@ -1,18 +0,0 @@ - - -

    {{ label }}

    -

    {{ hostname }}

    - -
    -
    - - - - - - - - - -
    -
    diff --git a/web/projects/ui/src/app/common/interface-addresses/interface-addresses.component.html b/web/projects/ui/src/app/common/interface-addresses/interface-addresses.component.html deleted file mode 100644 index 3e80a3c8c..000000000 --- a/web/projects/ui/src/app/common/interface-addresses/interface-addresses.component.html +++ /dev/null @@ -1,125 +0,0 @@ - - -

    Clearnet

    - - - -

    - Add clearnet to expose this interface to the public Internet. - - View instructions - -

    -
    -
    - - -
    - - Update - - - Remove - -
    -
    -
    - -
    - - - Add Clearnet - -
    -
    -
    - - -

    Tor

    - - - -

    - Use a Tor-enabled browser to access this address. Tor connections can - be slow and unreliable. - - View instructions - -

    -
    -
    - -
    - - -

    Local

    - - - -

    - Local addresses can only be accessed while connected to the same Local - Area Network (LAN) as your server, either directly or using a VPN. - - View instructions - -

    -
    - - - Download Root CA - -
    -
    -
    - - - - - -
    - - - -
    diff --git a/web/projects/ui/src/app/common/interface-addresses/interface-addresses.component.scss b/web/projects/ui/src/app/common/interface-addresses/interface-addresses.component.scss deleted file mode 100644 index fab7b4db2..000000000 --- a/web/projects/ui/src/app/common/interface-addresses/interface-addresses.component.scss +++ /dev/null @@ -1,15 +0,0 @@ -ion-item-divider { - text-transform: unset; - padding-bottom: 12px; - padding-left: 0; -} - -ion-item-group { - background-color: #1e2024; - border: 1px solid #717171; - border-radius: 6px; -} - -ion-item { - --background: #1e2024; -} diff --git a/web/projects/ui/src/app/common/interface-addresses/interface-addresses.component.ts b/web/projects/ui/src/app/common/interface-addresses/interface-addresses.component.ts deleted file mode 100644 index 623bd1d50..000000000 --- a/web/projects/ui/src/app/common/interface-addresses/interface-addresses.component.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Inject, - Input, -} from '@angular/core' -import { LoadingService, CopyService, ErrorService } from '@start9labs/shared' -import { Config } from '@start9labs/start-sdk/lib/config/builder/config' -import { Value } from '@start9labs/start-sdk/lib/config/builder/value' -import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes' -import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core' -import { filter } from 'rxjs' -import { - AddressInfo, - DataModel, - DomainInfo, - NetworkInfo, -} from 'src/app/services/patch-db/data-model' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { Pipe, PipeTransform } from '@angular/core' -import { getClearnetAddress } from 'src/app/util/clearnetAddress' -import { DOCUMENT } from '@angular/common' -import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page' -import { PatchDB } from 'patch-db-client' -import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' -import { QRComponent } from 'src/app/common/qr/qr.component' - -export type ClearnetForm = { - domain: string - subdomain: string | null -} - -@Component({ - selector: 'interface-addresses', - templateUrl: './interface-addresses.component.html', - styleUrls: ['./interface-addresses.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class InterfaceAddressesComponent { - @Input() packageContext?: { - packageId: string - interfaceId: string - } - @Input({ required: true }) addressInfo!: AddressInfo - @Input({ required: true }) isUi!: boolean - - readonly network$ = this.patch.watch$('server-info', 'network') - - constructor( - private readonly loader: LoadingService, - private readonly formDialog: FormDialogService, - private readonly errorService: ErrorService, - private readonly api: ApiService, - private readonly dialogs: TuiDialogService, - private readonly patch: PatchDB, - @Inject(DOCUMENT) private readonly document: Document, - ) {} - - installCert(): void { - this.document.getElementById('install-cert')?.click() - } - - async presentModalAddClearnet(networkInfo: NetworkInfo) { - const domainInfo = this.addressInfo.domainInfo - const options: Partial>> = { - label: 'Select Domain/Subdomain', - data: { - value: { - domain: domainInfo?.domain || '', - subdomain: domainInfo?.subdomain || '', - }, - spec: await getClearnetSpec(networkInfo), - buttons: [ - { - text: 'Manage domains', - link: '/system/domains', - }, - { - text: 'Save', - handler: async value => this.saveClearnet(value), - }, - ], - }, - } - this.formDialog.open(FormPage, options) - } - - presentAlertRemoveClearnet() { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: 'Remove clearnet address?', - yes: 'Remove', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.removeClearnet()) - } - - private async saveClearnet(domainInfo: ClearnetForm): Promise { - const loader = this.loader.open('Saving...').subscribe() - - try { - if (this.packageContext) { - await this.api.setInterfaceClearnetAddress({ - ...this.packageContext, - domainInfo, - }) - } else { - await this.api.setServerClearnetAddress({ domainInfo }) - } - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } - - private async removeClearnet(): Promise { - const loader = this.loader.open('Removing...').subscribe() - - try { - if (this.packageContext) { - await this.api.setInterfaceClearnetAddress({ - ...this.packageContext, - domainInfo: null, - }) - } else { - await this.api.setServerClearnetAddress({ domainInfo: null }) - } - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } -} - -function getClearnetSpec({ - domains, - start9ToSubdomain, -}: NetworkInfo): Promise { - const start9ToDomain = `${start9ToSubdomain?.value}.start9.to` - const base = start9ToSubdomain ? { [start9ToDomain]: start9ToDomain } : {} - - const values = domains.reduce((prev, curr) => { - return { - [curr.value]: curr.value, - ...prev, - } - }, base) - - return configBuilderToSpec( - Config.of({ - domain: Value.select({ - name: 'Domain', - required: { default: null }, - values, - }), - subdomain: Value.text({ - name: 'Subdomain', - required: false, - }), - }), - ) -} - -@Component({ - selector: 'interface-addresses-item', - templateUrl: './interface-addresses-item.component.html', - styleUrls: ['./interface-addresses.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class InterfaceAddressItemComponent { - @Input({ required: true }) label!: string - @Input({ required: true }) hostname!: string - @Input({ required: true }) isUi!: boolean - - constructor( - readonly copyService: CopyService, - private readonly dialogs: TuiDialogService, - @Inject(DOCUMENT) private readonly document: Document, - ) {} - - launch(url: string): void { - this.document.defaultView?.open(url, '_blank', 'noreferrer') - } - - showQR(data: string) { - this.dialogs - .open(new PolymorpheusComponent(QRComponent), { - size: 'auto', - data, - }) - .subscribe() - } -} - -@Pipe({ - name: 'interfaceClearnetPipe', -}) -export class InterfaceClearnetPipe implements PipeTransform { - transform(clearnet: DomainInfo): string { - return getClearnetAddress('https', clearnet) - } -} diff --git a/web/projects/ui/src/app/common/interface-addresses/interface-addresses.module.ts b/web/projects/ui/src/app/common/interface-addresses/interface-addresses.module.ts deleted file mode 100644 index 5ca092095..000000000 --- a/web/projects/ui/src/app/common/interface-addresses/interface-addresses.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { - InterfaceAddressesComponent, - InterfaceAddressItemComponent, - InterfaceClearnetPipe, -} from './interface-addresses.component' - -@NgModule({ - imports: [CommonModule, IonicModule], - declarations: [ - InterfaceAddressesComponent, - InterfaceAddressItemComponent, - InterfaceClearnetPipe, - ], - exports: [InterfaceAddressesComponent], -}) -export class InterfaceAddressesComponentModule {} diff --git a/web/projects/ui/src/app/routing.module.ts b/web/projects/ui/src/app/routing.module.ts index be417216d..ebea48dd2 100644 --- a/web/projects/ui/src/app/routing.module.ts +++ b/web/projects/ui/src/app/routing.module.ts @@ -30,9 +30,8 @@ const routes: Routes = [ }, { path: '', - canActivate: [AuthGuard], - canActivateChild: [AuthGuard], - loadChildren: () => import('./apps/ui/ui.module').then(m => m.UiModule), + redirectTo: 'portal', + pathMatch: 'full', }, ] diff --git a/web/projects/ui/src/app/services/api/mock-patch.ts b/web/projects/ui/src/app/services/api/mock-patch.ts index 663496214..944a08183 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -1,5 +1,4 @@ import { DataModel } from 'src/app/services/patch-db/data-model' -import { BUILT_IN_WIDGETS } from 'src/app/apps/ui/pages/widgets/built-in/widgets' import { Mock } from './api.fixures' export const mockPatchData: DataModel = { @@ -8,13 +7,7 @@ export const mockPatchData: DataModel = { 'ack-welcome': '1.0.0', theme: 'Dark', desktop: ['lnd'], - widgets: BUILT_IN_WIDGETS.filter( - ({ id }) => - id === 'favorites' || - id === 'health' || - id === 'network' || - id === 'metrics', - ), + widgets: [], marketplace: { 'selected-url': 'https://registry.start9.com/', 'known-hosts': { diff --git a/web/projects/ui/src/app/services/proxy.service.ts b/web/projects/ui/src/app/services/proxy.service.ts index be9349689..2cb09ac97 100644 --- a/web/projects/ui/src/app/services/proxy.service.ts +++ b/web/projects/ui/src/app/services/proxy.service.ts @@ -1,20 +1,23 @@ import { Injectable } from '@angular/core' -import { PatchDB } from 'patch-db-client' -import { - DataModel, - OsOutboundProxy, - ServiceOutboundProxy, -} from './patch-db/data-model' -import { firstValueFrom } from 'rxjs' +import { ErrorService, LoadingService } from '@start9labs/shared' import { Config } from '@start9labs/start-sdk/lib/config/builder/config' import { Value } from '@start9labs/start-sdk/lib/config/builder/value' import { Variants } from '@start9labs/start-sdk/lib/config/builder/variants' -import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' import { TuiDialogOptions } from '@taiga-ui/core' +import { PatchDB } from 'patch-db-client' +import { firstValueFrom } from 'rxjs' +import { + FormComponent, + FormContext, +} from 'src/app/apps/portal/components/form.component' import { FormDialogService } from 'src/app/services/form-dialog.service' -import { FormContext, FormPage } from '../apps/ui/modals/form/form.page' +import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' import { ApiService } from './api/embassy-api.service' -import { ErrorService, LoadingService } from '@start9labs/shared' +import { + DataModel, + OsOutboundProxy, + ServiceOutboundProxy, +} from './patch-db/data-model' @Injectable({ providedIn: 'root', @@ -42,10 +45,10 @@ export class ProxyService { const defaultValue = !outboundProxy ? 'none' : outboundProxy === 'primary' - ? 'primary' - : outboundProxy === 'mirror' - ? 'mirror' - : 'other' + ? 'primary' + : outboundProxy === 'mirror' + ? 'mirror' + : 'other' let variants: Record }> = {} @@ -126,10 +129,10 @@ export class ProxyService { value.proxy.unionSelectKey === 'none' ? null : value.proxy.unionSelectKey === 'primary' - ? 'primary' - : value.proxy.unionSelectKey === 'mirror' - ? 'mirror' - : { proxyId: value.proxy.unionValueKey.proxyId } + ? 'primary' + : value.proxy.unionSelectKey === 'mirror' + ? 'mirror' + : { proxyId: value.proxy.unionValueKey.proxyId } await this.saveOutboundProxy(proxy, serviceContext?.packageId) return true }, @@ -137,7 +140,7 @@ export class ProxyService { ], }, } - this.formDialog.open(FormPage, options) + this.formDialog.open(FormComponent, options) } private async saveOutboundProxy(