mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
@@ -265,6 +265,35 @@ extension _Actions on _ServerEditPageState {
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyCommand configuration
|
||||
ProxyCommandConfig? proxyCommand;
|
||||
if (_proxyCommandEnabled.value) {
|
||||
final command = _proxyCommandController.text.trim();
|
||||
if (command.isEmpty) {
|
||||
context.showSnackBar('ProxyCommand is enabled but command is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if command contains required placeholders
|
||||
if (!command.contains('%h')) {
|
||||
context.showSnackBar('ProxyCommand must contain %h (hostname) placeholder');
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if this requires an executable
|
||||
final parts = command.split(' ');
|
||||
final executable = parts.first;
|
||||
final requiresExecutable = !['ssh', 'nc', 'socat'].contains(executable);
|
||||
|
||||
proxyCommand = ProxyCommandConfig(
|
||||
command: command,
|
||||
timeout: Duration(seconds: _proxyCommandTimeout.value),
|
||||
retryOnFailure: true,
|
||||
requiresExecutable: requiresExecutable,
|
||||
executableName: requiresExecutable ? executable : null,
|
||||
);
|
||||
}
|
||||
|
||||
final spi = Spi(
|
||||
name: _nameController.text.isEmpty ? _ipController.text : _nameController.text,
|
||||
ip: _ipController.text,
|
||||
@@ -284,6 +313,7 @@ extension _Actions on _ServerEditPageState {
|
||||
id: widget.args?.spi.id ?? ShortId.generate(),
|
||||
customSystemType: _systemType.value,
|
||||
disabledCmdTypes: _disabledCmdTypes.value.isEmpty ? null : _disabledCmdTypes.value.toList(),
|
||||
proxyCommand: proxyCommand,
|
||||
);
|
||||
|
||||
if (this.spi == null) {
|
||||
@@ -450,5 +480,27 @@ extension _Utils on _ServerEditPageState {
|
||||
final allAvailableCmdTypes = ShellCmdType.all.map((e) => e.displayName);
|
||||
disabledCmdTypes.removeWhere((e) => !allAvailableCmdTypes.contains(e));
|
||||
_disabledCmdTypes.value = disabledCmdTypes;
|
||||
|
||||
// Load ProxyCommand configuration
|
||||
final proxyCommand = spi.proxyCommand;
|
||||
if (proxyCommand != null) {
|
||||
_proxyCommandEnabled.value = true;
|
||||
_proxyCommandController.text = proxyCommand.command;
|
||||
_proxyCommandTimeout.value = proxyCommand.timeout.inSeconds;
|
||||
|
||||
// Try to match with a preset
|
||||
final presets = ProxyCommandExecutor.getPresets();
|
||||
for (final entry in presets.entries) {
|
||||
if (entry.value.command == proxyCommand.command) {
|
||||
_proxyCommandPreset.value = entry.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_proxyCommandEnabled.value = false;
|
||||
_proxyCommandController.text = '';
|
||||
_proxyCommandTimeout.value = 30;
|
||||
_proxyCommandPreset.value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/route.dart';
|
||||
import 'package:server_box/core/utils/proxy_command_executor.dart';
|
||||
import 'package:server_box/core/utils/server_dedup.dart';
|
||||
import 'package:server_box/core/utils/ssh_config.dart';
|
||||
import 'package:server_box/data/model/app/scripts/cmd_types.dart';
|
||||
import 'package:server_box/data/model/server/custom.dart';
|
||||
import 'package:server_box/data/model/server/discovery_result.dart';
|
||||
import 'package:server_box/data/model/server/proxy_command_config.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
import 'package:server_box/data/model/server/wol_cfg.dart';
|
||||
@@ -74,6 +76,12 @@ class _ServerEditPageState extends ConsumerState<ServerEditPage> with AfterLayou
|
||||
final _systemType = ValueNotifier<SystemType?>(null);
|
||||
final _disabledCmdTypes = <String>{}.vn;
|
||||
|
||||
// ProxyCommand fields
|
||||
final _proxyCommandEnabled = ValueNotifier(false);
|
||||
final _proxyCommandController = TextEditingController();
|
||||
final _proxyCommandPreset = nvn<String>();
|
||||
final _proxyCommandTimeout = ValueNotifier(30);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
@@ -107,6 +115,11 @@ class _ServerEditPageState extends ConsumerState<ServerEditPage> with AfterLayou
|
||||
_tags.dispose();
|
||||
_systemType.dispose();
|
||||
_disabledCmdTypes.dispose();
|
||||
|
||||
_proxyCommandEnabled.dispose();
|
||||
_proxyCommandController.dispose();
|
||||
_proxyCommandPreset.dispose();
|
||||
_proxyCommandTimeout.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -200,6 +213,7 @@ class _ServerEditPageState extends ConsumerState<ServerEditPage> with AfterLayou
|
||||
_buildAuth(),
|
||||
_buildSystemType(),
|
||||
_buildJumpServer(),
|
||||
_buildProxyCommand(),
|
||||
_buildMore(),
|
||||
];
|
||||
return AutoMultiList(children: children);
|
||||
|
||||
@@ -449,6 +449,115 @@ extension _Widgets on _ServerEditPageState {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProxyCommand() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text('ProxyCommand'),
|
||||
subtitle: Text('Use a proxy command for SSH connection'),
|
||||
trailing: _proxyCommandEnabled.listenVal(
|
||||
(enabled) => Switch(
|
||||
value: enabled,
|
||||
onChanged: (value) {
|
||||
_proxyCommandEnabled.value = value;
|
||||
if (value && _proxyCommandController.text.isEmpty) {
|
||||
// Set default preset when enabled
|
||||
_proxyCommandPreset.value = 'cloudflare_access';
|
||||
_proxyCommandController.text = 'cloudflared access ssh --hostname %h';
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
_proxyCommandEnabled.listenVal((enabled) {
|
||||
if (!enabled) return const SizedBox.shrink();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Preset selection
|
||||
Text('Presets:', style: Theme.of(context).textTheme.titleSmall),
|
||||
const SizedBox(height: 8),
|
||||
_proxyCommandPreset.listenVal((preset) {
|
||||
final presets = ProxyCommandExecutor.getPresets();
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: presets.entries.map((entry) {
|
||||
final isSelected = preset == entry.key;
|
||||
return FilterChip(
|
||||
label: Text(_getPresetDisplayName(entry.key)),
|
||||
selected: isSelected,
|
||||
onSelected: (selected) {
|
||||
if (selected) {
|
||||
_proxyCommandPreset.value = entry.key;
|
||||
_proxyCommandController.text = entry.value.command;
|
||||
}
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Custom command input
|
||||
Input(
|
||||
controller: _proxyCommandController,
|
||||
type: TextInputType.text,
|
||||
label: 'Proxy Command',
|
||||
icon: Icons.settings_ethernet,
|
||||
hint: 'e.g., cloudflared access ssh --hostname %h',
|
||||
suggestion: false,
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Help text
|
||||
Text(
|
||||
'Available placeholders:\n'
|
||||
'• %h - hostname\n'
|
||||
'• %p - port\n'
|
||||
'• %r - username',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Timeout setting
|
||||
_proxyCommandTimeout.listenVal((timeout) {
|
||||
return ListTile(
|
||||
title: Text('Connection Timeout'),
|
||||
subtitle: Text('$timeout seconds'),
|
||||
trailing: DropdownButton<int>(
|
||||
value: timeout,
|
||||
items: [10, 30, 60, 120].map((seconds) {
|
||||
return DropdownMenuItem(
|
||||
value: seconds,
|
||||
child: Text('${seconds}s'),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
_proxyCommandTimeout.value = value;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDelBtn() {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
@@ -472,4 +581,21 @@ extension _Widgets on _ServerEditPageState {
|
||||
icon: const Icon(Icons.delete),
|
||||
);
|
||||
}
|
||||
|
||||
String _getPresetDisplayName(String presetKey) {
|
||||
switch (presetKey) {
|
||||
case 'cloudflare_access':
|
||||
return 'Cloudflare Access';
|
||||
case 'ssh_via_bastion':
|
||||
return 'SSH via Bastion';
|
||||
case 'nc_netcat':
|
||||
return 'Netcat';
|
||||
case 'socat':
|
||||
return 'Socat';
|
||||
default:
|
||||
return presetKey.split('_').map((word) =>
|
||||
word[0].toUpperCase() + word.substring(1)
|
||||
).join(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user