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()
|
||||
.with_about("about.update-port-forward-label")
|
||||
.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(
|
||||
@@ -497,7 +505,7 @@ pub async fn add_forward(
|
||||
m.insert(source, rc);
|
||||
});
|
||||
|
||||
let entry = PortForwardEntry { target, label };
|
||||
let entry = PortForwardEntry { target, label, enabled: true };
|
||||
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
@@ -568,3 +576,64 @@ pub async fn update_forward_label(
|
||||
.await
|
||||
.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();
|
||||
for (from, entry) in peek.as_port_forwards().de()?.0 {
|
||||
if !entry.enabled {
|
||||
continue;
|
||||
}
|
||||
let to = entry.target;
|
||||
let prefix = net_iface
|
||||
.peek(|i| {
|
||||
|
||||
@@ -76,6 +76,12 @@ pub struct PortForwardEntry {
|
||||
pub target: SocketAddrV4,
|
||||
#[serde(default)]
|
||||
pub label: String,
|
||||
#[serde(default = "default_true")]
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||
|
||||
@@ -3,19 +3,22 @@ import {
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
signal,
|
||||
Signal,
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { utils } from '@start9labs/start-sdk'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiLoader,
|
||||
TuiTextfield,
|
||||
} from '@taiga-ui/core'
|
||||
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 { filter, map } from 'rxjs'
|
||||
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">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Label</th>
|
||||
<th>External IP</th>
|
||||
<th>External Port</th>
|
||||
@@ -45,6 +49,22 @@ import { MappedDevice, MappedForward } from './utils'
|
||||
<tbody>
|
||||
@for (forward of forwards(); track $index) {
|
||||
<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.externalip }}</td>
|
||||
<td>{{ forward.externalport }}</td>
|
||||
@@ -88,7 +108,15 @@ import { MappedDevice, MappedForward } from './utils'
|
||||
</table>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, TuiDropdown, TuiDataList, TuiTextfield],
|
||||
imports: [
|
||||
FormsModule,
|
||||
TuiButton,
|
||||
TuiDropdown,
|
||||
TuiDataList,
|
||||
TuiLoader,
|
||||
TuiSwitch,
|
||||
TuiTextfield,
|
||||
],
|
||||
})
|
||||
export default class PortForwards {
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
@@ -136,10 +164,26 @@ export default class PortForwards {
|
||||
device: this.devices().find(d => d.ip === targetSplit[0])!,
|
||||
internalport: targetSplit[1]!,
|
||||
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 {
|
||||
this.dialogs
|
||||
.open(PORT_FORWARDS_ADD, {
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface MappedForward {
|
||||
readonly device: MappedDevice
|
||||
readonly internalport: string
|
||||
readonly label: string
|
||||
readonly enabled: boolean
|
||||
}
|
||||
|
||||
export interface PortForwardsData {
|
||||
|
||||
@@ -26,6 +26,7 @@ export abstract class ApiService {
|
||||
abstract addForward(params: AddForwardReq): Promise<null> // port-forward.add
|
||||
abstract deleteForward(params: DeleteForwardReq): Promise<null> // port-forward.remove
|
||||
abstract updateForwardLabel(params: UpdateForwardLabelReq): Promise<null> // port-forward.update-label
|
||||
abstract setForwardEnabled(params: SetForwardEnabledReq): Promise<null> // port-forward.set-enabled
|
||||
// update
|
||||
abstract checkUpdate(): Promise<TunnelUpdateResult> // update.check
|
||||
abstract applyUpdate(): Promise<TunnelUpdateResult> // update.apply
|
||||
@@ -73,6 +74,11 @@ export type UpdateForwardLabelReq = {
|
||||
label: string
|
||||
}
|
||||
|
||||
export type SetForwardEnabledReq = {
|
||||
source: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export type TunnelUpdateResult = {
|
||||
status: string
|
||||
installed: string
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
LoginReq,
|
||||
SubscribeRes,
|
||||
TunnelUpdateResult,
|
||||
SetForwardEnabledReq,
|
||||
UpdateForwardLabelReq,
|
||||
UpsertDeviceReq,
|
||||
UpsertSubnetReq,
|
||||
@@ -109,6 +110,10 @@ export class LiveApiService extends ApiService {
|
||||
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
|
||||
|
||||
async checkUpdate(): Promise<TunnelUpdateResult> {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
LoginReq,
|
||||
SubscribeRes,
|
||||
TunnelUpdateResult,
|
||||
SetForwardEnabledReq,
|
||||
UpdateForwardLabelReq,
|
||||
UpsertDeviceReq,
|
||||
UpsertSubnetReq,
|
||||
@@ -181,7 +182,11 @@ export class MockApiService extends ApiService {
|
||||
{
|
||||
op: PatchOp.ADD,
|
||||
path: `/portForwards/${params.source}`,
|
||||
value: { target: params.target, label: params.label || '' },
|
||||
value: {
|
||||
target: params.target,
|
||||
label: params.label || '',
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
@@ -204,6 +209,21 @@ export class MockApiService extends ApiService {
|
||||
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> {
|
||||
await pauseFor(1000)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { T } from '@start9labs/start-sdk'
|
||||
export type PortForwardEntry = {
|
||||
target: string
|
||||
label: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export type TunnelData = {
|
||||
@@ -44,8 +45,12 @@ export const mockTunnelData: TunnelData = {
|
||||
},
|
||||
},
|
||||
portForwards: {
|
||||
'69.1.1.42:443': { target: '10.59.0.2:443', label: 'HTTPS' },
|
||||
'69.1.1.42:3000': { target: '10.59.0.2:3000', label: 'Grafana' },
|
||||
'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',
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
gateways: {
|
||||
eth0: {
|
||||
|
||||
Reference in New Issue
Block a user