mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 15:24:35 +01:00
support docker image management
This commit is contained in:
47
lib/data/model/docker/image.dart
Normal file
47
lib/data/model/docker/image.dart
Normal 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)!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
@@ -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("更改将会立即生效")
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -139,5 +139,7 @@
|
|||||||
"dockerContainerName": "容器名",
|
"dockerContainerName": "容器名",
|
||||||
"extraArgs": "额外参数",
|
"extraArgs": "额外参数",
|
||||||
"preview": "预览",
|
"preview": "预览",
|
||||||
"isBusy": "当前正忙"
|
"isBusy": "当前正忙",
|
||||||
|
"imagesList": "镜像列表",
|
||||||
|
"dockerImagesFmt": "共 {count} 个镜像"
|
||||||
}
|
}
|
||||||
@@ -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,25 +253,62 @@ 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 _buildImages(DockerProvider docker) {
|
||||||
|
if (docker.images == null) {
|
||||||
|
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) {
|
Widget _buildLoading(DockerProvider docker) {
|
||||||
if (docker.isBusy) {
|
if (!docker.isBusy) return const SizedBox();
|
||||||
return const Padding(
|
final haveLog = docker.runLog != null;
|
||||||
padding: EdgeInsets.all(17),
|
return Padding(
|
||||||
child: Center(
|
padding: const EdgeInsets.all(17),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Center(
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
|
haveLog ? const SizedBox(height: 17) : const SizedBox(),
|
||||||
|
haveLog ? Text(docker.runLog!) : const SizedBox()
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return 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(
|
||||||
@@ -287,8 +324,6 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showEditHostDialog(DockerProvider docker) async {
|
Future<void> _showEditHostDialog(DockerProvider docker) async {
|
||||||
await showRoundDialog(
|
await showRoundDialog(
|
||||||
|
|||||||
Reference in New Issue
Block a user