mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
merge
This commit is contained in:
@@ -12,16 +12,17 @@ import {
|
|||||||
tuiButtonOptionsProvider,
|
tuiButtonOptionsProvider,
|
||||||
TuiDataList,
|
TuiDataList,
|
||||||
TuiDropdown,
|
TuiDropdown,
|
||||||
|
TuiTextfield,
|
||||||
} from '@taiga-ui/core'
|
} from '@taiga-ui/core'
|
||||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
import { QRModal } from 'src/app/routes/portal/modals/qr.component'
|
import { QRModal } from 'src/app/routes/portal/modals/qr.component'
|
||||||
|
|
||||||
import { InterfaceComponent } from '../interface.component'
|
import { InterfaceComponent } from '../interface.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'td[actions]',
|
selector: 'td[actions]',
|
||||||
template: `
|
template: `
|
||||||
<div class="desktop">
|
<div class="desktop">
|
||||||
<ng-content />
|
|
||||||
@if (interface.value().type === 'ui') {
|
@if (interface.value().type === 'ui') {
|
||||||
<button
|
<button
|
||||||
tuiIconButton
|
tuiIconButton
|
||||||
@@ -52,46 +53,48 @@ import { InterfaceComponent } from '../interface.component'
|
|||||||
</div>
|
</div>
|
||||||
<div class="mobile">
|
<div class="mobile">
|
||||||
<button
|
<button
|
||||||
|
tuiDropdown
|
||||||
tuiIconButton
|
tuiIconButton
|
||||||
|
appearance="flat-grayscale"
|
||||||
iconStart="@tui.ellipsis-vertical"
|
iconStart="@tui.ellipsis-vertical"
|
||||||
tuiDropdownOpen
|
[tuiAppearanceState]="open ? 'hover' : null"
|
||||||
[tuiDropdown]="dropdown"
|
[(tuiDropdownOpen)]="open"
|
||||||
>
|
>
|
||||||
{{ 'Actions' | i18n }}
|
{{ 'Actions' | i18n }}
|
||||||
<ng-template #dropdown let-close>
|
<tui-data-list *tuiTextfieldDropdown="let close">
|
||||||
<tui-data-list>
|
<button tuiOption new iconStart="@tui.eye" (click)="instructions()">
|
||||||
<tui-opt-group>
|
{{ 'View instructions' | i18n }}
|
||||||
@if (interface.value().type === 'ui') {
|
</button>
|
||||||
<button
|
@if (interface.value().type === 'ui') {
|
||||||
tuiOption
|
<button
|
||||||
iconStart="@tui.external-link"
|
tuiOption
|
||||||
[disabled]="disabled()"
|
new
|
||||||
(click)="openUI()"
|
iconStart="@tui.external-link"
|
||||||
>
|
[disabled]="disabled()"
|
||||||
{{ 'Open' | i18n }}
|
(click)="openUI()"
|
||||||
</button>
|
>
|
||||||
}
|
{{ 'Open' | i18n }}
|
||||||
<button tuiOption iconStart="@tui.qr-code" (click)="showQR()">
|
</button>
|
||||||
{{ 'Show QR' | i18n }}
|
}
|
||||||
</button>
|
<button tuiOption new iconStart="@tui.qr-code" (click)="showQR()">
|
||||||
<button
|
{{ 'Show QR' | i18n }}
|
||||||
tuiOption
|
</button>
|
||||||
iconStart="@tui.copy"
|
<button
|
||||||
(click)="copyService.copy(href()); close()"
|
tuiOption
|
||||||
>
|
new
|
||||||
{{ 'Copy URL' | i18n }}
|
iconStart="@tui.copy"
|
||||||
</button>
|
(click)="copyService.copy(href()); close()"
|
||||||
</tui-opt-group>
|
>
|
||||||
<tui-opt-group><ng-content select="[tuiOption]" /></tui-opt-group>
|
{{ 'Copy URL' | i18n }}
|
||||||
</tui-data-list>
|
</button>
|
||||||
</ng-template>
|
</tui-data-list>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
styles: `
|
styles: `
|
||||||
:host {
|
:host {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
grid-area: 1 / 2 / 3 / 3;
|
grid-area: 1 / 2 / 4 / 3;
|
||||||
place-content: center;
|
place-content: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -110,7 +113,7 @@ import { InterfaceComponent } from '../interface.component'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe],
|
imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe, TuiTextfield],
|
||||||
providers: [tuiButtonOptionsProvider({ appearance: 'icon' })],
|
providers: [tuiButtonOptionsProvider({ appearance: 'icon' })],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
@@ -125,6 +128,8 @@ export class AddressActionsComponent {
|
|||||||
readonly href = input.required<string>()
|
readonly href = input.required<string>()
|
||||||
readonly disabled = input.required<boolean>()
|
readonly disabled = input.required<boolean>()
|
||||||
|
|
||||||
|
open = false
|
||||||
|
|
||||||
showQR() {
|
showQR() {
|
||||||
this.dialog
|
this.dialog
|
||||||
.openComponent(new PolymorpheusComponent(QRModal), {
|
.openComponent(new PolymorpheusComponent(QRModal), {
|
||||||
@@ -138,4 +143,6 @@ export class AddressActionsComponent {
|
|||||||
openUI() {
|
openUI() {
|
||||||
this.document.defaultView?.open(this.href(), '_blank', 'noreferrer')
|
this.document.defaultView?.open(this.href(), '_blank', 'noreferrer')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instructions() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
import { i18nPipe } from '@start9labs/shared'
|
||||||
import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
|
import { TuiButton } from '@taiga-ui/core'
|
||||||
|
import { TuiAccordion } from '@taiga-ui/experimental'
|
||||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||||
import { MappedServiceInterface } from '../interface.utils'
|
import { MappedServiceInterface } from '../interface.utils'
|
||||||
@@ -10,70 +11,95 @@ import { AddressActionsComponent } from './actions.component'
|
|||||||
selector: 'section[addresses]',
|
selector: 'section[addresses]',
|
||||||
template: `
|
template: `
|
||||||
<header>{{ 'Addresses' | i18n }}</header>
|
<header>{{ 'Addresses' | i18n }}</header>
|
||||||
@if (addresses().common.length) {
|
<table [appTable]="[null, 'Type', 'Gateway', 'URL', null]">
|
||||||
<section class="g-card">
|
@for (address of addresses().common; track $index) {
|
||||||
<header>{{ 'Common' | i18n }}</header>
|
<tr>
|
||||||
<table [appTable]="[null, 'Type', 'Gateway', 'URL', null]">
|
<td>
|
||||||
@for (address of addresses().common; track $index) {
|
<button
|
||||||
<tr>
|
tuiIconButton
|
||||||
<td>
|
appearance="flat-grayscale"
|
||||||
<button
|
iconStart="@tui.eye"
|
||||||
tuiIconButton
|
(click)="instructions()"
|
||||||
appearance="flat-grayscale"
|
>
|
||||||
iconStart="@tui.eye"
|
{{ 'View instructions' | i18n }}
|
||||||
(click)="instructions()"
|
</button>
|
||||||
>
|
</td>
|
||||||
{{ 'View instructions' | i18n }}
|
<td>{{ address.type }}</td>
|
||||||
</button>
|
<td [style.order]="-1">{{ address.gateway }}</td>
|
||||||
</td>
|
<td>{{ address.url }}</td>
|
||||||
<td>{{ address.type }}</td>
|
<td actions [disabled]="!isRunning()" [href]="address.url"></td>
|
||||||
<td>{{ address.gateway }}</td>
|
</tr>
|
||||||
<td>{{ address.url }}</td>
|
} @empty {
|
||||||
<td actions [disabled]="!isRunning()" [href]="address.url"></td>
|
<tr>
|
||||||
</tr>
|
<td colspan="5">
|
||||||
}
|
<app-placeholder icon="@tui.app-window">
|
||||||
</table>
|
{{ 'No addresses' | i18n }}
|
||||||
</section>
|
</app-placeholder>
|
||||||
} @else {
|
</td>
|
||||||
<app-placeholder icon="@tui.app-window">
|
</tr>
|
||||||
{{ 'No addresses' | i18n }}
|
}
|
||||||
</app-placeholder>
|
</table>
|
||||||
}
|
|
||||||
|
|
||||||
@if (addresses().uncommon.length) {
|
@if (addresses().uncommon.length) {
|
||||||
<section class="g-card">
|
<tui-accordion>
|
||||||
<header>{{ 'Uncommon' | i18n }}</header>
|
<button tuiAccordion>{{ 'Uncommon' | i18n }}</button>
|
||||||
<table [appTable]="[null, 'Type', 'Gateway', 'URL', null]">
|
<tui-expand>
|
||||||
@for (address of addresses().uncommon; track $index) {
|
<table [appTable]="[null, 'Type', 'Gateway', 'URL', null]">
|
||||||
<tr>
|
@for (address of addresses().uncommon; track $index) {
|
||||||
<td>
|
<tr>
|
||||||
<button
|
<td>
|
||||||
tuiIconButton
|
<button
|
||||||
appearance="flat-grayscale"
|
tuiIconButton
|
||||||
iconStart="@tui.eye"
|
appearance="flat-grayscale"
|
||||||
(click)="instructions()"
|
iconStart="@tui.eye"
|
||||||
>
|
(click)="instructions()"
|
||||||
{{ 'View instructions' | i18n }}
|
>
|
||||||
</button>
|
{{ 'View instructions' | i18n }}
|
||||||
</td>
|
</button>
|
||||||
<td>{{ address.type }}</td>
|
</td>
|
||||||
<td>{{ address.gateway }}</td>
|
<td>{{ address.type }}</td>
|
||||||
<td>{{ address.url }}</td>
|
<td [style.order]="-1">{{ address.gateway }}</td>
|
||||||
<td actions [disabled]="!isRunning()" [href]="address.url"></td>
|
<td>{{ address.url }}</td>
|
||||||
</tr>
|
<td actions [disabled]="!isRunning()" [href]="address.url"></td>
|
||||||
}
|
</tr>
|
||||||
</table>
|
}
|
||||||
</section>
|
</table>
|
||||||
|
</tui-expand>
|
||||||
|
</tui-accordion>
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
styles: `
|
||||||
|
[tuiAccordion],
|
||||||
|
tui-expand {
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0;
|
||||||
|
background: none !important;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
margin-inline-end: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(tui-root._mobile) {
|
||||||
|
td:first-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:nth-child(2) {
|
||||||
|
font: var(--tui-font-text-m);
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--tui-text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
host: { class: 'g-card' },
|
||||||
imports: [
|
imports: [
|
||||||
TableComponent,
|
TableComponent,
|
||||||
PlaceholderComponent,
|
PlaceholderComponent,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
TuiDropdown,
|
|
||||||
TuiDataList,
|
|
||||||
AddressActionsComponent,
|
AddressActionsComponent,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
|
TuiAccordion,
|
||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,35 +1,10 @@
|
|||||||
import {
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||||
ChangeDetectionStrategy,
|
import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
|
||||||
Component,
|
import { TuiButton } from '@taiga-ui/core'
|
||||||
inject,
|
|
||||||
input,
|
|
||||||
} from '@angular/core'
|
|
||||||
import {
|
|
||||||
DialogService,
|
|
||||||
DocsLinkDirective,
|
|
||||||
ErrorService,
|
|
||||||
i18nPipe,
|
|
||||||
LoadingService,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import { ISB, utils } from '@start9labs/start-sdk'
|
|
||||||
import {
|
|
||||||
TuiAppearance,
|
|
||||||
TuiButton,
|
|
||||||
TuiDataList,
|
|
||||||
TuiDropdown,
|
|
||||||
TuiLink,
|
|
||||||
} from '@taiga-ui/core'
|
|
||||||
import { filter } from 'rxjs'
|
|
||||||
import {
|
|
||||||
FormComponent,
|
|
||||||
FormContext,
|
|
||||||
} from 'src/app/routes/portal/components/form.component'
|
|
||||||
import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component'
|
|
||||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { DomainComponent } from './domain.component'
|
||||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
|
||||||
import { ClearnetDomain } from './interface.utils'
|
import { ClearnetDomain } from './interface.utils'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -38,13 +13,14 @@ import { ClearnetDomain } from './interface.utils'
|
|||||||
<header>
|
<header>
|
||||||
{{ 'Clearnet Domains' | i18n }}
|
{{ 'Clearnet Domains' | i18n }}
|
||||||
<a
|
<a
|
||||||
tuiLink
|
tuiIconButton
|
||||||
docsLink
|
docsLink
|
||||||
path="/user-manual/connecting-remotely/clearnet.html"
|
path="/user-manual/connecting-remotely/clearnet.html"
|
||||||
appearance="action-grayscale"
|
appearance="icon"
|
||||||
iconEnd="@tui.external-link"
|
iconStart="@tui.external-link"
|
||||||
[pseudo]="true"
|
>
|
||||||
></a>
|
{{ 'Documentation' | i18n }}
|
||||||
|
</a>
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
iconStart="@tui.plus"
|
iconStart="@tui.plus"
|
||||||
@@ -54,133 +30,40 @@ import { ClearnetDomain } from './interface.utils'
|
|||||||
{{ 'Add' | i18n }}
|
{{ 'Add' | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
@if (clearnetDomains().length) {
|
<table [appTable]="['Domain', 'Certificate Authority', 'Type', null]">
|
||||||
<table [appTable]="['Domain', 'Certificate Authority', 'Type', null]">
|
@for (domain of clearnetDomains(); track $index) {
|
||||||
@for (domain of clearnetDomains(); track $index) {
|
<tr [domain]="domain"></tr>
|
||||||
<tr>
|
} @empty {
|
||||||
<td>{{ domain.fqdn }}</td>
|
<tr>
|
||||||
<td>{{ domain.authority }}</td>
|
<td colspan="4">
|
||||||
<td>{{ domain.public ? 'public' : 'private' }}</td>
|
<app-placeholder icon="@tui.app-window">
|
||||||
<td>
|
{{ 'No clearnet domains' | i18n }}
|
||||||
<button
|
</app-placeholder>
|
||||||
tuiIconButton
|
</td>
|
||||||
tuiDropdown
|
</tr>
|
||||||
size="s"
|
}
|
||||||
appearance="flat-grayscale"
|
</table>
|
||||||
iconStart="@tui.ellipsis-vertical"
|
`,
|
||||||
[tuiAppearanceState]="open ? 'hover' : null"
|
styles: `
|
||||||
[(tuiDropdownOpen)]="open"
|
:host {
|
||||||
>
|
grid-column: span 3;
|
||||||
{{ 'More' | i18n }}
|
|
||||||
<tui-data-list size="s" *tuiTextfieldDropdown>
|
|
||||||
<tui-opt-group>
|
|
||||||
<button
|
|
||||||
tuiOption
|
|
||||||
new
|
|
||||||
[iconStart]="
|
|
||||||
domain.public ? '@tui.globe-lock' : '@tui.globe'
|
|
||||||
"
|
|
||||||
(click)="togglePrivate(domain)"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
domain.public
|
|
||||||
? ('Make private' | i18n)
|
|
||||||
: ('Make public' | i18n)
|
|
||||||
}}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
tuiOption
|
|
||||||
new
|
|
||||||
iconStart="@tui.award"
|
|
||||||
(click)="changeCa(domain)"
|
|
||||||
>
|
|
||||||
{{ 'Change CA' | i18n }}
|
|
||||||
</button>
|
|
||||||
</tui-opt-group>
|
|
||||||
<tui-opt-group>
|
|
||||||
<button
|
|
||||||
tuiOption
|
|
||||||
new
|
|
||||||
iconStart="@tui.trash"
|
|
||||||
class="g-negative"
|
|
||||||
(click)="remove(domain.fqdn)"
|
|
||||||
>
|
|
||||||
{{ 'Delete' | i18n }}
|
|
||||||
</button>
|
|
||||||
</tui-opt-group>
|
|
||||||
</tui-data-list>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</table>
|
|
||||||
} @else {
|
|
||||||
<app-placeholder icon="@tui.app-window">
|
|
||||||
{{ 'No clearnet domains' | i18n }}
|
|
||||||
</app-placeholder>
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
host: { class: 'g-card' },
|
||||||
imports: [
|
imports: [
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiLink,
|
|
||||||
TuiAppearance,
|
|
||||||
TableComponent,
|
TableComponent,
|
||||||
PlaceholderComponent,
|
PlaceholderComponent,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
DocsLinkDirective,
|
DocsLinkDirective,
|
||||||
TuiDropdown,
|
DomainComponent,
|
||||||
TuiDataList,
|
|
||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class InterfaceClearnetDomainsComponent {
|
export class InterfaceClearnetDomainsComponent {
|
||||||
private readonly dialog = inject(DialogService)
|
|
||||||
private readonly formDialog = inject(FormDialogService)
|
|
||||||
private readonly loader = inject(LoadingService)
|
|
||||||
private readonly errorService = inject(ErrorService)
|
|
||||||
private readonly api = inject(ApiService)
|
|
||||||
private readonly interface = inject(InterfaceComponent)
|
|
||||||
private readonly i18n = inject(i18nPipe)
|
|
||||||
|
|
||||||
readonly clearnetDomains = input.required<readonly ClearnetDomain[]>()
|
readonly clearnetDomains = input.required<readonly ClearnetDomain[]>()
|
||||||
|
|
||||||
open = false
|
open = false
|
||||||
|
|
||||||
// @TODO add, toggle, and change CA call same idempotent endpoint, either pkgAddDomain or osUiAddDomain
|
add() {}
|
||||||
|
|
||||||
async add() {
|
|
||||||
// @TODO baseDomain (select), subdomain (optional)(text), certificateAuthority (select), public/private (select)
|
|
||||||
}
|
|
||||||
|
|
||||||
async togglePrivate(domain: ClearnetDomain) {}
|
|
||||||
|
|
||||||
async changeCa(domain: ClearnetDomain) {}
|
|
||||||
|
|
||||||
async remove(fqdn: string) {
|
|
||||||
this.dialog
|
|
||||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
|
||||||
.pipe(filter(Boolean))
|
|
||||||
.subscribe(async () => {
|
|
||||||
const loader = this.loader.open('Removing').subscribe()
|
|
||||||
const params = { fqdn }
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (this.interface.packageId()) {
|
|
||||||
await this.api.pkgRemoveDomain({
|
|
||||||
...params,
|
|
||||||
package: this.interface.packageId(),
|
|
||||||
host: this.interface.value().addressInfo.hostId,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await this.api.osUiRemoveDomain(params)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errorService.handleError(e)
|
|
||||||
return false
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
inject,
|
||||||
|
input,
|
||||||
|
} from '@angular/core'
|
||||||
|
import {
|
||||||
|
DialogService,
|
||||||
|
ErrorService,
|
||||||
|
i18nPipe,
|
||||||
|
LoadingService,
|
||||||
|
} from '@start9labs/shared'
|
||||||
|
import {
|
||||||
|
TuiButton,
|
||||||
|
TuiDataList,
|
||||||
|
TuiDropdown,
|
||||||
|
TuiTextfield,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
|
import { TuiBadge } from '@taiga-ui/kit'
|
||||||
|
import { filter } from 'rxjs'
|
||||||
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
|
|
||||||
|
import { InterfaceComponent } from './interface.component'
|
||||||
|
import { ClearnetDomain } from './interface.utils'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tr[domain]',
|
||||||
|
template: `
|
||||||
|
<td>{{ domain().fqdn }}</td>
|
||||||
|
<td>{{ domain().authority || '-' }}</td>
|
||||||
|
<td>
|
||||||
|
@if (domain().public) {
|
||||||
|
<tui-badge size="s" appearance="primary-success">
|
||||||
|
{{ 'Public' | i18n }}
|
||||||
|
</tui-badge>
|
||||||
|
} @else {
|
||||||
|
<tui-badge size="s" appearance="primary-destructive">
|
||||||
|
{{ 'Private' | i18n }}
|
||||||
|
</tui-badge>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
tuiDropdown
|
||||||
|
size="s"
|
||||||
|
appearance="flat-grayscale"
|
||||||
|
iconStart="@tui.ellipsis-vertical"
|
||||||
|
[tuiAppearanceState]="open ? 'hover' : null"
|
||||||
|
[(tuiDropdownOpen)]="open"
|
||||||
|
>
|
||||||
|
{{ 'More' | i18n }}
|
||||||
|
<tui-data-list *tuiTextfieldDropdown>
|
||||||
|
<tui-opt-group>
|
||||||
|
<button
|
||||||
|
tuiOption
|
||||||
|
new
|
||||||
|
[iconStart]="domain().public ? '@tui.eye-off' : '@tui.eye'"
|
||||||
|
(click)="toggle()"
|
||||||
|
>
|
||||||
|
@if (domain().public) {
|
||||||
|
{{ 'Make private' | i18n }}
|
||||||
|
} @else {
|
||||||
|
{{ 'Make public' | i18n }}
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
<button tuiOption new iconStart="@tui.pencil" (click)="edit()">
|
||||||
|
{{ 'Edit' | i18n }}
|
||||||
|
</button>
|
||||||
|
</tui-opt-group>
|
||||||
|
<tui-opt-group>
|
||||||
|
<button
|
||||||
|
tuiOption
|
||||||
|
new
|
||||||
|
iconStart="@tui.trash"
|
||||||
|
class="g-negative"
|
||||||
|
(click)="remove()"
|
||||||
|
>
|
||||||
|
{{ 'Delete' | i18n }}
|
||||||
|
</button>
|
||||||
|
</tui-opt-group>
|
||||||
|
</tui-data-list>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
:host {
|
||||||
|
grid-template-columns: min-content 1fr min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:nth-child(2) {
|
||||||
|
order: -1;
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:last-child {
|
||||||
|
grid-area: 1 / 3 / 3;
|
||||||
|
align-self: center;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(tui-root._mobile) {
|
||||||
|
tui-badge {
|
||||||
|
vertical-align: bottom;
|
||||||
|
margin-inline-start: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [
|
||||||
|
TuiButton,
|
||||||
|
TuiDataList,
|
||||||
|
TuiDropdown,
|
||||||
|
i18nPipe,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiBadge,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class DomainComponent {
|
||||||
|
private readonly dialog = inject(DialogService)
|
||||||
|
private readonly loader = inject(LoadingService)
|
||||||
|
private readonly errorService = inject(ErrorService)
|
||||||
|
private readonly api = inject(ApiService)
|
||||||
|
private readonly interface = inject(InterfaceComponent)
|
||||||
|
|
||||||
|
readonly domain = input.required<ClearnetDomain>()
|
||||||
|
|
||||||
|
open = false
|
||||||
|
|
||||||
|
toggle() {}
|
||||||
|
|
||||||
|
edit() {}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
this.dialog
|
||||||
|
.openConfirm({ label: 'Are you sure?', size: 's' })
|
||||||
|
.pipe(filter(Boolean))
|
||||||
|
.subscribe(async () => {
|
||||||
|
const loader = this.loader.open('Removing').subscribe()
|
||||||
|
const params = { fqdn: this.domain().fqdn }
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.interface.packageId()) {
|
||||||
|
await this.api.pkgRemoveDomain({
|
||||||
|
...params,
|
||||||
|
package: this.interface.packageId(),
|
||||||
|
host: this.interface.value().addressInfo.hostId,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await this.api.osUiRemoveDomain(params)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
loader.unsubscribe()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,45 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||||
|
import { TuiTitle } from '@taiga-ui/core'
|
||||||
import { TuiSwitch } from '@taiga-ui/kit'
|
import { TuiSwitch } from '@taiga-ui/kit'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
import { i18nPipe } from '@start9labs/shared'
|
||||||
|
import { TuiCell } from '@taiga-ui/layout'
|
||||||
|
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'section[gateways]',
|
selector: 'section[gateways]',
|
||||||
template: `
|
template: `
|
||||||
<header>{{ 'Gateways' | i18n }}</header>
|
<header>{{ 'Gateways' | i18n }}</header>
|
||||||
<ul>
|
@for (gateway of gateways(); track $index) {
|
||||||
@for (gateway of gateways(); track $index) {
|
<label tuiCell="s">
|
||||||
<li>
|
<span tuiTitle>{{ gateway.name }}</span>
|
||||||
{{ gateway.name }}
|
<input
|
||||||
<input
|
type="checkbox"
|
||||||
type="checkbox"
|
tuiSwitch
|
||||||
tuiSwitch
|
size="s"
|
||||||
[style.margin-inline-start]="'auto'"
|
[showIcons]="false"
|
||||||
[showIcons]="false"
|
[ngModel]="gateway.enabled"
|
||||||
[ngModel]="gateway.enabled"
|
(ngModelChange)="onToggle(gateway)"
|
||||||
(ngModelChange)="onToggle(gateway)"
|
/>
|
||||||
/>
|
</label>
|
||||||
</li>
|
} @empty {
|
||||||
}
|
<app-placeholder icon="@tui.door-closed-locked">
|
||||||
<ul></ul>
|
No gateways
|
||||||
</ul>
|
</app-placeholder>
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
|
host: { class: 'g-card' },
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [CommonModule, FormsModule, TuiSwitch, i18nPipe],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
TuiSwitch,
|
||||||
|
i18nPipe,
|
||||||
|
TuiCell,
|
||||||
|
TuiTitle,
|
||||||
|
PlaceholderComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class InterfaceGatewaysComponent {
|
export class InterfaceGatewaysComponent {
|
||||||
readonly gateways = input.required<any>()
|
readonly gateways = input.required<any>()
|
||||||
|
|||||||
@@ -9,17 +9,14 @@ import { InterfaceAddressesComponent } from './addresses/addresses.component'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'service-interface',
|
selector: 'service-interface',
|
||||||
template: `
|
template: `
|
||||||
<section class="g-card" [gateways]="value().gateways"></section>
|
<!-- @TODO Alex / Matt translation in all nested components -->
|
||||||
<section class="g-card" [torDomains]="value().torDomains"></section>
|
<div [style.display]="'grid'">
|
||||||
<section
|
<section [gateways]="value().gateways"></section>
|
||||||
class="g-card"
|
<section [torDomains]="value().torDomains"></section>
|
||||||
[clearnetDomains]="value().clearnetDomains"
|
<section [clearnetDomains]="value().clearnetDomains"></section>
|
||||||
></section>
|
</div>
|
||||||
<section
|
<hr [style.width.rem]="10" />
|
||||||
class="g-card"
|
<section [addresses]="value().addresses" [isRunning]="true"></section>
|
||||||
[addresses]="value().addresses"
|
|
||||||
[isRunning]="true"
|
|
||||||
></section>
|
|
||||||
`,
|
`,
|
||||||
styles: `
|
styles: `
|
||||||
:host {
|
:host {
|
||||||
@@ -29,10 +26,14 @@ import { InterfaceAddressesComponent } from './addresses/addresses.component'
|
|||||||
color: var(--tui-text-secondary);
|
color: var(--tui-text-secondary);
|
||||||
font: var(--tui-font-text-l);
|
font: var(--tui-font-text-l);
|
||||||
|
|
||||||
::ng-deep td {
|
div {
|
||||||
overflow-wrap: anywhere;
|
gap: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host-context(tui-root._mobile) section {
|
||||||
|
grid-column: span 1;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
providers: [tuiButtonOptionsProvider({ size: 'xs' })],
|
providers: [tuiButtonOptionsProvider({ size: 'xs' })],
|
||||||
|
|||||||
@@ -12,19 +12,20 @@ import {
|
|||||||
LoadingService,
|
LoadingService,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { ISB, utils } from '@start9labs/start-sdk'
|
import { ISB, utils } from '@start9labs/start-sdk'
|
||||||
import { TuiAppearance, TuiButton, TuiLink } from '@taiga-ui/core'
|
import { TuiButton, TuiTitle } from '@taiga-ui/core'
|
||||||
|
import { TuiCell } from '@taiga-ui/layout'
|
||||||
import { filter } from 'rxjs'
|
import { filter } from 'rxjs'
|
||||||
import {
|
import {
|
||||||
FormComponent,
|
FormComponent,
|
||||||
FormContext,
|
FormContext,
|
||||||
} from 'src/app/routes/portal/components/form.component'
|
} from 'src/app/routes/portal/components/form.component'
|
||||||
import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component'
|
|
||||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||||
|
|
||||||
|
import { InterfaceComponent } from './interface.component'
|
||||||
|
|
||||||
type OnionForm = {
|
type OnionForm = {
|
||||||
key: string
|
key: string
|
||||||
}
|
}
|
||||||
@@ -33,16 +34,16 @@ type OnionForm = {
|
|||||||
selector: 'section[torDomains]',
|
selector: 'section[torDomains]',
|
||||||
template: `
|
template: `
|
||||||
<header>
|
<header>
|
||||||
<!-- @TODO translation -->
|
|
||||||
Tor Domains
|
Tor Domains
|
||||||
<a
|
<a
|
||||||
tuiLink
|
tuiIconButton
|
||||||
docsLink
|
docsLink
|
||||||
path="/user-manual/connecting-remotely/tor.html"
|
path="/user-manual/connecting-remotely/tor.html"
|
||||||
appearance="action-grayscale"
|
appearance="icon"
|
||||||
iconEnd="@tui.external-link"
|
iconStart="@tui.external-link"
|
||||||
[pseudo]="true"
|
>
|
||||||
></a>
|
{{ 'Documentation' | i18n }}
|
||||||
|
</a>
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
iconStart="@tui.plus"
|
iconStart="@tui.plus"
|
||||||
@@ -52,35 +53,34 @@ type OnionForm = {
|
|||||||
{{ 'Add' | i18n }}
|
{{ 'Add' | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
@if (torDomains().length) {
|
@for (domain of torDomains(); track $index) {
|
||||||
<table [appTable]="['Domain', null]">
|
<div tuiCell="s">
|
||||||
@for (domain of torDomains(); track $index) {
|
<span tuiTitle>{{ domain }}</span>
|
||||||
<tr>
|
<button
|
||||||
<td>{{ domain }}</td>
|
tuiIconButton
|
||||||
<td>
|
iconStart="@tui.trash"
|
||||||
<button
|
appearance="action-destructive"
|
||||||
tuiIconButton
|
(click)="remove(domain)"
|
||||||
iconStart="@tui.trash"
|
>
|
||||||
appearance="action-destructive"
|
{{ 'Delete' | i18n }}
|
||||||
(click)="remove(domain)"
|
</button>
|
||||||
>
|
</div>
|
||||||
{{ 'Delete' | i18n }}
|
} @empty {
|
||||||
</button>
|
<app-placeholder icon="@tui.target">
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</table>
|
|
||||||
} @else {
|
|
||||||
<app-placeholder icon="@tui.app-window">
|
|
||||||
{{ 'No Tor domains' | i18n }}
|
{{ 'No Tor domains' | i18n }}
|
||||||
</app-placeholder>
|
</app-placeholder>
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
styles: `
|
||||||
|
:host {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
host: { class: 'g-card' },
|
||||||
imports: [
|
imports: [
|
||||||
|
TuiCell,
|
||||||
|
TuiTitle,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiLink,
|
|
||||||
TuiAppearance,
|
|
||||||
TableComponent,
|
|
||||||
PlaceholderComponent,
|
PlaceholderComponent,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
DocsLinkDirective,
|
DocsLinkDirective,
|
||||||
@@ -98,13 +98,13 @@ export class InterfaceTorDomainsComponent {
|
|||||||
|
|
||||||
readonly torDomains = input.required<readonly string[]>()
|
readonly torDomains = input.required<readonly string[]>()
|
||||||
|
|
||||||
async remove(domain: string) {
|
async remove(onion: string) {
|
||||||
this.dialog
|
this.dialog
|
||||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
.openConfirm({ label: 'Are you sure?', size: 's' })
|
||||||
.pipe(filter(Boolean))
|
.pipe(filter(Boolean))
|
||||||
.subscribe(async () => {
|
.subscribe(async () => {
|
||||||
const loader = this.loader.open('Removing').subscribe()
|
const loader = this.loader.open('Removing').subscribe()
|
||||||
const params = { onion: domain }
|
const params = { onion }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.interface.packageId()) {
|
if (this.interface.packageId()) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import { Authority, AuthorityService } from './authority.service'
|
|||||||
[(tuiDropdownOpen)]="open"
|
[(tuiDropdownOpen)]="open"
|
||||||
>
|
>
|
||||||
{{ 'More' | i18n }}
|
{{ 'More' | i18n }}
|
||||||
<tui-data-list size="s" *tuiTextfieldDropdown>
|
<tui-data-list *tuiTextfieldDropdown>
|
||||||
@if (authority.url) {
|
@if (authority.url) {
|
||||||
<tui-opt-group>
|
<tui-opt-group>
|
||||||
<button
|
<button
|
||||||
@@ -82,11 +82,6 @@ import { Authority, AuthorityService } from './authority.service'
|
|||||||
:host-context(tui-root._mobile) {
|
:host-context(tui-root._mobile) {
|
||||||
grid-template-columns: 1fr min-content;
|
grid-template-columns: 1fr min-content;
|
||||||
|
|
||||||
td:first-child {
|
|
||||||
font: var(--tui-font-text-m);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { DomainService, MappedDomain } from './domain.service'
|
|||||||
[(tuiDropdownOpen)]="open"
|
[(tuiDropdownOpen)]="open"
|
||||||
>
|
>
|
||||||
{{ 'More' | i18n }}
|
{{ 'More' | i18n }}
|
||||||
<tui-data-list size="s" *tuiTextfieldDropdown>
|
<tui-data-list *tuiTextfieldDropdown>
|
||||||
<tui-opt-group>
|
<tui-opt-group>
|
||||||
<button
|
<button
|
||||||
tuiOption
|
tuiOption
|
||||||
@@ -74,11 +74,6 @@ import { DomainService, MappedDomain } from './domain.service'
|
|||||||
|
|
||||||
:host-context(tui-root._mobile) {
|
:host-context(tui-root._mobile) {
|
||||||
grid-template-columns: 1fr min-content;
|
grid-template-columns: 1fr min-content;
|
||||||
|
|
||||||
td:first-child {
|
|
||||||
font: var(--tui-font-text-m);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
|
import { RouterLink } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
DocsLinkDirective,
|
DocsLinkDirective,
|
||||||
ErrorService,
|
ErrorService,
|
||||||
@@ -55,11 +56,12 @@ import { GatewayPlus } from './item.component'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
RouterLink,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
|
TuiLink,
|
||||||
GatewaysTableComponent,
|
GatewaysTableComponent,
|
||||||
TitleDirective,
|
TitleDirective,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
TuiLink,
|
|
||||||
DocsLinkDirective,
|
DocsLinkDirective,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export type GatewayPlus = T.NetworkInterfaceInfo & {
|
|||||||
[(tuiDropdownOpen)]="open"
|
[(tuiDropdownOpen)]="open"
|
||||||
>
|
>
|
||||||
{{ 'More' | i18n }}
|
{{ 'More' | i18n }}
|
||||||
<tui-data-list size="s" *tuiTextfieldDropdown>
|
<tui-data-list *tuiTextfieldDropdown>
|
||||||
<tui-opt-group>
|
<tui-opt-group>
|
||||||
<button tuiOption new iconStart="@tui.pencil" (click)="rename()">
|
<button tuiOption new iconStart="@tui.pencil" (click)="rename()">
|
||||||
{{ 'Rename' | i18n }}
|
{{ 'Rename' | i18n }}
|
||||||
@@ -93,11 +93,6 @@ export type GatewayPlus = T.NetworkInterfaceInfo & {
|
|||||||
:host-context(tui-root._mobile) {
|
:host-context(tui-root._mobile) {
|
||||||
grid-template-columns: min-content 1fr min-content;
|
grid-template-columns: min-content 1fr min-content;
|
||||||
|
|
||||||
td:first-child {
|
|
||||||
font: var(--tui-font-text-m);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type {
|
.type {
|
||||||
order: -1;
|
order: -1;
|
||||||
|
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ hr {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
font: var(--tui-font-text-m);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--tui-text-primary);
|
color: var(--tui-text-primary);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user