diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/plugin.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/plugin.component.ts index 5a1566324..6107ace47 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/plugin.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/plugin.component.ts @@ -10,7 +10,6 @@ import { T } from '@start9labs/start-sdk' import { TUI_IS_MOBILE } from '@taiga-ui/cdk' import { TuiButton, - tuiButtonOptionsProvider, TuiDataList, TuiDropdown, TuiTextfield, @@ -30,6 +29,9 @@ import { selector: 'section[pluginGroup]', template: `
+ @if (pluginGroup().pluginPkgInfo; as pkgInfo) { + + } {{ pluginGroup().pluginName }} @if (pluginGroup().tableAction; as action) { + + + `, + styles: ` + .service-title { + display: inline-flex; + align-items: center; + margin-bottom: 1.5rem; + + img { + height: 1.25rem; + margin-right: 0.25rem; + border-radius: 100%; + } + + h4 { + margin: 0; + } + } + + footer { + margin-top: 1.5rem; + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiButton, TuiNotification, i18nPipe], +}) +export class ActionConfirmModal { + readonly context = + injectContext>() + + readonly pkgInfo = this.context.data.pkgInfo + readonly warning = this.context.data.actionInfo.metadata.warning +} + +export const ACTION_CONFIRM_MODAL = new PolymorpheusComponent( + ActionConfirmModal, +) diff --git a/web/projects/ui/src/app/services/action.service.ts b/web/projects/ui/src/app/services/action.service.ts index 8b2734841..02d659087 100644 --- a/web/projects/ui/src/app/services/action.service.ts +++ b/web/projects/ui/src/app/services/action.service.ts @@ -7,6 +7,7 @@ import { } from '@start9labs/shared' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { filter } from 'rxjs' +import { ACTION_CONFIRM_MODAL } from 'src/app/routes/portal/routes/services/modals/action-confirm.component' import { ActionInputModal, PackageActionData, @@ -36,17 +37,15 @@ export class ActionService { } else { if (actionInfo.metadata.warning) { this.dialog - .openConfirm({ - label: 'Warning', + .openComponent(ACTION_CONFIRM_MODAL, { + label: actionInfo.metadata.name as i18nKey, size: 's', - data: { - no: 'Cancel', - yes: 'Run', - content: actionInfo.metadata.warning as i18nKey, - }, + data, }) .pipe(filter(Boolean)) - .subscribe(() => this.execute(pkgInfo.id, null, actionInfo.id, data.prefill)) + .subscribe(() => + this.execute(pkgInfo.id, null, actionInfo.id, data.prefill), + ) } else { this.execute(pkgInfo.id, null, actionInfo.id, data.prefill) } diff --git a/web/projects/ui/src/app/services/api/api.fixures.ts b/web/projects/ui/src/app/services/api/api.fixures.ts index 5508fd388..3168326f7 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -262,7 +262,7 @@ export namespace Mock { ram: null, }, hardwareAcceleration: false, - plugins: [], + plugins: [], } export const MockManifestLnd: T.Manifest = { @@ -322,7 +322,54 @@ export namespace Mock { ram: null, }, hardwareAcceleration: false, - plugins: [], + plugins: [], + } + + export const MockManifestTor: T.Manifest = { + id: 'tor', + title: 'Tor', + version: '0.4.8:0', + satisfies: [], + canMigrateTo: '!', + canMigrateFrom: '*', + gitHash: 'torhash1', + description: { + short: 'An anonymous overlay network.', + long: 'Tor provides anonymous communication by directing traffic through a free, worldwide overlay network.', + }, + releaseNotes: 'Bug fixes and stability improvements.', + license: 'BSD-3-Clause', + packageRepo: 'https://github.com/start9labs/tor-wrapper', + upstreamRepo: 'https://gitlab.torproject.org/tpo/core/tor', + marketingUrl: 'https://www.torproject.org', + donationUrl: null, + docsUrls: ['https://docs.start9.com'], + alerts: { + install: null, + uninstall: null, + restore: null, + start: null, + stop: null, + }, + osVersion: '0.2.12', + sdkVersion: '0.4.0', + dependencies: {}, + images: { + main: { + source: 'packed', + arch: ['x86_64', 'aarch64'], + emulateMissingAs: 'aarch64', + nvidiaContainer: false, + }, + }, + volumes: ['main'], + hardwareRequirements: { + device: [], + arch: null, + ram: null, + }, + hardwareAcceleration: false, + plugins: ['url-v0'], } export const MockManifestBitcoinProxy: T.Manifest = { @@ -375,7 +422,7 @@ export namespace Mock { ram: null, }, hardwareAcceleration: false, - plugins: [], + plugins: [], } export const BitcoinDep: T.DependencyMetadata = { @@ -435,7 +482,7 @@ export namespace Mock { ], ], hardwareAcceleration: false, - plugins: [], + plugins: [], }, '#knots:26.1.20240325:0': { title: 'Bitcoin Knots', @@ -477,7 +524,7 @@ export namespace Mock { ], ], hardwareAcceleration: false, - plugins: [], + plugins: [], }, }, categories: ['bitcoin', 'featured'], @@ -529,7 +576,7 @@ export namespace Mock { ], ], hardwareAcceleration: false, - plugins: [], + plugins: [], }, '#knots:26.1.20240325:0': { title: 'Bitcoin Knots', @@ -571,7 +618,7 @@ export namespace Mock { ], ], hardwareAcceleration: false, - plugins: [], + plugins: [], }, }, categories: ['bitcoin', 'featured'], @@ -628,7 +675,7 @@ export namespace Mock { ], ], hardwareAcceleration: false, - plugins: [], + plugins: [], }, }, categories: ['lightning'], @@ -683,7 +730,7 @@ export namespace Mock { ], ], hardwareAcceleration: false, - plugins: [], + plugins: [], }, }, categories: ['lightning'], @@ -1444,6 +1491,31 @@ export namespace Mock { }, } + export const getCreateOnionServiceSpec = async (): Promise => + configBuilderToSpec( + ISB.InputSpec.of({ + ssl: ISB.Value.toggle({ + name: 'SSL', + description: 'Enable HTTPS for this onion service', + default: true, + }), + privateKey: ISB.Value.text({ + name: 'Private Key', + description: + 'Optionally provide an existing ed25519 private key to reuse a .onion address. Leave blank to generate a new one.', + required: false, + default: null, + masked: true, + }), + urlPluginMetadata: ISB.Value.hidden<{ + packageId: string + interfaceId: string + hostId: string + internalPort: number + }>(), + }), + ) + export const getActionInputSpec = async (): Promise => configBuilderToSpec( ISB.InputSpec.of({ diff --git a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 1e2232d98..e7bc02809 100644 --- a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -1069,6 +1069,18 @@ export class MockApiService extends ApiService { params: T.GetActionInputParams, ): Promise { await pauseFor(2000) + + if ( + params.packageId === 'tor' && + params.actionId === 'create-onion-service' + ) { + return { + eventId: 'ANZXNWIFRTTBZ6T52KQPZILIQQODDHXQ', + value: null, + spec: await Mock.getCreateOnionServiceSpec(), + } + } + return { eventId: 'ANZXNWIFRTTBZ6T52KQPZILIQQODDHXQ', value: Mock.MockConfig, 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 3d0247341..3cc3393e8 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -101,7 +101,7 @@ export const mockPatchData: DataModel = { kind: 'plugin', packageId: 'tor', removeAction: 'delete-onion-service', - overflowActions: [], + overflowActions: ['regenerate-key'], info: null, }, }, @@ -115,7 +115,7 @@ export const mockPatchData: DataModel = { kind: 'plugin', packageId: 'tor', removeAction: 'delete-onion-service', - overflowActions: [], + overflowActions: ['regenerate-key'], info: null, }, }, @@ -616,7 +616,7 @@ export const mockPatchData: DataModel = { kind: 'plugin', packageId: 'tor', removeAction: 'delete-onion-service', - overflowActions: [], + overflowActions: ['regenerate-key'], info: null, }, }, @@ -630,7 +630,7 @@ export const mockPatchData: DataModel = { kind: 'plugin', packageId: 'tor', removeAction: 'delete-onion-service', - overflowActions: [], + overflowActions: ['regenerate-key'], info: null, }, }, @@ -763,5 +763,62 @@ export const mockPatchData: DataModel = { }, }, }, + tor: { + stateInfo: { + state: 'installed', + manifest: { + ...Mock.MockManifestTor, + version: '0.4.8:0', + }, + }, + s9pk: '/media/startos/data/package-data/archive/installed/tor.s9pk', + icon: '/assets/img/service-icons/fallback.png', + lastBackup: null, + statusInfo: { + desired: { main: 'running' }, + error: null, + health: {}, + started: new Date().toISOString(), + }, + actions: { + 'create-onion-service': { + name: 'Create Onion Service', + description: 'Register a new .onion address for a service interface', + warning: null, + visibility: 'enabled', + allowedStatuses: 'only-running', + hasInput: true, + group: null, + }, + 'delete-onion-service': { + name: 'Delete Onion Service', + description: 'Remove an existing .onion address', + warning: 'This will permanently remove the .onion address.', + visibility: 'enabled', + allowedStatuses: 'only-running', + hasInput: false, + group: null, + }, + 'regenerate-key': { + name: 'Regenerate Key', + description: 'Generate a new key pair and .onion address', + warning: + 'This will change the .onion address. Any bookmarks or links to the old address will stop working.', + visibility: 'enabled', + allowedStatuses: 'only-running', + hasInput: false, + group: null, + }, + }, + serviceInterfaces: {}, + currentDependencies: {}, + hosts: {}, + storeExposedDependents: [], + outboundGateway: null, + registry: 'https://registry.start9.com/', + developerKey: 'developer-key', + plugin: { url: { tableAction: 'create-onion-service' } }, + tasks: {}, + }, }, }