support docker image management

This commit is contained in:
lollipopkit
2022-12-11 15:03:02 +08:00
parent 78b46998d1
commit 7e01c4cbb3
8 changed files with 238 additions and 105 deletions

View File

@@ -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<String, DockerImage> _cache = <String, DockerImage>{};
DockerImage({
required this.repo,
required this.tag,
required this.id,
required this.created,
required this.size,
});
Map<String, dynamic> 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)!,
);
}
}

View File

@@ -7,6 +7,7 @@ import 'package:toolbox/core/extension/ssh_client.dart';
import 'package:toolbox/core/extension/stringx.dart'; import 'package:toolbox/core/extension/stringx.dart';
import 'package:toolbox/core/extension/uint8list.dart'; import 'package:toolbox/core/extension/uint8list.dart';
import 'package:toolbox/core/provider_base.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/model/docker/ps.dart';
import 'package:toolbox/data/res/error.dart'; import 'package:toolbox/data/res/error.dart';
import 'package:toolbox/data/store/docker.dart'; import 'package:toolbox/data/store/docker.dart';
@@ -25,11 +26,13 @@ class DockerProvider extends BusyProvider {
SSHClient? client; SSHClient? client;
String? userName; String? userName;
List<DockerPsItem>? items; List<DockerPsItem>? items;
List<DockerImage>? images;
String? version; String? version;
String? edition; String? edition;
DockerErr? error; DockerErr? error;
PwdRequestFunc? onPwdReq; PwdRequestFunc? onPwdReq;
String? hostId; String? hostId;
String? runLog;
bool isRequestingPwd = false; bool isRequestingPwd = false;
void init(SSHClient client, String userName, PwdRequestFunc onPwdReq, void init(SSHClient client, String userName, PwdRequestFunc onPwdReq,
@@ -43,7 +46,7 @@ class DockerProvider extends BusyProvider {
void clear() { void clear() {
client = userName = error = items = version = edition = onPwdReq = null; client = userName = error = items = version = edition = onPwdReq = null;
isRequestingPwd = false; isRequestingPwd = false;
hostId = null; hostId = runLog = images = null;
} }
Future<void> refresh() async { Future<void> refresh() async {
@@ -62,14 +65,7 @@ class DockerProvider extends BusyProvider {
} }
try { try {
// judge whether to use DOCKER_HOST / sudo final cmd = _wrap(_dockerPS);
final dockerHost = locator<DockerStore>().getDockerHost(hostId!);
final cmd = () {
if (dockerHost == null || dockerHost.isEmpty) {
return 'sudo -S $_dockerPS'.withLangExport;
}
return 'export DOCKER_HOST=$dockerHost && $_dockerPS'.withLangExport;
}();
// run docker ps // run docker ps
var raw = ''; var raw = '';
@@ -84,6 +80,19 @@ class DockerProvider extends BusyProvider {
lines.removeAt(0); lines.removeAt(0);
lines.removeWhere((element) => element.isEmpty); lines.removeWhere((element) => element.isEmpty);
items = lines.map((e) => DockerPsItem.fromRawString(e)).toList(); 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) { } catch (e) {
error = DockerErr(type: DockerErrType.unknown, message: e.toString()); error = DockerErr(type: DockerErrType.unknown, message: e.toString());
rethrow; rethrow;
@@ -119,26 +128,36 @@ class DockerProvider extends BusyProvider {
} }
setBusyState(); setBusyState();
runLog = '';
final errs = <String>[]; final errs = <String>[];
final code = await client!.exec( final code = await client!.exec(
_wrapHost(cmd), _wrap(cmd),
onStderr: _onPwd, onStderr: (data, sink) {
_onPwd(data, sink);
errs.add(data);
},
onStdout: (data, _) {
runLog = '$runLog$data';
notifyListeners();
},
); );
runLog = null;
if (code != 0) { if (code != 0) {
setBusyState(false); setBusyState(false);
return DockerErr(type: DockerErrType.unknown, message: errs.join('\n')); return DockerErr(type: DockerErrType.unknown, message: errs.join('\n').trim());
} }
await refresh(); await refresh();
setBusyState(false); setBusyState(false);
return null; return null;
} }
String _wrapHost(String cmd) { // judge whether to use DOCKER_HOST / sudo
String _wrap(String cmd) {
final dockerHost = locator<DockerStore>().getDockerHost(hostId!); final dockerHost = locator<DockerStore>().getDockerHost(hostId!);
if (dockerHost == null || dockerHost.isEmpty) { 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;
} }
} }

View File

@@ -22,37 +22,39 @@ class MessageLookup extends MessageLookupByLibrary {
static String m0(fileName) => "Download [${fileName}] to local?"; 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."; "${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}."; "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); final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{ static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -102,14 +104,15 @@ class MessageLookup extends MessageLookupByLibrary {
"dockerEmptyRunningItems": MessageLookupByLibrary.simpleMessage( "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."), "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"), "dockerImage": MessageLookupByLibrary.simpleMessage("Image"),
"dockerImagesFmt": m1,
"dockerNotInstalled": "dockerNotInstalled":
MessageLookupByLibrary.simpleMessage("Docker not installed"), MessageLookupByLibrary.simpleMessage("Docker not installed"),
"dockerStatusRunningAndStoppedFmt": m1, "dockerStatusRunningAndStoppedFmt": m2,
"dockerStatusRunningFmt": m2, "dockerStatusRunningFmt": m3,
"download": MessageLookupByLibrary.simpleMessage("Download"), "download": MessageLookupByLibrary.simpleMessage("Download"),
"downloadFinished": "downloadFinished":
MessageLookupByLibrary.simpleMessage("Download finished"), MessageLookupByLibrary.simpleMessage("Download finished"),
"downloadStatus": m3, "downloadStatus": m4,
"edit": MessageLookupByLibrary.simpleMessage("Edit"), "edit": MessageLookupByLibrary.simpleMessage("Edit"),
"encode": MessageLookupByLibrary.simpleMessage("Encode"), "encode": MessageLookupByLibrary.simpleMessage("Encode"),
"error": MessageLookupByLibrary.simpleMessage("Error"), "error": MessageLookupByLibrary.simpleMessage("Error"),
@@ -124,12 +127,13 @@ class MessageLookup extends MessageLookupByLibrary {
"fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage( "fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage(
"These fields must not be empty."), "These fields must not be empty."),
"files": MessageLookupByLibrary.simpleMessage("Files"), "files": MessageLookupByLibrary.simpleMessage("Files"),
"foundNUpdate": m4, "foundNUpdate": m5,
"go": MessageLookupByLibrary.simpleMessage("Go"), "go": MessageLookupByLibrary.simpleMessage("Go"),
"goSftpDlPage": "goSftpDlPage":
MessageLookupByLibrary.simpleMessage("Go to SFTP download page?"), MessageLookupByLibrary.simpleMessage("Go to SFTP download page?"),
"host": MessageLookupByLibrary.simpleMessage("Host"), "host": MessageLookupByLibrary.simpleMessage("Host"),
"httpFailedWithCode": m5, "httpFailedWithCode": m6,
"imagesList": MessageLookupByLibrary.simpleMessage("Images list"),
"import": MessageLookupByLibrary.simpleMessage("Import"), "import": MessageLookupByLibrary.simpleMessage("Import"),
"importAndExport": "importAndExport":
MessageLookupByLibrary.simpleMessage("Import and Export"), MessageLookupByLibrary.simpleMessage("Import and Export"),
@@ -141,7 +145,7 @@ class MessageLookup extends MessageLookupByLibrary {
"invalidJson": MessageLookupByLibrary.simpleMessage("Invalid JSON"), "invalidJson": MessageLookupByLibrary.simpleMessage("Invalid JSON"),
"invalidVersion": "invalidVersion":
MessageLookupByLibrary.simpleMessage("Invalid version"), MessageLookupByLibrary.simpleMessage("Invalid version"),
"invalidVersionHelp": m6, "invalidVersionHelp": m7,
"isBusy": MessageLookupByLibrary.simpleMessage("Is busy now"), "isBusy": MessageLookupByLibrary.simpleMessage("Is busy now"),
"keepForeground": "keepForeground":
MessageLookupByLibrary.simpleMessage("Keep app foreground!"), MessageLookupByLibrary.simpleMessage("Keep app foreground!"),
@@ -152,7 +156,7 @@ class MessageLookup extends MessageLookupByLibrary {
"loadingFiles": "loadingFiles":
MessageLookupByLibrary.simpleMessage("Loading files..."), MessageLookupByLibrary.simpleMessage("Loading files..."),
"loss": MessageLookupByLibrary.simpleMessage("loss"), "loss": MessageLookupByLibrary.simpleMessage("loss"),
"madeWithLove": m7, "madeWithLove": m8,
"max": MessageLookupByLibrary.simpleMessage("max"), "max": MessageLookupByLibrary.simpleMessage("max"),
"min": MessageLookupByLibrary.simpleMessage("min"), "min": MessageLookupByLibrary.simpleMessage("min"),
"ms": MessageLookupByLibrary.simpleMessage("ms"), "ms": MessageLookupByLibrary.simpleMessage("ms"),
@@ -188,11 +192,11 @@ class MessageLookup extends MessageLookupByLibrary {
"privateKey": MessageLookupByLibrary.simpleMessage("Private Key"), "privateKey": MessageLookupByLibrary.simpleMessage("Private Key"),
"pwd": MessageLookupByLibrary.simpleMessage("Password"), "pwd": MessageLookupByLibrary.simpleMessage("Password"),
"rename": MessageLookupByLibrary.simpleMessage("Rename"), "rename": MessageLookupByLibrary.simpleMessage("Rename"),
"reportBugsOnGithubIssue": m8, "reportBugsOnGithubIssue": m9,
"restore": MessageLookupByLibrary.simpleMessage("Restore"), "restore": MessageLookupByLibrary.simpleMessage("Restore"),
"restoreSuccess": MessageLookupByLibrary.simpleMessage( "restoreSuccess": MessageLookupByLibrary.simpleMessage(
"Restore success. Restart app to apply."), "Restore success. Restart app to apply."),
"restoreSureWithDate": m9, "restoreSureWithDate": m10,
"result": MessageLookupByLibrary.simpleMessage("Result"), "result": MessageLookupByLibrary.simpleMessage("Result"),
"run": MessageLookupByLibrary.simpleMessage("Run"), "run": MessageLookupByLibrary.simpleMessage("Run"),
"save": MessageLookupByLibrary.simpleMessage("Save"), "save": MessageLookupByLibrary.simpleMessage("Save"),
@@ -216,13 +220,13 @@ class MessageLookup extends MessageLookupByLibrary {
"sftpSSHConnected": "sftpSSHConnected":
MessageLookupByLibrary.simpleMessage("SFTP Connected"), MessageLookupByLibrary.simpleMessage("SFTP Connected"),
"snippet": MessageLookupByLibrary.simpleMessage("Snippet"), "snippet": MessageLookupByLibrary.simpleMessage("Snippet"),
"spentTime": m10, "spentTime": m11,
"start": MessageLookupByLibrary.simpleMessage("Start"), "start": MessageLookupByLibrary.simpleMessage("Start"),
"stop": MessageLookupByLibrary.simpleMessage("Stop"), "stop": MessageLookupByLibrary.simpleMessage("Stop"),
"sureDelete": m11, "sureDelete": m12,
"sureNoPwd": MessageLookupByLibrary.simpleMessage( "sureNoPwd": MessageLookupByLibrary.simpleMessage(
"Are you sure to use no password?"), "Are you sure to use no password?"),
"sureToDeleteServer": m12, "sureToDeleteServer": m13,
"ttl": MessageLookupByLibrary.simpleMessage("ttl"), "ttl": MessageLookupByLibrary.simpleMessage("ttl"),
"unknown": MessageLookupByLibrary.simpleMessage("unknown"), "unknown": MessageLookupByLibrary.simpleMessage("unknown"),
"unknownError": MessageLookupByLibrary.simpleMessage("Unknown error"), "unknownError": MessageLookupByLibrary.simpleMessage("Unknown error"),
@@ -237,9 +241,9 @@ class MessageLookup extends MessageLookupByLibrary {
"upsideDown": MessageLookupByLibrary.simpleMessage("Upside Down"), "upsideDown": MessageLookupByLibrary.simpleMessage("Upside Down"),
"urlOrJson": MessageLookupByLibrary.simpleMessage("URL or JSON"), "urlOrJson": MessageLookupByLibrary.simpleMessage("URL or JSON"),
"user": MessageLookupByLibrary.simpleMessage("User"), "user": MessageLookupByLibrary.simpleMessage("User"),
"versionHaveUpdate": m13, "versionHaveUpdate": m14,
"versionUnknownUpdate": m14, "versionUnknownUpdate": m15,
"versionUpdated": m15, "versionUpdated": m16,
"waitConnection": MessageLookupByLibrary.simpleMessage( "waitConnection": MessageLookupByLibrary.simpleMessage(
"Please wait for the connection to be established."), "Please wait for the connection to be established."),
"willTakEeffectImmediately": "willTakEeffectImmediately":

View File

@@ -22,37 +22,39 @@ class MessageLookup extends MessageLookupByLibrary {
static String m0(fileName) => "下载 [${fileName}] 到本地?"; static String m0(fileName) => "下载 [${fileName}] 到本地?";
static String m1(runningCount, stoppedCount) => static String m1(count) => "${count} 个镜像";
static String m2(runningCount, stoppedCount) =>
"${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} 提交问题。"; "请确保正确安装了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); final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{ static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -95,12 +97,13 @@ class MessageLookup extends MessageLookupByLibrary {
"dockerEmptyRunningItems": MessageLookupByLibrary.simpleMessage( "dockerEmptyRunningItems": MessageLookupByLibrary.simpleMessage(
"没有正在运行的容器。\n这可能是因为环境变量 DOCKER_HOST 没有被正确读取。你可以通过在终端内运行 `echo \$DOCKER_HOST` 来获取。"), "没有正在运行的容器。\n这可能是因为环境变量 DOCKER_HOST 没有被正确读取。你可以通过在终端内运行 `echo \$DOCKER_HOST` 来获取。"),
"dockerImage": MessageLookupByLibrary.simpleMessage("镜像"), "dockerImage": MessageLookupByLibrary.simpleMessage("镜像"),
"dockerImagesFmt": m1,
"dockerNotInstalled": MessageLookupByLibrary.simpleMessage("Docker未安装"), "dockerNotInstalled": MessageLookupByLibrary.simpleMessage("Docker未安装"),
"dockerStatusRunningAndStoppedFmt": m1, "dockerStatusRunningAndStoppedFmt": m2,
"dockerStatusRunningFmt": m2, "dockerStatusRunningFmt": m3,
"download": MessageLookupByLibrary.simpleMessage("下载"), "download": MessageLookupByLibrary.simpleMessage("下载"),
"downloadFinished": MessageLookupByLibrary.simpleMessage("下载完成!"), "downloadFinished": MessageLookupByLibrary.simpleMessage("下载完成!"),
"downloadStatus": m3, "downloadStatus": m4,
"edit": MessageLookupByLibrary.simpleMessage("编辑"), "edit": MessageLookupByLibrary.simpleMessage("编辑"),
"encode": MessageLookupByLibrary.simpleMessage("编码"), "encode": MessageLookupByLibrary.simpleMessage("编码"),
"error": MessageLookupByLibrary.simpleMessage("出错了"), "error": MessageLookupByLibrary.simpleMessage("出错了"),
@@ -113,11 +116,12 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("如果你有任何问题请在GitHub反馈"), MessageLookupByLibrary.simpleMessage("如果你有任何问题请在GitHub反馈"),
"fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage("这些输入框不能为空。"), "fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage("这些输入框不能为空。"),
"files": MessageLookupByLibrary.simpleMessage("文件"), "files": MessageLookupByLibrary.simpleMessage("文件"),
"foundNUpdate": m4, "foundNUpdate": m5,
"go": MessageLookupByLibrary.simpleMessage("开始"), "go": MessageLookupByLibrary.simpleMessage("开始"),
"goSftpDlPage": MessageLookupByLibrary.simpleMessage("前往下载页?"), "goSftpDlPage": MessageLookupByLibrary.simpleMessage("前往下载页?"),
"host": MessageLookupByLibrary.simpleMessage("主机"), "host": MessageLookupByLibrary.simpleMessage("主机"),
"httpFailedWithCode": m5, "httpFailedWithCode": m6,
"imagesList": MessageLookupByLibrary.simpleMessage("镜像列表"),
"import": MessageLookupByLibrary.simpleMessage("导入"), "import": MessageLookupByLibrary.simpleMessage("导入"),
"importAndExport": MessageLookupByLibrary.simpleMessage("导入或导出"), "importAndExport": MessageLookupByLibrary.simpleMessage("导入或导出"),
"inputDomainHere": MessageLookupByLibrary.simpleMessage("在这里输入域名"), "inputDomainHere": MessageLookupByLibrary.simpleMessage("在这里输入域名"),
@@ -126,7 +130,7 @@ class MessageLookup extends MessageLookupByLibrary {
"请先 https://docs.docker.com/engine/install docker"), "请先 https://docs.docker.com/engine/install docker"),
"invalidJson": MessageLookupByLibrary.simpleMessage("无效的json存在格式问题"), "invalidJson": MessageLookupByLibrary.simpleMessage("无效的json存在格式问题"),
"invalidVersion": MessageLookupByLibrary.simpleMessage("不支持的版本"), "invalidVersion": MessageLookupByLibrary.simpleMessage("不支持的版本"),
"invalidVersionHelp": m6, "invalidVersionHelp": m7,
"isBusy": MessageLookupByLibrary.simpleMessage("当前正忙"), "isBusy": MessageLookupByLibrary.simpleMessage("当前正忙"),
"keepForeground": MessageLookupByLibrary.simpleMessage("请保持应用处于前台!"), "keepForeground": MessageLookupByLibrary.simpleMessage("请保持应用处于前台!"),
"keyAuth": MessageLookupByLibrary.simpleMessage("公钥认证"), "keyAuth": MessageLookupByLibrary.simpleMessage("公钥认证"),
@@ -135,7 +139,7 @@ class MessageLookup extends MessageLookupByLibrary {
"license": MessageLookupByLibrary.simpleMessage("开源证书"), "license": MessageLookupByLibrary.simpleMessage("开源证书"),
"loadingFiles": MessageLookupByLibrary.simpleMessage("正在加载目录。。。"), "loadingFiles": MessageLookupByLibrary.simpleMessage("正在加载目录。。。"),
"loss": MessageLookupByLibrary.simpleMessage("丢包率"), "loss": MessageLookupByLibrary.simpleMessage("丢包率"),
"madeWithLove": m7, "madeWithLove": m8,
"max": MessageLookupByLibrary.simpleMessage("最大"), "max": MessageLookupByLibrary.simpleMessage("最大"),
"min": MessageLookupByLibrary.simpleMessage("最小"), "min": MessageLookupByLibrary.simpleMessage("最小"),
"ms": MessageLookupByLibrary.simpleMessage("毫秒"), "ms": MessageLookupByLibrary.simpleMessage("毫秒"),
@@ -164,11 +168,11 @@ class MessageLookup extends MessageLookupByLibrary {
"privateKey": MessageLookupByLibrary.simpleMessage("私钥"), "privateKey": MessageLookupByLibrary.simpleMessage("私钥"),
"pwd": MessageLookupByLibrary.simpleMessage("密码"), "pwd": MessageLookupByLibrary.simpleMessage("密码"),
"rename": MessageLookupByLibrary.simpleMessage("重命名"), "rename": MessageLookupByLibrary.simpleMessage("重命名"),
"reportBugsOnGithubIssue": m8, "reportBugsOnGithubIssue": m9,
"restore": MessageLookupByLibrary.simpleMessage("恢复"), "restore": MessageLookupByLibrary.simpleMessage("恢复"),
"restoreSuccess": "restoreSuccess":
MessageLookupByLibrary.simpleMessage("恢复成功需要重启App来应用更改"), MessageLookupByLibrary.simpleMessage("恢复成功需要重启App来应用更改"),
"restoreSureWithDate": m9, "restoreSureWithDate": m10,
"result": MessageLookupByLibrary.simpleMessage("结果"), "result": MessageLookupByLibrary.simpleMessage("结果"),
"run": MessageLookupByLibrary.simpleMessage("运行"), "run": MessageLookupByLibrary.simpleMessage("运行"),
"save": MessageLookupByLibrary.simpleMessage("保存"), "save": MessageLookupByLibrary.simpleMessage("保存"),
@@ -187,12 +191,12 @@ class MessageLookup extends MessageLookupByLibrary {
"sftpSSHConnected": "sftpSSHConnected":
MessageLookupByLibrary.simpleMessage("SFTP 已连接,即将开始下载..."), MessageLookupByLibrary.simpleMessage("SFTP 已连接,即将开始下载..."),
"snippet": MessageLookupByLibrary.simpleMessage("代码片段"), "snippet": MessageLookupByLibrary.simpleMessage("代码片段"),
"spentTime": m10, "spentTime": m11,
"start": MessageLookupByLibrary.simpleMessage("开始"), "start": MessageLookupByLibrary.simpleMessage("开始"),
"stop": MessageLookupByLibrary.simpleMessage("停止"), "stop": MessageLookupByLibrary.simpleMessage("停止"),
"sureDelete": m11, "sureDelete": m12,
"sureNoPwd": MessageLookupByLibrary.simpleMessage("确认使用无密码?"), "sureNoPwd": MessageLookupByLibrary.simpleMessage("确认使用无密码?"),
"sureToDeleteServer": m12, "sureToDeleteServer": m13,
"ttl": MessageLookupByLibrary.simpleMessage("缓存时间"), "ttl": MessageLookupByLibrary.simpleMessage("缓存时间"),
"unknown": MessageLookupByLibrary.simpleMessage("未知"), "unknown": MessageLookupByLibrary.simpleMessage("未知"),
"unknownError": MessageLookupByLibrary.simpleMessage("未知错误"), "unknownError": MessageLookupByLibrary.simpleMessage("未知错误"),
@@ -206,9 +210,9 @@ class MessageLookup extends MessageLookupByLibrary {
"upsideDown": MessageLookupByLibrary.simpleMessage("上下交换"), "upsideDown": MessageLookupByLibrary.simpleMessage("上下交换"),
"urlOrJson": MessageLookupByLibrary.simpleMessage("链接或JSON"), "urlOrJson": MessageLookupByLibrary.simpleMessage("链接或JSON"),
"user": MessageLookupByLibrary.simpleMessage("用户"), "user": MessageLookupByLibrary.simpleMessage("用户"),
"versionHaveUpdate": m13, "versionHaveUpdate": m14,
"versionUnknownUpdate": m14, "versionUnknownUpdate": m15,
"versionUpdated": m15, "versionUpdated": m16,
"waitConnection": MessageLookupByLibrary.simpleMessage("请等待连接建立"), "waitConnection": MessageLookupByLibrary.simpleMessage("请等待连接建立"),
"willTakEeffectImmediately": "willTakEeffectImmediately":
MessageLookupByLibrary.simpleMessage("更改将会立即生效") MessageLookupByLibrary.simpleMessage("更改将会立即生效")

View File

@@ -1460,6 +1460,26 @@ class S {
args: [], 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<S> { class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@@ -139,5 +139,7 @@
"dockerContainerName": "Container name", "dockerContainerName": "Container name",
"extraArgs": "Extra args", "extraArgs": "Extra args",
"preview": "Preview", "preview": "Preview",
"isBusy": "Is busy now" "isBusy": "Is busy now",
"imagesList": "Images list",
"dockerImagesFmt": "{count} images"
} }

View File

@@ -139,5 +139,7 @@
"dockerContainerName": "容器名", "dockerContainerName": "容器名",
"extraArgs": "额外参数", "extraArgs": "额外参数",
"preview": "预览", "preview": "预览",
"isBusy": "当前正忙" "isBusy": "当前正忙",
"imagesList": "镜像列表",
"dockerImagesFmt": "共 {count} 个镜像"
} }

View File

@@ -165,9 +165,9 @@ class _DockerManagePageState extends State<DockerManagePage> {
suffix = '$args $image'; suffix = '$args $image';
} }
if (name.isEmpty) { 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) { String? getErrMsg(DockerErr err) {
@@ -253,41 +253,76 @@ class _DockerManagePageState extends State<DockerManagePage> {
_buildLoading(docker), _buildLoading(docker),
_buildVersion(docker.edition ?? s.unknown, docker.version ?? s.unknown), _buildVersion(docker.edition ?? s.unknown, docker.version ?? s.unknown),
_buildPsItems(running, docker), _buildPsItems(running, docker),
_buildImages(docker),
_buildEditHost(running, docker), _buildEditHost(running, docker),
].map((e) => RoundRectCard(e)).toList(), ].map((e) => RoundRectCard(e)).toList(),
); );
} }
Widget _buildLoading(DockerProvider docker) { Widget _buildImages(DockerProvider docker) {
if (docker.isBusy) { if (docker.images == null) {
return const Padding( return const SizedBox();
padding: EdgeInsets.all(17),
child: Center(
child: CircularProgressIndicator(),
),
);
} }
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<DockerPsItem> running, DockerProvider docker) { Widget _buildEditHost(List<DockerPsItem> running, DockerProvider docker) {
if (running.isEmpty) { if (running.isNotEmpty) return const SizedBox();
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(17, 17, 17, 0), padding: const EdgeInsets.fromLTRB(17, 17, 17, 0),
child: Column( child: Column(
children: [ children: [
Text( Text(
s.dockerEmptyRunningItems, s.dockerEmptyRunningItems,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
TextButton( TextButton(
onPressed: () => _showEditHostDialog(docker), onPressed: () => _showEditHostDialog(docker),
child: Text(s.dockerEditHost)) child: Text(s.dockerEditHost))
], ],
), ),
); );
}
return const SizedBox();
} }
Future<void> _showEditHostDialog(DockerProvider docker) async { Future<void> _showEditHostDialog(DockerProvider docker) async {