From 4ec263414dc57e34ead4ead5483f518e7bb038ff Mon Sep 17 00:00:00 2001 From: Deepak Mahat Date: Fri, 28 Mar 2025 16:51:30 +1030 Subject: [PATCH] vm conf editor added --- lib/src/model/vm_config.dart | 21 +++++++ lib/src/pages/manager.dart | 20 ++++++ lib/src/pages/vm_config_editor.dart | 97 +++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 lib/src/model/vm_config.dart create mode 100644 lib/src/pages/vm_config_editor.dart diff --git a/lib/src/model/vm_config.dart b/lib/src/model/vm_config.dart new file mode 100644 index 0000000..c2dcc78 --- /dev/null +++ b/lib/src/model/vm_config.dart @@ -0,0 +1,21 @@ +import 'dart:io'; + +class VmConfig { + final String name; + final File file; + String rawContent = ''; + + VmConfig({required this.name, required String confPath}) + : file = File(confPath); + + Future load() async { + if (file.existsSync()) { + rawContent = await file.readAsString(); + } + } + + Future save(String updatedContent) async { + rawContent = updatedContent; + await file.writeAsString(rawContent); + } +} diff --git a/lib/src/pages/manager.dart b/lib/src/pages/manager.dart index 6213562..35f2b8c 100644 --- a/lib/src/pages/manager.dart +++ b/lib/src/pages/manager.dart @@ -15,6 +15,7 @@ import '../globals.dart'; import '../mixins/preferences_mixin.dart'; import '../model/osicons.dart'; import '../model/vminfo.dart'; +import '../pages/vm_config_editor.dart'; /// VM manager page. /// Displays a list of available VMs, running state and connection info, @@ -28,6 +29,7 @@ class Manager extends StatefulWidget { class _ManagerState extends State with PreferencesMixin { List _currentVms = []; + Map _vmConfFiles = {}; Map _activeVms = {}; bool _spicy = false; final List _sshVms = []; @@ -146,6 +148,7 @@ class _ManagerState extends State with PreferencesMixin { void _getVms(context) async { List currentVms = []; + Map vmConfFiles = {}; Map activeVms = {}; await for (var entity @@ -153,6 +156,7 @@ class _ManagerState extends State with PreferencesMixin { if ((entity.path.endsWith('.conf')) && (_isValidConf(entity.path))) { String name = path.basenameWithoutExtension(entity.path); currentVms.add(name); + vmConfFiles[name] = entity.path; File pidFile = File('$name/$name.pid'); if (pidFile.existsSync()) { String pid = pidFile.readAsStringSync().trim(); @@ -172,6 +176,7 @@ class _ManagerState extends State with PreferencesMixin { currentVms.sort(); setState(() { _currentVms = currentVms; + _vmConfFiles = vmConfFiles; _activeVms = activeVms; }); } @@ -291,6 +296,21 @@ class _ManagerState extends State with PreferencesMixin { trailing: Row( mainAxisSize: MainAxisSize.min, children: [ + IconButton( + icon: const Icon(Icons.settings, semanticLabel: 'Settings'), + tooltip: context.t('Edit configuration'), + onPressed: active + ? null + : () { + showDialog( + context: context, + builder: (context) => VmConfigEditorDialog( + vmName: currentVm, + confPath: _vmConfFiles[currentVm]!, + ), + ); + }, + ), IconButton( icon: Icon( active ? Icons.play_arrow : Icons.play_arrow_outlined, diff --git a/lib/src/pages/vm_config_editor.dart b/lib/src/pages/vm_config_editor.dart new file mode 100644 index 0000000..57735c6 --- /dev/null +++ b/lib/src/pages/vm_config_editor.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../model/vm_config.dart'; + +class VmConfigEditorDialog extends StatefulWidget { + final String vmName; + final String confPath; + + const VmConfigEditorDialog({ + super.key, + required this.vmName, + required this.confPath, + }); + + @override + State createState() => _VmConfigEditorDialogState(); +} + +class _VmConfigEditorDialogState extends State { + late VmConfig config; + final TextEditingController _editorController = TextEditingController(); + bool _loading = true; + + @override + void initState() { + super.initState(); + config = VmConfig(name: widget.vmName, confPath: widget.confPath); + config.load().then((_) { + _editorController.text = config.rawContent; + setState(() => _loading = false); + }); + } + + void _save() async { + await config.save(_editorController.text); + if(!mounted) return; + Navigator.of(context).pop(); + } + + void _openDocs() async { + const url = 'https://github.com/quickemu-project/quickemu/blob/master/docs/quickemu_conf.5.md'; + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('Edit VM: ${widget.vmName}'), + content: _loading + ? const SizedBox(height: 100, child: Center(child: CircularProgressIndicator())) + : SizedBox( + width: double.maxFinite, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(config.file.path, + style: const TextStyle(fontSize: 12, color: Colors.grey)), + Align( + alignment: Alignment.centerRight, + child: TextButton.icon( + icon: const Icon(Icons.help_outline), + label: const Text("Open config documentation"), + onPressed: _openDocs, + ), + ), + const SizedBox(height: 8), + Flexible( + child: TextField( + controller: _editorController, + maxLines: null, + keyboardType: TextInputType.multiline, + style: const TextStyle(fontFamily: 'monospace'), + decoration: const InputDecoration( + border: OutlineInputBorder(), + contentPadding: EdgeInsets.all(8), + ), + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: _save, + child: const Text('Save'), + ), + ], + ); + } +}