support add docker container

This commit is contained in:
lollipopkit
2022-12-11 11:05:13 +08:00
parent 1c29b76455
commit d43d251d92
7 changed files with 191 additions and 63 deletions

View File

@@ -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<bool> _do(String id, String cmd) async {
setBusyState();
final result = await client!.run('$cmd $id').string;
await refresh();
setBusyState(false);
return result.contains(id);
}
Future<DockerErr?> stop(String id) async => await run('docker stop $id');
Future<bool> stop(String id) async => await _do(id, 'docker stop');
Future<DockerErr?> start(String id) async => await run('docker start $id');
Future<bool> start(String id) async => await _do(id, 'docker start');
Future<bool> delete(String id) async => await _do(id, 'docker rm');
Future<DockerErr?> delete(String id) async => await run('docker rm $id');
Future<DockerErr?> run(String cmd) async {
if (!cmd.startsWith('docker ')) {
if (!cmd.startsWith(_dockerPrefixReg)) {
return DockerErr(type: DockerErrType.cmdNoPrefix);
}
setBusyState();
final errs = <String>[];
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<DockerStore>().getDockerHost(hostId!);
if (dockerHost == null || dockerHost.isEmpty) {
return 'sudo $cmd';
}
return 'export DOCKER_HOST=$dockerHost && $cmd';
}
}

View File

@@ -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"),

View File

@@ -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("重命名"),

View File

@@ -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: [],
);

View File

@@ -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"
}

View File

@@ -134,5 +134,9 @@
"cmd": "命令",
"dockerEmptyRunningItems": "没有正在运行的容器。\n这可能是因为环境变量 DOCKER_HOST 没有被正确读取。你可以通过在终端内运行 `echo $DOCKER_HOST` 来获取。",
"dockerEditHost": "编辑 DOCKER_HOST",
"dockerCmdPrefixErr": "命令前缀错误,没有以 `docker` 开头"
"newContainer": "新建容器",
"dockerImage": "镜像",
"dockerContainerName": "容器名",
"extraArgs": "额外参数",
"preview": "预览"
}

View File

@@ -77,47 +77,103 @@ class _DockerManagePageState extends State<DockerManagePage> {
}
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<void> _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<void> _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<DockerManagePage> {
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<DockerPsItem> running, DockerProvider docker) {