port labels and move logout to settings

This commit is contained in:
Matt Hill
2026-03-09 17:15:09 -06:00
parent be921b7865
commit 30f6492abc
13 changed files with 280 additions and 59 deletions

View File

@@ -11,6 +11,7 @@ use crate::db::model::public::NetworkInterfaceType;
use crate::net::forward::add_iptables_rule; use crate::net::forward::add_iptables_rule;
use crate::prelude::*; use crate::prelude::*;
use crate::tunnel::context::TunnelContext; use crate::tunnel::context::TunnelContext;
use crate::tunnel::db::PortForwardEntry;
use crate::tunnel::wg::{WIREGUARD_INTERFACE_NAME, WgConfig, WgSubnetClients, WgSubnetConfig}; use crate::tunnel::wg::{WIREGUARD_INTERFACE_NAME, WgConfig, WgSubnetClients, WgSubnetConfig};
use crate::util::serde::{HandlerExtSerde, display_serializable}; use crate::util::serde::{HandlerExtSerde, display_serializable};
@@ -51,6 +52,14 @@ pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
.no_display() .no_display()
.with_about("about.remove-port-forward") .with_about("about.remove-port-forward")
.with_call_remote::<CliContext>(), .with_call_remote::<CliContext>(),
)
.subcommand(
"update-label",
from_fn_async(update_forward_label)
.with_metadata("sync_db", Value::Bool(true))
.no_display()
.with_about("about.update-port-forward-label")
.with_call_remote::<CliContext>(),
), ),
) )
.subcommand( .subcommand(
@@ -453,11 +462,17 @@ pub async fn show_config(
pub struct AddPortForwardParams { pub struct AddPortForwardParams {
source: SocketAddrV4, source: SocketAddrV4,
target: SocketAddrV4, target: SocketAddrV4,
#[arg(long)]
label: String,
} }
pub async fn add_forward( pub async fn add_forward(
ctx: TunnelContext, ctx: TunnelContext,
AddPortForwardParams { source, target }: AddPortForwardParams, AddPortForwardParams {
source,
target,
label,
}: AddPortForwardParams,
) -> Result<(), Error> { ) -> Result<(), Error> {
let prefix = ctx let prefix = ctx
.net_iface .net_iface
@@ -482,10 +497,12 @@ pub async fn add_forward(
m.insert(source, rc); m.insert(source, rc);
}); });
let entry = PortForwardEntry { target, label };
ctx.db ctx.db
.mutate(|db| { .mutate(|db| {
db.as_port_forwards_mut() db.as_port_forwards_mut()
.insert(&source, &target) .insert(&source, &entry)
.and_then(|replaced| { .and_then(|replaced| {
if replaced.is_some() { if replaced.is_some() {
Err(Error::new( Err(Error::new(
@@ -523,3 +540,31 @@ pub async fn remove_forward(
} }
Ok(()) Ok(())
} }
#[derive(Deserialize, Serialize, Parser)]
#[serde(rename_all = "camelCase")]
pub struct UpdatePortForwardLabelParams {
source: SocketAddrV4,
label: String,
}
pub async fn update_forward_label(
ctx: TunnelContext,
UpdatePortForwardLabelParams { source, label }: UpdatePortForwardLabelParams,
) -> Result<(), Error> {
ctx.db
.mutate(|db| {
db.as_port_forwards_mut().mutate(|pf| {
let entry = pf.0.get_mut(&source).ok_or_else(|| {
Error::new(
eyre!("Port forward from {source} not found"),
ErrorKind::NotFound,
)
})?;
entry.label = label.clone();
Ok(())
})
})
.await
.result
}

View File

@@ -184,7 +184,8 @@ impl TunnelContext {
} }
let mut active_forwards = BTreeMap::new(); let mut active_forwards = BTreeMap::new();
for (from, to) in peek.as_port_forwards().de()?.0 { for (from, entry) in peek.as_port_forwards().de()?.0 {
let to = entry.target;
let prefix = net_iface let prefix = net_iface
.peek(|i| { .peek(|i| {
i.iter() i.iter()

View File

@@ -53,7 +53,7 @@ impl Model<TunnelDatabase> {
} }
self.as_port_forwards_mut().mutate(|pf| { self.as_port_forwards_mut().mutate(|pf| {
Ok(pf.0.retain(|k, v| { Ok(pf.0.retain(|k, v| {
if keep_targets.contains(v.ip()) { if keep_targets.contains(v.target.ip()) {
keep_sources.insert(*k); keep_sources.insert(*k);
true true
} else { } else {
@@ -70,11 +70,19 @@ fn export_bindings_tunnel_db() {
TunnelDatabase::export_all_to("bindings/tunnel").unwrap(); TunnelDatabase::export_all_to("bindings/tunnel").unwrap();
} }
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct PortForwardEntry {
pub target: SocketAddrV4,
#[serde(default)]
pub label: String,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
pub struct PortForwards(pub BTreeMap<SocketAddrV4, SocketAddrV4>); pub struct PortForwards(pub BTreeMap<SocketAddrV4, PortForwardEntry>);
impl Map for PortForwards { impl Map for PortForwards {
type Key = SocketAddrV4; type Key = SocketAddrV4;
type Value = SocketAddrV4; type Value = PortForwardEntry;
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> { fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
Self::key_string(key) Self::key_string(key)
} }

View File

@@ -1,10 +1,7 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { Router, RouterLink, RouterLinkActive } from '@angular/router' import { RouterLink, RouterLinkActive } from '@angular/router'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiButton } from '@taiga-ui/core' import { TuiButton } from '@taiga-ui/core'
import { TuiBadgeNotification } from '@taiga-ui/kit' import { TuiBadgeNotification } from '@taiga-ui/kit'
import { ApiService } from 'src/app/services/api/api.service'
import { AuthService } from 'src/app/services/auth.service'
import { SidebarService } from 'src/app/services/sidebar.service' import { SidebarService } from 'src/app/services/sidebar.service'
import { UpdateService } from 'src/app/services/update.service' import { UpdateService } from 'src/app/services/update.service'
@@ -38,15 +35,6 @@ import { UpdateService } from 'src/app/services/update.service'
} }
</a> </a>
</div> </div>
<button
tuiButton
iconStart="@tui.log-out"
appearance="neutral"
size="s"
(click)="logout()"
>
Logout
</button>
`, `,
styles: ` styles: `
:host { :host {
@@ -79,12 +67,6 @@ import { UpdateService } from 'src/app/services/update.service'
} }
} }
button {
width: 100%;
border-radius: 0;
justify-content: flex-start;
}
:host-context(tui-root._mobile) { :host-context(tui-root._mobile) {
position: absolute; position: absolute;
top: 3.5rem; top: 3.5rem;
@@ -106,12 +88,7 @@ import { UpdateService } from 'src/app/services/update.service'
}, },
}) })
export class Nav { export class Nav {
private readonly service = inject(AuthService)
private readonly router = inject(Router)
protected readonly sidebars = inject(SidebarService) protected readonly sidebars = inject(SidebarService)
protected readonly api = inject(ApiService)
private readonly loader = inject(LoadingService)
private readonly errorService = inject(ErrorService)
protected readonly update = inject(UpdateService) protected readonly update = inject(UpdateService)
protected readonly routes = [ protected readonly routes = [
@@ -131,18 +108,4 @@ export class Nav {
link: 'port-forwards', link: 'port-forwards',
}, },
] as const ] as const
protected async logout() {
const loader = this.loader.open().subscribe()
try {
await this.api.logout()
this.service.authenticated.set(false)
this.router.navigate(['.'])
} catch (e: any) {
console.error(e)
this.errorService.handleError(e)
} finally {
loader.unsubscribe()
}
}
} }

View File

@@ -36,6 +36,11 @@ import { MappedDevice, PortForwardsData } from './utils'
@Component({ @Component({
template: ` template: `
<form tuiForm [formGroup]="form"> <form tuiForm [formGroup]="form">
<tui-textfield>
<label tuiLabel>Label</label>
<input tuiTextfield formControlName="label" />
</tui-textfield>
<tui-error formControlName="label" [error]="[] | tuiFieldError | async" />
<tui-textfield tuiChevron> <tui-textfield tuiChevron>
<label tuiLabel>External IP</label> <label tuiLabel>External IP</label>
@if (mobile) { @if (mobile) {
@@ -161,6 +166,7 @@ export class PortForwardsAdd {
injectContext<TuiDialogContext<void, PortForwardsData>>() injectContext<TuiDialogContext<void, PortForwardsData>>()
protected readonly form = inject(NonNullableFormBuilder).group({ protected readonly form = inject(NonNullableFormBuilder).group({
label: ['', Validators.required],
externalip: ['', Validators.required], externalip: ['', Validators.required],
externalport: [null as number | null, Validators.required], externalport: [null as number | null, Validators.required],
device: [null as MappedDevice | null, Validators.required], device: [null as MappedDevice | null, Validators.required],
@@ -185,19 +191,21 @@ export class PortForwardsAdd {
const loader = this.loading.open().subscribe() const loader = this.loading.open().subscribe()
const { externalip, externalport, device, internalport, also80 } = const { label, externalip, externalport, device, internalport, also80 } =
this.form.getRawValue() this.form.getRawValue()
try { try {
await this.api.addForward({ await this.api.addForward({
source: `${externalip}:${externalport}`, source: `${externalip}:${externalport}`,
target: `${device!.ip}:${internalport}`, target: `${device!.ip}:${internalport}`,
label,
}) })
if (externalport === 443 && internalport === 443 && also80) { if (externalport === 443 && internalport === 443 && also80) {
await this.api.addForward({ await this.api.addForward({
source: `${externalip}:80`, source: `${externalip}:80`,
target: `${device!.ip}:443`, target: `${device!.ip}:443`,
label: `${label} (HTTP redirect)`,
}) })
} }
} catch (e: any) { } catch (e: any) {

View File

@@ -0,0 +1,83 @@
import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import {
NonNullableFormBuilder,
ReactiveFormsModule,
Validators,
} from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared'
import {
TuiButton,
TuiDialogContext,
TuiError,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiFieldErrorPipe } from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { ApiService } from 'src/app/services/api/api.service'
export interface EditLabelData {
readonly source: string
readonly label: string
}
@Component({
template: `
<form tuiForm [formGroup]="form">
<tui-textfield>
<label tuiLabel>Label</label>
<input tuiTextfield formControlName="label" />
</tui-textfield>
<tui-error formControlName="label" [error]="[] | tuiFieldError | async" />
<footer>
<button tuiButton [disabled]="form.invalid" (click)="onSave()">
Save
</button>
</footer>
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncPipe,
ReactiveFormsModule,
TuiButton,
TuiError,
TuiFieldErrorPipe,
TuiTextfield,
TuiForm,
],
})
export class PortForwardsEditLabel {
private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService)
private readonly errorService = inject(ErrorService)
protected readonly context =
injectContext<TuiDialogContext<void, EditLabelData>>()
protected readonly form = inject(NonNullableFormBuilder).group({
label: [this.context.data.label, Validators.required],
})
protected async onSave() {
const loader = this.loading.open().subscribe()
try {
await this.api.updateForwardLabel({
source: this.context.data.source,
label: this.form.getRawValue().label,
})
} catch (e: any) {
console.error(e)
this.errorService.handleError(e)
} finally {
loader.unsubscribe()
this.context.$implicit.complete()
}
}
}
export const PORT_FORWARDS_EDIT_LABEL = new PolymorpheusComponent(
PortForwardsEditLabel,
)

View File

@@ -6,15 +6,20 @@ import {
Signal, Signal,
} from '@angular/core' } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { ReactiveFormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared' import { ErrorService, LoadingService } from '@start9labs/shared'
import { utils } from '@start9labs/start-sdk' import { utils } from '@start9labs/start-sdk'
import { TuiButton } from '@taiga-ui/core' import {
TuiButton,
TuiDataList,
TuiDropdown,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental' import { TuiDialogService } from '@taiga-ui/experimental'
import { TUI_CONFIRM } from '@taiga-ui/kit' import { TUI_CONFIRM } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { filter, map } from 'rxjs' import { filter, map } from 'rxjs'
import { PORT_FORWARDS_ADD } from 'src/app/routes/home/routes/port-forwards/add' import { PORT_FORWARDS_ADD } from 'src/app/routes/home/routes/port-forwards/add'
import { PORT_FORWARDS_EDIT_LABEL } from 'src/app/routes/home/routes/port-forwards/edit-label'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
import { TunnelData } from 'src/app/services/patch-db/data-model' import { TunnelData } from 'src/app/services/patch-db/data-model'
@@ -25,6 +30,7 @@ import { MappedDevice, MappedForward } from './utils'
<table class="g-table"> <table class="g-table">
<thead> <thead>
<tr> <tr>
<th>Label</th>
<th>External IP</th> <th>External IP</th>
<th>External Port</th> <th>External Port</th>
<th>Device</th> <th>Device</th>
@@ -39,6 +45,7 @@ import { MappedDevice, MappedForward } from './utils'
<tbody> <tbody>
@for (forward of forwards(); track $index) { @for (forward of forwards(); track $index) {
<tr> <tr>
<td>{{ forward.label || '—' }}</td>
<td>{{ forward.externalip }}</td> <td>{{ forward.externalip }}</td>
<td>{{ forward.externalport }}</td> <td>{{ forward.externalport }}</td>
<td>{{ forward.device.name }}</td> <td>{{ forward.device.name }}</td>
@@ -47,11 +54,30 @@ import { MappedDevice, MappedForward } from './utils'
<button <button
tuiIconButton tuiIconButton
size="xs" size="xs"
tuiDropdown
tuiDropdownOpen
appearance="flat-grayscale" appearance="flat-grayscale"
iconStart="@tui.trash" iconStart="@tui.ellipsis-vertical"
(click)="onDelete(forward)"
> >
Actions Actions
<tui-data-list *tuiTextfieldDropdown size="s">
<button
tuiOption
iconStart="@tui.pencil"
new
(click)="onEditLabel(forward)"
>
{{ forward.label ? 'Rename' : 'Add label' }}
</button>
<button
tuiOption
iconStart="@tui.trash"
new
(click)="onDelete(forward)"
>
Delete
</button>
</tui-data-list>
</button> </button>
</td> </td>
</tr> </tr>
@@ -62,7 +88,7 @@ import { MappedDevice, MappedForward } from './utils'
</table> </table>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule, TuiButton], imports: [TuiButton, TuiDropdown, TuiDataList, TuiTextfield],
}) })
export default class PortForwards { export default class PortForwards {
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiDialogService)
@@ -100,15 +126,16 @@ export default class PortForwards {
) )
protected readonly forwards = computed(() => protected readonly forwards = computed(() =>
Object.entries(this.portForwards() || {}).map(([source, target]) => { Object.entries(this.portForwards() || {}).map(([source, entry]) => {
const sourceSplit = source.split(':') const sourceSplit = source.split(':')
const targetSplit = target.split(':') const targetSplit = entry.target.split(':')
return { return {
externalip: sourceSplit[0]!, externalip: sourceSplit[0]!,
externalport: sourceSplit[1]!, externalport: sourceSplit[1]!,
device: this.devices().find(d => d.ip === targetSplit[0])!, device: this.devices().find(d => d.ip === targetSplit[0])!,
internalport: targetSplit[1]!, internalport: targetSplit[1]!,
label: entry.label,
} }
}), }),
) )
@@ -122,6 +149,18 @@ export default class PortForwards {
.subscribe() .subscribe()
} }
protected onEditLabel(forward: MappedForward): void {
this.dialogs
.open(PORT_FORWARDS_EDIT_LABEL, {
label: 'Edit label',
data: {
source: `${forward.externalip}:${forward.externalport}`,
label: forward.label,
},
})
.subscribe()
}
protected onDelete({ externalip, externalport }: MappedForward): void { protected onDelete({ externalip, externalport }: MappedForward): void {
this.dialogs this.dialogs
.open(TUI_CONFIRM, { label: 'Are you sure?' }) .open(TUI_CONFIRM, { label: 'Are you sure?' })

View File

@@ -10,6 +10,7 @@ export interface MappedForward {
readonly externalport: string readonly externalport: string
readonly device: MappedDevice readonly device: MappedDevice
readonly internalport: string readonly internalport: string
readonly label: string
} }
export interface PortForwardsData { export interface PortForwardsData {

View File

@@ -4,11 +4,14 @@ import {
inject, inject,
signal, signal,
} from '@angular/core' } from '@angular/core'
import { ErrorService } from '@start9labs/shared' import { Router } from '@angular/router'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiAppearance, TuiButton, TuiTitle } from '@taiga-ui/core' import { TuiAppearance, TuiButton, TuiTitle } from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental' import { TuiDialogService } from '@taiga-ui/experimental'
import { TuiBadge, TuiButtonLoading } from '@taiga-ui/kit' import { TuiBadge, TuiButtonLoading } from '@taiga-ui/kit'
import { TuiCard, TuiCell } from '@taiga-ui/layout' import { TuiCard, TuiCell } from '@taiga-ui/layout'
import { ApiService } from 'src/app/services/api/api.service'
import { AuthService } from 'src/app/services/auth.service'
import { UpdateService } from 'src/app/services/update.service' import { UpdateService } from 'src/app/services/update.service'
import { CHANGE_PASSWORD } from './change-password' import { CHANGE_PASSWORD } from './change-password'
@@ -50,6 +53,20 @@ import { CHANGE_PASSWORD } from './change-password'
</span> </span>
<button tuiButton size="s" (click)="onChangePassword()">Change</button> <button tuiButton size="s" (click)="onChangePassword()">Change</button>
</div> </div>
<div tuiCell>
<span tuiTitle>
<strong>Logout</strong>
</span>
<button
tuiButton
size="s"
appearance="secondary-destructive"
iconStart="@tui.log-out"
(click)="onLogout()"
>
Logout
</button>
</div>
</div> </div>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@@ -66,6 +83,10 @@ import { CHANGE_PASSWORD } from './change-password'
export default class Settings { export default class Settings {
private readonly dialogs = inject(TuiDialogService) private readonly dialogs = inject(TuiDialogService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService)
private readonly auth = inject(AuthService)
private readonly router = inject(Router)
private readonly loading = inject(LoadingService)
protected readonly update = inject(UpdateService) protected readonly update = inject(UpdateService)
protected readonly checking = signal(false) protected readonly checking = signal(false)
@@ -98,4 +119,18 @@ export default class Settings {
this.applying.set(false) this.applying.set(false)
} }
} }
protected async onLogout() {
const loader = this.loading.open().subscribe()
try {
await this.api.logout()
this.auth.authenticated.set(false)
this.router.navigate(['/'])
} catch (e: any) {
this.errorService.handleError(e)
} finally {
loader.unsubscribe()
}
}
} }

View File

@@ -25,6 +25,7 @@ export abstract class ApiService {
// forwards // forwards
abstract addForward(params: AddForwardReq): Promise<null> // port-forward.add abstract addForward(params: AddForwardReq): Promise<null> // port-forward.add
abstract deleteForward(params: DeleteForwardReq): Promise<null> // port-forward.remove abstract deleteForward(params: DeleteForwardReq): Promise<null> // port-forward.remove
abstract updateForwardLabel(params: UpdateForwardLabelReq): Promise<null> // port-forward.update-label
// update // update
abstract checkUpdate(): Promise<TunnelUpdateResult> // update.check abstract checkUpdate(): Promise<TunnelUpdateResult> // update.check
abstract applyUpdate(): Promise<TunnelUpdateResult> // update.apply abstract applyUpdate(): Promise<TunnelUpdateResult> // update.apply
@@ -60,12 +61,18 @@ export type DeleteDeviceReq = {
export type AddForwardReq = { export type AddForwardReq = {
source: string // externalip:port source: string // externalip:port
target: string // internalip:port target: string // internalip:port
label: string
} }
export type DeleteForwardReq = { export type DeleteForwardReq = {
source: string source: string
} }
export type UpdateForwardLabelReq = {
source: string
label: string
}
export type TunnelUpdateResult = { export type TunnelUpdateResult = {
status: string status: string
installed: string installed: string

View File

@@ -17,6 +17,7 @@ import {
LoginReq, LoginReq,
SubscribeRes, SubscribeRes,
TunnelUpdateResult, TunnelUpdateResult,
UpdateForwardLabelReq,
UpsertDeviceReq, UpsertDeviceReq,
UpsertSubnetReq, UpsertSubnetReq,
} from './api.service' } from './api.service'
@@ -104,6 +105,10 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'port-forward.remove', params }) return this.rpcRequest({ method: 'port-forward.remove', params })
} }
async updateForwardLabel(params: UpdateForwardLabelReq): Promise<null> {
return this.rpcRequest({ method: 'port-forward.update-label', params })
}
// update // update
async checkUpdate(): Promise<TunnelUpdateResult> { async checkUpdate(): Promise<TunnelUpdateResult> {

View File

@@ -10,6 +10,7 @@ import {
LoginReq, LoginReq,
SubscribeRes, SubscribeRes,
TunnelUpdateResult, TunnelUpdateResult,
UpdateForwardLabelReq,
UpsertDeviceReq, UpsertDeviceReq,
UpsertSubnetReq, UpsertSubnetReq,
} from './api.service' } from './api.service'
@@ -24,7 +25,12 @@ import {
Revision, Revision,
} from 'patch-db-client' } from 'patch-db-client'
import { toObservable } from '@angular/core/rxjs-interop' import { toObservable } from '@angular/core/rxjs-interop'
import { mockTunnelData, WgClient, WgSubnet } from '../patch-db/data-model' import {
mockTunnelData,
PortForwardEntry,
WgClient,
WgSubnet,
} from '../patch-db/data-model'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -171,11 +177,26 @@ export class MockApiService extends ApiService {
async addForward(params: AddForwardReq): Promise<null> { async addForward(params: AddForwardReq): Promise<null> {
await pauseFor(1000) await pauseFor(1000)
const patch: AddOperation<string>[] = [ const patch: AddOperation<PortForwardEntry>[] = [
{ {
op: PatchOp.ADD, op: PatchOp.ADD,
path: `/portForwards/${params.source}`, path: `/portForwards/${params.source}`,
value: params.target, value: { target: params.target, label: params.label || '' },
},
]
this.mockRevision(patch)
return null
}
async updateForwardLabel(params: UpdateForwardLabelReq): Promise<null> {
await pauseFor(1000)
const patch: ReplaceOperation<string>[] = [
{
op: PatchOp.REPLACE,
path: `/portForwards/${params.source}/label`,
value: params.label,
}, },
] ]
this.mockRevision(patch) this.mockRevision(patch)

View File

@@ -1,8 +1,13 @@
import { T } from '@start9labs/start-sdk' import { T } from '@start9labs/start-sdk'
export type PortForwardEntry = {
target: string
label: string
}
export type TunnelData = { export type TunnelData = {
wg: WgServer wg: WgServer
portForwards: Record<string, string> portForwards: Record<string, PortForwardEntry>
gateways: Record<string, T.NetworkInterfaceInfo> gateways: Record<string, T.NetworkInterfaceInfo>
} }
@@ -39,8 +44,8 @@ export const mockTunnelData: TunnelData = {
}, },
}, },
portForwards: { portForwards: {
'69.1.1.42:443': '10.59.0.2:443', '69.1.1.42:443': { target: '10.59.0.2:443', label: 'HTTPS' },
'69.1.1.42:3000': '10.59.0.2:3000', '69.1.1.42:3000': { target: '10.59.0.2:3000', label: 'Grafana' },
}, },
gateways: { gateways: {
eth0: { eth0: {