mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
enable-disable forwards
This commit is contained in:
@@ -60,6 +60,14 @@ pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.no_display()
|
.no_display()
|
||||||
.with_about("about.update-port-forward-label")
|
.with_about("about.update-port-forward-label")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"set-enabled",
|
||||||
|
from_fn_async(set_forward_enabled)
|
||||||
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
|
.no_display()
|
||||||
|
.with_about("about.enable-or-disable-port-forward")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
@@ -497,7 +505,7 @@ pub async fn add_forward(
|
|||||||
m.insert(source, rc);
|
m.insert(source, rc);
|
||||||
});
|
});
|
||||||
|
|
||||||
let entry = PortForwardEntry { target, label };
|
let entry = PortForwardEntry { target, label, enabled: true };
|
||||||
|
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
@@ -568,3 +576,64 @@ pub async fn update_forward_label(
|
|||||||
.await
|
.await
|
||||||
.result
|
.result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SetPortForwardEnabledParams {
|
||||||
|
source: SocketAddrV4,
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_forward_enabled(
|
||||||
|
ctx: TunnelContext,
|
||||||
|
SetPortForwardEnabledParams { source, enabled }: SetPortForwardEnabledParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let target = 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.enabled = enabled;
|
||||||
|
Ok(entry.target)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
|
||||||
|
if enabled {
|
||||||
|
let prefix = ctx
|
||||||
|
.net_iface
|
||||||
|
.peek(|i| {
|
||||||
|
i.iter()
|
||||||
|
.find_map(|(_, i)| {
|
||||||
|
i.ip_info.as_ref().and_then(|i| {
|
||||||
|
i.subnets
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.contains(&IpAddr::from(*target.ip())))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
|
.map(|s| s.prefix_len())
|
||||||
|
.unwrap_or(32);
|
||||||
|
let rc = ctx
|
||||||
|
.forward
|
||||||
|
.add_forward(source, target, prefix, None)
|
||||||
|
.await?;
|
||||||
|
ctx.active_forwards.mutate(|m| {
|
||||||
|
m.insert(source, rc);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if let Some(rc) = ctx.active_forwards.mutate(|m| m.remove(&source)) {
|
||||||
|
drop(rc);
|
||||||
|
ctx.forward.gc().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -185,6 +185,9 @@ impl TunnelContext {
|
|||||||
|
|
||||||
let mut active_forwards = BTreeMap::new();
|
let mut active_forwards = BTreeMap::new();
|
||||||
for (from, entry) in peek.as_port_forwards().de()?.0 {
|
for (from, entry) in peek.as_port_forwards().de()?.0 {
|
||||||
|
if !entry.enabled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let to = entry.target;
|
let to = entry.target;
|
||||||
let prefix = net_iface
|
let prefix = net_iface
|
||||||
.peek(|i| {
|
.peek(|i| {
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ pub struct PortForwardEntry {
|
|||||||
pub target: SocketAddrV4,
|
pub target: SocketAddrV4,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||||
|
|||||||
@@ -3,19 +3,22 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
|
signal,
|
||||||
Signal,
|
Signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
|
import { FormsModule } 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 {
|
import {
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiDataList,
|
TuiDataList,
|
||||||
TuiDropdown,
|
TuiDropdown,
|
||||||
|
TuiLoader,
|
||||||
TuiTextfield,
|
TuiTextfield,
|
||||||
} from '@taiga-ui/core'
|
} 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, TuiSwitch } 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'
|
||||||
@@ -30,6 +33,7 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
<table class="g-table">
|
<table class="g-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>Label</th>
|
<th>Label</th>
|
||||||
<th>External IP</th>
|
<th>External IP</th>
|
||||||
<th>External Port</th>
|
<th>External Port</th>
|
||||||
@@ -45,6 +49,22 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
<tbody>
|
<tbody>
|
||||||
@for (forward of forwards(); track $index) {
|
@for (forward of forwards(); track $index) {
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>
|
||||||
|
<tui-loader
|
||||||
|
[showLoader]="toggling() === $index"
|
||||||
|
size="xs"
|
||||||
|
[overlay]="true"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
tuiSwitch
|
||||||
|
type="checkbox"
|
||||||
|
size="s"
|
||||||
|
[showIcons]="false"
|
||||||
|
[ngModel]="forward.enabled"
|
||||||
|
(ngModelChange)="onToggle(forward, $index)"
|
||||||
|
/>
|
||||||
|
</tui-loader>
|
||||||
|
</td>
|
||||||
<td>{{ forward.label || '—' }}</td>
|
<td>{{ forward.label || '—' }}</td>
|
||||||
<td>{{ forward.externalip }}</td>
|
<td>{{ forward.externalip }}</td>
|
||||||
<td>{{ forward.externalport }}</td>
|
<td>{{ forward.externalport }}</td>
|
||||||
@@ -88,7 +108,15 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
</table>
|
</table>
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [TuiButton, TuiDropdown, TuiDataList, TuiTextfield],
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiButton,
|
||||||
|
TuiDropdown,
|
||||||
|
TuiDataList,
|
||||||
|
TuiLoader,
|
||||||
|
TuiSwitch,
|
||||||
|
TuiTextfield,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export default class PortForwards {
|
export default class PortForwards {
|
||||||
private readonly dialogs = inject(TuiDialogService)
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
@@ -136,10 +164,26 @@ export default class PortForwards {
|
|||||||
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,
|
label: entry.label,
|
||||||
|
enabled: entry.enabled,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
protected readonly toggling = signal<number | null>(null)
|
||||||
|
|
||||||
|
protected async onToggle(forward: MappedForward, index: number) {
|
||||||
|
this.toggling.set(index)
|
||||||
|
const source = `${forward.externalip}:${forward.externalport}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.setForwardEnabled({ source, enabled: !forward.enabled })
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
this.toggling.set(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected onAdd(): void {
|
protected onAdd(): void {
|
||||||
this.dialogs
|
this.dialogs
|
||||||
.open(PORT_FORWARDS_ADD, {
|
.open(PORT_FORWARDS_ADD, {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface MappedForward {
|
|||||||
readonly device: MappedDevice
|
readonly device: MappedDevice
|
||||||
readonly internalport: string
|
readonly internalport: string
|
||||||
readonly label: string
|
readonly label: string
|
||||||
|
readonly enabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortForwardsData {
|
export interface PortForwardsData {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export abstract class ApiService {
|
|||||||
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
|
abstract updateForwardLabel(params: UpdateForwardLabelReq): Promise<null> // port-forward.update-label
|
||||||
|
abstract setForwardEnabled(params: SetForwardEnabledReq): Promise<null> // port-forward.set-enabled
|
||||||
// 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
|
||||||
@@ -73,6 +74,11 @@ export type UpdateForwardLabelReq = {
|
|||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SetForwardEnabledReq = {
|
||||||
|
source: string
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type TunnelUpdateResult = {
|
export type TunnelUpdateResult = {
|
||||||
status: string
|
status: string
|
||||||
installed: string
|
installed: string
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
LoginReq,
|
LoginReq,
|
||||||
SubscribeRes,
|
SubscribeRes,
|
||||||
TunnelUpdateResult,
|
TunnelUpdateResult,
|
||||||
|
SetForwardEnabledReq,
|
||||||
UpdateForwardLabelReq,
|
UpdateForwardLabelReq,
|
||||||
UpsertDeviceReq,
|
UpsertDeviceReq,
|
||||||
UpsertSubnetReq,
|
UpsertSubnetReq,
|
||||||
@@ -109,6 +110,10 @@ export class LiveApiService extends ApiService {
|
|||||||
return this.rpcRequest({ method: 'port-forward.update-label', params })
|
return this.rpcRequest({ method: 'port-forward.update-label', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setForwardEnabled(params: SetForwardEnabledReq): Promise<null> {
|
||||||
|
return this.rpcRequest({ method: 'port-forward.set-enabled', params })
|
||||||
|
}
|
||||||
|
|
||||||
// update
|
// update
|
||||||
|
|
||||||
async checkUpdate(): Promise<TunnelUpdateResult> {
|
async checkUpdate(): Promise<TunnelUpdateResult> {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
LoginReq,
|
LoginReq,
|
||||||
SubscribeRes,
|
SubscribeRes,
|
||||||
TunnelUpdateResult,
|
TunnelUpdateResult,
|
||||||
|
SetForwardEnabledReq,
|
||||||
UpdateForwardLabelReq,
|
UpdateForwardLabelReq,
|
||||||
UpsertDeviceReq,
|
UpsertDeviceReq,
|
||||||
UpsertSubnetReq,
|
UpsertSubnetReq,
|
||||||
@@ -181,7 +182,11 @@ export class MockApiService extends ApiService {
|
|||||||
{
|
{
|
||||||
op: PatchOp.ADD,
|
op: PatchOp.ADD,
|
||||||
path: `/portForwards/${params.source}`,
|
path: `/portForwards/${params.source}`,
|
||||||
value: { target: params.target, label: params.label || '' },
|
value: {
|
||||||
|
target: params.target,
|
||||||
|
label: params.label || '',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
this.mockRevision(patch)
|
this.mockRevision(patch)
|
||||||
@@ -204,6 +209,21 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setForwardEnabled(params: SetForwardEnabledReq): Promise<null> {
|
||||||
|
await pauseFor(1000)
|
||||||
|
|
||||||
|
const patch: ReplaceOperation<boolean>[] = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: `/portForwards/${params.source}/enabled`,
|
||||||
|
value: params.enabled,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
async deleteForward(params: DeleteForwardReq): Promise<null> {
|
async deleteForward(params: DeleteForwardReq): Promise<null> {
|
||||||
await pauseFor(1000)
|
await pauseFor(1000)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { T } from '@start9labs/start-sdk'
|
|||||||
export type PortForwardEntry = {
|
export type PortForwardEntry = {
|
||||||
target: string
|
target: string
|
||||||
label: string
|
label: string
|
||||||
|
enabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TunnelData = {
|
export type TunnelData = {
|
||||||
@@ -44,8 +45,12 @@ export const mockTunnelData: TunnelData = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
portForwards: {
|
portForwards: {
|
||||||
'69.1.1.42:443': { target: '10.59.0.2:443', label: 'HTTPS' },
|
'69.1.1.42:443': { target: '10.59.0.2:443', label: 'HTTPS', enabled: true },
|
||||||
'69.1.1.42:3000': { target: '10.59.0.2:3000', label: 'Grafana' },
|
'69.1.1.42:3000': {
|
||||||
|
target: '10.59.0.2:3000',
|
||||||
|
label: 'Grafana',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
gateways: {
|
gateways: {
|
||||||
eth0: {
|
eth0: {
|
||||||
|
|||||||
Reference in New Issue
Block a user