diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/actions.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/actions.component.ts index 4cdee19c9..9baacfaab 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/actions.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/actions.component.ts @@ -12,16 +12,17 @@ import { tuiButtonOptionsProvider, TuiDataList, TuiDropdown, + TuiTextfield, } from '@taiga-ui/core' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { QRModal } from 'src/app/routes/portal/modals/qr.component' + import { InterfaceComponent } from '../interface.component' @Component({ selector: 'td[actions]', template: `
- @if (interface.value().type === 'ui') { - } - - - - - - + + + @if (interface.value().type === 'ui') { + + } + + +
`, styles: ` :host { text-align: right; - grid-area: 1 / 2 / 3 / 3; + grid-area: 1 / 2 / 4 / 3; place-content: center; 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' })], changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -125,6 +128,8 @@ export class AddressActionsComponent { readonly href = input.required() readonly disabled = input.required() + open = false + showQR() { this.dialog .openComponent(new PolymorpheusComponent(QRModal), { @@ -138,4 +143,6 @@ export class AddressActionsComponent { openUI() { this.document.defaultView?.open(this.href(), '_blank', 'noreferrer') } + + instructions() {} } diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts index 302362aea..bc717deff 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/addresses.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core' 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 { TableComponent } from 'src/app/routes/portal/components/table.component' import { MappedServiceInterface } from '../interface.utils' @@ -10,70 +11,95 @@ import { AddressActionsComponent } from './actions.component' selector: 'section[addresses]', template: `
{{ 'Addresses' | i18n }}
- @if (addresses().common.length) { -
-
{{ 'Common' | i18n }}
- - @for (address of addresses().common; track $index) { - - - - - - - - } -
- - {{ address.type }}{{ address.gateway }}{{ address.url }}
-
- } @else { - - {{ 'No addresses' | i18n }} - - } + + @for (address of addresses().common; track $index) { + + + + + + + + } @empty { + + + + } +
+ + {{ address.type }}{{ address.gateway }}{{ address.url }}
+ + {{ 'No addresses' | i18n }} + +
@if (addresses().uncommon.length) { -
-
{{ 'Uncommon' | i18n }}
- - @for (address of addresses().uncommon; track $index) { - - - - - - - - } -
- - {{ address.type }}{{ address.gateway }}{{ address.url }}
-
+ + + + + @for (address of addresses().uncommon; track $index) { + + + + + + + + } +
+ + {{ address.type }}{{ address.gateway }}{{ address.url }}
+
+
} `, + 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: [ TableComponent, PlaceholderComponent, i18nPipe, - TuiDropdown, - TuiDataList, AddressActionsComponent, TuiButton, + TuiAccordion, ], changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/clearnet-domains.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/clearnet-domains.component.ts index a511b963b..ea78dc59d 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/clearnet-domains.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/clearnet-domains.component.ts @@ -1,35 +1,10 @@ -import { - ChangeDetectionStrategy, - Component, - 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 { ChangeDetectionStrategy, Component, input } from '@angular/core' +import { DocsLinkDirective, i18nPipe } from '@start9labs/shared' +import { TuiButton } from '@taiga-ui/core' 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 { FormDialogService } from 'src/app/services/form-dialog.service' -import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' + +import { DomainComponent } from './domain.component' import { ClearnetDomain } from './interface.utils' @Component({ @@ -38,13 +13,14 @@ import { ClearnetDomain } from './interface.utils'
{{ 'Clearnet Domains' | i18n }} + appearance="icon" + iconStart="@tui.external-link" + > + {{ 'Documentation' | i18n }} +
- @if (clearnetDomains().length) { - - @for (domain of clearnetDomains(); track $index) { - - - - - - - } -
{{ domain.fqdn }}{{ domain.authority }}{{ domain.public ? 'public' : 'private' }} - - - - - - - - -
- } @else { - - {{ 'No clearnet domains' | i18n }} - + + @for (domain of clearnetDomains(); track $index) { + + } @empty { + + + + } +
+ + {{ 'No clearnet domains' | i18n }} + +
+ `, + styles: ` + :host { + grid-column: span 3; } `, + host: { class: 'g-card' }, imports: [ TuiButton, - TuiLink, - TuiAppearance, TableComponent, PlaceholderComponent, i18nPipe, DocsLinkDirective, - TuiDropdown, - TuiDataList, + DomainComponent, ], changeDetection: ChangeDetectionStrategy.OnPush, }) 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() open = false - // @TODO add, toggle, and change CA call same idempotent endpoint, either pkgAddDomain or osUiAddDomain - - 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() - } - }) - } + add() {} } diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/domain.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/domain.component.ts new file mode 100644 index 000000000..6967b29f4 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/domain.component.ts @@ -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: ` + {{ domain().fqdn }} + {{ domain().authority || '-' }} + + @if (domain().public) { + + {{ 'Public' | i18n }} + + } @else { + + {{ 'Private' | i18n }} + + } + + + + + + + + + + + + `, + 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() + + 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() + } + }) + } +} diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/gateways.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/gateways.component.ts index bc209a02f..bb2158b4f 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/gateways.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/gateways.component.ts @@ -1,32 +1,45 @@ import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component, input } from '@angular/core' +import { TuiTitle } from '@taiga-ui/core' import { TuiSwitch } from '@taiga-ui/kit' import { FormsModule } from '@angular/forms' import { i18nPipe } from '@start9labs/shared' +import { TuiCell } from '@taiga-ui/layout' +import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component' @Component({ selector: 'section[gateways]', template: `
{{ 'Gateways' | i18n }}
-
    - @for (gateway of gateways(); track $index) { -
  • - {{ gateway.name }} - -
  • - } -
      -
    + @for (gateway of gateways(); track $index) { + + } @empty { + + No gateways + + } `, + host: { class: 'g-card' }, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [CommonModule, FormsModule, TuiSwitch, i18nPipe], + imports: [ + CommonModule, + FormsModule, + TuiSwitch, + i18nPipe, + TuiCell, + TuiTitle, + PlaceholderComponent, + ], }) export class InterfaceGatewaysComponent { readonly gateways = input.required() diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts index 35d54a6d1..a62d01af4 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts @@ -9,17 +9,14 @@ import { InterfaceAddressesComponent } from './addresses/addresses.component' @Component({ selector: 'service-interface', template: ` -
    -
    -
    -
    + +
    +
    +
    +
    +
    +
    +
    `, styles: ` :host { @@ -29,10 +26,14 @@ import { InterfaceAddressesComponent } from './addresses/addresses.component' color: var(--tui-text-secondary); font: var(--tui-font-text-l); - ::ng-deep td { - overflow-wrap: anywhere; + div { + gap: inherit; } } + + :host-context(tui-root._mobile) section { + grid-column: span 1; + } `, changeDetection: ChangeDetectionStrategy.OnPush, providers: [tuiButtonOptionsProvider({ size: 'xs' })], diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/tor-domains.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/tor-domains.component.ts index dcaafbbb8..6834ca5fa 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/tor-domains.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/tor-domains.component.ts @@ -12,19 +12,20 @@ import { LoadingService, } from '@start9labs/shared' 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 { 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 { 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 { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' +import { InterfaceComponent } from './interface.component' + type OnionForm = { key: string } @@ -33,16 +34,16 @@ type OnionForm = { selector: 'section[torDomains]', template: `
    - Tor Domains + appearance="icon" + iconStart="@tui.external-link" + > + {{ 'Documentation' | i18n }} +
    - @if (torDomains().length) { - - @for (domain of torDomains(); track $index) { - - - - - } -
    {{ domain }} - -
    - } @else { - + @for (domain of torDomains(); track $index) { +
    + {{ domain }} + +
    + } @empty { + {{ 'No Tor domains' | i18n }} } `, + styles: ` + :host { + grid-column: span 2; + } + `, + host: { class: 'g-card' }, imports: [ + TuiCell, + TuiTitle, TuiButton, - TuiLink, - TuiAppearance, - TableComponent, PlaceholderComponent, i18nPipe, DocsLinkDirective, @@ -98,13 +98,13 @@ export class InterfaceTorDomainsComponent { readonly torDomains = input.required() - async remove(domain: string) { + async remove(onion: string) { this.dialog .openConfirm({ label: 'Are you sure?', size: 's' }) .pipe(filter(Boolean)) .subscribe(async () => { const loader = this.loader.open('Removing').subscribe() - const params = { onion: domain } + const params = { onion } try { if (this.interface.packageId()) { diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/item.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/item.component.ts index c3a8e2f01..5c52a05b5 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/item.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/item.component.ts @@ -31,7 +31,7 @@ import { Authority, AuthorityService } from './authority.service' [(tuiDropdownOpen)]="open" > {{ 'More' | i18n }} - + @if (authority.url) {