mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
touch up URL plugins table
This commit is contained in:
@@ -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: `
|
||||
<header>
|
||||
@if (pluginGroup().pluginPkgInfo; as pkgInfo) {
|
||||
<img [src]="pkgInfo.icon" alt="" class="plugin-icon" />
|
||||
}
|
||||
{{ pluginGroup().pluginName }}
|
||||
@if (pluginGroup().tableAction; as action) {
|
||||
<button
|
||||
@@ -43,7 +45,7 @@ import {
|
||||
}
|
||||
</header>
|
||||
<table [appTable]="['Protocol', 'URL', null]">
|
||||
@for (address of pluginGroup().addresses; track $index) {
|
||||
@for (address of pluginGroup().addresses; track $index; let i = $index) {
|
||||
<tr>
|
||||
<td>{{ address.hostnameInfo.ssl ? 'HTTPS' : 'HTTP' }}</td>
|
||||
<td [style.grid-area]="'2 / 1 / 2 / 2'">
|
||||
@@ -51,22 +53,6 @@ import {
|
||||
</td>
|
||||
<td [style.width.rem]="5">
|
||||
<div class="desktop">
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="flat-grayscale"
|
||||
iconStart="@tui.qr-code"
|
||||
(click)="showQR(address.url)"
|
||||
>
|
||||
{{ 'Show QR' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="flat-grayscale"
|
||||
iconStart="@tui.copy"
|
||||
(click)="copyService.copy(address.url)"
|
||||
>
|
||||
{{ 'Copy URL' | i18n }}
|
||||
</button>
|
||||
@if (address.hostnameInfo.metadata.kind === 'plugin') {
|
||||
@if (address.hostnameInfo.metadata.removeAction) {
|
||||
@if (
|
||||
@@ -91,7 +77,59 @@ import {
|
||||
</button>
|
||||
}
|
||||
}
|
||||
<!-- TODO @MattHill: overflow -->
|
||||
}
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="flat-grayscale"
|
||||
iconStart="@tui.qr-code"
|
||||
(click)="showQR(address.url)"
|
||||
>
|
||||
{{ 'Show QR' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="flat-grayscale"
|
||||
iconStart="@tui.copy"
|
||||
(click)="copyService.copy(address.url)"
|
||||
>
|
||||
{{ 'Copy URL' | i18n }}
|
||||
</button>
|
||||
@if (address.hostnameInfo.metadata.kind === 'plugin') {
|
||||
@if (address.hostnameInfo.metadata.overflowActions.length) {
|
||||
<button
|
||||
tuiIconButton
|
||||
tuiDropdown
|
||||
appearance="flat-grayscale"
|
||||
iconStart="@tui.ellipsis-vertical"
|
||||
[tuiAppearanceState]="overflowOpen() === i ? 'hover' : null"
|
||||
[tuiDropdownOpen]="overflowOpen() === i"
|
||||
(tuiDropdownOpenChange)="
|
||||
overflowOpen.set($event ? i : null)
|
||||
"
|
||||
>
|
||||
{{ 'More' | i18n }}
|
||||
<tui-data-list
|
||||
*tuiTextfieldDropdown
|
||||
(click)="overflowOpen.set(null)"
|
||||
>
|
||||
@for (
|
||||
actionId of address.hostnameInfo.metadata
|
||||
.overflowActions;
|
||||
track actionId
|
||||
) {
|
||||
@if (pluginGroup().pluginActions[actionId]; as meta) {
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
(click)="runRowAction(actionId, meta, address)"
|
||||
>
|
||||
{{ meta.name }}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</tui-data-list>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="mobile">
|
||||
@@ -105,22 +143,6 @@ import {
|
||||
>
|
||||
{{ 'Actions' | i18n }}
|
||||
<tui-data-list *tuiTextfieldDropdown (click)="open.set(false)">
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.qr-code"
|
||||
(click)="showQR(address.url)"
|
||||
>
|
||||
{{ 'Show QR' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.copy"
|
||||
(click)="copyService.copy(address.url)"
|
||||
>
|
||||
{{ 'Copy URL' | i18n }}
|
||||
</button>
|
||||
@if (address.hostnameInfo.metadata.kind === 'plugin') {
|
||||
@if (address.hostnameInfo.metadata.removeAction) {
|
||||
@if (
|
||||
@@ -145,7 +167,38 @@ import {
|
||||
</button>
|
||||
}
|
||||
}
|
||||
<!-- TODO @MattHill: overflow -->
|
||||
}
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.qr-code"
|
||||
(click)="showQR(address.url)"
|
||||
>
|
||||
{{ 'Show QR' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.copy"
|
||||
(click)="copyService.copy(address.url)"
|
||||
>
|
||||
{{ 'Copy URL' | i18n }}
|
||||
</button>
|
||||
@if (address.hostnameInfo.metadata.kind === 'plugin') {
|
||||
@for (
|
||||
actionId of address.hostnameInfo.metadata.overflowActions;
|
||||
track actionId
|
||||
) {
|
||||
@if (pluginGroup().pluginActions[actionId]; as meta) {
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
(click)="runRowAction(actionId, meta, address)"
|
||||
>
|
||||
{{ meta.name }}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
</tui-data-list>
|
||||
</button>
|
||||
@@ -164,6 +217,12 @@ import {
|
||||
</table>
|
||||
`,
|
||||
styles: `
|
||||
.plugin-icon {
|
||||
height: 1.25rem;
|
||||
margin-right: 0.25rem;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
:host ::ng-deep {
|
||||
th:first-child {
|
||||
width: 5rem;
|
||||
@@ -217,7 +276,6 @@ import {
|
||||
PlaceholderComponent,
|
||||
i18nPipe,
|
||||
],
|
||||
providers: [tuiButtonOptionsProvider({ appearance: 'icon' })],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PluginAddressesComponent {
|
||||
@@ -226,6 +284,7 @@ export class PluginAddressesComponent {
|
||||
private readonly actionService = inject(ActionService)
|
||||
readonly copyService = inject(CopyService)
|
||||
readonly open = signal(false)
|
||||
readonly overflowOpen = signal<number | null>(null)
|
||||
|
||||
readonly pluginGroup = input.required<PluginAddressGroup>()
|
||||
readonly packageId = input('')
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiButton, TuiDialogContext, TuiNotification } from '@taiga-ui/core'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PackageActionData } from './action-input.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div class="service-title">
|
||||
<img [src]="pkgInfo.icon" alt="" />
|
||||
<h4>{{ pkgInfo.title }}</h4>
|
||||
</div>
|
||||
<tui-notification appearance="warning">
|
||||
<div [innerHTML]="warning"></div>
|
||||
</tui-notification>
|
||||
<footer class="g-buttons">
|
||||
<button tuiButton appearance="flat" (click)="context.completeWith(false)">
|
||||
{{ 'Cancel' | i18n }}
|
||||
</button>
|
||||
<button tuiButton (click)="context.completeWith(true)">
|
||||
{{ 'Run' | i18n }}
|
||||
</button>
|
||||
</footer>
|
||||
`,
|
||||
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<TuiDialogContext<boolean, PackageActionData>>()
|
||||
|
||||
readonly pkgInfo = this.context.data.pkgInfo
|
||||
readonly warning = this.context.data.actionInfo.metadata.warning
|
||||
}
|
||||
|
||||
export const ACTION_CONFIRM_MODAL = new PolymorpheusComponent(
|
||||
ActionConfirmModal,
|
||||
)
|
||||
@@ -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<boolean>(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)
|
||||
}
|
||||
|
||||
@@ -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<IST.InputSpec> =>
|
||||
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<IST.InputSpec> =>
|
||||
configBuilderToSpec(
|
||||
ISB.InputSpec.of({
|
||||
|
||||
@@ -1069,6 +1069,18 @@ export class MockApiService extends ApiService {
|
||||
params: T.GetActionInputParams,
|
||||
): Promise<GetActionInputRes> {
|
||||
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,
|
||||
|
||||
@@ -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: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user