feat(port_forward): Supports local, remote, and dynamic port forwarding types (#1096)

* feat(port_forward): Supports local, remote, and dynamic port forwarding types

Added the PortForwardType enumeration to extend port forwarding functionality, supporting three modes:
1. Local forwarding (Local)
2. Remote forwarding (Remote)
3. Dynamic forwarding (SOCKS5)

Refactored the PortForwardConfig model and related adapters, and updated the UI configuration interface to support type selection

* fix(port_forward): Fixed display and validation issues with port forwarding configurations

Fixed the display logic for the local host; when the type is set to “Dynamic Forwarding,” 127.0.0.1 is used by default
Added validation for required fields in remote forwarding configurations to ensure that the remote host and port are not empty
Optimized remote forwarding log messages by removing redundant local address displays

* fix(port_forward): Fixed issues with remote port forwarding configuration and connections

- Fixed the handling of default values when the remote port forwarding type field is empty
- Corrected the labels for local/remote host and port displayed on the remote port forwarding interface
- Fixed the local port validation logic to disallow 0 or negative numbers
- Implemented connection management and error handling for remote port forwarding

* feat (Port Forwarding): Add localization labels for types and optimize code

Add localization labels for local and remote types in the port forwarding feature

Simplify the logic for retrieving prompt text on the port forwarding page
Change the default binding host from ‘0.0.0.0’ to 'localhost'

* fix(port_forward): Fixed an issue with the display format of remote port forwarding addresses

Added special handling for remote port forwarding types in the `displayAddr` method of `PortForwardConfig` to correctly display the remote bound address and port. Also optimized the code formatting to improve readability.

* refactor(port_forward): Remove automatically generated JSON serialization code and implement it manually

Modify the JSON parsing logic in PortForwardConfig and remove the automatically generated .g.dart files
Simplify the handling of localhost addresses in displayAddr
This commit is contained in:
GT610
2026-04-01 17:30:55 +08:00
committed by GitHub
parent 3c592baf2c
commit 183cdf98eb
28 changed files with 568 additions and 183 deletions

View File

@@ -1,7 +1,15 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'port_forward.freezed.dart';
part 'port_forward.g.dart';
enum PortForwardType {
@JsonValue('local')
local,
@JsonValue('remote')
remote,
@JsonValue('dynamic')
dynamic,
}
@freezed
abstract class PortForwardConfig with _$PortForwardConfig {
@@ -9,18 +17,50 @@ abstract class PortForwardConfig with _$PortForwardConfig {
required String id,
required String serverId,
required String name,
@Default('localhost') String localHost,
required int localPort,
required String remoteHost,
required int remotePort,
String? description,
required PortForwardType type,
String? localHost,
@Default(0) int localPort,
String? remoteHost,
int? remotePort,
}) = _PortForwardConfig;
factory PortForwardConfig.fromJson(Map<String, dynamic> json) => _$PortForwardConfigFromJson(json);
factory PortForwardConfig.fromJson(Map<String, dynamic> json) {
PortForwardType type;
if (json['type'] == null) {
type = PortForwardType.local;
} else {
final typeStr = json['type'] as String;
type = PortForwardType.values.firstWhere(
(e) => e.name == typeStr,
orElse: () => PortForwardType.local,
);
}
return PortForwardConfig(
id: json['id'] as String,
serverId: json['serverId'] as String,
name: json['name'] as String,
type: type,
localHost: json['localHost'] as String?,
localPort: (json['localPort'] as num?)?.toInt() ?? 0,
remoteHost: json['remoteHost'] as String?,
remotePort: (json['remotePort'] as num?)?.toInt(),
);
}
const PortForwardConfig._();
String get displayAddr => '$localHost:$localPort$remoteHost:$remotePort';
String get displayAddr {
final localBindHost =
localHost ?? 'localhost';
if (type == PortForwardType.dynamic) {
return '$localBindHost:$localPort (SOCKS5)';
}
if (type == PortForwardType.remote) {
final remoteBindHost = remoteHost ?? '?';
return '$remoteBindHost:${remotePort ?? "?"}$localBindHost:$localPort';
}
return '$localBindHost:$localPort${remoteHost ?? "?"}:${remotePort ?? "?"}';
}
}
@freezed

View File

@@ -11,33 +11,30 @@ part of 'port_forward.dart';
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$PortForwardConfig {
String get id; String get serverId; String get name; String get localHost; int get localPort; String get remoteHost; int get remotePort; String? get description;
String get id; String get serverId; String get name; PortForwardType get type; String? get localHost; int get localPort; String? get remoteHost; int? get remotePort;
/// Create a copy of PortForwardConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PortForwardConfigCopyWith<PortForwardConfig> get copyWith => _$PortForwardConfigCopyWithImpl<PortForwardConfig>(this as PortForwardConfig, _$identity);
/// Serializes this PortForwardConfig to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PortForwardConfig&&(identical(other.id, id) || other.id == id)&&(identical(other.serverId, serverId) || other.serverId == serverId)&&(identical(other.name, name) || other.name == name)&&(identical(other.localHost, localHost) || other.localHost == localHost)&&(identical(other.localPort, localPort) || other.localPort == localPort)&&(identical(other.remoteHost, remoteHost) || other.remoteHost == remoteHost)&&(identical(other.remotePort, remotePort) || other.remotePort == remotePort)&&(identical(other.description, description) || other.description == description));
return identical(this, other) || (other.runtimeType == runtimeType&&other is PortForwardConfig&&(identical(other.id, id) || other.id == id)&&(identical(other.serverId, serverId) || other.serverId == serverId)&&(identical(other.name, name) || other.name == name)&&(identical(other.type, type) || other.type == type)&&(identical(other.localHost, localHost) || other.localHost == localHost)&&(identical(other.localPort, localPort) || other.localPort == localPort)&&(identical(other.remoteHost, remoteHost) || other.remoteHost == remoteHost)&&(identical(other.remotePort, remotePort) || other.remotePort == remotePort));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,serverId,name,localHost,localPort,remoteHost,remotePort,description);
int get hashCode => Object.hash(runtimeType,id,serverId,name,type,localHost,localPort,remoteHost,remotePort);
@override
String toString() {
return 'PortForwardConfig(id: $id, serverId: $serverId, name: $name, localHost: $localHost, localPort: $localPort, remoteHost: $remoteHost, remotePort: $remotePort, description: $description)';
return 'PortForwardConfig(id: $id, serverId: $serverId, name: $name, type: $type, localHost: $localHost, localPort: $localPort, remoteHost: $remoteHost, remotePort: $remotePort)';
}
@@ -48,7 +45,7 @@ abstract mixin class $PortForwardConfigCopyWith<$Res> {
factory $PortForwardConfigCopyWith(PortForwardConfig value, $Res Function(PortForwardConfig) _then) = _$PortForwardConfigCopyWithImpl;
@useResult
$Res call({
String id, String serverId, String name, String localHost, int localPort, String remoteHost, int remotePort, String? description
String id, String serverId, String name, PortForwardType type, String? localHost, int localPort, String? remoteHost, int? remotePort
});
@@ -65,17 +62,17 @@ class _$PortForwardConfigCopyWithImpl<$Res>
/// Create a copy of PortForwardConfig
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? serverId = null,Object? name = null,Object? localHost = null,Object? localPort = null,Object? remoteHost = null,Object? remotePort = null,Object? description = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? serverId = null,Object? name = null,Object? type = null,Object? localHost = freezed,Object? localPort = null,Object? remoteHost = freezed,Object? remotePort = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,serverId: null == serverId ? _self.serverId : serverId // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,localHost: null == localHost ? _self.localHost : localHost // ignore: cast_nullable_to_non_nullable
as String,localPort: null == localPort ? _self.localPort : localPort // ignore: cast_nullable_to_non_nullable
as int,remoteHost: null == remoteHost ? _self.remoteHost : remoteHost // ignore: cast_nullable_to_non_nullable
as String,remotePort: null == remotePort ? _self.remotePort : remotePort // ignore: cast_nullable_to_non_nullable
as int,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as PortForwardType,localHost: freezed == localHost ? _self.localHost : localHost // ignore: cast_nullable_to_non_nullable
as String?,localPort: null == localPort ? _self.localPort : localPort // ignore: cast_nullable_to_non_nullable
as int,remoteHost: freezed == remoteHost ? _self.remoteHost : remoteHost // ignore: cast_nullable_to_non_nullable
as String?,remotePort: freezed == remotePort ? _self.remotePort : remotePort // ignore: cast_nullable_to_non_nullable
as int?,
));
}
@@ -160,10 +157,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String serverId, String name, String localHost, int localPort, String remoteHost, int remotePort, String? description)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String serverId, String name, PortForwardType type, String? localHost, int localPort, String? remoteHost, int? remotePort)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PortForwardConfig() when $default != null:
return $default(_that.id,_that.serverId,_that.name,_that.localHost,_that.localPort,_that.remoteHost,_that.remotePort,_that.description);case _:
return $default(_that.id,_that.serverId,_that.name,_that.type,_that.localHost,_that.localPort,_that.remoteHost,_that.remotePort);case _:
return orElse();
}
@@ -181,10 +178,10 @@ return $default(_that.id,_that.serverId,_that.name,_that.localHost,_that.localPo
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String serverId, String name, String localHost, int localPort, String remoteHost, int remotePort, String? description) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String serverId, String name, PortForwardType type, String? localHost, int localPort, String? remoteHost, int? remotePort) $default,) {final _that = this;
switch (_that) {
case _PortForwardConfig():
return $default(_that.id,_that.serverId,_that.name,_that.localHost,_that.localPort,_that.remoteHost,_that.remotePort,_that.description);case _:
return $default(_that.id,_that.serverId,_that.name,_that.type,_that.localHost,_that.localPort,_that.remoteHost,_that.remotePort);case _:
throw StateError('Unexpected subclass');
}
@@ -201,10 +198,10 @@ return $default(_that.id,_that.serverId,_that.name,_that.localHost,_that.localPo
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String serverId, String name, String localHost, int localPort, String remoteHost, int remotePort, String? description)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String serverId, String name, PortForwardType type, String? localHost, int localPort, String? remoteHost, int? remotePort)? $default,) {final _that = this;
switch (_that) {
case _PortForwardConfig() when $default != null:
return $default(_that.id,_that.serverId,_that.name,_that.localHost,_that.localPort,_that.remoteHost,_that.remotePort,_that.description);case _:
return $default(_that.id,_that.serverId,_that.name,_that.type,_that.localHost,_that.localPort,_that.remoteHost,_that.remotePort);case _:
return null;
}
@@ -213,20 +210,20 @@ return $default(_that.id,_that.serverId,_that.name,_that.localHost,_that.localPo
}
/// @nodoc
@JsonSerializable()
class _PortForwardConfig extends PortForwardConfig {
const _PortForwardConfig({required this.id, required this.serverId, required this.name, this.localHost = 'localhost', required this.localPort, required this.remoteHost, required this.remotePort, this.description}): super._();
factory _PortForwardConfig.fromJson(Map<String, dynamic> json) => _$PortForwardConfigFromJson(json);
const _PortForwardConfig({required this.id, required this.serverId, required this.name, required this.type, this.localHost, this.localPort = 0, this.remoteHost, this.remotePort}): super._();
@override final String id;
@override final String serverId;
@override final String name;
@override@JsonKey() final String localHost;
@override final int localPort;
@override final String remoteHost;
@override final int remotePort;
@override final String? description;
@override final PortForwardType type;
@override final String? localHost;
@override@JsonKey() final int localPort;
@override final String? remoteHost;
@override final int? remotePort;
/// Create a copy of PortForwardConfig
/// with the given fields replaced by the non-null parameter values.
@@ -234,23 +231,20 @@ class _PortForwardConfig extends PortForwardConfig {
@pragma('vm:prefer-inline')
_$PortForwardConfigCopyWith<_PortForwardConfig> get copyWith => __$PortForwardConfigCopyWithImpl<_PortForwardConfig>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$PortForwardConfigToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PortForwardConfig&&(identical(other.id, id) || other.id == id)&&(identical(other.serverId, serverId) || other.serverId == serverId)&&(identical(other.name, name) || other.name == name)&&(identical(other.localHost, localHost) || other.localHost == localHost)&&(identical(other.localPort, localPort) || other.localPort == localPort)&&(identical(other.remoteHost, remoteHost) || other.remoteHost == remoteHost)&&(identical(other.remotePort, remotePort) || other.remotePort == remotePort)&&(identical(other.description, description) || other.description == description));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PortForwardConfig&&(identical(other.id, id) || other.id == id)&&(identical(other.serverId, serverId) || other.serverId == serverId)&&(identical(other.name, name) || other.name == name)&&(identical(other.type, type) || other.type == type)&&(identical(other.localHost, localHost) || other.localHost == localHost)&&(identical(other.localPort, localPort) || other.localPort == localPort)&&(identical(other.remoteHost, remoteHost) || other.remoteHost == remoteHost)&&(identical(other.remotePort, remotePort) || other.remotePort == remotePort));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,serverId,name,localHost,localPort,remoteHost,remotePort,description);
int get hashCode => Object.hash(runtimeType,id,serverId,name,type,localHost,localPort,remoteHost,remotePort);
@override
String toString() {
return 'PortForwardConfig(id: $id, serverId: $serverId, name: $name, localHost: $localHost, localPort: $localPort, remoteHost: $remoteHost, remotePort: $remotePort, description: $description)';
return 'PortForwardConfig(id: $id, serverId: $serverId, name: $name, type: $type, localHost: $localHost, localPort: $localPort, remoteHost: $remoteHost, remotePort: $remotePort)';
}
@@ -261,7 +255,7 @@ abstract mixin class _$PortForwardConfigCopyWith<$Res> implements $PortForwardCo
factory _$PortForwardConfigCopyWith(_PortForwardConfig value, $Res Function(_PortForwardConfig) _then) = __$PortForwardConfigCopyWithImpl;
@override @useResult
$Res call({
String id, String serverId, String name, String localHost, int localPort, String remoteHost, int remotePort, String? description
String id, String serverId, String name, PortForwardType type, String? localHost, int localPort, String? remoteHost, int? remotePort
});
@@ -278,17 +272,17 @@ class __$PortForwardConfigCopyWithImpl<$Res>
/// Create a copy of PortForwardConfig
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? serverId = null,Object? name = null,Object? localHost = null,Object? localPort = null,Object? remoteHost = null,Object? remotePort = null,Object? description = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? serverId = null,Object? name = null,Object? type = null,Object? localHost = freezed,Object? localPort = null,Object? remoteHost = freezed,Object? remotePort = freezed,}) {
return _then(_PortForwardConfig(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,serverId: null == serverId ? _self.serverId : serverId // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,localHost: null == localHost ? _self.localHost : localHost // ignore: cast_nullable_to_non_nullable
as String,localPort: null == localPort ? _self.localPort : localPort // ignore: cast_nullable_to_non_nullable
as int,remoteHost: null == remoteHost ? _self.remoteHost : remoteHost // ignore: cast_nullable_to_non_nullable
as String,remotePort: null == remotePort ? _self.remotePort : remotePort // ignore: cast_nullable_to_non_nullable
as int,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as PortForwardType,localHost: freezed == localHost ? _self.localHost : localHost // ignore: cast_nullable_to_non_nullable
as String?,localPort: null == localPort ? _self.localPort : localPort // ignore: cast_nullable_to_non_nullable
as int,remoteHost: freezed == remoteHost ? _self.remoteHost : remoteHost // ignore: cast_nullable_to_non_nullable
as String?,remotePort: freezed == remotePort ? _self.remotePort : remotePort // ignore: cast_nullable_to_non_nullable
as int?,
));
}

View File

@@ -1,31 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'port_forward.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_PortForwardConfig _$PortForwardConfigFromJson(Map<String, dynamic> json) =>
_PortForwardConfig(
id: json['id'] as String,
serverId: json['serverId'] as String,
name: json['name'] as String,
localHost: json['localHost'] as String? ?? 'localhost',
localPort: (json['localPort'] as num).toInt(),
remoteHost: json['remoteHost'] as String,
remotePort: (json['remotePort'] as num).toInt(),
description: json['description'] as String?,
);
Map<String, dynamic> _$PortForwardConfigToJson(_PortForwardConfig instance) =>
<String, dynamic>{
'id': instance.id,
'serverId': instance.serverId,
'name': instance.name,
'localHost': instance.localHost,
'localPort': instance.localPort,
'remoteHost': instance.remoteHost,
'remotePort': instance.remotePort,
'description': instance.description,
};