mirror of
https://gitee.com/dromara/Jpom.git
synced 2024-12-01 19:38:09 +08:00
fix docker 控制台页面布局优化,支持单独查看 docker-compose
This commit is contained in:
parent
16b25431f7
commit
6d1853cdbf
@ -6,6 +6,8 @@
|
||||
|
||||
1. 【server】修复 资产管理机器管理当个分配工作空间无法正常使用(感谢@咻咻咻秀啊)
|
||||
2. 【server】修复 资产管理相关权限、操作日志无法记录问题(感谢@咻咻咻秀啊)
|
||||
3. 【server】修复 docker 控制台 、日志无法正常使用
|
||||
4. 【server】优化 docker 控制台页面布局优化,支持单独查看 docker-compose
|
||||
|
||||
------
|
||||
|
||||
|
74
PLANS.md
74
PLANS.md
@ -11,45 +11,47 @@
|
||||
5. ~~docker 集群~~
|
||||
6. ~~ssh 监控~~
|
||||
7. 仓库管理(待定,可以和凭证管理一起考虑)
|
||||
3. scp 发布实践案例
|
||||
4. netty-agent
|
||||
5. 凭证管理
|
||||
6. 升级 JDK 11 或者 17
|
||||
7. 端口监控、监控报警、机器监控、ssh 监控报警
|
||||
8. nginx 流量切换
|
||||
9. acme.sh ui
|
||||
10. 执行审计
|
||||
11. 执行部分命令耗时和直接执行相差太大
|
||||
12. ~~SSH 上传文件进度(前端分片+进度)~~
|
||||
13. ~~**用户体系支持接入第三方系统**~~
|
||||
14. 监控通知模块优化支持更多(飞书)
|
||||
15. ~~传输信息加密(混淆,避免 http 明文传输)~~
|
||||
16. 非 root 用户提升权限写入 root 用户文件
|
||||
17. 部分数据迁移工作空间(项目,构建,仓库、节点分发)
|
||||
18. ~~插件端证书验证迁移到服务端~~
|
||||
19. 前端表格用户自定义列显示
|
||||
20. 导入云效仓库
|
||||
21. 清理触发器表
|
||||
22. ~~稳定版/体验版~~
|
||||
23. ~~插件端自定义发布文件~~
|
||||
24. 节点取消,白名单配置和下载白名单(统一到服务端工作空间配置)
|
||||
25. 仓库、构建、分发、项目导入导出
|
||||
26. 使用服务端的 git 插件
|
||||
3. netty-agent
|
||||
4. 凭证管理
|
||||
5. 升级 JDK 11 或者 17
|
||||
6. 端口监控、监控报警、机器监控、ssh 监控报警
|
||||
7. nginx 流量切换
|
||||
8. acme.sh ui
|
||||
9. 执行审计
|
||||
10. 执行部分命令耗时和直接执行相差太大
|
||||
11. 监控通知模块优化支持更多(飞书)
|
||||
12. 非 root 用户提升权限写入 root 用户文件
|
||||
13. 部分数据迁移工作空间(项目,构建,仓库、节点分发)
|
||||
14. 前端表格用户自定义列显示
|
||||
15. 导入云效仓库
|
||||
16. 清理触发器表
|
||||
17. 节点取消,白名单配置和下载白名单(统一到服务端工作空间配置)
|
||||
18. 仓库、构建、分发、项目导入导出
|
||||
19. 使用服务端的 git 插件
|
||||
20. SSH 连接 docker、docker-compose
|
||||
21. 隧道节点
|
||||
|
||||
### DONE
|
||||
|
||||
1. ~~容器构建基础镜像的管理~~
|
||||
2. ~~tomcat 实践案例~~
|
||||
3. ~~**分片上传文件**~~
|
||||
4. ~~**支持 mysql 数据库**~~
|
||||
5. ~~配置文件优化~~
|
||||
6. ~~项目触发器~~
|
||||
7. ~~节点转发模块优化~~
|
||||
8. ~~构建事件触发新增更多(前后)~~
|
||||
9. ~~复制项目~~
|
||||
10. ~~测命令行参数~~
|
||||
11. ~~标签页缓存问题(定时器未清空)~~
|
||||
12. ~~发布到指定目录~~
|
||||
1. ~~scp 发布实践案例~~
|
||||
2. ~~SSH 上传文件进度(前端分片+进度)~~
|
||||
3. ~~**用户体系支持接入第三方系统**~~
|
||||
4. ~~传输信息加密(混淆,避免 http 明文传输)~~
|
||||
5. ~~插件端证书验证迁移到服务端~~
|
||||
6. ~~稳定版/体验版~~
|
||||
7. ~~插件端自定义发布文件~~
|
||||
8. ~~容器构建基础镜像的管理~~
|
||||
9. ~~tomcat 实践案例~~
|
||||
10. ~~**分片上传文件**~~
|
||||
11. ~~**支持 mysql 数据库**~~
|
||||
12. ~~配置文件优化~~
|
||||
13. ~~项目触发器~~
|
||||
14. ~~节点转发模块优化~~
|
||||
15. ~~构建事件触发新增更多(前后)~~
|
||||
16. ~~复制项目~~
|
||||
17. ~~测命令行参数~~
|
||||
18. ~~标签页缓存问题(定时器未清空)~~
|
||||
19. ~~发布到指定目录~~
|
||||
|
||||
## 2.8.x
|
||||
|
||||
|
@ -90,6 +90,22 @@ public abstract class BaseDockerContainerController extends BaseDockerController
|
||||
return JsonMessage.success("", listContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return json
|
||||
*/
|
||||
@PostMapping(value = "list-compose", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.LIST)
|
||||
public JsonMessage<List<JSONObject>> listCompose(@ValidatorItem String id) throws Exception {
|
||||
IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME);
|
||||
Map<String, Object> parameter = this.toDockerParameter(id);
|
||||
parameter.put("name", getParameter("name"));
|
||||
parameter.put("containerId", getParameter("containerId"));
|
||||
parameter.put("imageId", getParameter("imageId"));
|
||||
parameter.put("showAll", getParameter("showAll"));
|
||||
List<JSONObject> listContainer = (List<JSONObject>) plugin.execute("listComposeContainer", parameter);
|
||||
return JsonMessage.success("", listContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return json
|
||||
*/
|
||||
@ -165,7 +181,7 @@ public abstract class BaseDockerContainerController extends BaseDockerController
|
||||
*/
|
||||
@GetMapping(value = "inspect-container", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.EXECUTE)
|
||||
public JsonMessage<JSONObject> inspectContainer(@ValidatorItem String id, String containerId) throws Exception {
|
||||
public JsonMessage<JSONObject> inspectContainer(@ValidatorItem String id, @ValidatorItem String containerId) throws Exception {
|
||||
IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME);
|
||||
Map<String, Object> parameter = this.toDockerParameter(id);
|
||||
parameter.put("containerId", containerId);
|
||||
|
@ -37,7 +37,6 @@ import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.*;
|
||||
import cn.hutool.cron.task.Task;
|
||||
import cn.hutool.db.Entity;
|
||||
import cn.hutool.extra.ssh.JschRuntimeException;
|
||||
import cn.hutool.extra.ssh.JschUtil;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
@ -372,10 +371,10 @@ public class MachineSshServer extends BaseDbService<MachineSshModel> implements
|
||||
} else if (StrUtil.startWith(privateKey, JschUtils.HEADER)) {
|
||||
// 直接采用 private key content 登录,无需写入文件
|
||||
session = JschUtils.createSession(sshModel.getHost(),
|
||||
sshModel.getPort(),
|
||||
user,
|
||||
StrUtil.trim(privateKey),
|
||||
passwordByte);
|
||||
sshModel.getPort(),
|
||||
user,
|
||||
StrUtil.trim(privateKey),
|
||||
passwordByte);
|
||||
} else if (StrUtil.isEmpty(privateKey)) {
|
||||
File home = FileUtil.getUserHomeDir();
|
||||
Assert.notNull(home, "用户目录没有找到");
|
||||
@ -398,7 +397,7 @@ public class MachineSshServer extends BaseDbService<MachineSshModel> implements
|
||||
// 简要私钥文件是否存在
|
||||
Assert.state(FileUtil.isFile(rsaFile), "私钥文件不存在:" + FileUtil.getAbsolutePath(rsaFile));
|
||||
session = JschUtil.createSession(sshModel.getHost(),
|
||||
sshModel.getPort(), user, FileUtil.getAbsolutePath(rsaFile), passwordByte);
|
||||
sshModel.getPort(), user, FileUtil.getAbsolutePath(rsaFile), passwordByte);
|
||||
}
|
||||
try {
|
||||
session.setServerAliveInterval(timeout);
|
||||
@ -409,7 +408,7 @@ public class MachineSshServer extends BaseDbService<MachineSshModel> implements
|
||||
try {
|
||||
session.connect(timeout);
|
||||
} catch (JSchException e) {
|
||||
throw new JschRuntimeException(e);
|
||||
throw Lombok.sneakyThrow(e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的模式");
|
||||
|
@ -30,7 +30,6 @@ import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.ssh.ChannelType;
|
||||
import cn.hutool.extra.ssh.JschRuntimeException;
|
||||
import cn.hutool.extra.ssh.JschUtil;
|
||||
import cn.hutool.extra.ssh.Sftp;
|
||||
import com.jcraft.jsch.*;
|
||||
@ -209,7 +208,7 @@ public class JschUtils {
|
||||
Identity identity = ContentIdentity.newInstance(privateKeyByte, null, sshUser, jsch);
|
||||
jsch.addIdentity(identity, passphrase);
|
||||
} catch (Exception e) {
|
||||
throw new JschRuntimeException(e);
|
||||
throw Lombok.sneakyThrow(e);
|
||||
}
|
||||
return JschUtil.createSession(jsch, sshHost, sshPort, sshUser);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
*/
|
||||
package org.dromara.jpom;
|
||||
|
||||
import cn.hutool.core.collection.CollStreamUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.exceptions.InvocationTargetRuntimeException;
|
||||
@ -131,60 +132,60 @@ public class DefaultDockerPluginImpl implements IDockerConfigPlugin {
|
||||
UpdateContainerCmd updateContainerCmd = dockerClient.updateContainerCmd(containerId);
|
||||
//
|
||||
Optional.ofNullable(parameter.get("cpusetCpus"))
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.ifPresent(updateContainerCmd::withCpusetCpus);
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.ifPresent(updateContainerCmd::withCpusetCpus);
|
||||
|
||||
Optional.ofNullable(parameter.get("cpusetMems"))
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.ifPresent(updateContainerCmd::withCpusetMems);
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.ifPresent(updateContainerCmd::withCpusetMems);
|
||||
|
||||
Optional.ofNullable(parameter.get("cpuPeriod"))
|
||||
.map(Convert::toInt)
|
||||
.ifPresent(updateContainerCmd::withCpuPeriod);
|
||||
.map(Convert::toInt)
|
||||
.ifPresent(updateContainerCmd::withCpuPeriod);
|
||||
|
||||
Optional.ofNullable(parameter.get("cpuQuota"))
|
||||
.map(Convert::toInt)
|
||||
.ifPresent(updateContainerCmd::withCpuQuota);
|
||||
.map(Convert::toInt)
|
||||
.ifPresent(updateContainerCmd::withCpuQuota);
|
||||
|
||||
Optional.ofNullable(parameter.get("cpuShares"))
|
||||
.map(Convert::toInt)
|
||||
.ifPresent(updateContainerCmd::withCpuShares);
|
||||
.map(Convert::toInt)
|
||||
.ifPresent(updateContainerCmd::withCpuShares);
|
||||
|
||||
Optional.ofNullable(parameter.get("blkioWeight"))
|
||||
.map(Convert::toInt)
|
||||
.ifPresent(updateContainerCmd::withBlkioWeight);
|
||||
.map(Convert::toInt)
|
||||
.ifPresent(updateContainerCmd::withBlkioWeight);
|
||||
|
||||
Optional.ofNullable(parameter.get("memoryReservation"))
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.map(s -> {
|
||||
if (StrUtil.isEmpty(s)) {
|
||||
return null;
|
||||
}
|
||||
return DataSizeUtil.parse(s);
|
||||
})
|
||||
.ifPresent(updateContainerCmd::withMemoryReservation);
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.map(s -> {
|
||||
if (StrUtil.isEmpty(s)) {
|
||||
return null;
|
||||
}
|
||||
return DataSizeUtil.parse(s);
|
||||
})
|
||||
.ifPresent(updateContainerCmd::withMemoryReservation);
|
||||
|
||||
Optional.ofNullable(parameter.get("memory"))
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.map(s -> {
|
||||
if (StrUtil.isEmpty(s)) {
|
||||
return null;
|
||||
}
|
||||
return DataSizeUtil.parse(s);
|
||||
})
|
||||
.ifPresent(updateContainerCmd::withMemory);
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.map(s -> {
|
||||
if (StrUtil.isEmpty(s)) {
|
||||
return null;
|
||||
}
|
||||
return DataSizeUtil.parse(s);
|
||||
})
|
||||
.ifPresent(updateContainerCmd::withMemory);
|
||||
|
||||
// updateContainerCmd.withKernelMemory(DataSizeUtil.parse("10M"));
|
||||
|
||||
Optional.ofNullable(parameter.get("memorySwap"))
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.map(s -> {
|
||||
if (StrUtil.isEmpty(s)) {
|
||||
return null;
|
||||
}
|
||||
return DataSizeUtil.parse(s);
|
||||
})
|
||||
.ifPresent(updateContainerCmd::withMemorySwap);
|
||||
.map(StrUtil::toStringOrNull)
|
||||
.map(s -> {
|
||||
if (StrUtil.isEmpty(s)) {
|
||||
return null;
|
||||
}
|
||||
return DataSizeUtil.parse(s);
|
||||
})
|
||||
.ifPresent(updateContainerCmd::withMemorySwap);
|
||||
|
||||
UpdateContainerResponse updateContainerResponse = updateContainerCmd.exec();
|
||||
return DockerUtil.toJSON(updateContainerResponse);
|
||||
@ -230,8 +231,8 @@ public class DefaultDockerPluginImpl implements IDockerConfigPlugin {
|
||||
tag = StrUtil.emptyToDefault(tag, "latest");
|
||||
logConsumer.accept(StrUtil.format("start pull {}:{}", repository, tag));
|
||||
PullImageCmd pullImageCmd = dockerClient.pullImageCmd(repository)
|
||||
.withTag(tag)
|
||||
.withAuthConfig(dockerClient.authConfig());
|
||||
.withTag(tag)
|
||||
.withAuthConfig(dockerClient.authConfig());
|
||||
pullImageCmd.exec(new InvocationBuilder.AsyncResultCallback<PullResponseItem>() {
|
||||
@Override
|
||||
public void onNext(PullResponseItem object) {
|
||||
@ -263,33 +264,33 @@ public class DefaultDockerPluginImpl implements IDockerConfigPlugin {
|
||||
CreateContainerCmd containerCmd = dockerClient.createContainerCmd(imageId);
|
||||
containerCmd.withName(name);
|
||||
Opt.ofBlankAble(labels)
|
||||
.map(s -> UrlQuery.of(s, CharsetUtil.CHARSET_UTF_8))
|
||||
.map(UrlQuery::getQueryMap)
|
||||
.map((Function<Map<CharSequence, CharSequence>, Map<String, String>>) map -> {
|
||||
HashMap<String, String> labelMap = MapUtil.newHashMap();
|
||||
for (Map.Entry<CharSequence, CharSequence> entry : map.entrySet()) {
|
||||
labelMap.put(StrUtil.toString(entry.getKey()), StrUtil.toString(entry.getValue()));
|
||||
}
|
||||
return labelMap;
|
||||
})
|
||||
.ifPresent(containerCmd::withLabels);
|
||||
.map(s -> UrlQuery.of(s, CharsetUtil.CHARSET_UTF_8))
|
||||
.map(UrlQuery::getQueryMap)
|
||||
.map((Function<Map<CharSequence, CharSequence>, Map<String, String>>) map -> {
|
||||
HashMap<String, String> labelMap = MapUtil.newHashMap();
|
||||
for (Map.Entry<CharSequence, CharSequence> entry : map.entrySet()) {
|
||||
labelMap.put(StrUtil.toString(entry.getKey()), StrUtil.toString(entry.getValue()));
|
||||
}
|
||||
return labelMap;
|
||||
})
|
||||
.ifPresent(containerCmd::withLabels);
|
||||
|
||||
HostConfig hostConfig = HostConfig.newHostConfig();
|
||||
Opt.ofBlankAble(runtime).ifPresent(hostConfig::withRuntime);
|
||||
List<ExposedPort> exposedPortList = new ArrayList<>();
|
||||
if (StrUtil.isNotEmpty(exposedPorts)) {
|
||||
List<PortBinding> portBindings = StrUtil.splitTrim(exposedPorts, StrUtil.COMMA)
|
||||
.stream()
|
||||
.map(PortBinding::parse)
|
||||
.peek(portBinding -> exposedPortList.add(portBinding.getExposedPort()))
|
||||
.collect(Collectors.toList());
|
||||
.stream()
|
||||
.map(PortBinding::parse)
|
||||
.peek(portBinding -> exposedPortList.add(portBinding.getExposedPort()))
|
||||
.collect(Collectors.toList());
|
||||
hostConfig.withPortBindings(portBindings);
|
||||
}
|
||||
if (StrUtil.isNotEmpty(volumes)) {
|
||||
List<Bind> binds = StrUtil.splitTrim(volumes, StrUtil.COMMA)
|
||||
.stream()
|
||||
.map(Bind::parse)
|
||||
.collect(Collectors.toList());
|
||||
.stream()
|
||||
.map(Bind::parse)
|
||||
.collect(Collectors.toList());
|
||||
hostConfig.withBinds(binds);
|
||||
}
|
||||
Opt.ofBlankAble(networkMode).ifPresent(hostConfig::withNetworkMode);
|
||||
@ -298,9 +299,9 @@ public class DefaultDockerPluginImpl implements IDockerConfigPlugin {
|
||||
// 环境变量
|
||||
if (env != null) {
|
||||
List<String> envList = env.entrySet()
|
||||
.stream()
|
||||
.map(entry -> StrUtil.format("{}={}", entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
.stream()
|
||||
.map(entry -> StrUtil.format("{}={}", entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
containerCmd.withEnv(envList);
|
||||
}
|
||||
Optional.ofNullable(storageOpt).map(map -> {
|
||||
@ -315,8 +316,8 @@ public class DefaultDockerPluginImpl implements IDockerConfigPlugin {
|
||||
List<String> commands = (List<String>) parameter.get("commands");
|
||||
Optional.ofNullable(commands).ifPresent(strings -> {
|
||||
List<String> list = strings.stream()
|
||||
.filter(StrUtil::isNotEmpty)
|
||||
.collect(Collectors.toList());
|
||||
.filter(StrUtil::isNotEmpty)
|
||||
.collect(Collectors.toList());
|
||||
if (CollUtil.isNotEmpty(list)) {
|
||||
containerCmd.withCmd(list);
|
||||
}
|
||||
@ -385,18 +386,18 @@ public class DefaultDockerPluginImpl implements IDockerConfigPlugin {
|
||||
|
||||
BuildImageCmd buildImageCmd = dockerClient.buildImageCmd();
|
||||
buildImageCmd
|
||||
.withBaseDirectory(baseDirectory)
|
||||
.withDockerfile(dockerfile)
|
||||
.withBuildAuthConfigs(authConfigurations)
|
||||
.withTags(CollUtil.newHashSet(StrUtil.splitTrim(tags, StrUtil.COMMA)));
|
||||
.withBaseDirectory(baseDirectory)
|
||||
.withDockerfile(dockerfile)
|
||||
.withBuildAuthConfigs(authConfigurations)
|
||||
.withTags(CollUtil.newHashSet(StrUtil.splitTrim(tags, StrUtil.COMMA)));
|
||||
// 添加构建参数
|
||||
UrlQuery query = UrlQuery.of(buildArgs, CharsetUtil.CHARSET_UTF_8);
|
||||
query.getQueryMap()
|
||||
.forEach((key, value) -> {
|
||||
String valueStr = StrUtil.toString(value);
|
||||
valueStr = StringUtil.formatStrByMap(valueStr, env);
|
||||
buildImageCmd.withBuildArg(StrUtil.toString(key), valueStr);
|
||||
});
|
||||
.forEach((key, value) -> {
|
||||
String valueStr = StrUtil.toString(value);
|
||||
valueStr = StringUtil.formatStrByMap(valueStr, env);
|
||||
buildImageCmd.withBuildArg(StrUtil.toString(key), valueStr);
|
||||
});
|
||||
// 标签
|
||||
UrlQuery labelsQuery = UrlQuery.of(labels, CharsetUtil.CHARSET_UTF_8);
|
||||
HashMap<String, String> labelMap = MapUtil.newHashMap();
|
||||
@ -523,8 +524,25 @@ public class DefaultDockerPluginImpl implements IDockerConfigPlugin {
|
||||
dockerClient.removeImageCmd(imageId).withForce(true).exec();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 不包含 docker compose
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return list
|
||||
*/
|
||||
private List<JSONObject> listContainerCmd(Map<String, Object> parameter) {
|
||||
List<JSONObject> list = this.listContainerByLabelCmd(parameter, null);
|
||||
String composeLabel = "com.docker.compose.project";
|
||||
return list.stream()
|
||||
.filter(jsonObject -> {
|
||||
JSONObject labels = jsonObject.getJSONObject("labels");
|
||||
String project = MapUtil.get(labels, composeLabel, String.class);
|
||||
return project == null;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<JSONObject> listContainerByLabelCmd(Map<String, Object> parameter, String label) {
|
||||
DockerClient dockerClient = DockerUtil.get(parameter);
|
||||
|
||||
ListContainersCmd listContainersCmd = dockerClient.listContainersCmd();
|
||||
@ -537,14 +555,51 @@ public class DefaultDockerPluginImpl implements IDockerConfigPlugin {
|
||||
if (StrUtil.isNotEmpty(containerId)) {
|
||||
listContainersCmd.withIdFilter(CollUtil.newArrayList(containerId));
|
||||
}
|
||||
// String imageId = (String) parameter.get("imageId");
|
||||
// if (StrUtil.isNotEmpty(imageId)) {
|
||||
// listContainersCmd.withFilter("image", CollUtil.newArrayList(imageId));
|
||||
// }
|
||||
|
||||
Opt.ofBlankAble(label).ifPresent(s -> {
|
||||
// 只筛选 docker compose
|
||||
listContainersCmd.withLabelFilter(CollUtil.newArrayList(s));
|
||||
});
|
||||
String imageId = (String) parameter.get("imageId");
|
||||
List<Container> exec = listContainersCmd.exec();
|
||||
exec = ObjectUtil.defaultIfNull(exec, new ArrayList<>());
|
||||
return exec.stream().map(DockerUtil::toJSON).collect(Collectors.toList());
|
||||
return exec.stream()
|
||||
.map(DockerUtil::toJSON)
|
||||
.filter(jsonObject -> {
|
||||
if (StrUtil.isEmpty(imageId)) {
|
||||
return true;
|
||||
}
|
||||
String imageId1 = jsonObject.getString("imageId");
|
||||
return StrUtil.contains(imageId1, imageId);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 不包含 docker compose
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return list
|
||||
*/
|
||||
private List<JSONObject> listComposeContainerCmd(Map<String, Object> parameter) {
|
||||
String composeLabel = "com.docker.compose.project";
|
||||
List<JSONObject> list = this.listContainerByLabelCmd(parameter, composeLabel);
|
||||
//
|
||||
Map<String, List<JSONObject>> map = CollStreamUtil.groupKeyValue(list, jsonObject -> {
|
||||
JSONObject labels = jsonObject.getJSONObject("labels");
|
||||
return Optional.ofNullable(labels)
|
||||
.map(jsonObject1 -> jsonObject1.getString(composeLabel))
|
||||
.orElse("null");
|
||||
}, jsonObject -> jsonObject);
|
||||
//
|
||||
return map.entrySet().stream()
|
||||
.map(stringListEntry -> {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("name", stringListEntry.getKey());
|
||||
jsonObject.put("child", stringListEntry.getValue());
|
||||
return jsonObject;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void restartContainerCmd(Map<String, Object> parameter) {
|
||||
|
@ -44,8 +44,8 @@
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-vue": "^7.15.1",
|
||||
"prettier": "^2.3.2",
|
||||
"stylus": "^0.54.8",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^5.0.0",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
@ -78,7 +78,7 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@ -89,12 +89,12 @@ export default {
|
||||
}
|
||||
|
||||
.full-content {
|
||||
min-height calc(100vh - 120px);
|
||||
min-height: calc(100vh - 120px);
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.node-full-content {
|
||||
min-height calc(100vh - 130px) !important;
|
||||
min-height: calc(100vh - 130px) !important;
|
||||
}
|
||||
|
||||
.globalLoading {
|
||||
@ -104,37 +104,15 @@ export default {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
position: fixed !important;
|
||||
z-index: 99999;
|
||||
top:0;
|
||||
bottom:0;
|
||||
left:0;
|
||||
right:0;
|
||||
display:flex;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
}
|
||||
.ant-spin-text{
|
||||
.ant-spin-text {
|
||||
text-shadow: 0 0 black !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.hide-scrollbar *::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar * {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar pre::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar pre {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.ant-space {
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 0;
|
||||
|
@ -61,6 +61,21 @@ export function dockerContainerList(urlPrefix, params) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 容器中的列表
|
||||
* @param {JSON} params
|
||||
*/
|
||||
export function dockerContainerListCompose(urlPrefix, params) {
|
||||
return axios({
|
||||
url: urlPrefix + "/container/list-compose",
|
||||
method: "post",
|
||||
data: params,
|
||||
headers: {
|
||||
loading: "no",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看 docker info
|
||||
* @param {JSON} params
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.5 KiB |
1
web-vue/src/assets/images/gitee.svg
Normal file
1
web-vue/src/assets/images/gitee.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" fill="#c71d23" r="16"/><path d="m24.0987698 14.2225144h-9.0863697c-.4362899.000207-.7900048.3538292-.790326.7901191l-.0005173 1.9752185c-.0003277.4363707.353328.7902117.7896987.790326.0000712 0 .0001424 0 .0002135-.0002135l5.5317648-.0000461c.4363708-.0000102.7901221.3537352.7901257.790106 0 .0000022 0 .0000044-.0000066.0000066v.1975077.1975318c0 1.3091122-1.0612451 2.3703573-2.3703573 2.3703573h-7.5067195c-.4363081-.0000218-.790009-.353713-.7900429-.7900211l-.0002069-7.5059917c-.0001014-1.3091122 1.0611145-2.3703865 2.3702267-2.3704226.0000217 0 .0000435 0 .0000653.0000653h11.0602463c.4361793-.0004902.7898484-.35394.7906091-.79011894l.0012251-1.97521881c.0007606-.43637034-.3527683-.79033806-.7891389-.79060871-.0001634-.0000001-.0003268-.00000015-.0004901.00048976h-11.0617654c-3.27278051 0-5.92589329 2.65311278-5.92589329 5.9258933v11.0612755c0 .4363707.35374837.7901191.7901191.7901191h11.65447149c2.9454379 0 5.3331872-2.3877493 5.3331872-5.3331872v-4.5430682c0-.4363707-.3537484-.7901191-.7901191-.7901191z" fill="#fff"/></g></svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -1,51 +0,0 @@
|
||||
/* 全局样式,重置样式 */
|
||||
html,
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ant-drawer-body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.ant-table-wrapper {
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.ant-table-body {
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
|
||||
.search-input-item {
|
||||
width: 140px;
|
||||
/* margin-right: 10px; */
|
||||
}
|
||||
|
||||
.filter {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/*
|
||||
.ant-btn {
|
||||
margin-right: 10px;
|
||||
} */
|
||||
|
||||
.ant-btn-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ant-modal-confirm-body .ant-modal-confirm-content {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.ant-modal-confirm-body .ant-modal-confirm-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
103
web-vue/src/assets/reset.less
Normal file
103
web-vue/src/assets/reset.less
Normal file
@ -0,0 +1,103 @@
|
||||
/* 全局样式,重置样式 */
|
||||
html,
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ant-drawer-body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.ant-table-wrapper {
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.ant-table-body {
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
|
||||
.search-input-item {
|
||||
width: 140px;
|
||||
/* margin-right: 10px; */
|
||||
}
|
||||
|
||||
.filter {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/*
|
||||
.ant-btn {
|
||||
margin-right: 10px;
|
||||
} */
|
||||
|
||||
.ant-btn-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ant-modal-confirm-body .ant-modal-confirm-content {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.ant-modal-confirm-body .ant-modal-confirm-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hide-scrollbar *::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar * {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar pre::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar pre {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
@color-border-last: #e5e5e5;
|
||||
@color-neutral-last: #f7f8fa;
|
||||
@scrollbar-size: 4px;
|
||||
|
||||
// 兼容火狐
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: @color-border-last @color-neutral-last;
|
||||
}
|
||||
// 滚动条样式
|
||||
.ant-table-body::-webkit-scrollbar {
|
||||
width: @scrollbar-size;
|
||||
// height: @scrollbar-size;
|
||||
// width: 10px;
|
||||
height: 10px;
|
||||
// width: 2px;
|
||||
border-radius: 2px;
|
||||
background-color: transparent;
|
||||
}
|
||||
// 滚动条-活动按钮
|
||||
.ant-table-body::-webkit-scrollbar-thumb {
|
||||
background: @color-border-last;
|
||||
border-radius: 2px;
|
||||
box-shadow: inset 0 0 6px @color-border-last;
|
||||
}
|
||||
// 滚动条背景
|
||||
.ant-table-body::-webkit-scrollbar-track {
|
||||
background-color: @color-neutral-last;
|
||||
border-radius: 2px;
|
||||
box-shadow: inset 0 0 6px @color-neutral-last;
|
||||
}
|
@ -3,7 +3,7 @@ import App from "./App.vue";
|
||||
|
||||
import "./components/lazy_antd";
|
||||
|
||||
import "./assets/reset.css";
|
||||
import "./assets/reset.less";
|
||||
const introJs = require("intro.js");
|
||||
import "intro.js/introjs.css";
|
||||
// import 'intro.js/themes/introjs-flattener.css';
|
||||
|
@ -56,7 +56,7 @@
|
||||
</a-col>
|
||||
<a-col :span="7" style="text-align: right" class="text-overflow-hidden">
|
||||
<a-tooltip :title="`当前状态:${statusMap[item.status]} ${item.statusMsg ? '状态消息:' + item.statusMsg : ''} `">
|
||||
<a-tag :color="statusColor[item.status]" style="margin-right: 0px"> {{ statusMap[item.status] }}</a-tag>
|
||||
<a-tag :color="statusColor[item.status]" style="margin-right: 0px"> {{ statusMap[item.status] || "未知" }}</a-tag>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
@ -813,7 +813,7 @@ export default {
|
||||
totalProjectNum: 0,
|
||||
columns: [
|
||||
{ title: "分发 ID", dataIndex: "id", ellipsis: true, width: 110, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "分发名称", dataIndex: "name", ellipsis: true, width: 150, scopedSlots: { customRender: "name" } },
|
||||
{ title: "分发名称", dataIndex: "name", ellipsis: true, width: 200, scopedSlots: { customRender: "name" } },
|
||||
{ title: "项目分组", dataIndex: "group", sorter: true, width: "100px", ellipsis: true, scopedSlots: { customRender: "group" } },
|
||||
{ title: "分发类型", dataIndex: "outGivingProject", width: "90px", ellipsis: true, scopedSlots: { customRender: "outGivingProject" } },
|
||||
{ title: "分发后", dataIndex: "afterOpt", ellipsis: true, width: "150px", scopedSlots: { customRender: "afterOpt" } },
|
||||
|
@ -1,41 +1,66 @@
|
||||
<template>
|
||||
<a-layout class="docker-layout">
|
||||
<a-layout-sider width="100px" style="min-height: calc(100vh - 85px)">
|
||||
<a-menu theme="light" mode="inline" class="docker-menu" v-model="menuKeyArray" @click="menuClick">
|
||||
<a-menu-item key="containers">
|
||||
<span class="nav-text">容器</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="images">
|
||||
<span class="nav-text">镜像</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="volumes">
|
||||
<span class="nav-text">卷</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="networks">
|
||||
<span class="nav-text">网络</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="info">
|
||||
<span class="nav-text">信息</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="prune">
|
||||
<span class="nav-text">裁剪</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<!-- style="{ background: '#fff', padding: '10px' }" -->
|
||||
<a-layout class="layout-content drawer-layout-content">
|
||||
<!-- <a-layout-header :style="{ background: '#fff', padding: 0 }" /> -->
|
||||
<!-- :style="{ margin: '24px 16px 0' }" -->
|
||||
<a-layout-content>
|
||||
<container v-if="menuKey === 'containers'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<images v-if="menuKey === 'images'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<volumes v-if="menuKey === 'volumes'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<info v-if="menuKey === 'info'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<networks v-if="menuKey === 'networks'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<prune v-if="menuKey === 'prune'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
<div :class="`${this.scrollbarFlag ? '' : 'hide-scrollbar'}`">
|
||||
<!-- 控制台 -->
|
||||
<a-drawer
|
||||
destroyOnClose
|
||||
placement="right"
|
||||
:width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`"
|
||||
:visible="true"
|
||||
@close="onClose"
|
||||
:bodyStyle="{
|
||||
padding: '0',
|
||||
}"
|
||||
>
|
||||
<template #title>
|
||||
<a-space>
|
||||
【控制台】
|
||||
<a-menu theme="light" mode="horizontal" class="docker-menu" v-model="menuKeyArray" @click="menuClick">
|
||||
<a-menu-item key="containers">
|
||||
<span class="nav-text">独立容器</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="docker-compose">
|
||||
<span class="nav-text">docker-compose</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="images">
|
||||
<span class="nav-text">镜像</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="volumes">
|
||||
<span class="nav-text">卷</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="networks">
|
||||
<span class="nav-text">网络</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="info">
|
||||
<span class="nav-text">信息</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="prune">
|
||||
<span class="nav-text">裁剪</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<a-layout :class="`layout-content drawer-layout-content ${this.scrollbarFlag ? '' : 'hide-scrollbar'}`" style="padding-bottom: 10px">
|
||||
<a-layout-content>
|
||||
<container :key="menuKey" v-if="menuKey === 'containers'" type="container" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<container
|
||||
:key="`docker-compose`"
|
||||
v-else-if="menuKey === 'docker-compose'"
|
||||
type="compose"
|
||||
:id="this.id"
|
||||
:machineDockerId="this.machineDockerId"
|
||||
:visible="this.visible"
|
||||
:urlPrefix="this.urlPrefix"
|
||||
/>
|
||||
<images v-if="menuKey === 'images'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<volumes v-if="menuKey === 'volumes'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<info v-if="menuKey === 'info'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<networks v-if="menuKey === 'networks'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
<prune v-if="menuKey === 'prune'" :id="this.id" :machineDockerId="this.machineDockerId" :visible="this.visible" :urlPrefix="this.urlPrefix" />
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Container from "./container";
|
||||
@ -44,6 +69,7 @@ import Volumes from "./volumes";
|
||||
import Info from "./info";
|
||||
import Networks from "./networks";
|
||||
import Prune from "./prune";
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
@ -74,16 +100,21 @@ export default {
|
||||
menuKey: "containers",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["getCollapsed", "getGuideCache"]),
|
||||
scrollbarFlag() {
|
||||
return this.getGuideCache.scrollbarFlag === undefined ? true : this.getGuideCache.scrollbarFlag;
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
menuClick(item) {
|
||||
this.menuKey = item.key;
|
||||
},
|
||||
onClose() {
|
||||
this.$emit("close");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.docker-menu {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
@ -1,215 +1,405 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-table :data-source="list" size="middle" :columns="columns" :pagination="false" bordered rowKey="id" @expand="expand">
|
||||
<template slot="title">
|
||||
<a-space>
|
||||
<a-input v-model="listQuery['name']" @pressEnter="loadData" placeholder="名称" class="search-input-item" />
|
||||
<a-input v-model="listQuery['containerId']" @pressEnter="loadData" placeholder="容器id" class="search-input-item" />
|
||||
<!-- <a-input v-model="listQuery['imageId']" @keyup.enter="loadData" placeholder="镜像id" class="search-input-item" /> -->
|
||||
<div>
|
||||
显示所有
|
||||
<a-switch checked-children="是" un-checked-children="否" v-model="listQuery['showAll']" />
|
||||
</div>
|
||||
<a-button type="primary" @click="loadData" :loading="loading">搜索</a-button>
|
||||
<a-statistic-countdown format=" s 秒" title="刷新倒计时" :value="countdownTime" @finish="autoUpdate" />
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<a-popover :title="`容器名称:${(text || []).join(',')}`" slot="names" slot-scope="text, record">
|
||||
<template slot="content">
|
||||
<p>容器Id: {{ record.id }}</p>
|
||||
<p>镜像:{{ record.image }}</p>
|
||||
<p>镜像Id: {{ record.imageId }}</p>
|
||||
|
||||
<template v-if="record.labels">
|
||||
<p v-for="(value, key) in record.labels" :key="key">{{ key }}<a-icon type="arrow-right" />{{ value }}</p>
|
||||
</template>
|
||||
<template v-if="this.type === 'container'">
|
||||
<a-table :data-source="list" size="middle" :columns="columns" :pagination="false" bordered rowKey="id">
|
||||
<template slot="title">
|
||||
<a-space>
|
||||
<a-input v-model="listQuery['name']" @pressEnter="loadData" placeholder="名称" class="search-input-item" />
|
||||
<a-input v-model="listQuery['containerId']" @pressEnter="loadData" placeholder="容器id" class="search-input-item" />
|
||||
<a-input v-model="listQuery['imageId']" @keyup.enter="loadData" placeholder="镜像id" class="search-input-item" />
|
||||
<div>
|
||||
查看
|
||||
<a-switch checked-children="所有" un-checked-children="运行中" v-model="listQuery['showAll']" />
|
||||
</div>
|
||||
<a-button type="primary" @click="loadData" :loading="loading">搜索</a-button>
|
||||
<a-statistic-countdown format=" s 秒" title="刷新倒计时" :value="countdownTime" @finish="autoUpdate" />
|
||||
</a-space>
|
||||
</template>
|
||||
<a-icon v-if="record.labels && Object.keys(record.labels).length" type="pushpin" />
|
||||
<span>{{ (text || []).join(",") }}</span>
|
||||
</a-popover>
|
||||
|
||||
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
<a-popover :title="`容器名称:${(text || []).join(',')}`" slot="names" slot-scope="text, record">
|
||||
<template slot="content">
|
||||
<p>容器Id: {{ record.id }}</p>
|
||||
<p>镜像:{{ record.image }}</p>
|
||||
<p>镜像Id: {{ record.imageId }}</p>
|
||||
</template>
|
||||
|
||||
<a-tooltip slot="id" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span style="display: none"> {{ (array = text.split(":")) }}</span>
|
||||
<span>{{ array[array.length - 1].slice(0, 12) }}</span>
|
||||
</a-tooltip>
|
||||
<span>{{ (text || []).join(",") }}</span>
|
||||
</a-popover>
|
||||
|
||||
<a-popover
|
||||
slot="ports"
|
||||
slot-scope="text, record"
|
||||
placement="topLeft"
|
||||
:title="`
|
||||
网络信息 ${(text || [])
|
||||
.map((item) => {
|
||||
return item.type + ' ' + (item.ip || '') + ':' + (item.publicPort || '') + ':' + item.privatePort;
|
||||
})
|
||||
.join('/')}
|
||||
`"
|
||||
>
|
||||
<template slot="content">
|
||||
<template v-if="record.networkSettings">
|
||||
<template v-if="record.networkSettings.networks">
|
||||
<template v-if="record.networkSettings.networks.bridge">
|
||||
桥接模式:
|
||||
<p v-if="record.networkSettings.networks.bridge.ipAddress">
|
||||
IP: <a-tag>{{ record.networkSettings.networks.bridge.ipAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.macAddress">
|
||||
MAC: <a-tag>{{ record.networkSettings.networks.bridge.macAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.gateway">
|
||||
网关: <a-tag>{{ record.networkSettings.networks.bridge.gateway }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.networkID">
|
||||
networkID: <a-tag>{{ record.networkSettings.networks.bridge.networkID }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.endpointId">
|
||||
endpointId: <a-tag>{{ record.networkSettings.networks.bridge.endpointId }}</a-tag>
|
||||
</p>
|
||||
</template>
|
||||
<template v-if="record.networkSettings.networks.ingress">
|
||||
<p v-if="record.networkSettings.networks.ingress.ipAddress">
|
||||
IP: <a-tag>{{ record.networkSettings.networks.ingress.ipAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.macAddress">
|
||||
MAC: <a-tag>{{ record.networkSettings.networks.ingress.macAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.gateway">
|
||||
网关: <a-tag>{{ record.networkSettings.networks.ingress.gateway }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.networkID">
|
||||
networkID: <a-tag>{{ record.networkSettings.networks.ingress.networkID }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.endpointId">
|
||||
endpointId: <a-tag>{{ record.networkSettings.networks.ingress.endpointId }}</a-tag>
|
||||
<a-popover :title="`容器名标签`" slot="labels" slot-scope="text, record">
|
||||
<template slot="content">
|
||||
<template v-if="record.labels">
|
||||
<p v-for="(value, key) in record.labels" :key="key">{{ key }}<a-icon type="arrow-right" />{{ value }}</p>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="record.labels && Object.keys(record.labels).length">
|
||||
<span>{{ (record.labels && Object.keys(record.labels).length) || 0 }} <a-icon type="tags" /></span>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</a-popover>
|
||||
<a-popover :title="`挂载`" slot="mounts" slot-scope="text, record">
|
||||
<template slot="content">
|
||||
<template v-if="record.mounts">
|
||||
<div v-for="(item, index) in record.mounts" :key="index">
|
||||
<p>
|
||||
名称:{{ item.name }} <a-tag>{{ item.rw ? "读写" : "读" }}</a-tag>
|
||||
</p>
|
||||
<p>路径:{{ item.source }}(宿主机) => {{ item.destination }}(容器)</p>
|
||||
<a-divider></a-divider>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="record.mounts && Object.keys(record.mounts).length">
|
||||
<span>{{ (record.mounts && Object.keys(record.mounts).length) || 0 }} <a-icon type="api" /></span>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</a-popover>
|
||||
|
||||
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="showid" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span style="display: none"> {{ (array = text.split(":")) }}</span>
|
||||
<span>{{ array[array.length - 1].slice(0, 12) }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-popover slot="ports" slot-scope="text, record" placement="topLeft">
|
||||
<template #title>
|
||||
网络端口
|
||||
<ul>
|
||||
<li v-for="(item, index) in text || []" :key="index">
|
||||
{{ item.type + " " + (item.ip || "") + ":" + (item.publicPort || "") + ":" + item.privatePort }}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template slot="content">
|
||||
<template v-if="record.networkSettings">
|
||||
<template v-if="record.networkSettings.networks">
|
||||
<template v-if="record.networkSettings.networks.bridge">
|
||||
桥接模式:
|
||||
<p v-if="record.networkSettings.networks.bridge.ipAddress">
|
||||
IP: <a-tag>{{ record.networkSettings.networks.bridge.ipAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.macAddress">
|
||||
MAC: <a-tag>{{ record.networkSettings.networks.bridge.macAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.gateway">
|
||||
网关: <a-tag>{{ record.networkSettings.networks.bridge.gateway }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.networkID">
|
||||
networkID: <a-tag>{{ record.networkSettings.networks.bridge.networkID }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.endpointId">
|
||||
endpointId: <a-tag>{{ record.networkSettings.networks.bridge.endpointId }}</a-tag>
|
||||
</p>
|
||||
</template>
|
||||
<template v-if="record.networkSettings.networks.ingress">
|
||||
<p v-if="record.networkSettings.networks.ingress.ipAddress">
|
||||
IP: <a-tag>{{ record.networkSettings.networks.ingress.ipAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.macAddress">
|
||||
MAC: <a-tag>{{ record.networkSettings.networks.ingress.macAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.gateway">
|
||||
网关: <a-tag>{{ record.networkSettings.networks.ingress.gateway }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.networkID">
|
||||
networkID: <a-tag>{{ record.networkSettings.networks.ingress.networkID }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.endpointId">
|
||||
endpointId: <a-tag>{{ record.networkSettings.networks.ingress.endpointId }}</a-tag>
|
||||
</p>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<span>{{
|
||||
(text || [])
|
||||
.map((item) => {
|
||||
return item.type + " " + (item.publicPort || "") + ":" + item.privatePort;
|
||||
})
|
||||
.join("/")
|
||||
}}</span>
|
||||
</a-popover>
|
||||
<span>{{
|
||||
(text || [])
|
||||
.map((item) => {
|
||||
return item.type + " " + (item.publicPort || "") + ":" + item.privatePort;
|
||||
})
|
||||
.join("/")
|
||||
}}</span>
|
||||
</a-popover>
|
||||
|
||||
<template slot="state" slot-scope="text, record">
|
||||
<a-tooltip :title="(record.status || '') + ' 点击查看日志'" @click="viewLog(record)">
|
||||
<a-switch :checked="text === 'running'" :disabled="true">
|
||||
<a-icon slot="checkedChildren" type="check-circle" />
|
||||
<a-icon slot="unCheckedChildren" type="warning" />
|
||||
</a-switch>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-space>
|
||||
<a-tooltip title="容器是运行中可以进入终端">
|
||||
<a-button size="small" type="link" :disabled="record.state !== 'running'" @click="handleTerminal(record)"><a-icon type="code" /></a-button>
|
||||
<template slot="state" slot-scope="text, record">
|
||||
<a-tooltip :title="(record.status || '') + ' 点击查看日志'" @click="viewLog(record)">
|
||||
<a-switch :checked="text === 'running'" :disabled="true">
|
||||
<a-icon slot="checkedChildren" type="check-circle" />
|
||||
<a-icon slot="unCheckedChildren" type="warning" />
|
||||
</a-switch>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="停止" v-if="record.state === 'running'">
|
||||
<a-button size="small" type="link" @click="doAction(record, 'stop')"><a-icon type="stop" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="启动" v-else>
|
||||
<a-button size="small" type="link" @click="doAction(record, 'start')"> <a-icon type="play-circle" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="重启">
|
||||
<a-button size="small" type="link" :disabled="record.state !== 'running'" @click="doAction(record, 'restart')"><a-icon type="reload" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="删除">
|
||||
<a-button size="small" type="link" @click="doAction(record, 'remove')"><a-icon type="delete" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="点击查看日志">
|
||||
<a-button size="small" type="link" @click="viewLog(record)"><a-icon type="message" /></a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-space>
|
||||
<template v-if="record.state === 'running'">
|
||||
<a-tooltip title="容器是运行中可以进入终端">
|
||||
<a-button size="small" type="link" :disabled="record.state !== 'running'" @click="handleTerminal(record)"><a-icon type="code" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="停止">
|
||||
<a-button size="small" type="link" @click="doAction(record, 'stop')"><a-icon type="stop" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="重启">
|
||||
<a-button size="small" type="link" @click="doAction(record, 'restart')"><a-icon type="reload" /></a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tooltip title="启动">
|
||||
<a-button size="small" type="link" @click="doAction(record, 'start')"> <a-icon type="play-circle" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="停止">
|
||||
<a-button size="small" type="link" :disabled="true"><a-icon type="stop" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="重启">
|
||||
<a-button size="small" type="link" :disabled="true"><a-icon type="reload" /></a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- stats -->
|
||||
<a-table slot="expandedRowRender" :rowKey="(record, index) => index" slot-scope="parentRecord" :columns="statsColumns" :data-source="statsMap[parentRecord.id]" :pagination="false">
|
||||
<template slot="cpus" slot-scope="text, record">
|
||||
{{ (record.cpuStats && record.cpuStats.percpuUsage) || (record.cpuStats && record.cpuStats.onlineCpus) }}
|
||||
<a-dropdown>
|
||||
<a class="ant-dropdown-link" @click="(e) => e.preventDefault()">
|
||||
<a-icon type="more" />
|
||||
</a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item>
|
||||
<atooltip title="编辑容器的一些基础参数">
|
||||
<a-button size="small" type="link" icon="edit" :disabled="record.state !== 'running'" @click="editContainer(record)">编辑</a-button>
|
||||
</atooltip>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-tooltip title="点击查看日志">
|
||||
<a-button size="small" type="link" icon="message" @click="viewLog(record)">日志</a-button>
|
||||
</a-tooltip>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-tooltip title="强制删除">
|
||||
<a-button size="small" type="link" icon="delete" @click="doAction(record, 'remove')">删除</a-button>
|
||||
</a-tooltip>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
<template slot="cpuRatio" slot-scope="text, record">
|
||||
{{
|
||||
(
|
||||
((((record.cpuStats && record.cpuStats.cpuUsage && record.cpuStats.cpuUsage.totalUsage) || 0) -
|
||||
((record.precpuStats && record.precpuStats.cpuUsage && record.precpuStats.cpuUsage.totalUsage) || 0)) /
|
||||
((record.cpuStats && record.cpuStats.systemCpuUsage) || 0) -
|
||||
((record.precpuStats && record.precpuStats.systemCpuUsage) || 0)) *
|
||||
100.0
|
||||
).toFixed(4)
|
||||
}}
|
||||
%
|
||||
</template>
|
||||
<template slot="memory" slot-scope="text, record">
|
||||
<!-- record.state !== 'running' -->
|
||||
<!-- text === 'running' -->
|
||||
<template v-if="parentRecord.state === 'running'">
|
||||
<a-button type="link" icon="edit" @click="editContainer(parentRecord)">
|
||||
{{ renderSize(((record.memoryStats && record.memoryStats.usage) || 0) - ((record.memoryStats && record.memoryStats.stats && record.memoryStats.stats.cache) || 0)) }}
|
||||
/
|
||||
{{ renderSize((record.memoryStats && record.memoryStats.limit) || 0) }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ renderSize(((record.memoryStats && record.memoryStats.usage) || 0) - ((record.memoryStats && record.memoryStats.stats && record.memoryStats.stats.cache) || 0)) }}
|
||||
/
|
||||
{{ renderSize((record.memoryStats && record.memoryStats.limit) || 0) }}
|
||||
</template>
|
||||
</template>
|
||||
<template slot="memoryRatio" slot-scope="text, record">
|
||||
<!-- memoryRatio -->
|
||||
{{
|
||||
(
|
||||
((((record.memoryStats && record.memoryStats.usage) || 0) - ((record.memoryStats && record.memoryStats.stats && record.memoryStats.stats.cache) || 0)) /
|
||||
(record.memoryStats && record.memoryStats.limit)) *
|
||||
100.0
|
||||
).toFixed(4)
|
||||
}}
|
||||
%
|
||||
</template>
|
||||
<template slot="blockIo" slot-scope="text, record">
|
||||
<a-tooltip
|
||||
:title="`${
|
||||
(record.blkioStats && record.blkioStats.ioServiceBytesRecursive && record.blkioStats.ioServiceBytesRecursive[0] && record.blkioStats.ioServiceBytesRecursive[0].op) || 'blkioStats'
|
||||
}`"
|
||||
>
|
||||
{{ renderSize(record.blkioStats && record.blkioStats.ioServiceBytesRecursive && record.blkioStats.ioServiceBytesRecursive[0] && record.blkioStats.ioServiceBytesRecursive[0].value) || 0 }}
|
||||
</a-tooltip>
|
||||
/
|
||||
<a-tooltip
|
||||
:title="`${
|
||||
(record.blkioStats && record.blkioStats.ioServiceBytesRecursive && record.blkioStats.ioServiceBytesRecursive[1] && record.blkioStats.ioServiceBytesRecursive[1].op) || 'blkioStats'
|
||||
}`"
|
||||
>
|
||||
{{ renderSize(record.blkioStats && record.blkioStats.ioServiceBytesRecursive && record.blkioStats.ioServiceBytesRecursive[1] && record.blkioStats.ioServiceBytesRecursive[1].value) || 0 }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template slot="netIo" slot-scope="text, record">
|
||||
<!-- // rx_bytes 网卡接收流量 -->
|
||||
<!-- // tx_bytes 网卡输出流量 -->
|
||||
|
||||
<div :key="index" v-for="(item, index) in Object.keys(record.networks || {})">
|
||||
<a-tooltip :title="`${item} 接收流量`">
|
||||
{{ renderSize(record.networks[item] && record.networks[item].rxBytes) || 0 }}
|
||||
</a-tooltip>
|
||||
/
|
||||
<a-tooltip :title="`${item} 输出流量`">
|
||||
{{ renderSize(record.networks[item] && record.networks[item].txBytes) || 0 }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<!-- // 进程或线程的数量 -->
|
||||
<template slot="pids" slot-scope="text, record"> {{ record.pidsStats && record.pidsStats.current }}</template>
|
||||
</a-table>
|
||||
</a-table>
|
||||
</template>
|
||||
<template v-else-if="this.type === 'compose'">
|
||||
<a-table
|
||||
:data-source="list"
|
||||
size="middle"
|
||||
:columns="parentColumns"
|
||||
:pagination="false"
|
||||
bordered
|
||||
:rowKey="
|
||||
(item, index) => {
|
||||
return index;
|
||||
}
|
||||
"
|
||||
>
|
||||
<template slot="title">
|
||||
<a-space>
|
||||
<a-input v-model="listQuery['name']" @pressEnter="loadData" placeholder="名称" class="search-input-item" />
|
||||
<a-input v-model="listQuery['containerId']" @pressEnter="loadData" placeholder="容器id" class="search-input-item" />
|
||||
<a-input v-model="listQuery['imageId']" @keyup.enter="loadData" placeholder="镜像id" class="search-input-item" />
|
||||
<div>
|
||||
查看
|
||||
<a-switch checked-children="所有" un-checked-children="运行中" v-model="listQuery['showAll']" />
|
||||
</div>
|
||||
<a-button type="primary" @click="loadData" :loading="loading">搜索</a-button>
|
||||
<a-statistic-countdown format=" s 秒" title="刷新倒计时" :value="countdownTime" @finish="autoUpdate" />
|
||||
</a-space>
|
||||
</template>
|
||||
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
<template slot="state" slot-scope="text, record">
|
||||
<span style="display: none">
|
||||
{{
|
||||
(array = (record.child || []).map((item) => {
|
||||
return item.state;
|
||||
}))
|
||||
}}
|
||||
{{
|
||||
(runningCount = array
|
||||
.map((item) => {
|
||||
return item === "running" ? 1 : 0;
|
||||
})
|
||||
.reduce((prev, curr) => {
|
||||
return prev + curr;
|
||||
}, 0))
|
||||
}}</span
|
||||
>
|
||||
<span v-if="runningCount">Running({{ runningCount }}/{{ array.length }})</span>
|
||||
<span v-else>Exited</span>
|
||||
</template>
|
||||
|
||||
<a-table slot="expandedRowRender" slot-scope="record" :data-source="record.child" size="middle" :columns="columns" :pagination="false" bordered rowKey="id">
|
||||
<a-popover :title="`容器名称:${(text || []).join(',')}`" slot="names" slot-scope="text, record">
|
||||
<template slot="content">
|
||||
<p>容器Id: {{ record.id }}</p>
|
||||
<p>镜像:{{ record.image }}</p>
|
||||
<p>镜像Id: {{ record.imageId }}</p>
|
||||
</template>
|
||||
|
||||
<span>{{ (text || []).join(",") }}</span>
|
||||
</a-popover>
|
||||
|
||||
<a-popover :title="`容器名标签`" slot="labels" slot-scope="text, record">
|
||||
<template slot="content">
|
||||
<template v-if="record.labels">
|
||||
<p v-for="(value, key) in record.labels" :key="key">{{ key }}<a-icon type="arrow-right" />{{ value }}</p>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="record.labels && Object.keys(record.labels).length">
|
||||
<span>{{ (record.labels && Object.keys(record.labels).length) || 0 }} <a-icon type="tags" /></span>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</a-popover>
|
||||
<a-popover :title="`挂载`" slot="mounts" slot-scope="text, record">
|
||||
<template slot="content">
|
||||
<template v-if="record.mounts">
|
||||
<div v-for="(item, index) in record.mounts" :key="index">
|
||||
<p>
|
||||
名称:{{ item.name }} <a-tag>{{ item.rw ? "读写" : "读" }}</a-tag>
|
||||
</p>
|
||||
<p>路径:{{ item.source }}(宿主机) => {{ item.destination }}(容器)</p>
|
||||
<a-divider></a-divider>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="record.mounts && Object.keys(record.mounts).length">
|
||||
<span>{{ (record.mounts && Object.keys(record.mounts).length) || 0 }} <a-icon type="api" /></span>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</a-popover>
|
||||
|
||||
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="showid" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span style="display: none"> {{ (array = text.split(":")) }}</span>
|
||||
<span>{{ array[array.length - 1].slice(0, 12) }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-popover slot="ports" slot-scope="text, record" placement="topLeft">
|
||||
<template #title>
|
||||
网络端口
|
||||
<ul>
|
||||
<li v-for="(item, index) in text || []" :key="index">
|
||||
{{ item.type + " " + (item.ip || "") + ":" + (item.publicPort || "") + ":" + item.privatePort }}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template slot="content">
|
||||
<template v-if="record.networkSettings">
|
||||
<template v-if="record.networkSettings.networks">
|
||||
<template v-if="record.networkSettings.networks.bridge">
|
||||
桥接模式:
|
||||
<p v-if="record.networkSettings.networks.bridge.ipAddress">
|
||||
IP: <a-tag>{{ record.networkSettings.networks.bridge.ipAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.macAddress">
|
||||
MAC: <a-tag>{{ record.networkSettings.networks.bridge.macAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.gateway">
|
||||
网关: <a-tag>{{ record.networkSettings.networks.bridge.gateway }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.networkID">
|
||||
networkID: <a-tag>{{ record.networkSettings.networks.bridge.networkID }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.bridge.endpointId">
|
||||
endpointId: <a-tag>{{ record.networkSettings.networks.bridge.endpointId }}</a-tag>
|
||||
</p>
|
||||
</template>
|
||||
<template v-if="record.networkSettings.networks.ingress">
|
||||
<p v-if="record.networkSettings.networks.ingress.ipAddress">
|
||||
IP: <a-tag>{{ record.networkSettings.networks.ingress.ipAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.macAddress">
|
||||
MAC: <a-tag>{{ record.networkSettings.networks.ingress.macAddress }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.gateway">
|
||||
网关: <a-tag>{{ record.networkSettings.networks.ingress.gateway }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.networkID">
|
||||
networkID: <a-tag>{{ record.networkSettings.networks.ingress.networkID }}</a-tag>
|
||||
</p>
|
||||
<p v-if="record.networkSettings.networks.ingress.endpointId">
|
||||
endpointId: <a-tag>{{ record.networkSettings.networks.ingress.endpointId }}</a-tag>
|
||||
</p>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<span>{{
|
||||
(text || [])
|
||||
.map((item) => {
|
||||
return item.type + " " + (item.publicPort || "") + ":" + item.privatePort;
|
||||
})
|
||||
.join("/")
|
||||
}}</span>
|
||||
</a-popover>
|
||||
|
||||
<template slot="state" slot-scope="text, record">
|
||||
<a-tooltip :title="(record.status || '') + ' 点击查看日志'" @click="viewLog(record)">
|
||||
<a-switch :checked="text === 'running'" :disabled="true">
|
||||
<a-icon slot="checkedChildren" type="check-circle" />
|
||||
<a-icon slot="unCheckedChildren" type="warning" />
|
||||
</a-switch>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-space>
|
||||
<template v-if="record.state === 'running'">
|
||||
<a-tooltip title="容器是运行中可以进入终端">
|
||||
<a-button size="small" type="link" @click="handleTerminal(record)"><a-icon type="code" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="停止">
|
||||
<a-button size="small" type="link" @click="doAction(record, 'stop')"><a-icon type="stop" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="重启">
|
||||
<a-button size="small" type="link" @click="doAction(record, 'restart')"><a-icon type="reload" /></a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tooltip title="启动">
|
||||
<a-button size="small" type="link" @click="doAction(record, 'start')"> <a-icon type="play-circle" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="停止">
|
||||
<a-button size="small" type="link" :disabled="true"><a-icon type="stop" /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="重启">
|
||||
<a-button size="small" type="link" :disabled="true"><a-icon type="reload" /></a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<a-dropdown>
|
||||
<a class="ant-dropdown-link" @click="(e) => e.preventDefault()">
|
||||
<a-icon type="more" />
|
||||
</a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item>
|
||||
<atooltip title="编辑容器的一些基础参数">
|
||||
<a-button size="small" type="link" icon="edit" :disabled="record.state !== 'running'" @click="editContainer(record)">编辑</a-button>
|
||||
</atooltip>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-tooltip title="点击查看日志">
|
||||
<a-button size="small" type="link" icon="message" @click="viewLog(record)">日志</a-button>
|
||||
</a-tooltip>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-tooltip title="强制删除">
|
||||
<a-button size="small" type="link" icon="delete" @click="doAction(record, 'remove')">删除</a-button>
|
||||
</a-tooltip>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-table>
|
||||
</template>
|
||||
<!-- 日志 -->
|
||||
<a-modal destroyOnClose :width="'80vw'" v-model="logVisible" title="执行日志" :footer="null" :maskClosable="false">
|
||||
<log-view v-if="logVisible" :id="this.id" :machineDockerId="this.machineDockerId" :containerId="temp.id" />
|
||||
@ -231,122 +421,37 @@
|
||||
<terminal v-if="terminalVisible" :id="this.id" :machineDockerId="this.machineDockerId" :containerId="temp.id" />
|
||||
</a-modal>
|
||||
<!-- 编辑容器配置 -->
|
||||
<a-modal destroyOnClose v-model="editVisible" title="配置容器" @ok="handleEditOk" :maskClosable="false">
|
||||
<a-form-model ref="editForm" :model="temp" :label-col="{ span: 7 }" :wrapper-col="{ span: 17 }">
|
||||
<a-form-model-item prop="blkioWeight">
|
||||
<template slot="label">
|
||||
Block IO 权重
|
||||
<a-tooltip>
|
||||
<template slot="title"> Block IO 权重(相对权重)。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 100%" v-model="temp.blkioWeight" :min="0" :max="1000" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpuShares">
|
||||
<template slot="label">
|
||||
CPU 权重
|
||||
<a-tooltip>
|
||||
<template slot="title"> 一个整数值,表示此容器相对于其他容器的相对 CPU 权重。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 100%" v-model="temp.cpuShares" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpusetCpus">
|
||||
<template slot="label">
|
||||
执行的 CPU
|
||||
<a-tooltip>
|
||||
<template slot="title"> 允许执行的 CPU(例如,0-3、0,1)。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.cpusetCpus" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpusetMems">
|
||||
<template slot="label">
|
||||
CpusetMems
|
||||
<a-tooltip>
|
||||
<template slot="title"> 允许执行的内存节点 (MEM) (0-3, 0,1)。 仅在 NUMA 系统上有效。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.cpusetMems" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpuPeriod">
|
||||
<template slot="label">
|
||||
CPU 周期
|
||||
<a-tooltip>
|
||||
<template slot="title"> CPU 周期的长度,以微秒为单位。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 100%" v-model="temp.cpuPeriod" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpuQuota">
|
||||
<template slot="label">
|
||||
CPU 时间
|
||||
<a-tooltip>
|
||||
<template slot="title"> 容器在一个 CPU 周期内可以获得的 CPU 时间的微秒。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 100%" v-model="temp.cpuQuota" />
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item prop="memory">
|
||||
<template slot="label">
|
||||
内存
|
||||
<a-tooltip>
|
||||
<template slot="title"> 设置内存限制。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.memory" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="memorySwap">
|
||||
<template slot="label">
|
||||
总内存
|
||||
<a-tooltip>
|
||||
<template slot="title"> 总内存(内存 + 交换)。 设置为 -1 以禁用交换。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.memorySwap" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="memoryReservation">
|
||||
<template slot="label">
|
||||
软内存
|
||||
<a-tooltip>
|
||||
<template slot="title"> 软内存限制。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.memoryReservation" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
<a-modal
|
||||
destroyOnClose
|
||||
v-model="editVisible"
|
||||
width="60vw"
|
||||
title="配置容器"
|
||||
@ok="
|
||||
() => {
|
||||
this.$refs.editContainer.handleEditOk();
|
||||
this.editVisible = false;
|
||||
this.loadData();
|
||||
}
|
||||
"
|
||||
:maskClosable="false"
|
||||
>
|
||||
<editContainer ref="editContainer" :id="this.id" :machineDockerId="this.machineDockerId" :urlPrefix="this.urlPrefix" :containerId="temp.id"></editContainer>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { parseTime, renderSize } from "@/utils/const";
|
||||
import {
|
||||
dockerContainerList,
|
||||
dockerContainerRemove,
|
||||
dockerContainerRestart,
|
||||
dockerContainerStart,
|
||||
dockerContainerStats,
|
||||
dockerContainerStop,
|
||||
dockerInspectContainer,
|
||||
dockerUpdateContainer,
|
||||
} from "@/api/docker-api";
|
||||
import { parseTime } from "@/utils/const";
|
||||
import { dockerContainerList, dockerContainerRemove, dockerContainerRestart, dockerContainerStart, dockerContainerStop, dockerContainerListCompose } from "@/api/docker-api";
|
||||
import LogView from "@/pages/docker/log-view";
|
||||
import Terminal from "@/pages/docker/terminal";
|
||||
import editContainer from "./editContainer.vue";
|
||||
|
||||
export default {
|
||||
name: "container",
|
||||
components: {
|
||||
LogView,
|
||||
Terminal,
|
||||
editContainer,
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
@ -364,6 +469,11 @@ export default {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
// container or compose
|
||||
default: "container",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -377,41 +487,36 @@ export default {
|
||||
temp: {},
|
||||
|
||||
columns: [
|
||||
{ title: "序号", width: 80, ellipsis: true, align: "center", customRender: (text, record, index) => `${index + 1}` },
|
||||
{ title: "名称", dataIndex: "names", ellipsis: true, scopedSlots: { customRender: "names" } },
|
||||
{ title: "容器ID", dataIndex: "id", ellipsis: true, width: 150, scopedSlots: { customRender: "id" } },
|
||||
// { title: "镜像", dataIndex: "image", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
// { title: "镜像ID", dataIndex: "imageId", ellipsis: true, width: 150, scopedSlots: { customRender: "id" } },
|
||||
{ title: "序号", width: "80px", ellipsis: true, align: "center", customRender: (text, record, index) => `${index + 1}` },
|
||||
{ title: "名称", dataIndex: "names", ellipsis: true, width: 150, scopedSlots: { customRender: "names" } },
|
||||
{ title: "容器ID", dataIndex: "id", ellipsis: true, width: "130px", scopedSlots: { customRender: "showid" } },
|
||||
{ title: "镜像ID", dataIndex: "imageId", ellipsis: true, width: "130px", scopedSlots: { customRender: "showid" } },
|
||||
{ title: "状态", dataIndex: "state", ellipsis: true, align: "center", width: "80px", scopedSlots: { customRender: "state" } },
|
||||
{ title: "状态描述", dataIndex: "status", ellipsis: true, align: "center", width: 100, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "端口", dataIndex: "ports", ellipsis: true, width: 150, scopedSlots: { customRender: "ports" } },
|
||||
{ title: "状态", dataIndex: "state", ellipsis: true, align: "center", width: 90, scopedSlots: { customRender: "state" } },
|
||||
{ title: "命令", dataIndex: "command", ellipsis: true, width: 150, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "标签", dataIndex: "labels", ellipsis: true, width: "50px", scopedSlots: { customRender: "labels" } },
|
||||
{ title: "挂载", dataIndex: "mounts", ellipsis: true, width: "50px", scopedSlots: { customRender: "mounts" } },
|
||||
|
||||
{
|
||||
title: "创建时间",
|
||||
dataIndex: "created",
|
||||
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => new Number(a.created) - new Number(b.created),
|
||||
sorter: (a, b) => Number(a.created) - new Number(b.created),
|
||||
sortDirections: ["descend", "ascend"],
|
||||
defaultSortOrder: "descend",
|
||||
customRender: (text) => {
|
||||
return parseTime(text);
|
||||
},
|
||||
width: 170,
|
||||
customRender: (text) => parseTime(text),
|
||||
width: "170px",
|
||||
},
|
||||
{ title: "操作", dataIndex: "operation", scopedSlots: { customRender: "operation" }, width: 200 },
|
||||
{ title: "操作", dataIndex: "operation", fixed: "right", scopedSlots: { customRender: "operation" }, width: "160px" },
|
||||
],
|
||||
statsColumns: [
|
||||
{ title: "CPUS", width: "80px", dataIndex: "cpus", ellipsis: true, scopedSlots: { customRender: "cpus" } },
|
||||
{ title: "CPU %", width: 100, dataIndex: "cpuRatio", ellipsis: true, scopedSlots: { customRender: "cpuRatio" } },
|
||||
|
||||
{ title: "MEM USAGE / LIMIT", dataIndex: "memory", ellipsis: true, scopedSlots: { customRender: "memory" } },
|
||||
{ title: "MEM %", width: 100, dataIndex: "memoryRatio", ellipsis: true, scopedSlots: { customRender: "memoryRatio" } },
|
||||
{ title: "NET I/O", dataIndex: "netIo", ellipsis: true, scopedSlots: { customRender: "netIo" } },
|
||||
{ title: "BLOCK I/O", dataIndex: "blockIo", ellipsis: true, scopedSlots: { customRender: "blockIo" } },
|
||||
{ title: "PIDS", width: "80px", dataIndex: "pids", ellipsis: true, scopedSlots: { customRender: "pids" } },
|
||||
parentColumns: [
|
||||
{ title: "序号", width: "80px", ellipsis: true, align: "center", customRender: (text, record, index) => `${index + 1}` },
|
||||
{ title: "名称", width: 200, dataIndex: "name", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "状态", dataIndex: "state", width: "150px", ellipsis: true, scopedSlots: { customRender: "state" } },
|
||||
{ title: "操作", width: "80px", ellipsis: true, scopedSlots: { customRender: "pids" } },
|
||||
],
|
||||
|
||||
statsMap: {},
|
||||
expandedRowKeys: [],
|
||||
action: {
|
||||
remove: {
|
||||
msg: "您确定要删除当前容器吗?",
|
||||
@ -431,6 +536,7 @@ export default {
|
||||
},
|
||||
},
|
||||
editVisible: false,
|
||||
|
||||
countdownTime: Date.now(),
|
||||
};
|
||||
},
|
||||
@ -444,10 +550,8 @@ export default {
|
||||
this.autoUpdate();
|
||||
},
|
||||
methods: {
|
||||
renderSize,
|
||||
autoUpdate() {
|
||||
this.loadData();
|
||||
this.pullStats();
|
||||
},
|
||||
// 加载数据
|
||||
loadData() {
|
||||
@ -457,7 +561,7 @@ export default {
|
||||
this.loading = true;
|
||||
//this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page;
|
||||
this.listQuery.id = this.reqDataId;
|
||||
dockerContainerList(this.urlPrefix, this.listQuery).then((res) => {
|
||||
(this.type === "container" ? dockerContainerList(this.urlPrefix, this.listQuery) : dockerContainerListCompose(this.urlPrefix, this.listQuery)).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data;
|
||||
}
|
||||
@ -487,9 +591,6 @@ export default {
|
||||
message: res.msg,
|
||||
});
|
||||
this.loadData();
|
||||
if (actionKey === "remove") {
|
||||
this.expandedRowKeys = this.expandedRowKeys.filter((item2) => item2 !== record.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -504,76 +605,10 @@ export default {
|
||||
this.temp = Object.assign({}, record);
|
||||
this.terminalVisible = true;
|
||||
},
|
||||
// 展开
|
||||
expand(status, item) {
|
||||
if (status) {
|
||||
this.expandedRowKeys.push(item.id);
|
||||
this.pullStats();
|
||||
} else {
|
||||
this.expandedRowKeys = this.expandedRowKeys.filter((item2) => item2 !== item.id);
|
||||
}
|
||||
},
|
||||
// 获取数据
|
||||
pullStats() {
|
||||
if (!this.expandedRowKeys.length) {
|
||||
return;
|
||||
}
|
||||
dockerContainerStats(this.urlPrefix, {
|
||||
id: this.reqDataId,
|
||||
containerId: this.expandedRowKeys.join(","),
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
Object.keys(res.data).forEach((item) => {
|
||||
this.statsMap = { ...this.statsMap, [item]: [res.data[item]] };
|
||||
});
|
||||
}
|
||||
// console.log(res);
|
||||
});
|
||||
},
|
||||
// 编辑容器
|
||||
editContainer(record) {
|
||||
dockerInspectContainer(this.urlPrefix, {
|
||||
id: this.reqDataId,
|
||||
containerId: record.id,
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.editVisible = true;
|
||||
|
||||
const hostConfig = res.data.hostConfig || {};
|
||||
const data = {
|
||||
containerId: record.id,
|
||||
cpusetCpus: hostConfig.cpusetCpus,
|
||||
cpusetMems: hostConfig.cpusetMems,
|
||||
cpuPeriod: hostConfig.cpuPeriod,
|
||||
cpuShares: hostConfig.cpuShares,
|
||||
cpuQuota: hostConfig.cpuQuota,
|
||||
blkioWeight: hostConfig.blkioWeight,
|
||||
memoryReservation: renderSize(hostConfig.memoryReservation, hostConfig.memoryReservation),
|
||||
// Deprecated: This field is deprecated as the kernel 5.4 deprecated kmem.limit_in_bytes.
|
||||
// kernelMemory: hostConfig.kernelMemory,
|
||||
memory: renderSize(hostConfig.memory, hostConfig.memory),
|
||||
memorySwap: renderSize(hostConfig.memorySwap, hostConfig.memorySwap),
|
||||
};
|
||||
|
||||
this.temp = Object.assign({}, data);
|
||||
}
|
||||
});
|
||||
},
|
||||
handleEditOk() {
|
||||
this.$refs["editForm"].validate((valid) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
const temp = Object.assign({}, this.temp, { id: this.reqDataId });
|
||||
dockerUpdateContainer(this.urlPrefix, temp).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
});
|
||||
this.editVisible = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
this.temp = Object.assign({}, record);
|
||||
this.editVisible = true;
|
||||
// console.log(this.temp);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
277
web-vue/src/pages/docker/editContainer.vue
Normal file
277
web-vue/src/pages/docker/editContainer.vue
Normal file
@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- stats -->
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-descriptions bordered :column="1" size="small">
|
||||
<a-descriptions-item label="CPUS"> {{ (statsData.cpuStats && statsData.cpuStats.percpuUsage) || (statsData.cpuStats && statsData.cpuStats.onlineCpus) }} </a-descriptions-item>
|
||||
<a-descriptions-item label="CPU %">
|
||||
{{
|
||||
(
|
||||
((((statsData.cpuStats && statsData.cpuStats.cpuUsage && statsData.cpuStats.cpuUsage.totalUsage) || 0) -
|
||||
((statsData.precpuStats && statsData.precpuStats.cpuUsage && statsData.precpuStats.cpuUsage.totalUsage) || 0)) /
|
||||
((statsData.cpuStats && statsData.cpuStats.systemCpuUsage) || 0) -
|
||||
((statsData.precpuStats && statsData.precpuStats.systemCpuUsage) || 0)) *
|
||||
100.0
|
||||
).toFixed(4)
|
||||
}}
|
||||
%
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="MEM USAGE">
|
||||
{{ renderSize(((statsData.memoryStats && statsData.memoryStats.usage) || 0) - ((statsData.memoryStats && statsData.memoryStats.stats && statsData.memoryStats.stats.cache) || 0)) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="MEM LIMIT">
|
||||
{{ renderSize((statsData.memoryStats && statsData.memoryStats.limit) || 0) }}
|
||||
</a-descriptions-item>
|
||||
<!-- memoryRatio -->
|
||||
<a-descriptions-item label="MEM %">
|
||||
{{
|
||||
(
|
||||
((((statsData.memoryStats && statsData.memoryStats.usage) || 0) - ((statsData.memoryStats && statsData.memoryStats.stats && statsData.memoryStats.stats.cache) || 0)) /
|
||||
(statsData.memoryStats && statsData.memoryStats.limit)) *
|
||||
100.0
|
||||
).toFixed(4)
|
||||
}}
|
||||
%
|
||||
</a-descriptions-item>
|
||||
<!-- // rx_bytes 网卡接收流量 -->
|
||||
<!-- // tx_bytes 网卡输出流量 -->
|
||||
<a-descriptions-item label="NET I/O rx">
|
||||
<div :key="index" v-for="(item, index) in Object.keys(statsData.networks || {})">
|
||||
<a-tooltip :title="`${item} 接收流量`">
|
||||
{{ renderSize(statsData.networks[item] && statsData.networks[item].rxBytes) || 0 }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="NET I/O tx">
|
||||
<div :key="index" v-for="(item, index) in Object.keys(statsData.networks || {})">
|
||||
<a-tooltip :title="`${item} 输出流量`">
|
||||
{{ renderSize(statsData.networks[item] && statsData.networks[item].txBytes) || 0 }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="BLOCK I/O">
|
||||
<a-tooltip
|
||||
:title="`${
|
||||
(statsData.blkioStats && statsData.blkioStats.ioServiceBytesRecursive && statsData.blkioStats.ioServiceBytesRecursive[0] && statsData.blkioStats.ioServiceBytesRecursive[0].op) ||
|
||||
'blkioStats'
|
||||
}`"
|
||||
>
|
||||
{{
|
||||
renderSize(
|
||||
statsData.blkioStats && statsData.blkioStats.ioServiceBytesRecursive && statsData.blkioStats.ioServiceBytesRecursive[0] && statsData.blkioStats.ioServiceBytesRecursive[0].value
|
||||
) || 0
|
||||
}}
|
||||
</a-tooltip>
|
||||
/
|
||||
<a-tooltip
|
||||
:title="`${
|
||||
(statsData.blkioStats && statsData.blkioStats.ioServiceBytesRecursive && statsData.blkioStats.ioServiceBytesRecursive[1] && statsData.blkioStats.ioServiceBytesRecursive[1].op) ||
|
||||
'blkioStats'
|
||||
}`"
|
||||
>
|
||||
{{
|
||||
renderSize(
|
||||
statsData.blkioStats && statsData.blkioStats.ioServiceBytesRecursive && statsData.blkioStats.ioServiceBytesRecursive[1] && statsData.blkioStats.ioServiceBytesRecursive[1].value
|
||||
) || 0
|
||||
}}
|
||||
</a-tooltip>
|
||||
</a-descriptions-item>
|
||||
|
||||
<!-- // 进程或线程的数量 -->
|
||||
<a-descriptions-item label="PIDS"> {{ statsData.pidsStats && statsData.pidsStats.current }} </a-descriptions-item>
|
||||
</a-descriptions></a-col
|
||||
>
|
||||
<a-col :span="12">
|
||||
<a-form-model ref="editForm" :model="temp" :label-col="{ span: 7 }" :wrapper-col="{ span: 17 }">
|
||||
<a-form-model-item prop="blkioWeight">
|
||||
<template slot="label">
|
||||
Block IO 权重
|
||||
<a-tooltip>
|
||||
<template slot="title"> Block IO 权重(相对权重)。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 100%" v-model="temp.blkioWeight" placeholder="Block IO 权重" :min="0" :max="1000" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpuShares">
|
||||
<template slot="label">
|
||||
CPU 权重
|
||||
<a-tooltip>
|
||||
<template slot="title"> 一个整数值,表示此容器相对于其他容器的相对 CPU 权重。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 100%" v-model="temp.cpuShares" placeholder="一个整数值,表示此容器相对于其他容器的相对 CPU 权重。" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpusetCpus">
|
||||
<template slot="label">
|
||||
执行的 CPU
|
||||
<a-tooltip>
|
||||
<template slot="title"> 允许执行的 CPU(例如,0-3、0,1)。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.cpusetCpus" placeholder="允许执行的 CPU(例如,0-3、0,1)。" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpusetMems">
|
||||
<template slot="label">
|
||||
CpusetMems
|
||||
<a-tooltip>
|
||||
<template slot="title"> 允许执行的内存节点 (MEM) (0-3, 0,1)。 仅在 NUMA 系统上有效。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.cpusetMems" placeholder="允许执行的内存节点 (MEM) (0-3, 0,1)。 仅在 NUMA 系统上有效。" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpuPeriod">
|
||||
<template slot="label">
|
||||
CPU 周期
|
||||
<a-tooltip>
|
||||
<template slot="title"> CPU 周期的长度,以微秒为单位。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 100%" v-model="temp.cpuPeriod" placeholder=" CPU 周期的长度,以微秒为单位。" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="cpuQuota">
|
||||
<template slot="label">
|
||||
CPU 时间
|
||||
<a-tooltip>
|
||||
<template slot="title"> 容器在一个 CPU 周期内可以获得的 CPU 时间的微秒。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number style="width: 100%" v-model="temp.cpuQuota" placeholder="容器在一个 CPU 周期内可以获得的 CPU 时间的微秒。" />
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item prop="memory">
|
||||
<template slot="label">
|
||||
内存
|
||||
<a-tooltip>
|
||||
<template slot="title"> 设置内存限制。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.memory" placeholder="设置内存限制。" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="memorySwap">
|
||||
<template slot="label">
|
||||
总内存
|
||||
<a-tooltip>
|
||||
<template slot="title"> 总内存(内存 + 交换)。 设置为 -1 以禁用交换。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.memorySwap" placeholder="总内存(内存 + 交换)。 设置为 -1 以禁用交换。" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="memoryReservation">
|
||||
<template slot="label">
|
||||
软内存
|
||||
<a-tooltip>
|
||||
<template slot="title"> 软内存限制。 </template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 100%" v-model="temp.memoryReservation" placeholder="软内存限制。" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { dockerContainerStats, dockerInspectContainer, dockerUpdateContainer } from "@/api/docker-api";
|
||||
import { renderSize } from "@/utils/const";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
|
||||
urlPrefix: {
|
||||
type: String,
|
||||
},
|
||||
machineDockerId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
containerId: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statsData: {},
|
||||
temp: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
reqDataId() {
|
||||
return this.id || this.machineDockerId;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.editContainer();
|
||||
},
|
||||
methods: {
|
||||
renderSize,
|
||||
// 编辑容器
|
||||
editContainer() {
|
||||
dockerContainerStats(this.urlPrefix, {
|
||||
id: this.reqDataId,
|
||||
containerId: this.containerId,
|
||||
}).then((res2) => {
|
||||
if (res2.code === 200) {
|
||||
this.statsData = (res2.data && res2.data[this.containerId]) || {};
|
||||
dockerInspectContainer(this.urlPrefix, {
|
||||
id: this.reqDataId,
|
||||
containerId: this.containerId,
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.editVisible = true;
|
||||
|
||||
const hostConfig = res.data.hostConfig || {};
|
||||
const data = {
|
||||
containerId: this.containerId,
|
||||
cpusetCpus: hostConfig.cpusetCpus,
|
||||
cpusetMems: hostConfig.cpusetMems,
|
||||
cpuPeriod: hostConfig.cpuPeriod,
|
||||
cpuShares: hostConfig.cpuShares,
|
||||
cpuQuota: hostConfig.cpuQuota,
|
||||
blkioWeight: hostConfig.blkioWeight,
|
||||
memoryReservation: renderSize(hostConfig.memoryReservation, hostConfig.memoryReservation),
|
||||
// Deprecated: This field is deprecated as the kernel 5.4 deprecated kmem.limit_in_bytes.
|
||||
// kernelMemory: hostConfig.kernelMemory,
|
||||
memory: renderSize(hostConfig.memory, hostConfig.memory),
|
||||
memorySwap: renderSize(hostConfig.memorySwap, hostConfig.memorySwap),
|
||||
};
|
||||
|
||||
this.temp = Object.assign({}, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
// console.log(res);
|
||||
});
|
||||
},
|
||||
handleEditOk() {
|
||||
this.$refs["editForm"].validate((valid) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
const temp = Object.assign({}, this.temp, { id: this.reqDataId });
|
||||
dockerUpdateContainer(this.urlPrefix, temp).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
});
|
||||
this.editVisible = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -35,9 +35,18 @@
|
||||
<a-tag color="red">信息丢失</a-tag>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template slot="tags" slot-scope="tags">
|
||||
<a-tooltip
|
||||
slot="tags"
|
||||
slot-scope="tags"
|
||||
:title="
|
||||
(tags || '')
|
||||
.split(':')
|
||||
.filter((item) => item)
|
||||
.join(',')
|
||||
"
|
||||
>
|
||||
<a-tag v-for="item in (tags || '').split(':').filter((item) => item)" :key="item"> {{ item }}</a-tag>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-space>
|
||||
<a-button size="small" type="primary" :disabled="!record.machineDocker || record.machineDocker.status !== 1" @click="handleConsole(record)">控制台</a-button>
|
||||
@ -89,9 +98,9 @@
|
||||
</a-modal>
|
||||
|
||||
<!-- 控制台 -->
|
||||
<a-drawer destroyOnClose :title="`${temp.name} 控制台`" placement="right" :width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`" :visible="consoleVisible" @close="onClose">
|
||||
<console v-if="consoleVisible" :visible="consoleVisible" :id="temp.id" urlPrefix="/docker"></console>
|
||||
</a-drawer>
|
||||
<!-- <a-drawer destroyOnClose :title="`${temp.name} 控制台`" placement="right" :width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`" :visible="consoleVisible" @close="onClose"> -->
|
||||
<console v-if="consoleVisible" :visible="consoleVisible" :id="temp.id" urlPrefix="/docker" @close="onClose"></console>
|
||||
<!-- </a-drawer> -->
|
||||
<!-- 同步到其他工作空间 -->
|
||||
<a-modal destroyOnClose v-model="syncToWorkspaceVisible" title="同步到其他工作空间" @ok="handleSyncToWorkspace" :maskClosable="false">
|
||||
<a-alert message="温馨提示" type="warning">
|
||||
@ -141,7 +150,7 @@ export default {
|
||||
|
||||
columns: [
|
||||
{ title: "名称", dataIndex: "name", ellipsis: true, width: 100, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "host", dataIndex: "machineDocker.host", width: 120, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "host", dataIndex: "machineDocker.host", width: 150, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "状态", dataIndex: "machineDocker.status", ellipsis: true, align: "center", width: "100px", scopedSlots: { customRender: "status" } },
|
||||
{ title: "docker版本", dataIndex: "machineDocker.dockerVersion", ellipsis: true, width: "120px", scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "标签", dataIndex: "tags", width: 100, ellipsis: true, scopedSlots: { customRender: "tags" } },
|
||||
|
@ -42,9 +42,12 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["getLongTermToken"]),
|
||||
...mapGetters(["getLongTermToken", "getWorkspaceId"]),
|
||||
socketUrl() {
|
||||
return getWebSocketUrl("/socket/docker_log", `userId=${this.getLongTermToken}&id=${this.id}&machineDockerId=${this.machineDockerId}&type=dockerLog&nodeId=system`);
|
||||
return getWebSocketUrl(
|
||||
"/socket/docker_log",
|
||||
`userId=${this.getLongTermToken}&id=${this.id}&machineDockerId=${this.machineDockerId}&type=dockerLog&nodeId=system&workspaceId=${this.getWorkspaceId}`
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
@ -29,9 +29,12 @@ export default {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["getLongTermToken"]),
|
||||
...mapGetters(["getLongTermToken", "getWorkspaceId"]),
|
||||
socketUrl() {
|
||||
return getWebSocketUrl("/socket/docker_cli", `userId=${this.getLongTermToken}&id=${this.id}&machineDockerId=${this.machineDockerId}&nodeId=system&type=docker&containerId=${this.containerId}`);
|
||||
return getWebSocketUrl(
|
||||
"/socket/docker_cli",
|
||||
`userId=${this.getLongTermToken}&id=${this.id}&machineDockerId=${this.machineDockerId}&nodeId=system&type=docker&containerId=${this.containerId}&workspaceId=${this.getWorkspaceId}`
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
|
@ -172,7 +172,7 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="stylus">
|
||||
<style scoped>
|
||||
#app-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
@ -224,7 +224,7 @@ export default {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.layout-content-full-screen {
|
||||
height calc(100vh - 120px);
|
||||
height: calc(100vh - 120px);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
|
@ -55,15 +55,15 @@
|
||||
<a-divider>第三方登录</a-divider>
|
||||
<!-- <a-form-model-item :wrapper-col="{ span: 24 }"> </a-form-model-item> -->
|
||||
<a-form-model-item :wrapper-col="{ span: 24 }">
|
||||
<a-space>
|
||||
<div style="width: 32px; height: 32px" v-if="this.enabledOauth2Provides.indexOf('gitee') > -1">
|
||||
<a-tooltip @click="toOauth2Url('gitee')" title="gitee"><img :src="giteeImg" style="width: 100%; height: 100%; object-fit: cover" /></a-tooltip>
|
||||
<a-space :size="20">
|
||||
<div class="oauth2-item" v-if="this.enabledOauth2Provides.indexOf('gitee') > -1">
|
||||
<a-tooltip @click="toOauth2Url('gitee')" title="gitee"><img :src="giteeImg" /></a-tooltip>
|
||||
</div>
|
||||
<div style="width: 32px; height: 32px" v-if="this.enabledOauth2Provides.indexOf('maxkey') > -1">
|
||||
<a-tooltip @click="toOauth2Url('maxkey')" title="maxkey"><img :src="maxkeyImg" style="width: 100%; height: 100%; object-fit: cover" /></a-tooltip>
|
||||
<div class="oauth2-item" v-if="this.enabledOauth2Provides.indexOf('maxkey') > -1">
|
||||
<a-tooltip @click="toOauth2Url('maxkey')" title="maxkey"><img :src="maxkeyImg" /></a-tooltip>
|
||||
</div>
|
||||
<div style="width: 32px; height: 32px" v-if="this.enabledOauth2Provides.indexOf('github') > -1">
|
||||
<a-tooltip @click="toOauth2Url('github')" title="github"><img :src="githubImg" style="width: 100%; height: 100%; object-fit: cover" /></a-tooltip>
|
||||
<div class="oauth2-item" v-if="this.enabledOauth2Provides.indexOf('github') > -1">
|
||||
<a-tooltip @click="toOauth2Url('github')" title="github"><img :src="githubImg" /></a-tooltip>
|
||||
</div>
|
||||
</a-space>
|
||||
</a-form-model-item>
|
||||
@ -72,7 +72,7 @@
|
||||
</template>
|
||||
<template v-if="this.action === 'mfa'">
|
||||
<a-form-model ref="mfaDataForm" :label-col="{ span: 0 }" :model="mfaData" :rules="rules" @submit="handleMfa">
|
||||
<a-form-model-item label="验证码" :label-col="{ span: 5 }" :wrapper-col="{ span: 19 }" prop="mfaCode">
|
||||
<a-form-model-item label="验证码" :label-col="{ span: 5 }" :wrapper-col="{ span: 19 }" prop="mfaCode" help="需要验证 MFA">
|
||||
<a-input v-model="mfaData.mfaCode" placeholder="mfa 验证码" />
|
||||
</a-form-model-item>
|
||||
|
||||
@ -113,7 +113,7 @@ export default {
|
||||
disabledCaptcha: false,
|
||||
enabledOauth2Provides: [],
|
||||
maxkeyImg: require(`@/assets/images/maxkey.png`),
|
||||
giteeImg: require(`@/assets/images/gitee.png`),
|
||||
giteeImg: require(`@/assets/images/gitee.svg`),
|
||||
githubImg: require(`@/assets/images/github.png`),
|
||||
};
|
||||
},
|
||||
@ -350,18 +350,29 @@ export default {
|
||||
.rand-code img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
display: inherit;
|
||||
}
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.ant-card-meta-title {
|
||||
/deep/ .ant-card-meta-title {
|
||||
font-size: 30px;
|
||||
}
|
||||
.ant-card-body {
|
||||
/deep/ .ant-card-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.oauth2-item {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.oauth2-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
|
@ -514,8 +514,8 @@ export default {
|
||||
syncToWorkspaceVisible: false,
|
||||
|
||||
columns: [
|
||||
{ title: "节点名称", dataIndex: "name", width: 100, sorter: true, key: "name", ellipsis: true, scopedSlots: { customRender: "name" } },
|
||||
{ title: "状态", dataIndex: "status", width: 100, ellipsis: true, scopedSlots: { customRender: "status" } },
|
||||
{ title: "节点名称", dataIndex: "name", width: 200, sorter: true, key: "name", ellipsis: true, scopedSlots: { customRender: "name" } },
|
||||
{ title: "状态", dataIndex: "status", width: "100px", ellipsis: true, scopedSlots: { customRender: "status" } },
|
||||
{ title: "节点地址", dataIndex: "url", key: "url", width: "190px", ellipsis: true, scopedSlots: { customRender: "url" } },
|
||||
{ title: "系统名", dataIndex: "osName", key: "osName", width: "100px", ellipsis: true, scopedSlots: { customRender: "osName" } },
|
||||
{ title: "JDK 版本", dataIndex: "javaVersion", width: 100, key: "javaVersion", ellipsis: true, scopedSlots: { customRender: "javaVersion" } },
|
||||
|
@ -1,8 +1,15 @@
|
||||
<template>
|
||||
<a-layout class="node-layout" ref="nodeLayout" id="nodeLayout">
|
||||
<a-layout class="node-layout" ref="nodeLayout" id="nodeLayout" :class="` ${this.scrollbarFlag ? '' : 'hide-scrollbar'}`">
|
||||
<!-- 侧边栏 节点管理菜单 -->
|
||||
<a-layout-sider theme="light" :class="`node-sider jpom-node-sider ${this.fullScreenFlag ? 'sider-scroll' : 'sider-full-screen'}`">
|
||||
<a-menu theme="light" mode="inline" @openChange="openChange" :default-selected-keys="selectedKeys" :openKeys="openKey">
|
||||
<a-layout-sider theme="light" :class="`node-sider jpom-node-sider ${this.fullScreenFlag ? 'sider-scroll' : 'sider-full-screen'} ${this.scrollbarFlag ? '' : 'hide-scrollbar'}`">
|
||||
<a-menu
|
||||
theme="light"
|
||||
mode="inline"
|
||||
@openChange="openChange"
|
||||
:default-selected-keys="selectedKeys"
|
||||
:openKeys="openKey"
|
||||
:class="`${this.fullScreenFlag ? 'sider-scroll' : 'sider-full-screen'} ${this.scrollbarFlag ? '' : 'hide-scrollbar'}`"
|
||||
>
|
||||
<template v-for="(menu, index) in nodeMenuList">
|
||||
<a-sub-menu v-if="menu.childs" :key="menu.id" :class="menu.id">
|
||||
<span slot="title">
|
||||
@ -22,7 +29,8 @@
|
||||
</a-layout-sider>
|
||||
<!-- 节点管理的各个组件 -->
|
||||
<!-- class="layout-content jpom-node-content drawer-layout-content" -->
|
||||
<a-layout-content :class="`layout-content jpom-node-content ${this.fullScreenFlag ? 'layout-content-scroll' : 'layout-content-full-screen'}`">
|
||||
|
||||
<a-layout-content :class="`layout-content jpom-node-content ${this.fullScreenFlag ? 'layout-content-scroll' : 'layout-content-full-screen'} ${this.scrollbarFlag ? '' : 'hide-scrollbar'}`">
|
||||
<welcome v-if="currentId === 'welcome'" :node="node" />
|
||||
<project-list v-if="currentId === 'manageList'" :node="node" />
|
||||
|
||||
@ -97,6 +105,9 @@ export default {
|
||||
menuMultipleFlag() {
|
||||
return this.getGuideCache.menuMultipleFlag === undefined ? true : this.getGuideCache.menuMultipleFlag;
|
||||
},
|
||||
scrollbarFlag() {
|
||||
return this.getGuideCache.scrollbarFlag === undefined ? true : this.getGuideCache.scrollbarFlag;
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
created() {
|
||||
@ -180,15 +191,14 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="stylus">
|
||||
|
||||
<style scoped>
|
||||
.sider-scroll {
|
||||
min-height: calc(100vh -85px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.layout-content-scroll {
|
||||
min-height: calc(100vh - 85px)
|
||||
min-height: calc(100vh - 85px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@ -198,7 +208,7 @@ export default {
|
||||
}
|
||||
|
||||
.layout-content-full-screen {
|
||||
height: calc(100vh - 85px);
|
||||
height: calc(100vh - 85px);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
|
@ -249,7 +249,7 @@ export default {
|
||||
batchVisible: false,
|
||||
batchTitle: "",
|
||||
columns: [
|
||||
{ title: "项目名称", dataIndex: "name", width: 120, ellipsis: true, scopedSlots: { customRender: "name" } },
|
||||
{ title: "项目名称", dataIndex: "name", width: 200, ellipsis: true, scopedSlots: { customRender: "name" } },
|
||||
{ title: "项目分组", dataIndex: "group", sorter: true, width: "100px", ellipsis: true, scopedSlots: { customRender: "group" } },
|
||||
{ title: "节点名称", dataIndex: "nodeId", width: 90, ellipsis: true, scopedSlots: { customRender: "nodeId" } },
|
||||
{
|
||||
|
@ -557,10 +557,10 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="stylus">
|
||||
<style scoped>
|
||||
.ssh-file-layout {
|
||||
padding: 0;
|
||||
min-height calc(100vh - 75px);
|
||||
min-height: calc(100vh - 75px);
|
||||
}
|
||||
|
||||
.dir-container {
|
||||
@ -572,7 +572,7 @@ export default {
|
||||
border: 1px solid #e2e2e2;
|
||||
/* height: calc(100vh - 80px); */
|
||||
/* overflow-y: auto; */
|
||||
overflow-x: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.file-content {
|
||||
|
@ -206,9 +206,9 @@
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
<!-- 控制台 -->
|
||||
<a-drawer destroyOnClose :title="`${temp.name} 控制台`" placement="right" :width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`" :visible="consoleVisible" @close="onClose">
|
||||
<console v-if="consoleVisible" :visible="consoleVisible" :machineDockerId="temp.id" urlPrefix="/system/assets/docker"></console>
|
||||
</a-drawer>
|
||||
<!-- <a-drawer destroyOnClose :title="`${temp.name} 控制台`" placement="right" :width="`${this.getCollapsed ? 'calc(100vw - 80px)' : 'calc(100vw - 200px)'}`" :visible="consoleVisible" @close="onClose"> -->
|
||||
<console v-if="consoleVisible" :visible="consoleVisible" :machineDockerId="temp.id" urlPrefix="/system/assets/docker" @close="onClose"></console>
|
||||
<!-- </a-drawer> -->
|
||||
<!-- 集群控制台 -->
|
||||
<a-drawer
|
||||
destroyOnClose
|
||||
|
Loading…
Reference in New Issue
Block a user