mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 15:24:35 +01:00
support add docker container
This commit is contained in:
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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("重命名"),
|
||||
|
||||
@@ -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: [],
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -134,5 +134,9 @@
|
||||
"cmd": "命令",
|
||||
"dockerEmptyRunningItems": "没有正在运行的容器。\n这可能是因为环境变量 DOCKER_HOST 没有被正确读取。你可以通过在终端内运行 `echo $DOCKER_HOST` 来获取。",
|
||||
"dockerEditHost": "编辑 DOCKER_HOST",
|
||||
"dockerCmdPrefixErr": "命令前缀错误,没有以 `docker` 开头"
|
||||
"newContainer": "新建容器",
|
||||
"dockerImage": "镜像",
|
||||
"dockerContainerName": "容器名",
|
||||
"extraArgs": "额外参数",
|
||||
"preview": "预览"
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user