feat: Add compact table views with advanced filtering and batch operations
- Enhanced Flutter web app management in PowerShell scripts - Added Flutter web server to start-services.ps1 as 4th service - Updated stop-services.ps1 to stop Flutter web server - Improved service orchestration and startup sequence - Implemented server caching for improved resilience - Added ServerCacheService for browser localStorage caching - Server lists persist across service restarts - Automatic fallback to cached data when API unavailable - Action picker categories always visible regardless of server status - Redesigned Action Mappings view with compact table layout - Replaced card-based ListView with DataTable for higher density - Added real-time search across name, input, output, description - Implemented multi-filter support (status: enabled/disabled) - Added column sorting (name, input, output, status, executions) - Batch operations: select all/multiple, batch delete - Reduced row height from ~120px to 56px for better overview - Redesigned Servers Management view with compact table layout - Replaced card-based ListView with DataTable - Added search by alias, host, user - Multi-filter support (type: all/G-Core/GeViScope, status: all/enabled/disabled) - Column sorting (alias, host, user, type, status) - Batch operations: select all/multiple, batch delete - Color-coded type and status badges - Improved action picker dialog for GSC/G-Core actions - GSC and G-Core categories always visible - Server validation with clear error messages - Fixed duplicate checkbox issue in table headers - Debug logging for troubleshooting server parameter issues 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,10 @@ class _ActionPickerDialogState extends State<ActionPickerDialog> {
|
||||
_selectedActionName = actionName;
|
||||
_selectedTemplate = widget.templates[actionName];
|
||||
|
||||
print('[DEBUG] _selectAction: action=$actionName, categoryPrefix=$_categoryPrefix');
|
||||
print('[DEBUG] _selectAction: gscServers count=${widget.gscServers.length}');
|
||||
print('[DEBUG] _selectAction: gcoreServers count=${widget.gcoreServers.length}');
|
||||
|
||||
// Clear previous parameter controllers
|
||||
for (final controller in _paramControllers.values) {
|
||||
controller.dispose();
|
||||
@@ -121,6 +125,7 @@ class _ActionPickerDialogState extends State<ActionPickerDialog> {
|
||||
|
||||
// Initialize new parameter controllers
|
||||
if (_selectedTemplate != null) {
|
||||
print('[DEBUG] _selectAction: template parameters=${_selectedTemplate!.parameters}');
|
||||
for (final param in _selectedTemplate!.parameters) {
|
||||
_paramControllers[param] = TextEditingController();
|
||||
_paramEnabled[param] = false; // Start with parameters disabled
|
||||
@@ -130,29 +135,41 @@ class _ActionPickerDialogState extends State<ActionPickerDialog> {
|
||||
if (_categoryPrefix == 'gcore') {
|
||||
// Add G-Core server parameter
|
||||
const serverParam = 'GCoreServer';
|
||||
print('[DEBUG] Adding G-Core server parameter');
|
||||
if (!_paramControllers.containsKey(serverParam)) {
|
||||
_paramControllers[serverParam] = TextEditingController();
|
||||
print('[DEBUG] Created new controller for $serverParam');
|
||||
}
|
||||
_paramEnabled[serverParam] = true;
|
||||
// Auto-select first enabled server if available
|
||||
final enabledServers = widget.gcoreServers.where((s) => s.enabled).toList();
|
||||
if (enabledServers.isNotEmpty) {
|
||||
_paramControllers[serverParam]?.text = enabledServers.first.alias;
|
||||
print('[DEBUG] Set $serverParam to ${enabledServers.first.alias}');
|
||||
}
|
||||
} else if (_categoryPrefix == 'gsc') {
|
||||
// Add GSC server parameter
|
||||
const serverParam = 'GscServer';
|
||||
print('[DEBUG] Adding GSC server parameter');
|
||||
if (!_paramControllers.containsKey(serverParam)) {
|
||||
_paramControllers[serverParam] = TextEditingController();
|
||||
print('[DEBUG] Created new controller for $serverParam');
|
||||
}
|
||||
_paramEnabled[serverParam] = true;
|
||||
// Auto-select first enabled server if available
|
||||
final enabledServers = widget.gscServers.where((s) => s.enabled).toList();
|
||||
print('[DEBUG] GSC enabled servers: ${enabledServers.map((s) => s.alias).toList()}');
|
||||
if (enabledServers.isNotEmpty) {
|
||||
_paramControllers[serverParam]?.text = enabledServers.first.alias;
|
||||
print('[DEBUG] Set $serverParam to ${enabledServers.first.alias}');
|
||||
} else {
|
||||
print('[DEBUG] WARNING: No enabled GSC servers found!');
|
||||
}
|
||||
}
|
||||
|
||||
print('[DEBUG] Final _paramEnabled keys: ${_paramEnabled.keys.toList()}');
|
||||
print('[DEBUG] Final _paramControllers keys: ${_paramControllers.keys.toList()}');
|
||||
|
||||
// Auto-fill caption if empty
|
||||
if (_captionController.text.isEmpty) {
|
||||
_captionController.text = actionName;
|
||||
@@ -197,13 +214,47 @@ class _ActionPickerDialogState extends State<ActionPickerDialog> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate server parameter for GSC/G-Core actions
|
||||
if (_categoryPrefix == 'gsc') {
|
||||
final gscServerValue = _paramControllers['GscServer']?.text.trim() ?? '';
|
||||
if (gscServerValue.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('GSC server is required. Please configure a GeViScope server first.'),
|
||||
backgroundColor: Colors.red,
|
||||
duration: Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else if (_categoryPrefix == 'gcore') {
|
||||
final gcoreServerValue = _paramControllers['GCoreServer']?.text.trim() ?? '';
|
||||
if (gcoreServerValue.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('G-Core server is required. Please configure a G-Core server first.'),
|
||||
backgroundColor: Colors.red,
|
||||
duration: Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
print('[DEBUG] _onOk: Building parameters...');
|
||||
print('[DEBUG] _onOk: _categoryPrefix=$_categoryPrefix');
|
||||
print('[DEBUG] _onOk: _paramEnabled=${_paramEnabled}');
|
||||
|
||||
// Build parameters map from enabled parameters
|
||||
final parameters = <String, dynamic>{};
|
||||
for (final entry in _paramEnabled.entries) {
|
||||
print('[DEBUG] _onOk: Checking param ${entry.key}, enabled=${entry.value}');
|
||||
if (entry.value) {
|
||||
final value = _paramControllers[entry.key]?.text.trim() ?? '';
|
||||
print('[DEBUG] _onOk: Param ${entry.key} value="$value"');
|
||||
if (value.isNotEmpty) {
|
||||
parameters[entry.key] = value;
|
||||
print('[DEBUG] _onOk: Added param ${entry.key}=$value');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,12 +271,16 @@ class _ActionPickerDialogState extends State<ActionPickerDialog> {
|
||||
parameters['Delay'] = delay;
|
||||
}
|
||||
|
||||
print('[DEBUG] _onOk: Final parameters=$parameters');
|
||||
|
||||
// Create ActionOutput with the actual action name (NOT the caption!)
|
||||
final result = ActionOutput(
|
||||
action: _selectedActionName!,
|
||||
parameters: parameters,
|
||||
);
|
||||
|
||||
print('[DEBUG] _onOk: Created ActionOutput: action=$_selectedActionName, parameters=$parameters');
|
||||
|
||||
Navigator.of(context).pop(result);
|
||||
}
|
||||
|
||||
@@ -237,15 +292,19 @@ class _ActionPickerDialogState extends State<ActionPickerDialog> {
|
||||
enhanced.addAll(widget.categories);
|
||||
|
||||
// Add G-Core variants for applicable categories
|
||||
// ALWAYS show these categories - even if no servers are configured
|
||||
// User can see the category but won't be able to select a server if none exist
|
||||
for (final category in _serverCategories) {
|
||||
if (widget.categories.containsKey(category) && widget.gcoreServers.isNotEmpty) {
|
||||
if (widget.categories.containsKey(category)) {
|
||||
enhanced['G-Core: $category'] = widget.categories[category]!;
|
||||
}
|
||||
}
|
||||
|
||||
// Add GSC variants for applicable categories
|
||||
// ALWAYS show these categories - even if no servers are configured
|
||||
// User can see the category but won't be able to select a server if none exist
|
||||
for (final category in _serverCategories) {
|
||||
if (widget.categories.containsKey(category) && widget.gscServers.isNotEmpty) {
|
||||
if (widget.categories.containsKey(category)) {
|
||||
enhanced['GSC: $category'] = widget.categories[category]!;
|
||||
}
|
||||
}
|
||||
@@ -258,12 +317,15 @@ class _ActionPickerDialogState extends State<ActionPickerDialog> {
|
||||
if (displayCategory.startsWith('G-Core: ')) {
|
||||
_categoryPrefix = 'gcore';
|
||||
_selectedCategory = displayCategory.substring(8); // Remove "G-Core: "
|
||||
print('[DEBUG] Parsed category: prefix=gcore, base=$_selectedCategory');
|
||||
} else if (displayCategory.startsWith('GSC: ')) {
|
||||
_categoryPrefix = 'gsc';
|
||||
_selectedCategory = displayCategory.substring(5); // Remove "GSC: "
|
||||
print('[DEBUG] Parsed category: prefix=gsc, base=$_selectedCategory');
|
||||
} else {
|
||||
_categoryPrefix = null;
|
||||
_selectedCategory = displayCategory;
|
||||
print('[DEBUG] Parsed category: prefix=null, base=$_selectedCategory');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user