feat: add "Add new gateway" option (#3098)

* feat: add "Add new gateway" option

* Update web/projects/ui/src/app/routes/portal/components/form/controls/select.component.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* add translation

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
Alex Inkin
2026-01-18 08:37:30 +04:00
committed by GitHub
parent 0d4ddc3451
commit 65fc3e5c52
8 changed files with 52 additions and 20 deletions

View File

@@ -99,6 +99,7 @@ export default {
101: 'Sie haben nicht gespeicherte Änderungen. Möchten Sie die Seite wirklich verlassen?',
102: 'Verlassen',
103: 'Sind Sie sicher?',
104: 'Neues Netzwerk-Gateway',
108: 'Öffentlich',
109: 'privat',
111: 'Keine Onion-Domains',

View File

@@ -98,6 +98,7 @@ export const ENGLISH = {
'You have unsaved changes. Are you sure you want to leave?': 101,
'Leave': 102,
'Are you sure?': 103,
'New gateway': 104, // as in, a network gateway
'public': 108,
'private': 109,
'No Tor domains': 111,

View File

@@ -99,6 +99,7 @@ export default {
101: 'Tienes cambios no guardados. ¿Estás seguro de que deseas salir?',
102: 'Salir',
103: '¿Estás seguro?',
104: 'Nueva puerta de enlace de red',
108: 'público',
109: 'privado',
111: 'Sin dominios onion',

View File

@@ -99,6 +99,7 @@ export default {
101: 'Vous avez des modifications non enregistrées. Voulez-vous vraiment quitter ?',
102: 'Quitter',
103: 'Êtes-vous sûr ?',
104: 'Nouvelle passerelle réseau',
108: 'public',
109: 'privé',
111: 'Aucune domaine onion',

View File

@@ -99,6 +99,7 @@ export default {
101: 'Masz niezapisane zmiany. Czy na pewno chcesz opuścić tę stronę?',
102: 'Opuść',
103: 'Czy jesteś pewien?',
104: 'Nowa brama sieciowa',
108: 'publiczny',
109: 'prywatny',
111: 'Brak domeny onion',

View File

@@ -1,7 +1,7 @@
import { inject, provideAppInitializer } from '@angular/core'
import { UntypedFormBuilder } from '@angular/forms'
import { provideAnimations } from '@angular/platform-browser/animations'
import { Router } from '@angular/router'
import { ActivationStart, Router } from '@angular/router'
import { WA_LOCATION } from '@ng-web-apis/common'
import initArgon from '@start9labs/argon2'
import {
@@ -33,7 +33,7 @@ import {
TUI_DATE_VALUE_TRANSFORMER,
} from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { filter, identity, of, pairwise } from 'rxjs'
import { filter, identity, merge, of, pairwise } from 'rxjs'
import { ConfigService } from 'src/app/services/config.service'
import {
PATCH_CACHE,
@@ -116,11 +116,15 @@ export const APP_PROVIDERS = [
{
provide: TUI_DIALOGS_CLOSE,
useFactory: () =>
inject(StateService).pipe(
pairwise(),
filter(
([prev, curr]) =>
prev === 'running' && (curr === 'error' || curr === 'initializing'),
merge(
inject(Router).events.pipe(filter(e => e instanceof ActivationStart)),
inject(StateService).pipe(
pairwise(),
filter(
([prev, curr]) =>
prev === 'running' &&
(curr === 'error' || curr === 'initializing'),
),
),
),
},

View File

@@ -1,5 +1,6 @@
import { Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { Router, RouterLink } from '@angular/router'
import { invert } from '@start9labs/shared'
import { IST } from '@start9labs/start-sdk'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
@@ -36,6 +37,7 @@ import { HintPipe } from '../pipes/hint.pipe'
[placeholder]="spec.name"
[items]="items"
[(ngModel)]="selected"
(ngModelChange)="onChange($event)"
></select>
} @else {
<input
@@ -50,15 +52,27 @@ import { HintPipe } from '../pipes/hint.pipe'
@if (!mobile) {
<tui-data-list *tuiTextfieldDropdown>
@for (item of items; track item) {
<button
tuiOption
new
tuiFluidTypography
[style.white-space]="'nowrap'"
[value]="item"
>
{{ item }}
</button>
@if (inverted[item]?.startsWith('~')) {
<a
tuiOption
new
iconEnd="@tui.arrow-right"
tuiFluidTypography
[routerLink]="inverted[item]?.slice(1)"
>
{{ item }}
</a>
} @else {
<button
tuiOption
new
tuiFluidTypography
[style.white-space]="'nowrap'"
[value]="item"
>
{{ item }}
</button>
}
}
</tui-data-list>
}
@@ -70,6 +84,7 @@ import { HintPipe } from '../pipes/hint.pipe'
providers: [tuiFluidTypographyOptionsProvider({ max: 1 })],
imports: [
FormsModule,
RouterLink,
TuiTextfield,
TuiSelect,
TuiDataList,
@@ -81,8 +96,8 @@ import { HintPipe } from '../pipes/hint.pipe'
],
})
export class FormSelectComponent extends Control<IST.ValueSpecSelect, string> {
private readonly inverted = invert(this.spec.values)
protected readonly router = inject(Router)
protected readonly inverted = invert(this.spec.values)
protected readonly mobile = inject(TUI_IS_MOBILE)
protected readonly items = Object.values(this.spec.values)
protected readonly disabledItemHandler = (item: string) =>
@@ -101,4 +116,12 @@ export class FormSelectComponent extends Control<IST.ValueSpecSelect, string> {
set selected(value: string | null) {
this.value = (value && this.inverted[value]) || null
}
protected onChange(value: string) {
const mapped = this.inverted[value]
if (typeof mapped === 'string' && mapped.startsWith('~')) {
this.router.navigate([mapped.slice(1)])
}
}
}

View File

@@ -247,10 +247,10 @@ export class PublicDomainService {
),
values: gateways.reduce<Record<string, string>>(
(obj, gateway) => ({
...obj,
[gateway.id]: gateway.name || gateway.ipInfo.name,
...obj,
}),
{},
{ '~/system/gateways': this.i18n.transform('New gateway') },
),
default: '',
disabled: gateways