From d43d251d92715a354d430e45c6dd253ace303f55 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Sun, 11 Dec 2022 11:05:13 +0800 Subject: [PATCH] support add `docker container` --- lib/data/provider/docker.dart | 31 +++--- lib/generated/intl/messages_en.dart | 8 +- lib/generated/intl/messages_zh.dart | 7 +- lib/generated/l10n.dart | 48 ++++++++- lib/l10n/intl_en.arb | 6 +- lib/l10n/intl_zh.arb | 6 +- lib/view/page/docker.dart | 148 +++++++++++++++++++++------- 7 files changed, 191 insertions(+), 63 deletions(-) diff --git a/lib/data/provider/docker.dart b/lib/data/provider/docker.dart index e687828a..c1f491f4 100644 --- a/lib/data/provider/docker.dart +++ b/lib/data/provider/docker.dart @@ -15,6 +15,7 @@ import 'package:toolbox/locator.dart'; final _dockerNotFound = RegExp(r'command not found|Unknown command'); final _versionReg = RegExp(r'(Version:)\s+([0-9]+\.[0-9]+\.[0-9]+)'); final _editionReg = RegExp(r'(Client:)\s+(.+-.+)'); +final _dockerPrefixReg = RegExp(r'(sudo )?docker '); const _dockerPS = 'docker ps -a'; @@ -107,30 +108,22 @@ class DockerProvider extends BusyProvider { isRequestingPwd = false; } - Future _do(String id, String cmd) async { - setBusyState(); - final result = await client!.run('$cmd $id').string; - await refresh(); - setBusyState(false); - return result.contains(id); - } + Future stop(String id) async => await run('docker stop $id'); - Future stop(String id) async => await _do(id, 'docker stop'); + Future start(String id) async => await run('docker start $id'); - Future start(String id) async => await _do(id, 'docker start'); - - Future delete(String id) async => await _do(id, 'docker rm'); + Future delete(String id) async => await run('docker rm $id'); Future run(String cmd) async { - if (!cmd.startsWith('docker ')) { + if (!cmd.startsWith(_dockerPrefixReg)) { return DockerErr(type: DockerErrType.cmdNoPrefix); } setBusyState(); final errs = []; final code = await client!.exec( - cmd, - onStderr: (data, _) => errs.add(data), + _wrapHost(cmd), + onStderr: _onPwd, onStdout: (data, _) { runLog = '$runLog$data'; notifyListeners(); @@ -139,7 +132,7 @@ class DockerProvider extends BusyProvider { runLog = null; - if (errs.isNotEmpty || code != 0) { + if (code != 0) { setBusyState(false); return DockerErr(type: DockerErrType.unknown, message: errs.join('\n')); } @@ -147,4 +140,12 @@ class DockerProvider extends BusyProvider { setBusyState(false); return null; } + + String _wrapHost(String cmd) { + final dockerHost = locator().getDockerHost(hostId!); + if (dockerHost == null || dockerHost.isEmpty) { + return 'sudo $cmd'; + } + return 'export DOCKER_HOST=$dockerHost && $cmd'; + } } diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 3bb2cd2f..b00006bf 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -95,12 +95,13 @@ class MessageLookup extends MessageLookupByLibrary { "delete": MessageLookupByLibrary.simpleMessage("Delete"), "disconnected": MessageLookupByLibrary.simpleMessage("Disconnected"), "dl2Local": m0, - "dockerCmdPrefixErr": MessageLookupByLibrary.simpleMessage( - "Please make sure that the docker command prefix is correct."), + "dockerContainerName": + MessageLookupByLibrary.simpleMessage("Container name"), "dockerEditHost": MessageLookupByLibrary.simpleMessage("Edit DOCKER_HOST"), "dockerEmptyRunningItems": MessageLookupByLibrary.simpleMessage( "No running container. \nIt may be that the env DOCKER_HOST is not read correctly. You can found it by running `echo \$DOCKER_HOST` in terminal."), + "dockerImage": MessageLookupByLibrary.simpleMessage("Image"), "dockerNotInstalled": MessageLookupByLibrary.simpleMessage("Docker not installed"), "dockerStatusRunningAndStoppedFmt": m1, @@ -116,6 +117,7 @@ class MessageLookup extends MessageLookupByLibrary { "experimentalFeature": MessageLookupByLibrary.simpleMessage("Experimental feature"), "export": MessageLookupByLibrary.simpleMessage("Export"), + "extraArgs": MessageLookupByLibrary.simpleMessage("Extra args"), "feedback": MessageLookupByLibrary.simpleMessage("Feedback"), "feedbackOnGithub": MessageLookupByLibrary.simpleMessage( "If you have any questions, please feedback on Github."), @@ -154,6 +156,7 @@ class MessageLookup extends MessageLookupByLibrary { "min": MessageLookupByLibrary.simpleMessage("min"), "ms": MessageLookupByLibrary.simpleMessage("ms"), "name": MessageLookupByLibrary.simpleMessage("Name"), + "newContainer": MessageLookupByLibrary.simpleMessage("New container"), "noClient": MessageLookupByLibrary.simpleMessage("No client"), "noInterface": MessageLookupByLibrary.simpleMessage("No interface"), "noResult": MessageLookupByLibrary.simpleMessage("No result"), @@ -180,6 +183,7 @@ class MessageLookup extends MessageLookupByLibrary { "plzSelectKey": MessageLookupByLibrary.simpleMessage("Please select a key."), "port": MessageLookupByLibrary.simpleMessage("Port"), + "preview": MessageLookupByLibrary.simpleMessage("Preview"), "privateKey": MessageLookupByLibrary.simpleMessage("Private Key"), "pwd": MessageLookupByLibrary.simpleMessage("Password"), "rename": MessageLookupByLibrary.simpleMessage("Rename"), diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart index 6a6cf89b..1e4146eb 100644 --- a/lib/generated/intl/messages_zh.dart +++ b/lib/generated/intl/messages_zh.dart @@ -89,12 +89,12 @@ class MessageLookup extends MessageLookupByLibrary { "delete": MessageLookupByLibrary.simpleMessage("删除"), "disconnected": MessageLookupByLibrary.simpleMessage("连接断开"), "dl2Local": m0, - "dockerCmdPrefixErr": - MessageLookupByLibrary.simpleMessage("命令前缀错误,没有以 `docker` 开头"), + "dockerContainerName": MessageLookupByLibrary.simpleMessage("容器名"), "dockerEditHost": MessageLookupByLibrary.simpleMessage("编辑 DOCKER_HOST"), "dockerEmptyRunningItems": MessageLookupByLibrary.simpleMessage( "没有正在运行的容器。\n这可能是因为环境变量 DOCKER_HOST 没有被正确读取。你可以通过在终端内运行 `echo \$DOCKER_HOST` 来获取。"), + "dockerImage": MessageLookupByLibrary.simpleMessage("镜像"), "dockerNotInstalled": MessageLookupByLibrary.simpleMessage("Docker未安装"), "dockerStatusRunningAndStoppedFmt": m1, "dockerStatusRunningFmt": m2, @@ -107,6 +107,7 @@ class MessageLookup extends MessageLookupByLibrary { "exampleName": MessageLookupByLibrary.simpleMessage("名称示例"), "experimentalFeature": MessageLookupByLibrary.simpleMessage("实验性功能"), "export": MessageLookupByLibrary.simpleMessage("导出"), + "extraArgs": MessageLookupByLibrary.simpleMessage("额外参数"), "feedback": MessageLookupByLibrary.simpleMessage("反馈"), "feedbackOnGithub": MessageLookupByLibrary.simpleMessage("如果你有任何问题,请在GitHub反馈"), @@ -138,6 +139,7 @@ class MessageLookup extends MessageLookupByLibrary { "min": MessageLookupByLibrary.simpleMessage("最小"), "ms": MessageLookupByLibrary.simpleMessage("毫秒"), "name": MessageLookupByLibrary.simpleMessage("名称"), + "newContainer": MessageLookupByLibrary.simpleMessage("新建容器"), "noClient": MessageLookupByLibrary.simpleMessage("没有SSH连接"), "noInterface": MessageLookupByLibrary.simpleMessage("没有可用的接口"), "noResult": MessageLookupByLibrary.simpleMessage("无结果"), @@ -157,6 +159,7 @@ class MessageLookup extends MessageLookupByLibrary { "plzEnterHost": MessageLookupByLibrary.simpleMessage("请输入主机"), "plzSelectKey": MessageLookupByLibrary.simpleMessage("请选择私钥"), "port": MessageLookupByLibrary.simpleMessage("端口"), + "preview": MessageLookupByLibrary.simpleMessage("预览"), "privateKey": MessageLookupByLibrary.simpleMessage("私钥"), "pwd": MessageLookupByLibrary.simpleMessage("密码"), "rename": MessageLookupByLibrary.simpleMessage("重命名"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 67f603c9..75806453 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -1401,11 +1401,51 @@ class S { ); } - /// `Please make sure that the docker command prefix is correct.` - String get dockerCmdPrefixErr { + /// `New container` + String get newContainer { return Intl.message( - 'Please make sure that the docker command prefix is correct.', - name: 'dockerCmdPrefixErr', + 'New container', + name: 'newContainer', + desc: '', + args: [], + ); + } + + /// `Image` + String get dockerImage { + return Intl.message( + 'Image', + name: 'dockerImage', + desc: '', + args: [], + ); + } + + /// `Container name` + String get dockerContainerName { + return Intl.message( + 'Container name', + name: 'dockerContainerName', + desc: '', + args: [], + ); + } + + /// `Extra args` + String get extraArgs { + return Intl.message( + 'Extra args', + name: 'extraArgs', + desc: '', + args: [], + ); + } + + /// `Preview` + String get preview { + return Intl.message( + 'Preview', + name: 'preview', desc: '', args: [], ); diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 414a396a..8d0722ae 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -134,5 +134,9 @@ "cmd": "Command", "dockerEmptyRunningItems": "No running container. \nIt may be that the env DOCKER_HOST is not read correctly. You can found it by running `echo $DOCKER_HOST` in terminal.", "dockerEditHost": "Edit DOCKER_HOST", - "dockerCmdPrefixErr": "Please make sure that the docker command prefix is correct." + "newContainer": "New container", + "dockerImage": "Image", + "dockerContainerName": "Container name", + "extraArgs": "Extra args", + "preview": "Preview" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index cdd473a4..f1a4a1ca 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -134,5 +134,9 @@ "cmd": "命令", "dockerEmptyRunningItems": "没有正在运行的容器。\n这可能是因为环境变量 DOCKER_HOST 没有被正确读取。你可以通过在终端内运行 `echo $DOCKER_HOST` 来获取。", "dockerEditHost": "编辑 DOCKER_HOST", - "dockerCmdPrefixErr": "命令前缀错误,没有以 `docker` 开头" + "newContainer": "新建容器", + "dockerImage": "镜像", + "dockerContainerName": "容器名", + "extraArgs": "额外参数", + "preview": "预览" } \ No newline at end of file diff --git a/lib/view/page/docker.dart b/lib/view/page/docker.dart index 77c820ae..a13fd982 100644 --- a/lib/view/page/docker.dart +++ b/lib/view/page/docker.dart @@ -77,47 +77,103 @@ class _DockerManagePageState extends State { } Widget _buildFAB(DockerProvider docker) { - final c = TextEditingController(); return FloatingActionButton( - onPressed: () { - showRoundDialog( - context, - s.cmd, + onPressed: () async => await _showAddFAB(docker), + child: const Icon(Icons.add), + ); + } + + Future _showAddFAB(DockerProvider docker) async { + final imageCtrl = TextEditingController(); + final nameCtrl = TextEditingController(); + final argsCtrl = TextEditingController(); + await showRoundDialog( + context, + s.newContainer, + Column( + mainAxisSize: MainAxisSize.min, + children: [ TextField( - keyboardType: TextInputType.multiline, - maxLines: 7, - controller: c, + keyboardType: TextInputType.text, + decoration: InputDecoration( + labelText: s.dockerImage, hintText: 'ubuntu:22.10'), + controller: imageCtrl, autocorrect: false, ), - [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text(s.cancel), - ), - TextButton( - onPressed: () async { - Navigator.of(context).pop(); - final result = await docker.run(c.text.trim()); - if (result != null) { - showSnackBar( - context, Text(getErrMsg(result) ?? s.unknownError)); - } - }, - child: Text(s.run), - ) - ], - ); - }, - child: const Icon(Icons.code), + TextField( + keyboardType: TextInputType.text, + controller: nameCtrl, + decoration: InputDecoration( + labelText: s.dockerContainerName, hintText: 'ubuntu22'), + autocorrect: false, + ), + TextField( + keyboardType: TextInputType.text, + controller: argsCtrl, + decoration: InputDecoration( + labelText: s.extraArgs, hintText: '-p 2222:22 -v ~/.xxx/:/xxx'), + autocorrect: false, + ), + ], + ), + [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(s.cancel), + ), + TextButton( + onPressed: () async { + Navigator.of(context).pop(); + await _showAddCmdPreview(_buildAddCmd(imageCtrl.text.trim(), + nameCtrl.text.trim(), argsCtrl.text.trim())); + }, + child: Text(s.ok), + ) + ], ); } + Future _showAddCmdPreview(String cmd) async { + await showRoundDialog( + context, + s.preview, + Text(cmd), + [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(s.cancel), + ), + TextButton( + onPressed: () async { + Navigator.of(context).pop(); + final result = await _docker.run(cmd); + if (result != null) { + showSnackBar(context, Text(getErrMsg(result) ?? s.unknownError)); + } + }, + child: Text(s.run), + ) + ], + ); + } + + String _buildAddCmd(String image, String name, String args) { + var suffix = ''; + if (args.isEmpty) { + suffix = image; + } else { + suffix = '$args $image'; + } + if (name.isEmpty) { + return 'docker run -d $suffix'; + } + return 'docker run -d --name $name $suffix'; + } + String? getErrMsg(DockerErr err) { switch (err.type) { - case DockerErrType.cmdNoPrefix: - return s.dockerCmdPrefixErr; default: - return null; + return err.message; } } @@ -194,20 +250,36 @@ class _DockerManagePageState extends State { return ListView( padding: const EdgeInsets.all(7), children: [ + _buildLoading(docker), _buildVersion(docker.edition ?? s.unknown, docker.version ?? s.unknown), _buildPsItems(running, docker), _buildEditHost(running, docker), - _buildRunLog(docker), ].map((e) => RoundRectCard(e)).toList(), ); } - Widget _buildRunLog(DockerProvider docker) { - if (docker.runLog == null) return const SizedBox(); - return Padding( - padding: const EdgeInsets.all(17), - child: Text(docker.runLog!, maxLines: 1,), - ); + Widget _buildLoading(DockerProvider docker) { + if (docker.isBusy) { + final runLog = + docker.runLog == null ? const SizedBox() : Text(docker.runLog!); + return Padding( + padding: const EdgeInsets.all(17), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + runLog, + SizedBox( + width: docker.runLog == null ? 0 : 17, + ), + const Center( + child: CircularProgressIndicator(), + ), + ], + ), + ); + } + return const SizedBox(); } Widget _buildEditHost(List running, DockerProvider docker) {