multiple bugs, better outbound gateway UX

This commit is contained in:
Matt Hill
2026-03-05 23:20:13 -07:00
parent 3901d38d65
commit 7693b0febc
18 changed files with 192 additions and 76 deletions

View File

@@ -16,7 +16,7 @@ export const VERSION = new InjectionToken<string>('VERSION')
host: { host: {
target: '_blank', target: '_blank',
rel: 'noreferrer', rel: 'noreferrer',
'[href]': 'url()', '[attr.href]': 'url()',
}, },
}) })
export class DocsLinkDirective { export class DocsLinkDirective {

View File

@@ -658,8 +658,6 @@ export default {
721: 'Gateway für ausgehenden Datenverkehr auswählen', 721: 'Gateway für ausgehenden Datenverkehr auswählen',
722: 'Der Typ des Gateways', 722: 'Der Typ des Gateways',
723: 'Nur ausgehend', 723: 'Nur ausgehend',
724: 'Als Standard für ausgehenden Verkehr festlegen',
725: 'Gesamten ausgehenden Datenverkehr über dieses Gateway leiten',
726: 'WireGuard-Konfigurationsdatei', 726: 'WireGuard-Konfigurationsdatei',
727: 'Eingehend/Ausgehend', 727: 'Eingehend/Ausgehend',
728: 'StartTunnel (Eingehend/Ausgehend)', 728: 'StartTunnel (Eingehend/Ausgehend)',
@@ -668,7 +666,6 @@ export default {
731: 'Öffentliche Domain', 731: 'Öffentliche Domain',
732: 'Private Domain', 732: 'Private Domain',
733: 'Ausblenden', 733: 'Ausblenden',
734: 'Standard ausgehend',
735: 'Zertifikat', 735: 'Zertifikat',
736: 'Selbstsigniert', 736: 'Selbstsigniert',
737: 'Portweiterleitung', 737: 'Portweiterleitung',
@@ -710,4 +707,7 @@ export default {
781: 'Lokal', 781: 'Lokal',
782: 'Unbekanntes Laufwerk', 782: 'Unbekanntes Laufwerk',
783: 'Muss eine gültige E-Mail-Adresse sein', 783: 'Muss eine gültige E-Mail-Adresse sein',
786: 'Automatisch',
787: 'Ausgehender Datenverkehr',
788: 'Gateway verwenden',
} satisfies i18n } satisfies i18n

View File

@@ -658,8 +658,6 @@ export const ENGLISH: Record<string, number> = {
'Select the gateway for outbound traffic': 721, 'Select the gateway for outbound traffic': 721,
'The type of gateway': 722, 'The type of gateway': 722,
'Outbound Only': 723, 'Outbound Only': 723,
'Set as default outbound': 724,
'Route all outbound traffic through this gateway': 725,
'WireGuard Config File': 726, 'WireGuard Config File': 726,
'Inbound/Outbound': 727, 'Inbound/Outbound': 727,
'StartTunnel (Inbound/Outbound)': 728, 'StartTunnel (Inbound/Outbound)': 728,
@@ -668,7 +666,6 @@ export const ENGLISH: Record<string, number> = {
'Public Domain': 731, 'Public Domain': 731,
'Private Domain': 732, 'Private Domain': 732,
'Hide': 733, 'Hide': 733,
'default outbound': 734,
'Certificate': 735, 'Certificate': 735,
'Self signed': 736, 'Self signed': 736,
'Port Forwarding': 737, 'Port Forwarding': 737,
@@ -710,4 +707,7 @@ export const ENGLISH: Record<string, number> = {
'Local': 781, // as in, locally accessible 'Local': 781, // as in, locally accessible
'Unknown Drive': 782, 'Unknown Drive': 782,
'Must be a valid email address': 783, 'Must be a valid email address': 783,
'Auto': 786,
'Outbound Traffic': 787,
'Use gateway': 788,
} }

View File

@@ -658,8 +658,6 @@ export default {
721: 'Selecciona la puerta de enlace para el tráfico saliente', 721: 'Selecciona la puerta de enlace para el tráfico saliente',
722: 'El tipo de puerta de enlace', 722: 'El tipo de puerta de enlace',
723: 'Solo saliente', 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', 726: 'Archivo de configuración WireGuard',
727: 'Entrante/Saliente', 727: 'Entrante/Saliente',
728: 'StartTunnel (Entrante/Saliente)', 728: 'StartTunnel (Entrante/Saliente)',
@@ -668,7 +666,6 @@ export default {
731: 'Dominio público', 731: 'Dominio público',
732: 'Dominio privado', 732: 'Dominio privado',
733: 'Ocultar', 733: 'Ocultar',
734: 'saliente predeterminado',
735: 'Certificado', 735: 'Certificado',
736: 'Autofirmado', 736: 'Autofirmado',
737: 'Reenvío de puertos', 737: 'Reenvío de puertos',
@@ -710,4 +707,7 @@ export default {
781: 'Local', 781: 'Local',
782: 'Unidad desconocida', 782: 'Unidad desconocida',
783: 'Debe ser una dirección de correo electrónico válida', 783: 'Debe ser una dirección de correo electrónico válida',
786: 'Automático',
787: 'Tráfico saliente',
788: 'Usar gateway',
} satisfies i18n } satisfies i18n

View File

@@ -658,8 +658,6 @@ export default {
721: 'Sélectionnez la passerelle pour le trafic sortant', 721: 'Sélectionnez la passerelle pour le trafic sortant',
722: 'Le type de passerelle', 722: 'Le type de passerelle',
723: 'Sortant uniquement', 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', 726: 'Fichier de configuration WireGuard',
727: 'Entrant/Sortant', 727: 'Entrant/Sortant',
728: 'StartTunnel (Entrant/Sortant)', 728: 'StartTunnel (Entrant/Sortant)',
@@ -668,7 +666,6 @@ export default {
731: 'Domaine public', 731: 'Domaine public',
732: 'Domaine privé', 732: 'Domaine privé',
733: 'Masquer', 733: 'Masquer',
734: 'sortant par défaut',
735: 'Certificat', 735: 'Certificat',
736: 'Auto-signé', 736: 'Auto-signé',
737: 'Redirection de ports', 737: 'Redirection de ports',
@@ -710,4 +707,7 @@ export default {
781: 'Local', 781: 'Local',
782: 'Lecteur inconnu', 782: 'Lecteur inconnu',
783: 'Doit être une adresse e-mail valide', 783: 'Doit être une adresse e-mail valide',
786: 'Automatique',
787: 'Trafic sortant',
788: 'Utiliser la passerelle',
} satisfies i18n } satisfies i18n

View File

@@ -658,8 +658,6 @@ export default {
721: 'Wybierz bramę dla ruchu wychodzącego', 721: 'Wybierz bramę dla ruchu wychodzącego',
722: 'Typ bramy', 722: 'Typ bramy',
723: 'Tylko wychodzący', 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', 726: 'Plik konfiguracyjny WireGuard',
727: 'Przychodzący/Wychodzący', 727: 'Przychodzący/Wychodzący',
728: 'StartTunnel (Przychodzący/Wychodzący)', 728: 'StartTunnel (Przychodzący/Wychodzący)',
@@ -668,7 +666,6 @@ export default {
731: 'Domena publiczna', 731: 'Domena publiczna',
732: 'Domena prywatna', 732: 'Domena prywatna',
733: 'Ukryj', 733: 'Ukryj',
734: 'domyślne wychodzące',
735: 'Certyfikat', 735: 'Certyfikat',
736: 'Samopodpisany', 736: 'Samopodpisany',
737: 'Przekierowanie portów', 737: 'Przekierowanie portów',
@@ -710,4 +707,7 @@ export default {
781: 'Lokalny', 781: 'Lokalny',
782: 'Nieznany dysk', 782: 'Nieznany dysk',
783: 'Musi być prawidłowy adres e-mail', 783: 'Musi być prawidłowy adres e-mail',
786: 'Automatycznie',
787: 'Ruch wychodzący',
788: 'Użyj bramy',
} satisfies i18n } satisfies i18n

View File

@@ -45,7 +45,7 @@ import { ABOUT } from './about.component'
} }
<tui-data-list [style.width.rem]="13"> <tui-data-list [style.width.rem]="13">
<tui-opt-group> <tui-opt-group>
<button tuiOption iconStart="@tui.info" (click)="about()"> <button tuiOption iconStart="@tui.info" new (click)="about()">
{{ 'About this server' | i18n }} {{ 'About this server' | i18n }}
</button> </button>
</tui-opt-group> </tui-opt-group>
@@ -53,13 +53,15 @@ import { ABOUT } from './about.component'
<a <a
tuiOption tuiOption
docsLink docsLink
iconStart="@tui.book-open" new
path="/start-os/user-manual/index.html" iconStart="@tui.book-open-text"
path="/start-os/user-manual"
> >
{{ 'User manual' | i18n }} {{ 'User manual' | i18n }}
</a> </a>
<a <a
tuiOption tuiOption
new
iconStart="@tui.headphones" iconStart="@tui.headphones"
href="https://start9.com/contact" href="https://start9.com/contact"
> >
@@ -67,6 +69,7 @@ import { ABOUT } from './about.component'
</a> </a>
<a <a
tuiOption tuiOption
new
iconStart="@tui.dollar-sign" iconStart="@tui.dollar-sign"
href="https://donate.start9.com" href="https://donate.start9.com"
> >
@@ -76,6 +79,7 @@ import { ABOUT } from './about.component'
<tui-opt-group label=""> <tui-opt-group label="">
<a <a
tuiOption tuiOption
new
iconStart="@tui.settings" iconStart="@tui.settings"
routerLink="/system" routerLink="/system"
(click)="open = false" (click)="open = false"
@@ -86,6 +90,7 @@ import { ABOUT } from './about.component'
<tui-opt-group label=""> <tui-opt-group label="">
<button <button
tuiOption tuiOption
new
iconStart="@tui.refresh-cw" iconStart="@tui.refresh-cw"
(click)="promptPower('restart')" (click)="promptPower('restart')"
> >
@@ -93,12 +98,13 @@ import { ABOUT } from './about.component'
</button> </button>
<button <button
tuiOption tuiOption
new
iconStart="@tui.power" iconStart="@tui.power"
(click)="promptPower('shutdown')" (click)="promptPower('shutdown')"
> >
{{ 'Shutdown' | i18n }} {{ 'Shutdown' | i18n }}
</button> </button>
<button tuiOption iconStart="@tui.log-out" (click)="logout()"> <button tuiOption new iconStart="@tui.log-out" (click)="logout()">
{{ 'Logout' | i18n }} {{ 'Logout' | i18n }}
</button> </button>
</tui-opt-group> </tui-opt-group>

View File

@@ -23,7 +23,7 @@ import { AuthoritiesTableComponent } from './table.component'
docsLink docsLink
path="/start-os/user-manual/trust-ca.html" path="/start-os/user-manual/trust-ca.html"
appearance="icon" appearance="icon"
iconStart="@tui.external-link" iconStart="@tui.book-open-text"
> >
{{ 'Documentation' | i18n }} {{ 'Documentation' | i18n }}
</a> </a>

View File

@@ -49,7 +49,7 @@ const ipv6 =
docsLink docsLink
path="/start-os/user-manual/dns.html" path="/start-os/user-manual/dns.html"
appearance="icon" appearance="icon"
iconStart="@tui.external-link" iconStart="@tui.book-open-text"
> >
{{ 'Documentation' | i18n }} {{ 'Documentation' | i18n }}
</a> </a>

View File

@@ -57,7 +57,7 @@ function detectProviderKey(host: string | undefined): string {
docsLink docsLink
path="/start-os/user-manual/smtp.html" path="/start-os/user-manual/smtp.html"
appearance="icon" appearance="icon"
iconStart="@tui.external-link" iconStart="@tui.book-open-text"
> >
{{ 'Documentation' | i18n }} {{ 'Documentation' | i18n }}
</a> </a>

View File

@@ -1,5 +1,12 @@
import { CommonModule } from '@angular/common' 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 { RouterLink } from '@angular/router'
import { import {
DocsLinkDirective, DocsLinkDirective,
@@ -7,14 +14,18 @@ import {
i18nPipe, i18nPipe,
LoadingService, LoadingService,
} from '@start9labs/shared' } 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 { 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({ @Component({
template: ` template: `
@@ -34,7 +45,7 @@ import { ISB } from '@start9labs/start-sdk'
docsLink docsLink
path="/start-os/user-manual/gateways.html" path="/start-os/user-manual/gateways.html"
appearance="icon" appearance="icon"
iconStart="@tui.external-link" iconStart="@tui.book-open-text"
> >
{{ 'Documentation' | i18n }} {{ 'Documentation' | i18n }}
</a> </a>
@@ -50,12 +61,99 @@ import { ISB } from '@start9labs/start-sdk'
</header> </header>
<gateways-table /> <gateways-table />
</section> </section>
@if (outboundOptions(); as options) {
<section class="outbound">
<header tuiHeader="body-l">
<h3 tuiTitle>
<b>
{{ 'Outbound Traffic' | i18n }}
<a
tuiIconButton
size="xs"
docsLink
path="/start-os/user-manual/gateways.html"
fragment="#outbound-traffic"
appearance="icon"
iconStart="@tui.book-open-text"
>
{{ 'Documentation' | i18n }}
</a>
</b>
</h3>
</header>
<tui-textfield
tuiChevron
[stringify]="stringifyOutbound"
[tuiTextfieldCleaner]="false"
>
<label tuiLabel>{{ 'Use gateway' | i18n }}</label>
@if (mobile) {
<select
tuiSelect
[ngModel]="selectedOutbound()"
(ngModelChange)="selectedOutbound.set($event)"
[items]="options"
></select>
} @else {
<input
tuiSelect
[ngModel]="selectedOutbound()"
(ngModelChange)="selectedOutbound.set($event)"
/>
}
@if (!mobile) {
<tui-data-list-wrapper
new
*tuiTextfieldDropdown
[items]="options"
/>
}
</tui-textfield>
<footer>
<button
tuiButton
[disabled]="
selectedOutbound()?.id ===
(gatewayService.defaultOutbound() ?? null)
"
(click)="saveOutbound()"
>
{{ 'Save' | i18n }}
</button>
</footer>
</section>
}
`,
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, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [GatewayService],
imports: [ imports: [
CommonModule, CommonModule,
FormsModule,
RouterLink, RouterLink,
TuiButton, TuiButton,
TuiTextfield,
TuiTitle,
TuiChevron,
TuiSelect,
TuiDataListWrapper,
TuiHeader,
GatewaysTableComponent, GatewaysTableComponent,
TitleDirective, TitleDirective,
i18nPipe, i18nPipe,
@@ -68,6 +166,50 @@ export default class GatewaysComponent {
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly formDialog = inject(FormDialogService) private readonly formDialog = inject(FormDialogService)
private readonly i18n = inject(i18nPipe) 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() { async add() {
const spec = ISB.InputSpec.of({ 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, { this.formDialog.open(FormComponent, {
@@ -135,7 +270,7 @@ export default class GatewaysComponent {
? input.config.value.file ? input.config.value.file
: await (input.config.value.file as any as File).text(), : await (input.config.value.file as any as File).text(),
type: null, // @TODO Aiden why is attr here? type: null, // @TODO Aiden why is attr here?
setAsDefaultOutbound: input.setAsDefaultOutbound, setAsDefaultOutbound: false,
}) })
return true return true
} catch (e: any) { } catch (e: any) {

View File

@@ -23,9 +23,8 @@ import { filter } from 'rxjs'
import { FormComponent } from 'src/app/routes/portal/components/form.component' import { FormComponent } from 'src/app/routes/portal/components/form.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 { GatewayPlus } from 'src/app/services/gateway.service' 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' import { PORT_FORWARDS_MODAL } from './port-forwards.component'
@Component({ @Component({
@@ -45,11 +44,6 @@ import { PORT_FORWARDS_MODAL } from './port-forwards.component'
} }
} }
{{ gateway.name }} {{ gateway.name }}
@if (gateway.isDefaultOutbound) {
<tui-badge appearance="primary-success">
{{ 'default outbound' | i18n }}
</tui-badge>
}
</td> </td>
<td> <td>
@if (gateway.type === 'outbound-only') { @if (gateway.type === 'outbound-only') {
@@ -91,13 +85,6 @@ import { PORT_FORWARDS_MODAL } from './port-forwards.component'
</button> </button>
</tui-opt-group> </tui-opt-group>
} }
@if (!gateway.isDefaultOutbound) {
<tui-opt-group>
<button tuiOption new (click)="setDefaultOutbound()">
{{ 'Set as default outbound' | i18n }}
</button>
</tui-opt-group>
}
@if (gateway.ipInfo.deviceType === 'wireguard') { @if (gateway.ipInfo.deviceType === 'wireguard') {
<tui-opt-group> <tui-opt-group>
<button tuiOption new class="g-negative" (click)="remove()"> <button tuiOption new class="g-negative" (click)="remove()">
@@ -116,8 +103,8 @@ import { PORT_FORWARDS_MODAL } from './port-forwards.component'
margin-right: 0.7rem; margin-right: 0.7rem;
} }
tui-badge { td:first-child {
margin-left: 1rem; width: 24rem;
} }
td:last-child { td:last-child {
@@ -171,7 +158,6 @@ import { PORT_FORWARDS_MODAL } from './port-forwards.component'
TuiOptGroup, TuiOptGroup,
TuiTextfield, TuiTextfield,
i18nPipe, i18nPipe,
TuiBadge,
], ],
}) })
export class GatewaysItemComponent { export class GatewaysItemComponent {
@@ -214,18 +200,6 @@ export class GatewaysItemComponent {
}) })
} }
async setDefaultOutbound() {
const loader = this.loader.open().subscribe()
try {
await this.api.setDefaultOutbound({ gateway: this.gateway().id })
} catch (e: any) {
this.errorService.handleError(e)
} finally {
loader.unsubscribe()
}
}
async rename() { async rename() {
const { id, name } = this.gateway() const { id, name } = this.gateway()
const renameSpec = ISB.InputSpec.of({ const renameSpec = ISB.InputSpec.of({

View File

@@ -21,7 +21,6 @@ import { GatewayService } from 'src/app/services/gateway.service'
</table> </table>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [GatewayService],
imports: [TuiSkeleton, i18nPipe, TableComponent, GatewaysItemComponent], imports: [TuiSkeleton, i18nPipe, TableComponent, GatewaysItemComponent],
}) })
export class GatewaysTableComponent { export class GatewaysTableComponent {

View File

@@ -41,7 +41,7 @@ import { SSHTableComponent } from './table.component'
docsLink docsLink
path="/start-os/user-manual/ssh.html" path="/start-os/user-manual/ssh.html"
appearance="icon" appearance="icon"
iconStart="@tui.external-link" iconStart="@tui.book-open-text"
> >
{{ 'Documentation' | i18n }} {{ 'Documentation' | i18n }}
</a> </a>

View File

@@ -56,7 +56,7 @@ import { wifiSpec } from './wifi.const'
docsLink docsLink
path="/start-os/user-manual/wifi.html" path="/start-os/user-manual/wifi.html"
appearance="icon" appearance="icon"
iconStart="@tui.external-link" iconStart="@tui.book-open-text"
> >
{{ 'Documentation' | i18n }} {{ 'Documentation' | i18n }}
</a> </a>

View File

@@ -12,7 +12,6 @@ export type GatewayPlus = T.NetworkInterfaceInfo & {
subnets: utils.IpNet[] subnets: utils.IpNet[]
lanIpv4: string[] lanIpv4: string[]
wanIp?: utils.IpAddress wanIp?: utils.IpAddress
isDefaultOutbound: boolean
} }
@Injectable() @Injectable()
@@ -29,7 +28,6 @@ export class GatewayService {
this.network$.pipe( this.network$.pipe(
map(network => { map(network => {
const gateways = network.gateways const gateways = network.gateways
const defaultOutbound = network.defaultOutbound
return Object.entries(gateways) return Object.entries(gateways)
.filter(([_, val]) => !!val?.ipInfo) .filter(([_, val]) => !!val?.ipInfo)
.filter( .filter(
@@ -49,7 +47,6 @@ export class GatewayService {
lanIpv4: subnets.filter(s => s.isIpv4()).map(s => s.address), lanIpv4: subnets.filter(s => s.isIpv4()).map(s => s.address),
wanIp: wanIp:
val.ipInfo?.wanIp && utils.IpAddress.parse(val.ipInfo?.wanIp), val.ipInfo?.wanIp && utils.IpAddress.parse(val.ipInfo?.wanIp),
isDefaultOutbound: id === defaultOutbound,
} as GatewayPlus } as GatewayPlus
}) })
}), }),

View File

@@ -161,7 +161,6 @@ export class MarketplaceService {
} }
private fetchRegistry$(url: string): Observable<StoreDataWithUrl | null> { private fetchRegistry$(url: string): Observable<StoreDataWithUrl | null> {
console.log('FETCHING REGISTRY: ', url)
return combineLatest([this.fetchInfo$(url), this.fetchPackages$(url)]).pipe( return combineLatest([this.fetchInfo$(url), this.fetchPackages$(url)]).pipe(
map(([info, packages]) => ({ info, packages, url })), map(([info, packages]) => ({ info, packages, url })),
catchError(e => { catchError(e => {

View File

@@ -70,6 +70,12 @@ hr {
min-height: fit-content; min-height: fit-content;
flex: 1; flex: 1;
padding: 1rem; padding: 1rem;
&::after {
content: '';
display: block;
height: 1rem;
}
} }
.g-aside { .g-aside {