diff --git a/lib/data/model/docker/image.dart b/lib/data/model/docker/image.dart new file mode 100644 index 00000000..1af89011 --- /dev/null +++ b/lib/data/model/docker/image.dart @@ -0,0 +1,47 @@ +final _dockerImageReg = RegExp(r'(\S+) +(\S+) +(\S+) +(.+) +(\S+)'); +class DockerImage { + final String repo; + final String tag; + final String id; + final String created; + final String size; + + static final Map _cache = {}; + + DockerImage({ + required this.repo, + required this.tag, + required this.id, + required this.created, + required this.size, + }); + + Map toJson() { + return { + 'repo': repo, + 'tag': tag, + 'id': id, + 'created': created, + 'size': size, + }; + } + + factory DockerImage.fromRawStr(String raw) { + return _cache.putIfAbsent(raw, () => _parse(raw)); + } + + static DockerImage _parse(String raw) { + final match = _dockerImageReg.firstMatch(raw); + if (match == null) { + throw Exception('Invalid docker image: $raw'); + } + return DockerImage( + repo: match.group(1)!, + tag: match.group(2)!, + id: match.group(3)!, + created: match.group(4)!, + size: match.group(5)!, + ); + } +} + diff --git a/lib/data/provider/docker.dart b/lib/data/provider/docker.dart index 326eaf32..796c932e 100644 --- a/lib/data/provider/docker.dart +++ b/lib/data/provider/docker.dart @@ -7,6 +7,7 @@ import 'package:toolbox/core/extension/ssh_client.dart'; import 'package:toolbox/core/extension/stringx.dart'; import 'package:toolbox/core/extension/uint8list.dart'; import 'package:toolbox/core/provider_base.dart'; +import 'package:toolbox/data/model/docker/image.dart'; import 'package:toolbox/data/model/docker/ps.dart'; import 'package:toolbox/data/res/error.dart'; import 'package:toolbox/data/store/docker.dart'; @@ -25,11 +26,13 @@ class DockerProvider extends BusyProvider { SSHClient? client; String? userName; List? items; + List? images; String? version; String? edition; DockerErr? error; PwdRequestFunc? onPwdReq; String? hostId; + String? runLog; bool isRequestingPwd = false; void init(SSHClient client, String userName, PwdRequestFunc onPwdReq, @@ -43,7 +46,7 @@ class DockerProvider extends BusyProvider { void clear() { client = userName = error = items = version = edition = onPwdReq = null; isRequestingPwd = false; - hostId = null; + hostId = runLog = images = null; } Future refresh() async { @@ -62,14 +65,7 @@ class DockerProvider extends BusyProvider { } try { - // judge whether to use DOCKER_HOST / sudo - final dockerHost = locator().getDockerHost(hostId!); - final cmd = () { - if (dockerHost == null || dockerHost.isEmpty) { - return 'sudo -S $_dockerPS'.withLangExport; - } - return 'export DOCKER_HOST=$dockerHost && $_dockerPS'.withLangExport; - }(); + final cmd = _wrap(_dockerPS); // run docker ps var raw = ''; @@ -84,6 +80,19 @@ class DockerProvider extends BusyProvider { lines.removeAt(0); lines.removeWhere((element) => element.isEmpty); items = lines.map((e) => DockerPsItem.fromRawString(e)).toList(); + + final imageCmd = _wrap('docker image ls'); + raw = ''; + await client!.exec( + imageCmd, + onStderr: _onPwd, + onStdout: (data, _) => raw = '$raw$data', + ); + + final imageLines = raw.split('\n'); + imageLines.removeAt(0); + imageLines.removeWhere((element) => element.isEmpty); + images = imageLines.map((e) => DockerImage.fromRawStr(e)).toList(); } catch (e) { error = DockerErr(type: DockerErrType.unknown, message: e.toString()); rethrow; @@ -119,26 +128,36 @@ class DockerProvider extends BusyProvider { } setBusyState(); + runLog = ''; final errs = []; final code = await client!.exec( - _wrapHost(cmd), - onStderr: _onPwd, + _wrap(cmd), + onStderr: (data, sink) { + _onPwd(data, sink); + errs.add(data); + }, + onStdout: (data, _) { + runLog = '$runLog$data'; + notifyListeners(); + }, ); + runLog = null; if (code != 0) { setBusyState(false); - return DockerErr(type: DockerErrType.unknown, message: errs.join('\n')); + return DockerErr(type: DockerErrType.unknown, message: errs.join('\n').trim()); } await refresh(); setBusyState(false); return null; } - String _wrapHost(String cmd) { + // judge whether to use DOCKER_HOST / sudo + String _wrap(String cmd) { final dockerHost = locator().getDockerHost(hostId!); if (dockerHost == null || dockerHost.isEmpty) { - return 'sudo $cmd'; + return 'sudo $cmd'.withLangExport; } - return 'export DOCKER_HOST=$dockerHost && $cmd'; + return 'export DOCKER_HOST=$dockerHost && $cmd'.withLangExport; } } diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index c835d03f..169ddd83 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -22,37 +22,39 @@ class MessageLookup extends MessageLookupByLibrary { static String m0(fileName) => "Download [${fileName}] to local?"; - static String m1(runningCount, stoppedCount) => + static String m1(count) => "${count} images"; + + static String m2(runningCount, stoppedCount) => "${runningCount} running, ${stoppedCount} container stopped."; - static String m2(count) => "${count} container running."; + static String m3(count) => "${count} container running."; - static String m3(percent, size) => "${percent}% of ${size}"; + static String m4(percent, size) => "${percent}% of ${size}"; - static String m4(count) => "Found ${count} update"; + static String m5(count) => "Found ${count} update"; - static String m5(code) => "request failed, status code: ${code}"; + static String m6(code) => "request failed, status code: ${code}"; - static String m6(url) => + static String m7(url) => "Please make sure that docker is installed correctly, or that you are using a non-self-compiled version. If you don\'t have the above issues, please submit an issue on ${url}."; - static String m7(myGithub) => "\nMade with ❤️ by ${myGithub}"; + static String m8(myGithub) => "\nMade with ❤️ by ${myGithub}"; - static String m8(url) => "Please report bugs on ${url}"; + static String m9(url) => "Please report bugs on ${url}"; - static String m9(date) => "Are you sure to restore from ${date} ?"; + static String m10(date) => "Are you sure to restore from ${date} ?"; - static String m10(time) => "Spent time: ${time}"; + static String m11(time) => "Spent time: ${time}"; - static String m11(name) => "Are you sure to delete [${name}]?"; + static String m12(name) => "Are you sure to delete [${name}]?"; - static String m12(server) => "Are you sure to delete server [${server}]?"; + static String m13(server) => "Are you sure to delete server [${server}]?"; - static String m13(build) => "Found: v1.0.${build}, click to update"; + static String m14(build) => "Found: v1.0.${build}, click to update"; - static String m14(build) => "Current: v1.0.${build}"; + static String m15(build) => "Current: v1.0.${build}"; - static String m15(build) => "Current: v1.0.${build}, is up to date"; + static String m16(build) => "Current: v1.0.${build}, is up to date"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -102,14 +104,15 @@ class MessageLookup extends MessageLookupByLibrary { "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"), + "dockerImagesFmt": m1, "dockerNotInstalled": MessageLookupByLibrary.simpleMessage("Docker not installed"), - "dockerStatusRunningAndStoppedFmt": m1, - "dockerStatusRunningFmt": m2, + "dockerStatusRunningAndStoppedFmt": m2, + "dockerStatusRunningFmt": m3, "download": MessageLookupByLibrary.simpleMessage("Download"), "downloadFinished": MessageLookupByLibrary.simpleMessage("Download finished"), - "downloadStatus": m3, + "downloadStatus": m4, "edit": MessageLookupByLibrary.simpleMessage("Edit"), "encode": MessageLookupByLibrary.simpleMessage("Encode"), "error": MessageLookupByLibrary.simpleMessage("Error"), @@ -124,12 +127,13 @@ class MessageLookup extends MessageLookupByLibrary { "fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage( "These fields must not be empty."), "files": MessageLookupByLibrary.simpleMessage("Files"), - "foundNUpdate": m4, + "foundNUpdate": m5, "go": MessageLookupByLibrary.simpleMessage("Go"), "goSftpDlPage": MessageLookupByLibrary.simpleMessage("Go to SFTP download page?"), "host": MessageLookupByLibrary.simpleMessage("Host"), - "httpFailedWithCode": m5, + "httpFailedWithCode": m6, + "imagesList": MessageLookupByLibrary.simpleMessage("Images list"), "import": MessageLookupByLibrary.simpleMessage("Import"), "importAndExport": MessageLookupByLibrary.simpleMessage("Import and Export"), @@ -141,7 +145,7 @@ class MessageLookup extends MessageLookupByLibrary { "invalidJson": MessageLookupByLibrary.simpleMessage("Invalid JSON"), "invalidVersion": MessageLookupByLibrary.simpleMessage("Invalid version"), - "invalidVersionHelp": m6, + "invalidVersionHelp": m7, "isBusy": MessageLookupByLibrary.simpleMessage("Is busy now"), "keepForeground": MessageLookupByLibrary.simpleMessage("Keep app foreground!"), @@ -152,7 +156,7 @@ class MessageLookup extends MessageLookupByLibrary { "loadingFiles": MessageLookupByLibrary.simpleMessage("Loading files..."), "loss": MessageLookupByLibrary.simpleMessage("loss"), - "madeWithLove": m7, + "madeWithLove": m8, "max": MessageLookupByLibrary.simpleMessage("max"), "min": MessageLookupByLibrary.simpleMessage("min"), "ms": MessageLookupByLibrary.simpleMessage("ms"), @@ -188,11 +192,11 @@ class MessageLookup extends MessageLookupByLibrary { "privateKey": MessageLookupByLibrary.simpleMessage("Private Key"), "pwd": MessageLookupByLibrary.simpleMessage("Password"), "rename": MessageLookupByLibrary.simpleMessage("Rename"), - "reportBugsOnGithubIssue": m8, + "reportBugsOnGithubIssue": m9, "restore": MessageLookupByLibrary.simpleMessage("Restore"), "restoreSuccess": MessageLookupByLibrary.simpleMessage( "Restore success. Restart app to apply."), - "restoreSureWithDate": m9, + "restoreSureWithDate": m10, "result": MessageLookupByLibrary.simpleMessage("Result"), "run": MessageLookupByLibrary.simpleMessage("Run"), "save": MessageLookupByLibrary.simpleMessage("Save"), @@ -216,13 +220,13 @@ class MessageLookup extends MessageLookupByLibrary { "sftpSSHConnected": MessageLookupByLibrary.simpleMessage("SFTP Connected"), "snippet": MessageLookupByLibrary.simpleMessage("Snippet"), - "spentTime": m10, + "spentTime": m11, "start": MessageLookupByLibrary.simpleMessage("Start"), "stop": MessageLookupByLibrary.simpleMessage("Stop"), - "sureDelete": m11, + "sureDelete": m12, "sureNoPwd": MessageLookupByLibrary.simpleMessage( "Are you sure to use no password?"), - "sureToDeleteServer": m12, + "sureToDeleteServer": m13, "ttl": MessageLookupByLibrary.simpleMessage("ttl"), "unknown": MessageLookupByLibrary.simpleMessage("unknown"), "unknownError": MessageLookupByLibrary.simpleMessage("Unknown error"), @@ -237,9 +241,9 @@ class MessageLookup extends MessageLookupByLibrary { "upsideDown": MessageLookupByLibrary.simpleMessage("Upside Down"), "urlOrJson": MessageLookupByLibrary.simpleMessage("URL or JSON"), "user": MessageLookupByLibrary.simpleMessage("User"), - "versionHaveUpdate": m13, - "versionUnknownUpdate": m14, - "versionUpdated": m15, + "versionHaveUpdate": m14, + "versionUnknownUpdate": m15, + "versionUpdated": m16, "waitConnection": MessageLookupByLibrary.simpleMessage( "Please wait for the connection to be established."), "willTakEeffectImmediately": diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart index 2d97b3b2..a5cb7b72 100644 --- a/lib/generated/intl/messages_zh.dart +++ b/lib/generated/intl/messages_zh.dart @@ -22,37 +22,39 @@ class MessageLookup extends MessageLookupByLibrary { static String m0(fileName) => "下载 [${fileName}] 到本地?"; - static String m1(runningCount, stoppedCount) => + static String m1(count) => "共 ${count} 个镜像"; + + static String m2(runningCount, stoppedCount) => "${runningCount}个正在运行, ${stoppedCount}个已停止"; - static String m2(count) => "${count}个容器正在运行"; + static String m3(count) => "${count}个容器正在运行"; - static String m3(percent, size) => "${size} 的 ${percent}%"; + static String m4(percent, size) => "${size} 的 ${percent}%"; - static String m4(count) => "找到 ${count} 个更新"; + static String m5(count) => "找到 ${count} 个更新"; - static String m5(code) => "请求失败, 状态码: ${code}"; + static String m6(code) => "请求失败, 状态码: ${code}"; - static String m6(url) => + static String m7(url) => "请确保正确安装了docker,或者使用的非自编译版本。如果没有以上问题,请在 ${url} 提交问题。"; - static String m7(myGithub) => "\n用❤️制作 by ${myGithub}"; + static String m8(myGithub) => "\n用❤️制作 by ${myGithub}"; - static String m8(url) => "请到 ${url} 提交问题"; + static String m9(url) => "请到 ${url} 提交问题"; - static String m9(date) => "确定恢复 ${date} 的备份吗?"; + static String m10(date) => "确定恢复 ${date} 的备份吗?"; - static String m10(time) => "耗时: ${time}"; + static String m11(time) => "耗时: ${time}"; - static String m11(name) => "确定删除[${name}]?"; + static String m12(name) => "确定删除[${name}]?"; - static String m12(server) => "你确定要删除服务器 [${server}] 吗?"; + static String m13(server) => "你确定要删除服务器 [${server}] 吗?"; - static String m13(build) => "找到新版本:v1.0.${build}, 点击更新"; + static String m14(build) => "找到新版本:v1.0.${build}, 点击更新"; - static String m14(build) => "当前:v1.0.${build}"; + static String m15(build) => "当前:v1.0.${build}"; - static String m15(build) => "当前:v1.0.${build}, 已是最新版本"; + static String m16(build) => "当前:v1.0.${build}, 已是最新版本"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -95,12 +97,13 @@ class MessageLookup extends MessageLookupByLibrary { "dockerEmptyRunningItems": MessageLookupByLibrary.simpleMessage( "没有正在运行的容器。\n这可能是因为环境变量 DOCKER_HOST 没有被正确读取。你可以通过在终端内运行 `echo \$DOCKER_HOST` 来获取。"), "dockerImage": MessageLookupByLibrary.simpleMessage("镜像"), + "dockerImagesFmt": m1, "dockerNotInstalled": MessageLookupByLibrary.simpleMessage("Docker未安装"), - "dockerStatusRunningAndStoppedFmt": m1, - "dockerStatusRunningFmt": m2, + "dockerStatusRunningAndStoppedFmt": m2, + "dockerStatusRunningFmt": m3, "download": MessageLookupByLibrary.simpleMessage("下载"), "downloadFinished": MessageLookupByLibrary.simpleMessage("下载完成!"), - "downloadStatus": m3, + "downloadStatus": m4, "edit": MessageLookupByLibrary.simpleMessage("编辑"), "encode": MessageLookupByLibrary.simpleMessage("编码"), "error": MessageLookupByLibrary.simpleMessage("出错了"), @@ -113,11 +116,12 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("如果你有任何问题,请在GitHub反馈"), "fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage("这些输入框不能为空。"), "files": MessageLookupByLibrary.simpleMessage("文件"), - "foundNUpdate": m4, + "foundNUpdate": m5, "go": MessageLookupByLibrary.simpleMessage("开始"), "goSftpDlPage": MessageLookupByLibrary.simpleMessage("前往下载页?"), "host": MessageLookupByLibrary.simpleMessage("主机"), - "httpFailedWithCode": m5, + "httpFailedWithCode": m6, + "imagesList": MessageLookupByLibrary.simpleMessage("镜像列表"), "import": MessageLookupByLibrary.simpleMessage("导入"), "importAndExport": MessageLookupByLibrary.simpleMessage("导入或导出"), "inputDomainHere": MessageLookupByLibrary.simpleMessage("在这里输入域名"), @@ -126,7 +130,7 @@ class MessageLookup extends MessageLookupByLibrary { "请先 https://docs.docker.com/engine/install docker"), "invalidJson": MessageLookupByLibrary.simpleMessage("无效的json,存在格式问题"), "invalidVersion": MessageLookupByLibrary.simpleMessage("不支持的版本"), - "invalidVersionHelp": m6, + "invalidVersionHelp": m7, "isBusy": MessageLookupByLibrary.simpleMessage("当前正忙"), "keepForeground": MessageLookupByLibrary.simpleMessage("请保持应用处于前台!"), "keyAuth": MessageLookupByLibrary.simpleMessage("公钥认证"), @@ -135,7 +139,7 @@ class MessageLookup extends MessageLookupByLibrary { "license": MessageLookupByLibrary.simpleMessage("开源证书"), "loadingFiles": MessageLookupByLibrary.simpleMessage("正在加载目录。。。"), "loss": MessageLookupByLibrary.simpleMessage("丢包率"), - "madeWithLove": m7, + "madeWithLove": m8, "max": MessageLookupByLibrary.simpleMessage("最大"), "min": MessageLookupByLibrary.simpleMessage("最小"), "ms": MessageLookupByLibrary.simpleMessage("毫秒"), @@ -164,11 +168,11 @@ class MessageLookup extends MessageLookupByLibrary { "privateKey": MessageLookupByLibrary.simpleMessage("私钥"), "pwd": MessageLookupByLibrary.simpleMessage("密码"), "rename": MessageLookupByLibrary.simpleMessage("重命名"), - "reportBugsOnGithubIssue": m8, + "reportBugsOnGithubIssue": m9, "restore": MessageLookupByLibrary.simpleMessage("恢复"), "restoreSuccess": MessageLookupByLibrary.simpleMessage("恢复成功,需要重启App来应用更改"), - "restoreSureWithDate": m9, + "restoreSureWithDate": m10, "result": MessageLookupByLibrary.simpleMessage("结果"), "run": MessageLookupByLibrary.simpleMessage("运行"), "save": MessageLookupByLibrary.simpleMessage("保存"), @@ -187,12 +191,12 @@ class MessageLookup extends MessageLookupByLibrary { "sftpSSHConnected": MessageLookupByLibrary.simpleMessage("SFTP 已连接,即将开始下载..."), "snippet": MessageLookupByLibrary.simpleMessage("代码片段"), - "spentTime": m10, + "spentTime": m11, "start": MessageLookupByLibrary.simpleMessage("开始"), "stop": MessageLookupByLibrary.simpleMessage("停止"), - "sureDelete": m11, + "sureDelete": m12, "sureNoPwd": MessageLookupByLibrary.simpleMessage("确认使用无密码?"), - "sureToDeleteServer": m12, + "sureToDeleteServer": m13, "ttl": MessageLookupByLibrary.simpleMessage("缓存时间"), "unknown": MessageLookupByLibrary.simpleMessage("未知"), "unknownError": MessageLookupByLibrary.simpleMessage("未知错误"), @@ -206,9 +210,9 @@ class MessageLookup extends MessageLookupByLibrary { "upsideDown": MessageLookupByLibrary.simpleMessage("上下交换"), "urlOrJson": MessageLookupByLibrary.simpleMessage("链接或JSON"), "user": MessageLookupByLibrary.simpleMessage("用户"), - "versionHaveUpdate": m13, - "versionUnknownUpdate": m14, - "versionUpdated": m15, + "versionHaveUpdate": m14, + "versionUnknownUpdate": m15, + "versionUpdated": m16, "waitConnection": MessageLookupByLibrary.simpleMessage("请等待连接建立"), "willTakEeffectImmediately": MessageLookupByLibrary.simpleMessage("更改将会立即生效") diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 5c414794..15681b9c 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -1460,6 +1460,26 @@ class S { args: [], ); } + + /// `Images list` + String get imagesList { + return Intl.message( + 'Images list', + name: 'imagesList', + desc: '', + args: [], + ); + } + + /// `{count} images` + String dockerImagesFmt(Object count) { + return Intl.message( + '$count images', + name: 'dockerImagesFmt', + desc: '', + args: [count], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 3544ff22..c5062b4f 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -139,5 +139,7 @@ "dockerContainerName": "Container name", "extraArgs": "Extra args", "preview": "Preview", - "isBusy": "Is busy now" + "isBusy": "Is busy now", + "imagesList": "Images list", + "dockerImagesFmt": "{count} images" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 20908fd9..c1c83f23 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -139,5 +139,7 @@ "dockerContainerName": "容器名", "extraArgs": "额外参数", "preview": "预览", - "isBusy": "当前正忙" + "isBusy": "当前正忙", + "imagesList": "镜像列表", + "dockerImagesFmt": "共 {count} 个镜像" } \ No newline at end of file diff --git a/lib/view/page/docker.dart b/lib/view/page/docker.dart index 41396e01..228d8f86 100644 --- a/lib/view/page/docker.dart +++ b/lib/view/page/docker.dart @@ -165,9 +165,9 @@ class _DockerManagePageState extends State { suffix = '$args $image'; } if (name.isEmpty) { - return 'docker run -d $suffix'; + return 'docker run -itd $suffix'; } - return 'docker run -d --name $name $suffix'; + return 'docker run -itd --name $name $suffix'; } String? getErrMsg(DockerErr err) { @@ -253,41 +253,76 @@ class _DockerManagePageState extends State { _buildLoading(docker), _buildVersion(docker.edition ?? s.unknown, docker.version ?? s.unknown), _buildPsItems(running, docker), + _buildImages(docker), _buildEditHost(running, docker), ].map((e) => RoundRectCard(e)).toList(), ); } - Widget _buildLoading(DockerProvider docker) { - if (docker.isBusy) { - return const Padding( - padding: EdgeInsets.all(17), - child: Center( - child: CircularProgressIndicator(), - ), - ); + Widget _buildImages(DockerProvider docker) { + if (docker.images == null) { + return const SizedBox(); } - return const SizedBox(); + return ExpansionTile( + title: Text(s.imagesList), + subtitle: Text( + s.dockerImagesFmt(docker.images!.length), + style: greyTextStyle, + ), + children: docker.images! + .map( + (e) => ListTile( + title: Text(e.repo), + subtitle: Text('${e.tag} - ${e.size}'), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + final result = await _docker.run('docker rmi ${e.id} -f'); + if (result != null) { + showSnackBar( + context, Text(getErrMsg(result) ?? s.unknownError)); + } + }, + ), + ), + ) + .toList(), + ); + } + + Widget _buildLoading(DockerProvider docker) { + if (!docker.isBusy) return const SizedBox(); + final haveLog = docker.runLog != null; + return Padding( + padding: const EdgeInsets.all(17), + child: Column( + children: [ + const Center( + child: CircularProgressIndicator(), + ), + haveLog ? const SizedBox(height: 17) : const SizedBox(), + haveLog ? Text(docker.runLog!) : const SizedBox() + ], + ), + ); } Widget _buildEditHost(List running, DockerProvider docker) { - if (running.isEmpty) { - return Padding( - padding: const EdgeInsets.fromLTRB(17, 17, 17, 0), - child: Column( - children: [ - Text( - s.dockerEmptyRunningItems, - textAlign: TextAlign.center, - ), - TextButton( - onPressed: () => _showEditHostDialog(docker), - child: Text(s.dockerEditHost)) - ], - ), - ); - } - return const SizedBox(); + if (running.isNotEmpty) return const SizedBox(); + return Padding( + padding: const EdgeInsets.fromLTRB(17, 17, 17, 0), + child: Column( + children: [ + Text( + s.dockerEmptyRunningItems, + textAlign: TextAlign.center, + ), + TextButton( + onPressed: () => _showEditHostDialog(docker), + child: Text(s.dockerEditHost)) + ], + ), + ); } Future _showEditHostDialog(DockerProvider docker) async {