From e999d89bbc0cf45ad595e532a34e5adfeafb4e2a Mon Sep 17 00:00:00 2001
From: Matt Hill
Date: Tue, 3 Mar 2026 19:04:20 -0700
Subject: [PATCH] multiple bugs and better port forward ux
---
.../shared/src/i18n/dictionaries/de.ts | 3 ++
.../shared/src/i18n/dictionaries/en.ts | 3 ++
.../shared/src/i18n/dictionaries/es.ts | 3 ++
.../shared/src/i18n/dictionaries/fr.ts | 3 ++
.../shared/src/i18n/dictionaries/pl.ts | 3 ++
.../interfaces/addresses/actions.component.ts | 3 ++
.../addresses/addresses.component.ts | 28 ++++++----
.../interfaces/addresses/dns.component.ts | 37 ++++++++------
.../addresses/domain-health.service.ts | 51 ++++++++++++-------
.../interfaces/addresses/item.component.ts | 7 ++-
.../addresses/port-forward.component.ts | 45 +++++++++-------
.../components/port-check-icon.component.ts | 37 ++++++++++++++
.../port-check-warnings.component.ts | 38 ++++++++++++++
.../services/components/task.component.ts | 39 ++++++++++++--
.../services/routes/actions.component.ts | 28 ++--------
.../gateways/port-forwards.component.ts | 32 +++++-------
.../services/api/embassy-mock-api.service.ts | 24 +++++----
.../services/pkg-status-rendering.service.ts | 24 +++++++++
18 files changed, 290 insertions(+), 118 deletions(-)
create mode 100644 web/projects/ui/src/app/routes/portal/components/port-check-icon.component.ts
create mode 100644 web/projects/ui/src/app/routes/portal/components/port-check-warnings.component.ts
diff --git a/web/projects/shared/src/i18n/dictionaries/de.ts b/web/projects/shared/src/i18n/dictionaries/de.ts
index 11b4dab9a..7926e9644 100644
--- a/web/projects/shared/src/i18n/dictionaries/de.ts
+++ b/web/projects/shared/src/i18n/dictionaries/de.ts
@@ -701,4 +701,7 @@ export default {
771: 'Spiel vorbei',
772: 'Beliebige Taste drücken oder tippen zum Starten',
773: 'Beliebige Taste drücken oder tippen zum Neustarten',
+ 774: 'Der Portstatus kann nicht ermittelt werden, solange der Dienst nicht läuft',
+ 775: 'Diese Adresse funktioniert nicht aus Ihrem lokalen Netzwerk aufgrund einer Router-Hairpinning-Einschränkung',
+ 776: 'Aktion nicht gefunden',
} satisfies i18n
diff --git a/web/projects/shared/src/i18n/dictionaries/en.ts b/web/projects/shared/src/i18n/dictionaries/en.ts
index 399299c36..6b8e5e178 100644
--- a/web/projects/shared/src/i18n/dictionaries/en.ts
+++ b/web/projects/shared/src/i18n/dictionaries/en.ts
@@ -701,4 +701,7 @@ export const ENGLISH: Record = {
'Game Over': 771,
'Press any key or tap to start': 772,
'Press any key or tap to play again': 773,
+ 'Port status cannot be determined while service is not running': 774,
+ 'This address will not work from your local network due to a router hairpinning limitation': 775,
+ 'Action not found': 776,
}
diff --git a/web/projects/shared/src/i18n/dictionaries/es.ts b/web/projects/shared/src/i18n/dictionaries/es.ts
index 1151b79a0..1acc39a5b 100644
--- a/web/projects/shared/src/i18n/dictionaries/es.ts
+++ b/web/projects/shared/src/i18n/dictionaries/es.ts
@@ -701,4 +701,7 @@ export default {
771: 'Fin del juego',
772: 'Pulsa cualquier tecla o toca para empezar',
773: 'Pulsa cualquier tecla o toca para jugar de nuevo',
+ 774: 'El estado del puerto no se puede determinar mientras el servicio no está en ejecución',
+ 775: 'Esta dirección no funcionará desde tu red local debido a una limitación de hairpinning del router',
+ 776: 'Acción no encontrada',
} satisfies i18n
diff --git a/web/projects/shared/src/i18n/dictionaries/fr.ts b/web/projects/shared/src/i18n/dictionaries/fr.ts
index eeab14084..d3e2eeb21 100644
--- a/web/projects/shared/src/i18n/dictionaries/fr.ts
+++ b/web/projects/shared/src/i18n/dictionaries/fr.ts
@@ -701,4 +701,7 @@ export default {
771: 'Partie terminée',
772: "Appuyez sur une touche ou touchez l'écran pour commencer",
773: "Appuyez sur une touche ou touchez l'écran pour rejouer",
+ 774: "L'état du port ne peut pas être déterminé tant que le service n'est pas en cours d'exécution",
+ 775: "Cette adresse ne fonctionnera pas depuis votre réseau local en raison d'une limitation de hairpinning du routeur",
+ 776: 'Action introuvable',
} satisfies i18n
diff --git a/web/projects/shared/src/i18n/dictionaries/pl.ts b/web/projects/shared/src/i18n/dictionaries/pl.ts
index 5d436b6e9..b616b4f79 100644
--- a/web/projects/shared/src/i18n/dictionaries/pl.ts
+++ b/web/projects/shared/src/i18n/dictionaries/pl.ts
@@ -701,4 +701,7 @@ export default {
771: 'Koniec gry',
772: 'Naciśnij dowolny klawisz lub dotknij, aby rozpocząć',
773: 'Naciśnij dowolny klawisz lub dotknij, aby zagrać ponownie',
+ 774: 'Status portu nie może być określony, gdy usługa nie jest uruchomiona',
+ 775: 'Ten adres nie będzie działać z Twojej sieci lokalnej z powodu ograniczenia hairpinning routera',
+ 776: 'Nie znaleziono akcji',
} satisfies i18n
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 47aae1b73..ed0272cc4 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
@@ -287,9 +287,12 @@ export class AddressActionsComponent {
}
showDnsValidation() {
+ const port = this.address().hostnameInfo.port
+ if (port === null) return
this.domainHealth.showPublicDomainSetup(
this.address().hostnameInfo.hostname,
this.gatewayId(),
+ port,
)
}
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 af720d69c..d314349b9 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
@@ -171,14 +171,18 @@ export class InterfaceAddressesComponent {
default: null,
patterns: [utils.Patterns.domain],
}).map(f => f.toLocaleLowerCase()),
- authority: ISB.Value.select({
- name: this.i18n.transform('Certificate Authority'),
- description: this.i18n.transform(
- 'Select a Certificate Authority to issue SSL/TLS certificates for this domain',
- ),
- values: authorities,
- default: Object.keys(network.acme)[0] || 'local',
- }),
+ ...(iface.addSsl
+ ? {
+ authority: ISB.Value.select({
+ name: this.i18n.transform('Certificate Authority'),
+ description: this.i18n.transform(
+ 'Select a Certificate Authority to issue SSL/TLS certificates for this domain',
+ ),
+ values: authorities,
+ default: Object.keys(network.acme)[0] || 'local',
+ }),
+ }
+ : {}),
})
this.formDialog.open(FormComponent, {
@@ -250,7 +254,13 @@ export class InterfaceAddressesComponent {
await this.api.osUiAddPublicDomain(params)
}
- await this.domainHealth.checkPublicDomain(fqdn, gatewayId)
+ const port = this.gatewayGroup().addresses.find(
+ a => a.access === 'public' && a.hostnameInfo.port !== null,
+ )?.hostnameInfo.port
+
+ if (port !== undefined && port !== null) {
+ await this.domainHealth.checkPublicDomain(fqdn, gatewayId, port)
+ }
return true
} catch (e: any) {
diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/dns.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/dns.component.ts
index 77d639a9f..d5715cc33 100644
--- a/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/dns.component.ts
+++ b/web/projects/ui/src/app/routes/portal/components/interfaces/addresses/dns.component.ts
@@ -14,6 +14,8 @@ import {
tuiSwitchOptionsProvider,
} from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
+import { PortCheckIconComponent } from 'src/app/routes/portal/components/port-check-icon.component'
+import { PortCheckWarningsComponent } from 'src/app/routes/portal/components/port-check-warnings.component'
import { TableComponent } from 'src/app/routes/portal/components/table.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { T } from '@start9labs/start-sdk'
@@ -28,7 +30,7 @@ export type DomainValidationData = {
fqdn: string
gateway: DnsGateway
port: number
- initialResults?: { dnsPass: boolean; portPass: boolean }
+ initialResults?: { dnsPass: boolean; portResult: T.CheckPortRes | null }
}
@Component({
@@ -93,18 +95,12 @@ export type DomainValidationData = {
{{ 'create this port forwarding rule' | i18n }}
+ @let portRes = portResult();
+
|
- @if (portLoading()) {
-
- } @else if (portPass() === true) {
-
- } @else if (portPass() === false) {
-
- } @else {
-
- }
+
|
{{ context.data.port }} |
{{ context.data.port }} |
@@ -121,6 +117,8 @@ export type DomainValidationData = {
+
+
@if (!isManualMode) {