diff --git a/web/projects/shared/src/directives/docs-link.directive.ts b/web/projects/shared/src/directives/docs-link.directive.ts index 2e2610954..b46551ea1 100644 --- a/web/projects/shared/src/directives/docs-link.directive.ts +++ b/web/projects/shared/src/directives/docs-link.directive.ts @@ -16,7 +16,7 @@ export const VERSION = new InjectionToken('VERSION') host: { target: '_blank', rel: 'noreferrer', - '[href]': 'url()', + '[attr.href]': 'url()', }, }) export class DocsLinkDirective { diff --git a/web/projects/shared/src/i18n/dictionaries/de.ts b/web/projects/shared/src/i18n/dictionaries/de.ts index 8f01bf86e..b4f265cb5 100644 --- a/web/projects/shared/src/i18n/dictionaries/de.ts +++ b/web/projects/shared/src/i18n/dictionaries/de.ts @@ -658,8 +658,6 @@ export default { 721: 'Gateway für ausgehenden Datenverkehr auswählen', 722: 'Der Typ des Gateways', 723: 'Nur ausgehend', - 724: 'Als Standard für ausgehenden Verkehr festlegen', - 725: 'Gesamten ausgehenden Datenverkehr über dieses Gateway leiten', 726: 'WireGuard-Konfigurationsdatei', 727: 'Eingehend/Ausgehend', 728: 'StartTunnel (Eingehend/Ausgehend)', @@ -668,7 +666,6 @@ export default { 731: 'Öffentliche Domain', 732: 'Private Domain', 733: 'Ausblenden', - 734: 'Standard ausgehend', 735: 'Zertifikat', 736: 'Selbstsigniert', 737: 'Portweiterleitung', @@ -710,4 +707,7 @@ export default { 781: 'Lokal', 782: 'Unbekanntes Laufwerk', 783: 'Muss eine gültige E-Mail-Adresse sein', + 786: 'Automatisch', + 787: 'Ausgehender Datenverkehr', + 788: 'Gateway verwenden', } satisfies i18n diff --git a/web/projects/shared/src/i18n/dictionaries/en.ts b/web/projects/shared/src/i18n/dictionaries/en.ts index a02636381..1b110f7cc 100644 --- a/web/projects/shared/src/i18n/dictionaries/en.ts +++ b/web/projects/shared/src/i18n/dictionaries/en.ts @@ -658,8 +658,6 @@ export const ENGLISH: Record = { 'Select the gateway for outbound traffic': 721, 'The type of gateway': 722, 'Outbound Only': 723, - 'Set as default outbound': 724, - 'Route all outbound traffic through this gateway': 725, 'WireGuard Config File': 726, 'Inbound/Outbound': 727, 'StartTunnel (Inbound/Outbound)': 728, @@ -668,7 +666,6 @@ export const ENGLISH: Record = { 'Public Domain': 731, 'Private Domain': 732, 'Hide': 733, - 'default outbound': 734, 'Certificate': 735, 'Self signed': 736, 'Port Forwarding': 737, @@ -710,4 +707,7 @@ export const ENGLISH: Record = { 'Local': 781, // as in, locally accessible 'Unknown Drive': 782, 'Must be a valid email address': 783, + 'Auto': 786, + 'Outbound Traffic': 787, + 'Use gateway': 788, } diff --git a/web/projects/shared/src/i18n/dictionaries/es.ts b/web/projects/shared/src/i18n/dictionaries/es.ts index 749c9bb1a..bd6c3944a 100644 --- a/web/projects/shared/src/i18n/dictionaries/es.ts +++ b/web/projects/shared/src/i18n/dictionaries/es.ts @@ -658,8 +658,6 @@ export default { 721: 'Selecciona la puerta de enlace para el tráfico saliente', 722: 'El tipo de puerta de enlace', 723: 'Solo saliente', - 724: 'Establecer como saliente predeterminado', - 725: 'Enrutar todo el tráfico saliente a través de esta puerta de enlace', 726: 'Archivo de configuración WireGuard', 727: 'Entrante/Saliente', 728: 'StartTunnel (Entrante/Saliente)', @@ -668,7 +666,6 @@ export default { 731: 'Dominio público', 732: 'Dominio privado', 733: 'Ocultar', - 734: 'saliente predeterminado', 735: 'Certificado', 736: 'Autofirmado', 737: 'Reenvío de puertos', @@ -710,4 +707,7 @@ export default { 781: 'Local', 782: 'Unidad desconocida', 783: 'Debe ser una dirección de correo electrónico válida', + 786: 'Automático', + 787: 'Tráfico saliente', + 788: 'Usar gateway', } satisfies i18n diff --git a/web/projects/shared/src/i18n/dictionaries/fr.ts b/web/projects/shared/src/i18n/dictionaries/fr.ts index 31b957bbf..b5c72b984 100644 --- a/web/projects/shared/src/i18n/dictionaries/fr.ts +++ b/web/projects/shared/src/i18n/dictionaries/fr.ts @@ -658,8 +658,6 @@ export default { 721: 'Sélectionnez la passerelle pour le trafic sortant', 722: 'Le type de passerelle', 723: 'Sortant uniquement', - 724: 'Définir comme sortant par défaut', - 725: 'Acheminer tout le trafic sortant via cette passerelle', 726: 'Fichier de configuration WireGuard', 727: 'Entrant/Sortant', 728: 'StartTunnel (Entrant/Sortant)', @@ -668,7 +666,6 @@ export default { 731: 'Domaine public', 732: 'Domaine privé', 733: 'Masquer', - 734: 'sortant par défaut', 735: 'Certificat', 736: 'Auto-signé', 737: 'Redirection de ports', @@ -710,4 +707,7 @@ export default { 781: 'Local', 782: 'Lecteur inconnu', 783: 'Doit être une adresse e-mail valide', + 786: 'Automatique', + 787: 'Trafic sortant', + 788: 'Utiliser la passerelle', } satisfies i18n diff --git a/web/projects/shared/src/i18n/dictionaries/pl.ts b/web/projects/shared/src/i18n/dictionaries/pl.ts index f85349560..78df10d9d 100644 --- a/web/projects/shared/src/i18n/dictionaries/pl.ts +++ b/web/projects/shared/src/i18n/dictionaries/pl.ts @@ -658,8 +658,6 @@ export default { 721: 'Wybierz bramę dla ruchu wychodzącego', 722: 'Typ bramy', 723: 'Tylko wychodzący', - 724: 'Ustaw jako domyślne wychodzące', - 725: 'Kieruj cały ruch wychodzący przez tę bramę', 726: 'Plik konfiguracyjny WireGuard', 727: 'Przychodzący/Wychodzący', 728: 'StartTunnel (Przychodzący/Wychodzący)', @@ -668,7 +666,6 @@ export default { 731: 'Domena publiczna', 732: 'Domena prywatna', 733: 'Ukryj', - 734: 'domyślne wychodzące', 735: 'Certyfikat', 736: 'Samopodpisany', 737: 'Przekierowanie portów', @@ -710,4 +707,7 @@ export default { 781: 'Lokalny', 782: 'Nieznany dysk', 783: 'Musi być prawidłowy adres e-mail', + 786: 'Automatycznie', + 787: 'Ruch wychodzący', + 788: 'Użyj bramy', } satisfies i18n diff --git a/web/projects/ui/src/app/routes/portal/components/header/menu.component.ts b/web/projects/ui/src/app/routes/portal/components/header/menu.component.ts index d21e62e3d..55f5bc354 100644 --- a/web/projects/ui/src/app/routes/portal/components/header/menu.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/header/menu.component.ts @@ -45,7 +45,7 @@ import { ABOUT } from './about.component' } - @@ -53,13 +53,15 @@ import { ABOUT } from './about.component' {{ 'User manual' | i18n }} @@ -67,6 +69,7 @@ import { ABOUT } from './about.component' @@ -76,6 +79,7 @@ import { ABOUT } from './about.component' - diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authorities.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authorities.component.ts index 95e37e4f3..330849f52 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authorities.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authorities.component.ts @@ -23,7 +23,7 @@ import { AuthoritiesTableComponent } from './table.component' docsLink path="/start-os/user-manual/trust-ca.html" appearance="icon" - iconStart="@tui.external-link" + iconStart="@tui.book-open-text" > {{ 'Documentation' | i18n }} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/dns/dns.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/dns/dns.component.ts index f8cbcfa09..59b65b58c 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/dns/dns.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/dns/dns.component.ts @@ -49,7 +49,7 @@ const ipv6 = docsLink path="/start-os/user-manual/dns.html" appearance="icon" - iconStart="@tui.external-link" + iconStart="@tui.book-open-text" > {{ 'Documentation' | i18n }} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/email/email.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/email/email.component.ts index 0340ae2b7..3cf1b0ca0 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/email/email.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/email/email.component.ts @@ -57,7 +57,7 @@ function detectProviderKey(host: string | undefined): string { docsLink path="/start-os/user-manual/smtp.html" appearance="icon" - iconStart="@tui.external-link" + iconStart="@tui.book-open-text" > {{ 'Documentation' | i18n }} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/gateways.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/gateways.component.ts index 3bd243957..c2371202f 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/gateways.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/gateways.component.ts @@ -1,5 +1,12 @@ import { CommonModule } from '@angular/common' -import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + linkedSignal, +} from '@angular/core' +import { FormsModule } from '@angular/forms' import { RouterLink } from '@angular/router' import { DocsLinkDirective, @@ -7,14 +14,18 @@ import { i18nPipe, LoadingService, } from '@start9labs/shared' -import { TuiButton } from '@taiga-ui/core' -import { FormComponent } from 'src/app/routes/portal/components/form.component' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { GatewaysTableComponent } from './table.component' -import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' -import { TitleDirective } from 'src/app/services/title.service' import { ISB } from '@start9labs/start-sdk' +import { TUI_IS_MOBILE } from '@taiga-ui/cdk' +import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core' +import { TuiChevron, TuiDataListWrapper, TuiSelect } from '@taiga-ui/kit' +import { TuiHeader } from '@taiga-ui/layout' +import { FormComponent } from 'src/app/routes/portal/components/form.component' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { FormDialogService } from 'src/app/services/form-dialog.service' +import { GatewayService } from 'src/app/services/gateway.service' +import { TitleDirective } from 'src/app/services/title.service' +import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' +import { GatewaysTableComponent } from './table.component' @Component({ template: ` @@ -34,7 +45,7 @@ import { ISB } from '@start9labs/start-sdk' docsLink path="/start-os/user-manual/gateways.html" appearance="icon" - iconStart="@tui.external-link" + iconStart="@tui.book-open-text" > {{ 'Documentation' | i18n }} @@ -50,12 +61,99 @@ import { ISB } from '@start9labs/start-sdk' + + @if (outboundOptions(); as options) { +
+
+

+ + {{ 'Outbound Traffic' | i18n }} + + {{ 'Documentation' | i18n }} + + +

+
+ + + @if (mobile) { + + } @else { + + } + @if (!mobile) { + + } + +
+ +
+
+ } + `, + styles: ` + .outbound { + max-width: 24rem; + margin-top: 2rem; + } + + .outbound header { + margin-bottom: 1rem; + } + + .outbound footer { + display: flex; + justify-content: flex-end; + margin-top: 1rem; + } `, changeDetection: ChangeDetectionStrategy.OnPush, + providers: [GatewayService], imports: [ CommonModule, + FormsModule, RouterLink, TuiButton, + TuiTextfield, + TuiTitle, + TuiChevron, + TuiSelect, + TuiDataListWrapper, + TuiHeader, GatewaysTableComponent, TitleDirective, i18nPipe, @@ -68,6 +166,50 @@ export default class GatewaysComponent { private readonly api = inject(ApiService) private readonly formDialog = inject(FormDialogService) private readonly i18n = inject(i18nPipe) + readonly gatewayService = inject(GatewayService) + readonly mobile = inject(TUI_IS_MOBILE) + + private readonly autoOption = { + id: null, + name: this.i18n.transform('Auto') ?? 'Auto', + } + + readonly outboundOptions = computed(() => { + const gateways = this.gatewayService.gateways() + if (!gateways) return null + return [ + this.autoOption, + ...gateways.map(g => ({ id: g.id as string | null, name: g.name })), + ] + }) + + readonly selectedOutbound = linkedSignal(() => { + const options = this.outboundOptions() + const defaultId = this.gatewayService.defaultOutbound() ?? null + if (options) { + return options.find(o => o.id === defaultId) ?? options[0] + } + return this.autoOption + }) + + readonly stringifyOutbound = (opt: { id: string | null; name: string }) => + opt.name + + async saveOutbound() { + const loader = this.loader.open('Saving').subscribe() + + console.log('outbound', this.selectedOutbound()) + + try { + await this.api.setDefaultOutbound({ + gateway: this.selectedOutbound()?.id ?? null, + }) + } catch (e: any) { + this.errorService.handleError(e) + } finally { + loader.unsubscribe() + } + } async add() { const spec = ISB.InputSpec.of({ @@ -108,13 +250,6 @@ export default class GatewaysComponent { }, }), }), - setAsDefaultOutbound: ISB.Value.toggle({ - name: this.i18n.transform('Set as default outbound'), - description: this.i18n.transform( - 'Route all outbound traffic through this gateway', - ), - default: false, - }), }) this.formDialog.open(FormComponent, { @@ -135,7 +270,7 @@ export default class GatewaysComponent { ? input.config.value.file : await (input.config.value.file as any as File).text(), type: null, // @TODO Aiden why is attr here? - setAsDefaultOutbound: input.setAsDefaultOutbound, + setAsDefaultOutbound: false, }) return true } catch (e: any) { diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/item.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/item.component.ts index 75c806d90..0bc6f0648 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/item.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/item.component.ts @@ -23,9 +23,8 @@ import { filter } from 'rxjs' import { FormComponent } from 'src/app/routes/portal/components/form.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 { GatewayPlus } from 'src/app/services/gateway.service' -import { TuiBadge } from '@taiga-ui/kit' +import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec' import { PORT_FORWARDS_MODAL } from './port-forwards.component' @Component({ @@ -45,11 +44,6 @@ import { PORT_FORWARDS_MODAL } from './port-forwards.component' } } {{ gateway.name }} - @if (gateway.isDefaultOutbound) { - - {{ 'default outbound' | i18n }} - - } @if (gateway.type === 'outbound-only') { @@ -91,13 +85,6 @@ import { PORT_FORWARDS_MODAL } from './port-forwards.component' } - @if (!gateway.isDefaultOutbound) { - - - - } @if (gateway.ipInfo.deviceType === 'wireguard') {