mirror of
https://gitee.com/dromara/Jpom.git
synced 2024-12-02 11:58:01 +08:00
Merge branch 'master' of gitee.com:dromara/Jpom into dev
Signed-off-by: 健身的码农 <fangzhong.top@gmail.com>
This commit is contained in:
commit
0c6bafd350
2
.env
2
.env
@ -1,3 +1,3 @@
|
||||
JPOM_VERSION=2.10.28
|
||||
JPOM_VERSION=2.10.29
|
||||
# Server Token 生产部署请更换
|
||||
SERVER_TOKEN=7094f673-2c53-4fc1-82e7-86e528449d97
|
||||
|
@ -29,14 +29,14 @@ stages:
|
||||
artifacts:
|
||||
- name: all_zip
|
||||
path:
|
||||
- modules/server/target/server-2.10.28-release.zip
|
||||
- modules/agent/target/agent-2.10.28-release.zip
|
||||
- modules/server/target/server-2.10.29-release.zip
|
||||
- modules/agent/target/agent-2.10.29-release.zip
|
||||
- name: server_zip
|
||||
path:
|
||||
- modules/server/target/server-2.10.28-release.zip
|
||||
- modules/server/target/server-2.10.29-release.zip
|
||||
- name: agent_zip
|
||||
path:
|
||||
- modules/agent/target/agent-2.10.28-release.zip
|
||||
- modules/agent/target/agent-2.10.29-release.zip
|
||||
settings: []
|
||||
strategy:
|
||||
retry: '0'
|
||||
@ -50,7 +50,7 @@ stages:
|
||||
name: publish_general_artifacts
|
||||
displayName: 合并打包
|
||||
dependArtifact: all_zip
|
||||
artifactName: jpom-2.10.28
|
||||
artifactName: jpom-2.10.29
|
||||
strategy:
|
||||
retry: '0'
|
||||
strategy:
|
||||
|
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,11 +1,13 @@
|
||||
# 🚀 版本日志
|
||||
|
||||
## 2.10.29
|
||||
## 2.10.29 (2023-03-10)
|
||||
|
||||
### 🐣 新增功能
|
||||
|
||||
1. 【server】新增 导入仓库支持 `gitea` 系统
|
||||
(感谢 [@Smith](https://gitee.com/autools) [Gitee pr 173](https://gitee.com/dromara/Jpom/pulls/173) )
|
||||
2. 【server】新增 用户登录日志(取消用户登录生成操作日志的执行日志)
|
||||
3. 【server】新增 在线工具验证 cron 表达式 (感谢@奇奇)
|
||||
|
||||
### 🐞 解决BUG、优化功能
|
||||
|
||||
@ -14,6 +16,12 @@
|
||||
3. 【agent】优化 节点分发配置白名单到插件端需要验证合法性
|
||||
4. 【server】优化 docker 创建容器忽略未配置存储选项参数(感谢@D¹⁹⁹¹)
|
||||
5. 【server】优化 docker 管理裁剪功能独立菜单
|
||||
6. 【server】修复 资产管理未记录操作日志的问题
|
||||
7. 【server】优化 操作日志存储用户名、工作空间名字段
|
||||
8. 【server】优化 容器构建查询可用标签容器相关提示
|
||||
9. 【server】优化 构建历史列表页面在小屏幕数据显示不全
|
||||
(感谢 [@一只羊](https://gitee.com/hjdyzy) [Gitee issues I6LLA0](https://gitee.com/dromara/Jpom/issues/I6LLA0) )
|
||||
10. 【server】修复 在线构建发布到集群无法正常选择集群服务(感谢@心光)
|
||||
|
||||
------
|
||||
|
||||
|
@ -1 +1 @@
|
||||
2.10.28
|
||||
2.10.29
|
||||
|
@ -30,7 +30,7 @@
|
||||
<parent>
|
||||
<groupId>io.jpom.agent-transport</groupId>
|
||||
<artifactId>jpom-agent-transport-parent</artifactId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
<parent>
|
||||
<groupId>io.jpom.agent-transport</groupId>
|
||||
<artifactId>jpom-agent-transport-parent</artifactId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
<parent>
|
||||
<artifactId>jpom-parent</artifactId>
|
||||
<groupId>io.jpom</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
@ -38,7 +38,7 @@
|
||||
<module>agent-transport-http</module>
|
||||
</modules>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<groupId>io.jpom.agent-transport</groupId>
|
||||
<artifactId>jpom-agent-transport-parent</artifactId>
|
||||
<name>Jpom Agent Transport</name>
|
||||
|
@ -29,12 +29,12 @@
|
||||
<parent>
|
||||
<artifactId>jpom-parent</artifactId>
|
||||
<groupId>io.jpom</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>agent</artifactId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<name>Jpom Agent</name>
|
||||
<properties>
|
||||
<start-class>io.jpom.JpomAgentApplication</start-class>
|
||||
|
@ -29,13 +29,13 @@
|
||||
<parent>
|
||||
<artifactId>jpom-parent</artifactId>
|
||||
<groupId>io.jpom</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<name>Jpom Common</name>
|
||||
<artifactId>common</artifactId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
|
||||
<dependencies>
|
||||
|
||||
|
@ -53,7 +53,7 @@ public class WebAopLog {
|
||||
this.aopLogInterface = SpringUtil.getBeansOfType(AopLogInterface.class).values();
|
||||
}
|
||||
|
||||
@Pointcut("execution(public * io.jpom.controller..*.*(..))")
|
||||
@Pointcut("execution(public * io.jpom..*.*.controller..*.*(..))")
|
||||
public void webLog() {
|
||||
//
|
||||
}
|
||||
|
@ -8,4 +8,4 @@
|
||||
| |
|
||||
|_|
|
||||
|
||||
➜ Jpom \ (•◡•) / (v2.10.28)
|
||||
➜ Jpom \ (•◡•) / (v2.10.29)
|
||||
|
@ -29,7 +29,7 @@ LABEL maintainer="bwcx-jzy <bwcx_jzy@163.com>"
|
||||
LABEL documentation="https://jpom.top"
|
||||
|
||||
ENV JPOM_HOME /usr/local/jpom-server
|
||||
ENV JPOM_PKG_VERSION 2.10.28
|
||||
ENV JPOM_PKG_VERSION 2.10.29
|
||||
ENV JPOM_PKG server-${JPOM_PKG_VERSION}-release.tar.gz
|
||||
ENV SHA1_NAME server-${JPOM_PKG_VERSION}-release.tar.gz.sha1
|
||||
|
||||
|
@ -29,13 +29,13 @@
|
||||
<parent>
|
||||
<artifactId>jpom-parent</artifactId>
|
||||
<groupId>io.jpom</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<name>Jpom Server</name>
|
||||
<artifactId>server</artifactId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<properties>
|
||||
<start-class>io.jpom.JpomServerApplication</start-class>
|
||||
</properties>
|
||||
|
@ -753,11 +753,11 @@ public class BuildExecuteService {
|
||||
List<DockerInfoModel> dockerInfoModels = buildExecuteService
|
||||
.dockerInfoService
|
||||
.queryByTag(buildInfoModel.getWorkspaceId(), fromTag);
|
||||
DockerInfoModel dockerInfoModel = CollUtil.getFirst(dockerInfoModels);
|
||||
Assert.notNull(dockerInfoModel, "没有可用的 docker server");
|
||||
logRecorder.system("use docker {}", dockerInfoModel.getName());
|
||||
Map<String, Object> map = buildExecuteService.machineDockerServer.dockerParameter(dockerInfoModels);
|
||||
Assert.notNull(map, fromTag + " 没有可用的 docker server");
|
||||
logRecorder.system("use docker {}", map.get("name"));
|
||||
String workingDir = "/home/jpom/";
|
||||
Map<String, Object> map = buildExecuteService.machineDockerServer.dockerParameter(dockerInfoModel);
|
||||
|
||||
map.put("runsOn", dockerYmlDsl.getRunsOn());
|
||||
map.put("workingDir", workingDir);
|
||||
map.put("tempDir", JpomApplication.getInstance().getTempPath());
|
||||
|
@ -147,7 +147,7 @@ public class ReleaseManage {
|
||||
} else if (releaseMethod == BuildReleaseMethod.LocalCommand.getCode()) {
|
||||
return this.localCommand();
|
||||
} else if (releaseMethod == BuildReleaseMethod.DockerImage.getCode()) {
|
||||
this.doDockerImage();
|
||||
return this.doDockerImage();
|
||||
} else if (releaseMethod == BuildReleaseMethod.No.getCode()) {
|
||||
return true;
|
||||
} else {
|
||||
@ -199,7 +199,7 @@ public class ReleaseManage {
|
||||
}).collect(Collectors.joining(StrUtil.COMMA));
|
||||
}
|
||||
|
||||
private void doDockerImage() {
|
||||
private boolean doDockerImage() {
|
||||
// 生成临时目录
|
||||
File tempPath = FileUtil.file(JpomApplication.getInstance().getTempPath(), "build_temp", "docker_image", this.buildExtraModule.getId() + StrUtil.DASHED + this.buildNumberId);
|
||||
try {
|
||||
@ -221,7 +221,7 @@ public class ReleaseManage {
|
||||
File dockerfile = FileUtil.file(tempPath, dockerFile);
|
||||
if (!FileUtil.isFile(dockerfile)) {
|
||||
logRecorder.systemError("仓库目录下没有找到 Dockerfile 文件: {}", dockerFile);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
File baseDir = FileUtil.file(tempPath, list.size() == 1 ? StrUtil.SLASH : CollUtil.get(list, 0));
|
||||
//
|
||||
@ -230,10 +230,10 @@ public class ReleaseManage {
|
||||
List<DockerInfoModel> dockerInfoModels = buildExecuteService
|
||||
.dockerInfoService
|
||||
.queryByTag(this.buildExtraModule.getWorkspaceId(), fromTag);
|
||||
DockerInfoModel dockerInfoModel = CollUtil.getFirst(dockerInfoModels);
|
||||
if (dockerInfoModel == null) {
|
||||
logRecorder.systemError("没有可用的 docker server");
|
||||
return;
|
||||
Map<String, Object> map = buildExecuteService.machineDockerServer.dockerParameter(dockerInfoModels);
|
||||
if (map == null) {
|
||||
logRecorder.systemError("{} 没有可用的 docker server", fromTag);
|
||||
return false;
|
||||
}
|
||||
//String dockerBuildArgs = this.buildExtraModule.getDockerBuildArgs();
|
||||
for (DockerInfoModel infoModel : dockerInfoModels) {
|
||||
@ -244,9 +244,7 @@ public class ReleaseManage {
|
||||
if (pushToRepository != null && pushToRepository) {
|
||||
List<String> repositoryList = StrUtil.splitTrim(dockerTag, StrUtil.COMMA);
|
||||
for (String repositoryItem : repositoryList) {
|
||||
logRecorder.system("start push to repository in({}),{} {}", dockerInfoModel.getName(), StrUtil.emptyToDefault(dockerInfoModel.getRegistryUrl(), StrUtil.EMPTY), repositoryItem);
|
||||
Map<String, Object> map = buildExecuteService.machineDockerServer.dockerParameter(dockerInfoModel);
|
||||
//dockerInfoModel.toParameter();
|
||||
logRecorder.system("start push to repository in({}),{} {}", map.get("name"), StrUtil.emptyToDefault((String) map.get("registryUrl"), StrUtil.EMPTY), repositoryItem);
|
||||
//
|
||||
map.put("repository", repositoryItem);
|
||||
Consumer<String> logConsumer = s -> logRecorder.info(s);
|
||||
@ -264,6 +262,7 @@ public class ReleaseManage {
|
||||
} finally {
|
||||
CommandUtil.systemFastDel(tempPath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateSwarmService(String dockerTag, String swarmId, String serviceName) {
|
||||
|
@ -43,12 +43,12 @@ import io.jpom.common.interceptor.NotLogin;
|
||||
import io.jpom.common.validator.ValidatorConfig;
|
||||
import io.jpom.common.validator.ValidatorItem;
|
||||
import io.jpom.common.validator.ValidatorRule;
|
||||
import io.jpom.func.user.server.UserLoginLogServer;
|
||||
import io.jpom.model.data.WorkspaceModel;
|
||||
import io.jpom.model.dto.UserLoginDto;
|
||||
import io.jpom.model.user.UserModel;
|
||||
import io.jpom.permission.ClassFeature;
|
||||
import io.jpom.permission.Feature;
|
||||
import io.jpom.permission.MethodFeature;
|
||||
import io.jpom.service.user.UserBindWorkspaceService;
|
||||
import io.jpom.service.user.UserService;
|
||||
import io.jpom.system.ServerConfig;
|
||||
@ -89,14 +89,17 @@ public class LoginControl extends BaseServerController {
|
||||
private final UserBindWorkspaceService userBindWorkspaceService;
|
||||
private final ServerConfig.UserConfig userConfig;
|
||||
private final ServerConfig.WebConfig webConfig;
|
||||
private final UserLoginLogServer userLoginLogServer;
|
||||
|
||||
public LoginControl(UserService userService,
|
||||
UserBindWorkspaceService userBindWorkspaceService,
|
||||
ServerConfig serverConfig) {
|
||||
ServerConfig serverConfig,
|
||||
UserLoginLogServer userLoginLogServer) {
|
||||
this.userService = userService;
|
||||
this.userBindWorkspaceService = userBindWorkspaceService;
|
||||
this.userConfig = serverConfig.getUser();
|
||||
this.webConfig = serverConfig.getWeb();
|
||||
this.userLoginLogServer = userLoginLogServer;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,31 +158,34 @@ public class LoginControl extends BaseServerController {
|
||||
/**
|
||||
* 登录接口
|
||||
*
|
||||
* @param userName 登录名
|
||||
* @param userPwd 登录密码
|
||||
* @param code 验证码
|
||||
* @param loginName 登录名
|
||||
* @param userPwd 登录密码
|
||||
* @param code 验证码
|
||||
* @return json
|
||||
*/
|
||||
@PostMapping(value = "userLogin", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@NotLogin
|
||||
@Feature(method = MethodFeature.EXECUTE, resultCode = {200, 201}, logResponse = false)
|
||||
public JsonMessage<Object> userLogin(
|
||||
@ValidatorConfig(value = {
|
||||
@ValidatorItem(value = ValidatorRule.NOT_EMPTY, msg = "请输入登录信息")
|
||||
}) String userName,
|
||||
}) String loginName,
|
||||
@ValidatorConfig(value = {
|
||||
@ValidatorItem(value = ValidatorRule.NOT_EMPTY, msg = "请输入登录信息")
|
||||
}) String userPwd,
|
||||
String code) {
|
||||
String code, HttpServletRequest request) {
|
||||
if (this.ipLock()) {
|
||||
return new JsonMessage<>(400, "尝试次数太多,请稍后再来");
|
||||
}
|
||||
synchronized (userName.intern()) {
|
||||
UserModel userModel = userService.getByKey(userName);
|
||||
synchronized (loginName.intern()) {
|
||||
UserModel userModel = userService.getByKey(loginName);
|
||||
if (userModel == null) {
|
||||
this.ipError();
|
||||
return new JsonMessage<>(400, "登录失败,请输入正确的密码和账号,多次失败将锁定账号");
|
||||
}
|
||||
if (userModel.getStatus() != null && userModel.getStatus() == 0) {
|
||||
userLoginLogServer.fail(userModel, 4, false, request);
|
||||
return new JsonMessage<>(ServerConst.ACCOUNT_LOCKED, ServerConst.ACCOUNT_LOCKED_TIP);
|
||||
}
|
||||
if (!webConfig.isDisabledGuide()) {
|
||||
// 获取验证码
|
||||
String sCode = getSessionAttribute(LOGIN_CODE);
|
||||
@ -193,27 +199,31 @@ public class LoginControl extends BaseServerController {
|
||||
String msg = DateUtil.formatBetween(lockTime * 1000, BetweenFormatter.Level.SECOND);
|
||||
updateModel = userModel.errorLock(userConfig.getAlwaysLoginError());
|
||||
this.ipError();
|
||||
userLoginLogServer.fail(userModel, 2, false, request);
|
||||
return new JsonMessage<>(400, "该账户登录失败次数过多,已被锁定" + msg + ",请不要再次尝试");
|
||||
}
|
||||
// 验证
|
||||
if (userService.simpleLogin(userName, userPwd) != null) {
|
||||
updateModel = UserModel.unLock(userName);
|
||||
if (userService.simpleLogin(loginName, userPwd) != null) {
|
||||
updateModel = UserModel.unLock(loginName);
|
||||
this.ipSuccess();
|
||||
// 判断是否开启 两步验证
|
||||
boolean bindMfa = userService.hasBindMfa(userName);
|
||||
boolean bindMfa = userService.hasBindMfa(loginName);
|
||||
if (bindMfa) {
|
||||
//
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
String uuid = IdUtil.fastSimpleUUID();
|
||||
MFA_TOKEN.put(uuid, userName);
|
||||
MFA_TOKEN.put(uuid, loginName);
|
||||
jsonObject.put("tempToken", uuid);
|
||||
userLoginLogServer.success(userModel, 5, true, request);
|
||||
return new JsonMessage<>(201, "请输入两步验证码", jsonObject);
|
||||
}
|
||||
UserLoginDto userLoginDto = this.createToken(userModel);
|
||||
userLoginLogServer.success(userModel, false, request);
|
||||
return new JsonMessage<>(200, "登录成功", userLoginDto);
|
||||
} else {
|
||||
updateModel = userModel.errorLock(userConfig.getAlwaysLoginError());
|
||||
this.ipError();
|
||||
userLoginLogServer.fail(userModel, 1, false, request);
|
||||
return new JsonMessage<>(501, "登录失败,请输入正确的密码和账号,多次失败将锁定账号");
|
||||
}
|
||||
} finally {
|
||||
@ -240,7 +250,7 @@ public class LoginControl extends BaseServerController {
|
||||
|
||||
@GetMapping(value = "mfa_verify", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@NotLogin
|
||||
public JsonMessage<UserLoginDto> mfaVerify(String token, String code) {
|
||||
public JsonMessage<UserLoginDto> mfaVerify(String token, String code, HttpServletRequest request) {
|
||||
String userId = MFA_TOKEN.get(token);
|
||||
if (StrUtil.isEmpty(userId)) {
|
||||
return new JsonMessage<>(201, "登录信息已经过期请重新登录");
|
||||
@ -251,6 +261,7 @@ public class LoginControl extends BaseServerController {
|
||||
//
|
||||
UserLoginDto userLoginDto = this.createToken(userModel);
|
||||
MFA_TOKEN.remove(token);
|
||||
userLoginLogServer.success(userModel, true, request);
|
||||
return JsonMessage.success("登录成功", userLoginDto);
|
||||
}
|
||||
|
||||
@ -290,6 +301,7 @@ public class LoginControl extends BaseServerController {
|
||||
return new JsonMessage<>(ServerConst.AUTHORIZE_TIME_OUT_CODE, "没有对应的用户");
|
||||
}
|
||||
UserLoginDto userLoginDto = userService.getUserJwtId(userModel);
|
||||
userLoginLogServer.success(userModel, 3, true, request);
|
||||
return JsonMessage.success("", userLoginDto);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Code Technology Studio
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.jpom.controller;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.pattern.CronPatternUtil;
|
||||
import io.jpom.common.JsonMessage;
|
||||
import io.jpom.common.validator.ValidatorItem;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author bwcx_jzy
|
||||
* @since 2023/3/10
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/tools")
|
||||
public class ToolsController {
|
||||
|
||||
@GetMapping(value = "cron", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public JsonMessage<List<Long>> cron(@ValidatorItem String cron, @ValidatorItem int count, String date, boolean isMatchSecond) {
|
||||
Date startDate = null;
|
||||
Date endDate = null;
|
||||
if (StrUtil.isNotEmpty(date)) {
|
||||
List<String> split = StrUtil.splitTrim(date, "~");
|
||||
try {
|
||||
startDate = DateUtil.parse(split.get(0));
|
||||
startDate = DateUtil.beginOfDay(startDate);
|
||||
endDate = DateUtil.parse(split.get(1));
|
||||
endDate = DateUtil.endOfDay(endDate);
|
||||
} catch (Exception e) {
|
||||
return new JsonMessage<>(405, "日期格式错误:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
try {
|
||||
List<Date> dateList;
|
||||
if (startDate != null) {
|
||||
dateList = CronPatternUtil.matchedDates(cron, startDate, endDate, count, isMatchSecond);
|
||||
} else {
|
||||
dateList = CronPatternUtil.matchedDates(cron, DateTime.now(), count, isMatchSecond);
|
||||
}
|
||||
return JsonMessage.success("", dateList.stream().map(Date::getTime).collect(Collectors.toList()));
|
||||
} catch (Exception e) {
|
||||
return new JsonMessage<>(405, "cron 表达式不正确," + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -284,7 +284,7 @@ public class BuildInfoController extends BaseServerController {
|
||||
//
|
||||
String workspaceId = dockerInfoService.getCheckUserWorkspace(request);
|
||||
int count = dockerInfoService.countByTag(workspaceId, fromTag);
|
||||
Assert.state(count > 0, "docker tag 填写不正确,没有找到任何docker");
|
||||
Assert.state(count > 0, fromTag + " 没有找到任何 docker。可能docker tag 填写不正确,需要为 docker 配置标签");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,25 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Code Technology Studio
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.jpom.controller.build.repository;
|
||||
|
||||
import cn.hutool.db.Page;
|
||||
|
@ -1,3 +1,25 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Code Technology Studio
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.jpom.controller.build.repository;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
|
@ -1,3 +1,25 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Code Technology Studio
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.jpom.controller.build.repository;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
|
@ -1,3 +1,25 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Code Technology Studio
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.jpom.controller.build.repository;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
|
@ -33,10 +33,16 @@ import io.jpom.common.JsonMessage;
|
||||
import io.jpom.common.interceptor.PermissionInterceptor;
|
||||
import io.jpom.common.validator.ValidatorItem;
|
||||
import io.jpom.common.validator.ValidatorRule;
|
||||
import io.jpom.func.user.model.UserLoginLogModel;
|
||||
import io.jpom.func.user.server.UserLoginLogServer;
|
||||
import io.jpom.model.data.MailAccountModel;
|
||||
import io.jpom.model.data.WorkspaceModel;
|
||||
import io.jpom.model.log.UserOperateLogV1;
|
||||
import io.jpom.model.user.UserModel;
|
||||
import io.jpom.monitor.EmailUtil;
|
||||
import io.jpom.permission.Feature;
|
||||
import io.jpom.permission.MethodFeature;
|
||||
import io.jpom.service.dblog.DbUserOperateLogService;
|
||||
import io.jpom.service.system.SystemParametersServer;
|
||||
import io.jpom.service.user.UserBindWorkspaceService;
|
||||
import io.jpom.service.user.UserService;
|
||||
@ -49,7 +55,9 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.jpom.model.PageResultDto;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -71,15 +79,21 @@ public class UserBasicInfoController extends BaseServerController {
|
||||
private final UserBindWorkspaceService userBindWorkspaceService;
|
||||
private final UserService userService;
|
||||
private final ServerConfig.UserConfig userConfig;
|
||||
private final UserLoginLogServer userLoginLogServer;
|
||||
private final DbUserOperateLogService dbUserOperateLogService;
|
||||
|
||||
public UserBasicInfoController(SystemParametersServer systemParametersServer,
|
||||
UserBindWorkspaceService userBindWorkspaceService,
|
||||
UserService userService,
|
||||
ServerConfig serverConfig) {
|
||||
ServerConfig serverConfig,
|
||||
UserLoginLogServer userLoginLogServer,
|
||||
DbUserOperateLogService dbUserOperateLogService) {
|
||||
this.systemParametersServer = systemParametersServer;
|
||||
this.userBindWorkspaceService = userBindWorkspaceService;
|
||||
this.userService = userService;
|
||||
this.userConfig = serverConfig.getUser();
|
||||
this.userLoginLogServer = userLoginLogServer;
|
||||
this.dbUserOperateLogService = dbUserOperateLogService;
|
||||
}
|
||||
|
||||
|
||||
@ -221,4 +235,30 @@ public class UserBasicInfoController extends BaseServerController {
|
||||
userService.bindMfa(user.getId(), mfa);
|
||||
return JsonMessage.success("绑定成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录日志列表
|
||||
*
|
||||
* @return json
|
||||
*/
|
||||
@RequestMapping(value = "list-login-log-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.LIST)
|
||||
public JsonMessage<PageResultDto<UserLoginLogModel>> listLoginLogData(HttpServletRequest request) {
|
||||
UserModel user = getUser();
|
||||
PageResultDto<UserLoginLogModel> pageResult = userLoginLogServer.listPageByUserId(request, user.getId());
|
||||
return JsonMessage.success("", pageResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作日志
|
||||
*
|
||||
* @return json
|
||||
*/
|
||||
@RequestMapping(value = "list-operate-log-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.LIST)
|
||||
public JsonMessage<PageResultDto<UserOperateLogV1>> listOperateLogData(HttpServletRequest request) {
|
||||
UserModel user = getUser();
|
||||
PageResultDto<UserOperateLogV1> pageResult = dbUserOperateLogService.listPageByUserId(request, user.getId());
|
||||
return JsonMessage.success("", pageResult);
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.jpom.model.PageResultDto;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 用户操作日志
|
||||
*
|
||||
@ -61,8 +63,8 @@ public class UserOptLogController extends BaseServerController {
|
||||
*/
|
||||
@RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.LIST)
|
||||
public JsonMessage<PageResultDto<UserOperateLogV1>> listData() {
|
||||
PageResultDto<UserOperateLogV1> pageResult = dbUserOperateLogService.listPage(getRequest());
|
||||
return JsonMessage.success("获取成功", pageResult);
|
||||
public JsonMessage<PageResultDto<UserOperateLogV1>> listData(HttpServletRequest request) {
|
||||
PageResultDto<UserOperateLogV1> pageResult = dbUserOperateLogService.listPage(request);
|
||||
return JsonMessage.success("", pageResult);
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ public class MachineDockerModel extends BaseGroupNameModel {
|
||||
public Map<String, Object> toParameter() {
|
||||
Map<String, Object> parameter = new HashMap<>(10);
|
||||
parameter.put("dockerHost", this.getHost());
|
||||
// parameter.put("apiVersion", this.getApiVersion());
|
||||
parameter.put("name", this.getName());
|
||||
parameter.put("registryUsername", this.getRegistryUsername());
|
||||
parameter.put("registryPassword", this.getRegistryPassword());
|
||||
parameter.put("registryEmail", this.getRegistryEmail());
|
||||
|
@ -270,6 +270,29 @@ public class MachineDockerServer extends BaseDbService<MachineDockerModel> imple
|
||||
return machineDockerModel.toParameter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟进 docker 列表找到一个可用的 docker 信息
|
||||
*
|
||||
* @param dockerInfoModels docker 列表
|
||||
* @return map
|
||||
*/
|
||||
public Map<String, Object> dockerParameter(List<DockerInfoModel> dockerInfoModels) {
|
||||
for (DockerInfoModel dockerInfoModel : dockerInfoModels) {
|
||||
String machineDockerId = dockerInfoModel.getMachineDockerId();
|
||||
MachineDockerModel machineDockerModel = this.getByKey(machineDockerId, false);
|
||||
if (machineDockerModel != null) {
|
||||
Integer status = machineDockerModel.getStatus();
|
||||
if (status != null && status == 1) {
|
||||
Map<String, Object> parameter = machineDockerModel.toParameter();
|
||||
// 更新名称
|
||||
parameter.put("name", dockerInfoModel.getName());
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过集群 id 获取 docker 管理参数
|
||||
*
|
||||
@ -300,6 +323,9 @@ public class MachineDockerServer extends BaseDbService<MachineDockerModel> imple
|
||||
}
|
||||
|
||||
public MachineDockerModel tryMachineDockerBySwarmId(String swarmId) {
|
||||
if (StrUtil.isEmpty(swarmId)) {
|
||||
return null;
|
||||
}
|
||||
//
|
||||
MachineDockerModel dockerInfoModel = new MachineDockerModel();
|
||||
dockerInfoModel.setSwarmId(swarmId);
|
||||
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Code Technology Studio
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.jpom.func.user.controller;
|
||||
|
||||
import io.jpom.common.BaseServerController;
|
||||
import io.jpom.common.JsonMessage;
|
||||
import io.jpom.func.user.model.UserLoginLogModel;
|
||||
import io.jpom.func.user.server.UserLoginLogServer;
|
||||
import io.jpom.permission.ClassFeature;
|
||||
import io.jpom.permission.Feature;
|
||||
import io.jpom.permission.MethodFeature;
|
||||
import io.jpom.permission.SystemPermission;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.jpom.model.PageResultDto;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* @author bwcx_jzy
|
||||
* @since 2023/3/9
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/user/login-log")
|
||||
@Feature(cls = ClassFeature.USER_LOGIN_LOG)
|
||||
@SystemPermission
|
||||
public class UserLoginLogController extends BaseServerController {
|
||||
|
||||
private final UserLoginLogServer userLoginLogServer;
|
||||
|
||||
public UserLoginLogController(UserLoginLogServer userLoginLogServer) {
|
||||
this.userLoginLogServer = userLoginLogServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录日志列表
|
||||
*
|
||||
* @return json
|
||||
*/
|
||||
@RequestMapping(value = "list-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.LIST)
|
||||
public JsonMessage<PageResultDto<UserLoginLogModel>> listData(HttpServletRequest request) {
|
||||
PageResultDto<UserLoginLogModel> pageResult = userLoginLogServer.listPage(request);
|
||||
return JsonMessage.success("", pageResult);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Code Technology Studio
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.jpom.func.user.model;
|
||||
|
||||
import io.jpom.model.BaseUserModifyDbModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import top.jpom.h2db.TableName;
|
||||
|
||||
/**
|
||||
* @author bwcx_jzy
|
||||
* @since 2023/3/9
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName(value = "USER_LOGIN_LOG", name = "用户登录日志")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class UserLoginLogModel extends BaseUserModifyDbModel {
|
||||
|
||||
/**
|
||||
* 操作ip
|
||||
*/
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 浏览器标识
|
||||
*/
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 是否使用 mfa
|
||||
*/
|
||||
private Boolean useMfa;
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private Boolean success;
|
||||
|
||||
/**
|
||||
* 错误原因
|
||||
* <p>
|
||||
* 0 正常登录
|
||||
* 1 密码错误
|
||||
* 2 被锁定
|
||||
* 3 续期
|
||||
* 4 账号被禁用
|
||||
* 5 登录成功,但是需要 mfa 验证
|
||||
*/
|
||||
private Integer operateCode;
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Code Technology Studio
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.jpom.func.user.server;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import io.jpom.func.user.model.UserLoginLogModel;
|
||||
import io.jpom.model.user.UserModel;
|
||||
import io.jpom.service.h2db.BaseDbService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import top.jpom.model.PageResultDto;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author bwcx_jzy
|
||||
* @since 2023/3/9
|
||||
*/
|
||||
@Service
|
||||
public class UserLoginLogServer extends BaseDbService<UserLoginLogModel> {
|
||||
|
||||
|
||||
/**
|
||||
* 查询指定用户的登录日志
|
||||
*
|
||||
* @param request 请求信息
|
||||
* @param userId 用户id
|
||||
* @return page
|
||||
*/
|
||||
public PageResultDto<UserLoginLogModel> listPageByUserId(HttpServletRequest request, String userId) {
|
||||
Map<String, String> paramMap = ServletUtil.getParamMap(request);
|
||||
paramMap.put("modifyUser", userId);
|
||||
return super.listPage(paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录日志
|
||||
*
|
||||
* @param userModel 用户
|
||||
* @param success 是否成功
|
||||
* @param useMfa 是否使用 mfa
|
||||
* @param request 请求信息
|
||||
*/
|
||||
public void log(UserModel userModel, boolean success, boolean useMfa, int operateCode, HttpServletRequest request) {
|
||||
UserLoginLogModel userLoginLogModel = new UserLoginLogModel();
|
||||
userLoginLogModel.setModifyUser(userModel.getId());
|
||||
userLoginLogModel.setUsername(userModel.getName());
|
||||
userLoginLogModel.setSuccess(success);
|
||||
userLoginLogModel.setUseMfa(useMfa);
|
||||
userLoginLogModel.setOperateCode(operateCode);
|
||||
userLoginLogModel.setIp(ServletUtil.getClientIP(request));
|
||||
userLoginLogModel.setUserAgent(ServletUtil.getHeader(request, Header.USER_AGENT.getValue(), CharsetUtil.CHARSET_UTF_8));
|
||||
this.insert(userLoginLogModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录日志
|
||||
*
|
||||
* @param userModel 用户
|
||||
* @param useMfa 是否使用 mfa
|
||||
* @param request 请求信息
|
||||
*/
|
||||
public void success(UserModel userModel, boolean useMfa, HttpServletRequest request) {
|
||||
this.success(userModel, 0, useMfa, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录日志
|
||||
*
|
||||
* @param userModel 用户
|
||||
* @param useMfa 是否使用 mfa
|
||||
* @param request 请求信息
|
||||
*/
|
||||
public void success(UserModel userModel, int code, boolean useMfa, HttpServletRequest request) {
|
||||
this.log(userModel, true, useMfa, code, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录日志
|
||||
*
|
||||
* @param userModel 用户
|
||||
* @param useMfa 是否使用 mfa
|
||||
* @param code 错误码
|
||||
* @param request 请求信息
|
||||
*/
|
||||
public void fail(UserModel userModel, int code, boolean useMfa, HttpServletRequest request) {
|
||||
this.log(userModel, false, useMfa, code, request);
|
||||
}
|
||||
}
|
@ -61,12 +61,6 @@ public class UserOperateLogV1 extends BaseWorkspaceModel {
|
||||
* 完整消息
|
||||
*/
|
||||
private String resultMsg;
|
||||
/**
|
||||
* 操作id
|
||||
* 用于socket 回话回调更新
|
||||
*/
|
||||
@Deprecated
|
||||
private String reqId;
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
@ -86,10 +80,10 @@ public class UserOperateLogV1 extends BaseWorkspaceModel {
|
||||
|
||||
private String classFeature;
|
||||
private String methodFeature;
|
||||
/**
|
||||
* 工作空间名称
|
||||
*/
|
||||
private String workspaceName;
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
super.setId(id);
|
||||
this.setReqId(id);
|
||||
}
|
||||
private String username;
|
||||
}
|
||||
|
@ -22,6 +22,10 @@
|
||||
*/
|
||||
package io.jpom.permission;
|
||||
|
||||
import io.jpom.func.assets.server.MachineDockerServer;
|
||||
import io.jpom.func.assets.server.MachineNodeServer;
|
||||
import io.jpom.func.assets.server.MachineSshServer;
|
||||
import io.jpom.func.user.server.UserLoginLogServer;
|
||||
import io.jpom.service.dblog.*;
|
||||
import io.jpom.service.docker.DockerInfoService;
|
||||
import io.jpom.service.docker.DockerSwarmInfoService;
|
||||
@ -84,14 +88,15 @@ public enum ClassFeature {
|
||||
BUILD_REPOSITORY("仓库信息", RepositoryService.class),
|
||||
USER("用户管理", UserService.class),
|
||||
USER_LOG("操作日志", DbUserOperateLogService.class),
|
||||
USER_LOGIN_LOG("登录日志", UserLoginLogServer.class),
|
||||
USER_PERMISSION_GROUP("权限分组", UserPermissionGroupServer.class),
|
||||
SYSTEM_EMAIL("邮箱配置"),
|
||||
SYSTEM_CACHE("系统缓存"),
|
||||
SYSTEM_LOG("系统日志"),
|
||||
SYSTEM_UPGRADE("在线升级"),
|
||||
SYSTEM_ASSETS_MACHINE("机器资产管理"),
|
||||
SYSTEM_ASSETS_MACHINE_SSH("SSH资产管理"),
|
||||
SYSTEM_ASSETS_MACHINE_DOCKER("DOCKER资产管理"),
|
||||
SYSTEM_ASSETS_MACHINE("机器资产管理", MachineNodeServer.class),
|
||||
SYSTEM_ASSETS_MACHINE_SSH("SSH资产管理", MachineSshServer.class),
|
||||
SYSTEM_ASSETS_MACHINE_DOCKER("DOCKER资产管理", MachineDockerServer.class),
|
||||
SYSTEM_CONFIG("服务端系统配置"),
|
||||
SYSTEM_EXT_CONFIG("系统配置目录"),
|
||||
SYSTEM_CONFIG_IP("系统配置IP白名单"),
|
||||
|
@ -49,13 +49,6 @@ public @interface Feature {
|
||||
*/
|
||||
MethodFeature method() default MethodFeature.NULL;
|
||||
|
||||
/**
|
||||
* 只记录哪些 状态码
|
||||
*
|
||||
* @return code
|
||||
*/
|
||||
int[] resultCode() default {};
|
||||
|
||||
/**
|
||||
* 是否记录响应 日志
|
||||
*
|
||||
|
@ -80,6 +80,19 @@ public class DbUserOperateLogService extends BaseWorkspaceService<UserOperateLog
|
||||
this.workspaceService = workspaceService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定用户的操作日志
|
||||
*
|
||||
* @param request 请求信息
|
||||
* @param userId 用户id
|
||||
* @return page
|
||||
*/
|
||||
public PageResultDto<UserOperateLogV1> listPageByUserId(HttpServletRequest request, String userId) {
|
||||
Map<String, String> paramMap = ServletUtil.getParamMap(request);
|
||||
paramMap.put("userId", userId);
|
||||
return super.listPage(paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 数据ID 和 节点ID 查询相关数据名称
|
||||
*
|
||||
@ -247,6 +260,19 @@ public class DbUserOperateLogService extends BaseWorkspaceService<UserOperateLog
|
||||
public void insert(UserOperateLogV1 userOperateLogV1, OperateLogController.CacheInfo cacheInfo) {
|
||||
super.insert(userOperateLogV1);
|
||||
ThreadUtil.execute(() -> {
|
||||
// 更新用户名和工作空间名
|
||||
try {
|
||||
UserOperateLogV1 update = new UserOperateLogV1();
|
||||
update.setId(userOperateLogV1.getId());
|
||||
UserModel userModel = userService.getByKey(userOperateLogV1.getUserId());
|
||||
Optional.ofNullable(userModel).ifPresent(userModel1 -> update.setUsername(userModel1.getName()));
|
||||
WorkspaceModel workspaceModel = workspaceService.getByKey(userOperateLogV1.getWorkspaceId());
|
||||
Optional.ofNullable(workspaceModel).ifPresent(workspaceModel1 -> update.setWorkspaceName(workspaceModel1.getName()));
|
||||
this.update(update);
|
||||
} catch (Exception e) {
|
||||
log.error("更新操作日志失败", e);
|
||||
}
|
||||
// 检查操作监控
|
||||
try {
|
||||
Map<String, Object> monitor = this.checkMonitor(userOperateLogV1, cacheInfo);
|
||||
if (monitor != null) {
|
||||
|
@ -132,7 +132,7 @@ public class TriggerTokenLogServer extends BaseDbService<TriggerTokenLogBean> im
|
||||
* @param userId 用户ID
|
||||
* @return token
|
||||
*/
|
||||
public String createToken(String type, String dataId, String userId) {
|
||||
private String createToken(String type, String dataId, String userId) {
|
||||
TriggerTokenLogBean trigger = new TriggerTokenLogBean();
|
||||
String uuid = IdUtil.fastSimpleUUID();
|
||||
trigger.setId(uuid);
|
||||
|
@ -24,7 +24,6 @@ package io.jpom.system.init;
|
||||
|
||||
import cn.hutool.core.date.SystemClock;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.Entity;
|
||||
@ -109,10 +108,8 @@ public class OperateLogController implements AopLogInterface {
|
||||
cacheInfo.setClassFeature(classFeature);
|
||||
cacheInfo.setMethodFeature(methodFeature);
|
||||
cacheInfo.setOptTime(SystemClock.now());
|
||||
cacheInfo.setResultCode(feature.resultCode());
|
||||
cacheInfo.setLogResponse(feature.logResponse());
|
||||
//
|
||||
|
||||
return cacheInfo;
|
||||
}
|
||||
|
||||
@ -235,11 +232,6 @@ public class OperateLogController implements AopLogInterface {
|
||||
try {
|
||||
JsonMessage<?> jsonMessage = JSONObject.parseObject(json, JsonMessage.class);
|
||||
int code = jsonMessage.getCode();
|
||||
int[] resultCode = cacheInfo.getResultCode();
|
||||
if (ArrayUtil.isNotEmpty(resultCode) && !ArrayUtil.contains(resultCode, code)) {
|
||||
// 忽略
|
||||
return;
|
||||
}
|
||||
userOperateLogV1.setOptStatus(code);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
@ -301,7 +293,6 @@ public class OperateLogController implements AopLogInterface {
|
||||
private String dataId;
|
||||
private String userAgent;
|
||||
private String reqData;
|
||||
private int[] resultCode;
|
||||
private Boolean logResponse;
|
||||
/**
|
||||
* 操作到数据到名称相关 map
|
||||
|
@ -136,5 +136,16 @@
|
||||
"title": "操作监控"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "在线工具",
|
||||
"icon_v3": "tool",
|
||||
"id": "tools",
|
||||
"childs": [
|
||||
{
|
||||
"id": "cronTools",
|
||||
"title": "Cron表达式"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -57,6 +57,11 @@
|
||||
"id": "user_log",
|
||||
"title": "操作日志",
|
||||
"role": "system"
|
||||
},
|
||||
{
|
||||
"id": "user_login_log",
|
||||
"title": "登录日志",
|
||||
"role": "system"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -12,4 +12,7 @@ DROP,DOCKER_SWARM_INFO,status,
|
||||
DROP,DOCKER_SWARM_INFO,failureMsg,
|
||||
DROP,DOCKER_INFO,status
|
||||
DROP,DOCKER_INFO,failureMsg
|
||||
ADD,MACHINE_NODE_INFO,transportEncryption,TINYINT,,,传输加密方式 0 不加密 1 BASE64 2 AES
|
||||
DROP,USEROPERATELOGV1,reqId
|
||||
ADD,USEROPERATELOGV1,workspaceName,String,50,,工作空间名
|
||||
ADD,USEROPERATELOGV1,username,String,50,,用户名
|
||||
ADD,MACHINE_NODE_INFO,transportEncryption,TINYINT,,,传输加密方式 0 不加密 1 BASE64 2 AES
|
|
@ -307,7 +307,6 @@ OUT_GIVING,status,Integer,,0,false,false,状态{0: 未分发; 1: 分发中; 2:
|
||||
OUT_GIVING,secondaryDirectory,String,200,,false,false,二级目录,
|
||||
OUT_GIVING,uploadCloseFirst,TINYINT,,0,false,false,是否清空旧包发布,
|
||||
USEROPERATELOGV1,id,String,50,,true,true,id,操作日志
|
||||
USEROPERATELOGV1,reqId,String,50,,false,false,请求ID,
|
||||
USEROPERATELOGV1,ip,String,80,,false,false,客户端IP地址,
|
||||
USEROPERATELOGV1,userId,String,30,,false,false,操作的用户ID,
|
||||
USEROPERATELOGV1,resultMsg,TEXT,,,false,false,操作的结果信息,
|
||||
|
|
@ -84,3 +84,13 @@ MACHINE_DOCKER_INFO,registryUsername,String,255,,false,false,仓库账号,
|
||||
MACHINE_DOCKER_INFO,registryPassword,String,255,,false,false,仓库密码,
|
||||
MACHINE_DOCKER_INFO,registryEmail,String,255,,false,false,仓库邮箱,
|
||||
MACHINE_DOCKER_INFO,registryUrl,String,255,,false,false,仓库地址,
|
||||
USER_LOGIN_LOG,id,String,50,,true,true,id,用户登录日志
|
||||
USER_LOGIN_LOG,createTimeMillis,Long,,,false,false,数据创建时间,
|
||||
USER_LOGIN_LOG,modifyTimeMillis,Long,,,false,false,数据修改时间,
|
||||
USER_LOGIN_LOG,modifyUser,String,50,,false,false,修改人,
|
||||
USER_LOGIN_LOG,ip,String,80,,false,false,客户端IP地址,
|
||||
USER_LOGIN_LOG,userAgent,TEXT,,,false,false,浏览器标识,
|
||||
USER_LOGIN_LOG,useMfa,TINYINT,,,false,false,是否使用 mfa,
|
||||
USER_LOGIN_LOG,success,TINYINT,,,false,false,是否登录成功,
|
||||
USER_LOGIN_LOG,operateCode,TINYINT,,,false,false,操作状态码(备注码),
|
||||
USER_LOGIN_LOG,username,String,50,,false,false,昵称,
|
||||
|
|
@ -20,7 +20,17 @@
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.cron.CronUtil;
|
||||
import cn.hutool.cron.pattern.CronPattern;
|
||||
import cn.hutool.cron.pattern.CronPatternUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by jiangzeyin on 2019/3/4.
|
||||
@ -36,4 +46,25 @@ public class TestCron {
|
||||
CronUtil.restart();
|
||||
// System.out.println(JpomApplicationEvent.getPid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String cron = "0 0 23 ? * 5 ";
|
||||
|
||||
CronPattern cronPattern = CronPattern.of(cron);
|
||||
|
||||
// Date date = CronPatternUtil.nextDateAfter(cronPattern, DateUtil.offsetDay(DateTime.now(), -1), false);
|
||||
|
||||
List<Date> dateList = CronPatternUtil.matchedDates(cron, DateUtil.offsetDay(DateTime.now(), -1), 10, true);
|
||||
for (Date date1 : dateList) {
|
||||
System.out.println(DateUtil.format(date1, DatePattern.NORM_DATETIME_FORMAT));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
Calendar calendar = DateTime.now().toCalendar();
|
||||
System.out.println(calendar.get(Calendar.DAY_OF_WEEK) - 1);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
||||
<parent>
|
||||
<artifactId>jpom-parent</artifactId>
|
||||
<groupId>io.jpom</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
@ -39,7 +39,7 @@
|
||||
<module>storage-module-mysql</module>
|
||||
</modules>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<groupId>io.jpom.storage-module</groupId>
|
||||
<artifactId>jpom-storage-module-parent</artifactId>
|
||||
<name>Jpom storage module</name>
|
||||
|
@ -30,7 +30,7 @@
|
||||
<parent>
|
||||
<groupId>io.jpom.storage-module</groupId>
|
||||
<artifactId>jpom-storage-module-parent</artifactId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
<parent>
|
||||
<groupId>io.jpom.storage-module</groupId>
|
||||
<artifactId>jpom-storage-module-parent</artifactId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
<parent>
|
||||
<groupId>io.jpom.storage-module</groupId>
|
||||
<artifactId>jpom-storage-module-parent</artifactId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
<parent>
|
||||
<artifactId>jpom-plugins-parent</artifactId>
|
||||
<groupId>io.jpom.plugins</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -29,7 +29,7 @@
|
||||
<parent>
|
||||
<artifactId>jpom-plugins-parent</artifactId>
|
||||
<groupId>io.jpom.plugins</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -29,7 +29,7 @@
|
||||
<parent>
|
||||
<artifactId>jpom-plugins-parent</artifactId>
|
||||
<groupId>io.jpom.plugins</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -29,7 +29,7 @@
|
||||
<parent>
|
||||
<artifactId>jpom-parent</artifactId>
|
||||
<groupId>io.jpom</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
@ -42,7 +42,7 @@
|
||||
<module>encrypt</module>
|
||||
</modules>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<groupId>io.jpom.plugins</groupId>
|
||||
<artifactId>jpom-plugins-parent</artifactId>
|
||||
<name>Jpom Plugins</name>
|
||||
|
@ -29,7 +29,7 @@
|
||||
<parent>
|
||||
<artifactId>jpom-plugins-parent</artifactId>
|
||||
<groupId>io.jpom.plugins</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -29,7 +29,7 @@
|
||||
<parent>
|
||||
<artifactId>jpom-plugins-parent</artifactId>
|
||||
<groupId>io.jpom.plugins</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -29,7 +29,7 @@
|
||||
<parent>
|
||||
<artifactId>jpom-parent</artifactId>
|
||||
<groupId>io.jpom</groupId>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
2
pom.xml
2
pom.xml
@ -50,7 +50,7 @@
|
||||
简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件
|
||||
</description>
|
||||
<inceptionYear>2017</inceptionYear>
|
||||
<version>2.10.28</version>
|
||||
<version>2.10.29</version>
|
||||
<url>https://gitee.com/dromara/Jpom</url>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
@ -31,12 +31,12 @@
|
||||
# docker buildx create --use
|
||||
|
||||
# 服务端
|
||||
docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:2.10.28 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push .
|
||||
docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:2.10.29 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push .
|
||||
#
|
||||
#docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push .
|
||||
|
||||
# docker logs --tail="100" jpom-server
|
||||
# docker run -d -p 2122:2122 --name jpom-server -v /etc/localtime:/etc/localtime:ro -v jpom-server-vol:/usr/local/jpom-server jpomdocker/jpom:mac-arm-2.10.28
|
||||
# docker run -d -p 2122:2122 --name jpom-server -v /etc/localtime:/etc/localtime:ro -v jpom-server-vol:/usr/local/jpom-server jpomdocker/jpom:mac-arm-2.10.29
|
||||
# docker run -d -p 2122:2122 --name jpom-server -v D:/home/jpom-server/logs:/usr/local/jpom-server/logs -v D:/home/jpom-server/data:/usr/local/jpom-server/data -v D:/home/jpom-server/conf:/usr/local/jpom-server/conf jpomdocker/jpom
|
||||
# docker stop jpom-server
|
||||
# docker rm jpom-server
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
|
||||
# 版本
|
||||
jpom_version=2.10.28
|
||||
jpom_version=2.10.29
|
||||
|
||||
#Mirror_Host=download.fastgit.org
|
||||
#Mirror_Host=hub.fastgit.xyz
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jpom-vue",
|
||||
"version": "2.10.28",
|
||||
"version": "2.10.29",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service --max-old-space-size=900 serve --mode dev",
|
||||
|
13
web-vue/src/api/tools.js
Normal file
13
web-vue/src/api/tools.js
Normal file
@ -0,0 +1,13 @@
|
||||
import axios from "./config";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
export function cronTools(data) {
|
||||
return axios({
|
||||
url: "/tools/cron",
|
||||
method: "get",
|
||||
params: data,
|
||||
});
|
||||
}
|
18
web-vue/src/api/user/user-login-log.js
Normal file
18
web-vue/src/api/user/user-login-log.js
Normal file
@ -0,0 +1,18 @@
|
||||
import axios from "../config";
|
||||
|
||||
export function userLoginLgin(params) {
|
||||
return axios({
|
||||
url: "/user/login-log/list-data",
|
||||
method: "post",
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
export const operateCodeMap = {
|
||||
0: "正常登录",
|
||||
1: "密码错误",
|
||||
2: "账号被锁定",
|
||||
3: "自动续期",
|
||||
4: "账号被禁用",
|
||||
5: "登录成功,需要验证 MFA",
|
||||
};
|
@ -238,3 +238,19 @@ export function demoInfo() {
|
||||
params: {},
|
||||
});
|
||||
}
|
||||
|
||||
export function listLoginLog(params) {
|
||||
return axios({
|
||||
url: "/user/list-login-log-data",
|
||||
method: "post",
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
export function listOperaterLog(params) {
|
||||
return axios({
|
||||
url: "/user/list-operate-log-data",
|
||||
method: "post",
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
@ -128,9 +128,9 @@ export default {
|
||||
buildLogVisible: false,
|
||||
tableSelections: [],
|
||||
columns: [
|
||||
{ title: "构建名称", dataIndex: "buildName", /*width: 120,*/ ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "构建 ID", dataIndex: "buildNumberId", width: 90, align: "center", ellipsis: true, scopedSlots: { customRender: "buildNumberId" } },
|
||||
{ title: "备注", dataIndex: "buildRemark", /*width: 120,*/ ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "构建名称", dataIndex: "buildName", width: 120, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "构建 ID", dataIndex: "buildNumberId", width: "90px", align: "center", ellipsis: true, scopedSlots: { customRender: "buildNumberId" } },
|
||||
{ title: "备注", dataIndex: "buildRemark", width: 120, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
|
||||
{ title: "状态", dataIndex: "status", width: "100px", align: "center", ellipsis: true, scopedSlots: { customRender: "status" } },
|
||||
{ title: "触发类型", dataIndex: "triggerBuildType", align: "center", width: "100px", ellipsis: true, scopedSlots: { customRender: "triggerBuildType" } },
|
||||
@ -151,7 +151,7 @@ export default {
|
||||
},
|
||||
{ title: "发布方式", dataIndex: "releaseMethod", width: "100px", ellipsis: true, scopedSlots: { customRender: "releaseMethod" } },
|
||||
{ title: "构建人", dataIndex: "modifyUser", width: "130px", ellipsis: true, scopedSlots: { customRender: "modifyUser" } },
|
||||
{ title: "操作", dataIndex: "operation", scopedSlots: { customRender: "operation" }, width: "200px", align: "center" },
|
||||
{ title: "操作", dataIndex: "operation", scopedSlots: { customRender: "operation" }, width: "200px", align: "center", fixed: "right" },
|
||||
],
|
||||
};
|
||||
},
|
||||
|
@ -1168,16 +1168,17 @@ export default {
|
||||
afterOptListSimple,
|
||||
buildConfirmVisible: false,
|
||||
columns: [
|
||||
{ title: "名称", dataIndex: "name", sorter: true, ellipsis: true, scopedSlots: { customRender: "name" } },
|
||||
|
||||
{ title: "名称", dataIndex: "name", sorter: true, width: 200, ellipsis: true, scopedSlots: { customRender: "name" } },
|
||||
{ title: "分组", dataIndex: "group", width: 100, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{
|
||||
title: "分支/标签",
|
||||
dataIndex: "branchName",
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
scopedSlots: { customRender: "branchName" },
|
||||
},
|
||||
{ title: "产物", dataIndex: "resultDirFile", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "方式", dataIndex: "buildMode", align: "center", width: "80px", ellipsis: true, scopedSlots: { customRender: "buildMode" } },
|
||||
{ title: "产物", dataIndex: "resultDirFile", width: 100, ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "方式", dataIndex: "buildMode", align: "center", width: "80px", sorter: true, ellipsis: true, scopedSlots: { customRender: "buildMode" } },
|
||||
{ title: "状态", dataIndex: "status", align: "center", width: "100px", ellipsis: true, scopedSlots: { customRender: "status" } },
|
||||
{
|
||||
title: "构建 ID",
|
||||
@ -1200,28 +1201,24 @@ export default {
|
||||
dataIndex: "modifyTimeMillis",
|
||||
sorter: true,
|
||||
customRender: (text) => {
|
||||
if (!text) {
|
||||
return "";
|
||||
}
|
||||
return parseTime(text);
|
||||
},
|
||||
width: 170,
|
||||
width: "170px",
|
||||
},
|
||||
{
|
||||
title: "其他信息",
|
||||
title: "发布方式",
|
||||
dataIndex: "releaseMethod",
|
||||
width: 100,
|
||||
width: "100px",
|
||||
ellipsis: true,
|
||||
scopedSlots: { customRender: "releaseMethod" },
|
||||
},
|
||||
// {
|
||||
// title: "产物目录",
|
||||
// dataIndex: "resultDirFile",
|
||||
// ellipsis: true,
|
||||
// width: 100,
|
||||
// scopedSlots: { customRender: "resultDirFile" },
|
||||
// },
|
||||
// { title: "构建命令", width: 100, dataIndex: "script", ellipsis: true, scopedSlots: { customRender: "script" } },
|
||||
{
|
||||
title: "定时构建",
|
||||
dataIndex: "autoBuildCron",
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
scopedSlots: { customRender: "tooltip" },
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "operation",
|
||||
@ -1804,9 +1801,10 @@ export default {
|
||||
// 选择发布集群时 渲染服务名称 数据
|
||||
selectSwarm() {
|
||||
this.swarmServiceListOptions = [];
|
||||
this.tempExtraData = { ...this.tempExtraData, dockerSwarmServiceName: undefined };
|
||||
if (this.tempExtraData.dockerSwarmId) {
|
||||
// 选中时才处理
|
||||
dockerSwarmServicesList({
|
||||
dockerSwarmServicesList("", {
|
||||
id: this.tempExtraData.dockerSwarmId,
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
|
@ -46,7 +46,7 @@
|
||||
<a-input v-model="temp.name" placeholder="容器名称" />
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item label="标签" prop="tagInput">
|
||||
<a-form-model-item label="标签" prop="tagInput" help="标签用于容器构建选择容器功能(fromTag)">
|
||||
<template>
|
||||
<div>
|
||||
<a-tooltip :key="index" :title="tag" v-for="(tag, index) in temp.tagsArray">
|
||||
|
@ -31,21 +31,19 @@
|
||||
</a-sub-menu>
|
||||
<a-menu-divider />
|
||||
<a-menu-item @click="handleUpdatePwd">
|
||||
<a-button type="link">
|
||||
<a-space><a-icon type="lock" />安全管理</a-space>
|
||||
</a-button>
|
||||
<a-button type="link" icon="lock"> 安全管理 </a-button>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item @click="handleUpdateUser">
|
||||
<a-button type="link">
|
||||
<a-space><a-icon type="profile" />用户资料</a-space>
|
||||
</a-button>
|
||||
<a-button type="link" icon="profile"> 用户资料 </a-button>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item>
|
||||
<a-button @click="customize" type="link">
|
||||
<a-space><a-icon type="skin" /> 个性配置</a-space>
|
||||
</a-button>
|
||||
<a-menu-item @click="handleUserlog">
|
||||
<a-button type="link" icon="bars"> 操作日志 </a-button>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item @click="customize">
|
||||
<a-button type="link" icon="skin"> 个性配置 </a-button>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item @click="logOut">
|
||||
@ -277,6 +275,10 @@
|
||||
</a-row>
|
||||
</a-space>
|
||||
</a-modal>
|
||||
<!-- 查看操作日志 -->
|
||||
<a-modal destroyOnClose v-model="viewLogVisible" :width="'90vw'" title="操作日志" :footer="null" :maskClosable="false">
|
||||
<user-log v-if="viewLogVisible"></user-log>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@ -286,8 +288,12 @@ import QRCode from "qrcodejs2";
|
||||
import sha1 from "js-sha1";
|
||||
import Vue from "vue";
|
||||
import { MFA_APP_TIP_ARRAY } from "@/utils/const";
|
||||
import UserLog from "./user-log.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserLog,
|
||||
},
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
@ -336,6 +342,7 @@ export default {
|
||||
},
|
||||
MFA_APP_TIP_ARRAY,
|
||||
bindMfaTip: false,
|
||||
viewLogVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -693,6 +700,9 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
handleUserlog() {
|
||||
this.viewLogVisible = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
226
web-vue/src/pages/layout/user-log.vue
Normal file
226
web-vue/src/pages/layout/user-log.vue
Normal file
@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-tabs>
|
||||
<a-tab-pane key="1" tab="操作日志">
|
||||
<!-- 数据表格 -->
|
||||
<a-table
|
||||
size="middle"
|
||||
:data-source="operatelist"
|
||||
:columns="operatecolumns"
|
||||
:pagination="operatecpagination"
|
||||
bordered
|
||||
rowKey="id"
|
||||
@change="
|
||||
(pagination, filters, sorter) => {
|
||||
this.operatelistQuery = CHANGE_PAGE(this.operatelistQuery, { pagination, sorter });
|
||||
this.operaterloadData();
|
||||
}
|
||||
"
|
||||
>
|
||||
<template slot="title">
|
||||
<a-space>
|
||||
<a-select show-search option-filter-prop="children" v-model="operatelistQuery.classFeature" allowClear placeholder="操作功能" class="search-input-item">
|
||||
<a-select-option v-for="item in classFeature" :key="item.value">{{ item.title }}</a-select-option>
|
||||
</a-select>
|
||||
<a-select show-search option-filter-prop="children" v-model="operatelistQuery.methodFeature" allowClear placeholder="操作方法" class="search-input-item">
|
||||
<a-select-option v-for="item in methodFeature" :key="item.value">{{ item.title }}</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
class="search-input-item"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
@change="
|
||||
(value, dateString) => {
|
||||
this.operatelistQuery.createTimeMillis = `${dateString[0]} ~ ${dateString[1]}`;
|
||||
}
|
||||
"
|
||||
/>
|
||||
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
|
||||
<a-button type="primary" :loading="operateloading" @click="operaterloadData">搜索</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<a-tooltip slot="classFeature" slot-scope="text" placement="topLeft" :title="classFeatureMap[text]">
|
||||
<span>{{ classFeatureMap[text] }}</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip slot="methodFeature" slot-scope="text" placement="topLeft" :title="methodFeatureMap[text]">
|
||||
<span>{{ methodFeatureMap[text] }}</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="username" slot-scope="text, item" placement="topLeft" :title="text">
|
||||
<span>{{ text || item.userId }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="optStatus" slot-scope="text" placement="topLeft" :title="`默认状态码为 200 表示执行成功,部分操作状态码可能为 0,状态码为 0 的操作大部分为没有操作结果或者异步执行`">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="登录日志">
|
||||
<a-table
|
||||
size="middle"
|
||||
:data-source="loginlist"
|
||||
:columns="logincolumns"
|
||||
:pagination="loginpagination"
|
||||
bordered
|
||||
rowKey="id"
|
||||
@change="
|
||||
(pagination, filters, sorter) => {
|
||||
this.loginlistQuery = CHANGE_PAGE(this.loginlistQuery, { pagination, sorter });
|
||||
this.loginloadData();
|
||||
}
|
||||
"
|
||||
>
|
||||
<template slot="title">
|
||||
<a-space>
|
||||
<a-input v-model="loginlistQuery['%username%']" @pressEnter="loginloadData" placeholder="用户名" class="search-input-item" />
|
||||
<a-input v-model="loginlistQuery['%ip%']" @pressEnter="loginloadData" placeholder="登录IP" class="search-input-item" />
|
||||
<a-range-picker
|
||||
class="search-input-item"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
@change="
|
||||
(value, dateString) => {
|
||||
this.loginlistQuery.createTimeMillis = `${dateString[0]} ~ ${dateString[1]}`;
|
||||
}
|
||||
"
|
||||
/>
|
||||
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
|
||||
<a-button type="primary" :loading="loginloading" @click="loginloadData">搜索</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-tooltip slot="success" slot-scope="text" placement="topLeft" :title="text ? '成功' : '失败'">
|
||||
<a-tag v-if="text" color="green">成功</a-tag>
|
||||
<a-tag v-else color="pink">失败</a-tag>
|
||||
</a-tooltip>
|
||||
<a-tooltip slot="useMfa" slot-scope="text" placement="topLeft" :title="text ? '使用' : '未使用'">
|
||||
<a-tag>{{ text ? "使用" : "未使用" }}</a-tag>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="operateCode" slot-scope="text" placement="topLeft" :title="operateCode[text] || '未知'">
|
||||
{{ operateCode[text] || "未知" }}
|
||||
</a-tooltip>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from "@/utils/const";
|
||||
import { listOperaterLog, listLoginLog } from "@/api/user/user";
|
||||
import { getMonitorOperateTypeList } from "@/api/monitor";
|
||||
import { operateCodeMap } from "@/api/user/user-login-log";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
operateloading: false,
|
||||
operatelist: [],
|
||||
operatelistQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
|
||||
methodFeature: [],
|
||||
classFeature: [],
|
||||
methodFeatureMap: {},
|
||||
classFeatureMap: {},
|
||||
operatecolumns: [
|
||||
{ title: "操作者", dataIndex: "username", ellipsis: true, scopedSlots: { customRender: "username" } },
|
||||
{ title: "IP", dataIndex: "ip", ellipsis: true, width: "130px" },
|
||||
{ title: "节点", dataIndex: "nodeId", width: 120, ellipsis: true, scopedSlots: { customRender: "nodeId" } },
|
||||
{ title: "数据名称", dataIndex: "dataName", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "工作空间名", dataIndex: "workspaceName", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
// { title: "数据 ID", dataIndex: "dataId", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "操作功能", dataIndex: "classFeature", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "classFeature" } },
|
||||
{ title: "操作方法", dataIndex: "methodFeature", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "methodFeature" } },
|
||||
{ title: "状态码", dataIndex: "optStatus", width: 90, scopedSlots: { customRender: "optStatus" } },
|
||||
{
|
||||
title: "操作时间",
|
||||
dataIndex: "createTimeMillis",
|
||||
sorter: true,
|
||||
customRender: (text, item) => {
|
||||
return parseTime(text || item.optTime);
|
||||
},
|
||||
width: "170px",
|
||||
},
|
||||
],
|
||||
loginloading: false,
|
||||
loginlist: [],
|
||||
operateCode: operateCodeMap,
|
||||
loginlistQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
|
||||
logincolumns: [
|
||||
{ title: "用户ID", dataIndex: "modifyUser", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "用户名称", dataIndex: "username", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "IP", dataIndex: "ip", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "浏览器", dataIndex: "userAgent", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "是否成功", dataIndex: "success", ellipsis: true, width: "100px", scopedSlots: { customRender: "success" } },
|
||||
{ title: "是否使用MFA", dataIndex: "useMfa", ellipsis: true, width: "130px", scopedSlots: { customRender: "useMfa" } },
|
||||
{ title: "结果描述", dataIndex: "operateCode", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "operateCode" } },
|
||||
|
||||
{
|
||||
title: "登录时间",
|
||||
dataIndex: "createTimeMillis",
|
||||
sorter: true,
|
||||
customRender: (text, item) => {
|
||||
return parseTime(text || item.optTime);
|
||||
},
|
||||
width: "170px",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
operatecpagination() {
|
||||
return COMPUTED_PAGINATION(this.operatelistQuery);
|
||||
},
|
||||
loginpagination() {
|
||||
return COMPUTED_PAGINATION(this.loginlistQuery);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.operaterloadData();
|
||||
this.loginloadData();
|
||||
getMonitorOperateTypeList().then((res) => {
|
||||
this.methodFeature = res.data.methodFeature;
|
||||
this.classFeature = res.data.classFeature;
|
||||
res.data.methodFeature.forEach((item) => {
|
||||
this.methodFeatureMap[item.value] = item.title;
|
||||
});
|
||||
res.data.classFeature.forEach((item) => {
|
||||
this.classFeatureMap[item.value] = item.title;
|
||||
});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
CHANGE_PAGE,
|
||||
// 加载数据
|
||||
operaterloadData(pointerEvent) {
|
||||
this.operateloading = true;
|
||||
this.operatelistQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.operatelistQuery.page;
|
||||
listOperaterLog(this.operatelistQuery).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.operatelist = res.data.result;
|
||||
this.operatelistQuery.total = res.data.total;
|
||||
}
|
||||
this.operateloading = false;
|
||||
});
|
||||
},
|
||||
loginloadData(pointerEvent) {
|
||||
this.loginloading = true;
|
||||
this.loginlistQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.loginlistQuery.page;
|
||||
listLoginLog(this.loginlistQuery).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.loginlist = res.data.result;
|
||||
this.loginlistQuery.total = res.data.total;
|
||||
}
|
||||
this.loginloading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -30,8 +30,8 @@
|
||||
<br />
|
||||
<template v-if="this.action === 'login'">
|
||||
<a-form-model ref="loginForm" :label-col="{ span: 0 }" :model="loginForm" :rules="rules" @submit="handleLogin">
|
||||
<a-form-model-item :wrapper-col="{ span: 24 }" prop="userName">
|
||||
<a-input v-model="loginForm.userName" placeholder="用户名" />
|
||||
<a-form-model-item :wrapper-col="{ span: 24 }" prop="loginName">
|
||||
<a-input v-model="loginForm.loginName" placeholder="用户名" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item :wrapper-col="{ span: 24 }" prop="userPwd">
|
||||
<a-input-password v-model="loginForm.userPwd" placeholder="密码" />
|
||||
@ -73,7 +73,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loginForm: {
|
||||
userName: "",
|
||||
loginName: "",
|
||||
userPwd: "",
|
||||
code: "",
|
||||
},
|
||||
@ -83,7 +83,7 @@ export default {
|
||||
dynamicBg: localStorage.getItem("dynamicBg") === "true",
|
||||
loginTitle: "登录JPOM",
|
||||
rules: {
|
||||
userName: [{ required: true, message: "请输入用户名" }],
|
||||
loginName: [{ required: true, message: "请输入用户名" }],
|
||||
userPwd: [{ required: true, message: "请输入密码" }],
|
||||
code: [{ required: true, message: "请输入验证码" }],
|
||||
mfaCode: [
|
||||
@ -147,7 +147,7 @@ export default {
|
||||
message: "温馨提示",
|
||||
description: h("div", null, [h("p", { domProps: { innerHTML: res.msg } }, null)]),
|
||||
});
|
||||
this.loginForm.userName = res.data.user;
|
||||
this.loginForm.loginName = res.data.user;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -10,6 +10,10 @@
|
||||
<a-tooltip slot="time" slot-scope="text" placement="topLeft" :title="parseTime(text)">
|
||||
<span>{{ parseTime(text) }}</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip slot="cron" slot-scope="text" placement="topLeft" :title="text">
|
||||
<!-- <a-icon type="unordered-list" /> -->
|
||||
<a-button type="link" style="padding: 0px" size="small" @click="toCronTaskList(text)"> {{ text }} <a-icon type="unordered-list" /> </a-button>
|
||||
</a-tooltip>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
@ -58,6 +62,7 @@ export default {
|
||||
{
|
||||
title: "cron",
|
||||
dataIndex: "cron",
|
||||
scopedSlots: { customRender: "cron" },
|
||||
// sorter: (a, b) => (a && b ? a.localeCompare(b, "zh-CN") : 0),
|
||||
// sortDirections: ["descend", "ascend"],
|
||||
},
|
||||
@ -100,6 +105,20 @@ export default {
|
||||
refresh() {
|
||||
this.$emit("refresh");
|
||||
},
|
||||
// 前往 cron 详情
|
||||
toCronTaskList(cron) {
|
||||
const newpage = this.$router.resolve({
|
||||
name: "cron-task-list",
|
||||
path: "/tools/cron",
|
||||
query: {
|
||||
...this.$route.query,
|
||||
sPid: "tools",
|
||||
sId: "cronTools",
|
||||
cron: cron,
|
||||
},
|
||||
});
|
||||
window.open(newpage.href, "_blank");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -56,6 +56,7 @@
|
||||
<a-modal destroyOnClose v-model="configMenuVisible" :title="`${temp.name} 工作空间菜单`" @ok="onSubmitMenus" :maskClosable="false">
|
||||
<a-form-model ref="editWhiteForm" :model="menusConfigData">
|
||||
<a-row type="flex" justify="center">
|
||||
<a-alert :message="`菜单配置只对非超级管理员生效`" style="margin-top: 10px; margin-bottom: 20px" banner />
|
||||
<a-col :span="12">
|
||||
<a-card title="服务端菜单" :bordered="false">
|
||||
<a-tree show-icon v-if="menusConfigData.serverMenus" checkable :tree-data="menusConfigData.serverMenus" :replaceFields="replaceFields" v-model="menusConfigData.serverMenuKeys">
|
||||
|
@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<code-editor v-model="code" showTool @checkJson="checkJson" :options="{ mode: 'application/json' }"> </code-editor>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import codeEditor from "@/components/codeEditor";
|
||||
export default {
|
||||
components: {
|
||||
codeEditor,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
code: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
checkJson() {
|
||||
console.log(11);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
153
web-vue/src/pages/tools/cron.vue
Normal file
153
web-vue/src/pages/tools/cron.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-row justify="center" type="flex">
|
||||
<a-col :span="18">
|
||||
<a-space direction="vertical" style="display: inline">
|
||||
<a-alert message="此工具用于检查 cron 表达式是否正确,以及计划运行时间" type="info" />
|
||||
<a-collapse>
|
||||
<a-collapse-panel key="1" header="更多说明">
|
||||
定时任务表达式<br />
|
||||
表达式类似于Linux的crontab表达式,表达式使用空格分成5个部分,按顺序依次为:
|
||||
<ol>
|
||||
<li><strong>分</strong> :范围:0~59</li>
|
||||
<li><strong>时</strong> :范围:0~23</li>
|
||||
<li><strong>日</strong> :范围:1~31,<strong>"L"</strong> 表示月的最后一天</li>
|
||||
<li><strong>月</strong> :范围:1~12,同时支持不区分大小写的别名:"jan","feb", "mar", "apr", "may","jun", "jul", "aug", "sep","oct", "nov", "dec"</li>
|
||||
<li><strong>周</strong> :范围:0 (Sunday)~6(Saturday),7也可以表示周日,同时支持不区分大小写的别名:"sun","mon", "tue", "wed", "thu","fri", "sat",<strong>"L"</strong> 表示周六</li>
|
||||
</ol>
|
||||
<p>为了兼容Quartz表达式,同时支持6位和7位表达式,其中:<br /></p>
|
||||
|
||||
<pre>
|
||||
当为6位时,第一位表示<strong>秒</strong> ,范围0~59,但是第一位不做匹配
|
||||
当为7位时,最后一位表示<strong>年</strong> ,范围1970~2099,但是第7位不做解析,也不做匹配
|
||||
</pre
|
||||
>
|
||||
<p>
|
||||
当定时任务运行到的时间匹配这些表达式后,任务被启动。<br />
|
||||
注意:
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
当isMatchSecond为 true 时才会匹配秒部分
|
||||
默认都是关闭的
|
||||
</pre
|
||||
>
|
||||
<p>对于每一个子表达式,同样支持以下形式:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>*</strong> :表示匹配这个位置所有的时间</li>
|
||||
<li><strong>?</strong> :表示匹配这个位置任意的时间(与"*"作用一致)</li>
|
||||
<li><strong>*/2</strong> :表示间隔时间,例如在分上,表示每两分钟,同样*可以使用数字列表代替,逗号分隔</li>
|
||||
<li><strong>2-8</strong> :表示连续区间,例如在分上,表示2,3,4,5,6,7,8分</li>
|
||||
<li><strong>2,3,5,8</strong> :表示列表</li>
|
||||
<li><strong>cronA | cronB</strong> :表示多个定时表达式</li>
|
||||
</ul>
|
||||
注意:在每一个子表达式中优先级:
|
||||
|
||||
<pre>
|
||||
间隔(/) > 区间(-) > 列表(,)
|
||||
</pre
|
||||
>
|
||||
<p>
|
||||
例如 2,3,6/3中,由于“/”优先级高,因此相当于2,3,(6/3),结果与 2,3,6等价<br />
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<p>一些例子:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>5 * * * *</strong> :每个点钟的5分执行,00:05,01:05……</li>
|
||||
<li><strong>* * * * *</strong> :每分钟执行</li>
|
||||
<li><strong>*/2 * * * *</strong> :每两分钟执行</li>
|
||||
<li><strong>* 12 * * *</strong> :12点的每分钟执行</li>
|
||||
<li><strong>59 11 * * 1,2</strong> :每周一和周二的11:59执行</li>
|
||||
<li><strong>3-18/5 * * * *</strong> :3~18分,每5分钟执行一次,即0:03, 0:08, 0:13, 0:18, 1:03, 1:08……</li>
|
||||
</ul>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-form-model :model="temp" ref="form" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
|
||||
<a-form-model-item label="cron表达式" prop="cron">
|
||||
<a-input v-model="temp.cron" placeholder="请输入要检查的 cron 表达式" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="计划次数" prop="count">
|
||||
<a-input-number v-model="temp.count" :min="1" placeholder="请输入获取的计划运行次数" style="width: 100%" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="匹配秒">
|
||||
<a-switch v-model="temp.isMatchSecond" checked-children="是" un-checked-children="否" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="时间范围" prop="date" help="默认是当前时间到今年结束">
|
||||
<a-range-picker format="YYYY-MM-DD" valueFormat="YYYY-MM-DD" separator="至" v-model="temp.date" style="width: 100%" />
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" @click="onSubmit"> 检查 </a-button>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-space>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="10">
|
||||
<a-list bordered :data-source="resultList" :locale="locale">
|
||||
<a-list-item slot="renderItem" slot-scope="item">
|
||||
{{ parseTime(item, "{y}-{m}-{d} {h}:{i}:{s} 周{a}") }}
|
||||
</a-list-item>
|
||||
<div slot="header">结果</div>
|
||||
</a-list>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { cronTools } from "@/api/tools";
|
||||
import { parseTime } from "@/utils/const";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
temp: {
|
||||
count: 10,
|
||||
},
|
||||
locale: {
|
||||
emptyText: "暂无数据",
|
||||
},
|
||||
resultList: [],
|
||||
// 表单校验规则
|
||||
rules: {
|
||||
cron: [{ required: true, message: "请输入要检查的 cron 表达式", trigger: "blur" }],
|
||||
count: [{ required: true, message: "请输入获取的计划运行次数", trigger: "blur" }],
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
const cron = this.$route.query.cron;
|
||||
if (cron) {
|
||||
this.temp = { ...this.temp, cron: cron };
|
||||
this.$nextTick(() => {
|
||||
this.onSubmit();
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseTime,
|
||||
onSubmit() {
|
||||
this.$refs["form"].validate((valid) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
this.resultList = [];
|
||||
this.locale = {
|
||||
emptyText: "暂无数据",
|
||||
};
|
||||
const temp = { ...this.temp, date: this.temp.date && this.temp.date[0] + " ~ " + this.temp.date[1] };
|
||||
|
||||
cronTools(temp).then((res) => {
|
||||
// console.log(res);
|
||||
this.resultList = res.data || [];
|
||||
this.locale = {
|
||||
emptyText: res.msg,
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -34,6 +34,11 @@
|
||||
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="username" slot-scope="text, item" placement="topLeft" :title="text">
|
||||
<span>{{ text || item.userId }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="optStatus" slot-scope="text" placement="topLeft" :title="`默认状态码为 200 表示执行成功,部分操作状态码可能为 0,状态码为 0 的操作大部分为没有操作结果或者异步执行`">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
@ -84,11 +89,12 @@ export default {
|
||||
detailVisible: false,
|
||||
detailData: [],
|
||||
columns: [
|
||||
{ title: "操作者", dataIndex: "userId" },
|
||||
{ title: "操作者", dataIndex: "username", ellipsis: true, scopedSlots: { customRender: "username" } },
|
||||
{ title: "IP", dataIndex: "ip" /*width: 130*/ },
|
||||
{ title: "节点", dataIndex: "nodeId", width: 120, ellipsis: true, scopedSlots: { customRender: "nodeId" } },
|
||||
{ title: "数据名称", dataIndex: "dataName", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "数据 ID", dataIndex: "dataId", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "工作空间名", dataIndex: "workspaceName", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
// { title: "数据 ID", dataIndex: "dataId", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "操作功能", dataIndex: "classFeature", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "classFeature" } },
|
||||
{ title: "操作方法", dataIndex: "methodFeature", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "methodFeature" } },
|
||||
{ title: "状态码", dataIndex: "optStatus", width: 90, scopedSlots: { customRender: "optStatus" } },
|
||||
@ -99,7 +105,7 @@ export default {
|
||||
customRender: (text, item) => {
|
||||
return parseTime(text || item.optTime);
|
||||
},
|
||||
width: 160,
|
||||
width: "170px",
|
||||
},
|
||||
{ title: "操作", align: "center", dataIndex: "operation", scopedSlots: { customRender: "operation" }, width: 80 },
|
||||
],
|
||||
@ -183,6 +189,7 @@ export default {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.detailData.push({ title: "数据Id", description: this.temp.dataId });
|
||||
this.detailData.push({ title: "浏览器标识", description: this.temp.userAgent });
|
||||
this.detailData.push({ title: "请求参数", json: true, value: this.temp.reqData });
|
||||
this.detailData.push({ title: "响应结果", json: true, value: this.temp.resultMsg });
|
||||
|
102
web-vue/src/pages/user/user-login-log.vue
Normal file
102
web-vue/src/pages/user/user-login-log.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="full-content">
|
||||
<!-- 数据表格 -->
|
||||
<a-table size="middle" :data-source="list" :columns="columns" :pagination="pagination" bordered :rowKey="(record, index) => index" @change="change">
|
||||
<template slot="title">
|
||||
<a-space>
|
||||
<a-input v-model="listQuery['%username%']" @pressEnter="loadData" placeholder="用户名" class="search-input-item" />
|
||||
<a-input v-model="listQuery['%ip%']" @pressEnter="loadData" placeholder="登录IP" class="search-input-item" />
|
||||
<a-range-picker class="search-input-item" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" @change="onchangeTime" />
|
||||
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
|
||||
<a-button type="primary" :loading="loading" @click="loadData">搜索</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-tooltip slot="success" slot-scope="text" placement="topLeft" :title="text ? '成功' : '失败'">
|
||||
<a-tag v-if="text" color="green">成功</a-tag>
|
||||
<a-tag v-else color="pink">失败</a-tag>
|
||||
</a-tooltip>
|
||||
<a-tooltip slot="useMfa" slot-scope="text" placement="topLeft" :title="text ? '使用' : '未使用'">
|
||||
<a-tag>{{ text ? "使用" : "未使用" }}</a-tag>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="operateCode" slot-scope="text" placement="topLeft" :title="operateCode[text] || '未知'">
|
||||
{{ operateCode[text] || "未知" }}
|
||||
</a-tooltip>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { userLoginLgin, operateCodeMap } from "@/api/user/user-login-log";
|
||||
|
||||
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from "@/utils/const";
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
list: [],
|
||||
operateCode: operateCodeMap,
|
||||
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
|
||||
|
||||
columns: [
|
||||
{ title: "用户ID", dataIndex: "modifyUser", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "用户名称", dataIndex: "username", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "IP", dataIndex: "ip", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "浏览器", dataIndex: "userAgent", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "是否成功", dataIndex: "success", ellipsis: true, width: "100px", scopedSlots: { customRender: "success" } },
|
||||
{ title: "是否使用MFA", dataIndex: "useMfa", ellipsis: true, width: "130px", scopedSlots: { customRender: "useMfa" } },
|
||||
{ title: "结果描述", dataIndex: "operateCode", /*width: 240,*/ ellipsis: true, scopedSlots: { customRender: "operateCode" } },
|
||||
|
||||
{
|
||||
title: "登录时间",
|
||||
dataIndex: "createTimeMillis",
|
||||
sorter: true,
|
||||
customRender: (text, item) => {
|
||||
return parseTime(text || item.optTime);
|
||||
},
|
||||
width: "170px",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pagination() {
|
||||
return COMPUTED_PAGINATION(this.listQuery);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
// 加载数据
|
||||
loadData(pointerEvent) {
|
||||
this.loading = true;
|
||||
this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page;
|
||||
userLoginLgin(this.listQuery).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.result;
|
||||
this.listQuery.total = res.data.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
// 分页、排序、筛选变化时触发
|
||||
change(pagination, filters, sorter) {
|
||||
this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter });
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
// 选择时间
|
||||
onchangeTime(value, dateString) {
|
||||
this.listQuery.createTimeMillis = `${dateString[0]} ~ ${dateString[1]}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped></style>
|
@ -129,6 +129,11 @@ const children = [
|
||||
name: "script-env-list",
|
||||
component: () => import("../pages/script/env"),
|
||||
},
|
||||
{
|
||||
path: "/tools/cron",
|
||||
name: "cron-tools",
|
||||
component: () => import("../pages/tools/cron"),
|
||||
},
|
||||
];
|
||||
|
||||
const management = [
|
||||
@ -162,7 +167,11 @@ const management = [
|
||||
name: "operation-log",
|
||||
component: () => import("../pages/user/operation-log"),
|
||||
},
|
||||
|
||||
{
|
||||
path: "/user/login-log",
|
||||
name: "user-login-log",
|
||||
component: () => import("../pages/user/user-login-log"),
|
||||
},
|
||||
// 工作空间
|
||||
{
|
||||
path: "/system/workspace",
|
||||
|
@ -31,6 +31,7 @@ const routeMenuMap = {
|
||||
userList: "/user/list",
|
||||
permission_group: "/user/permission-group",
|
||||
user_log: "/operation/log",
|
||||
user_login_log: "/user/login-log",
|
||||
monitorConfigEmail: "/system/mail",
|
||||
cacheManage: "/system/cache",
|
||||
logManage: "/system/log",
|
||||
@ -46,6 +47,7 @@ const routeMenuMap = {
|
||||
machine_ssh_info: "/system/assets/ssh-list",
|
||||
machine_docker_info: "/system/assets/docker-list",
|
||||
configWorkspaceEnv: "/script/env-list",
|
||||
cronTools: "/tools/cron",
|
||||
};
|
||||
|
||||
export default routeMenuMap;
|
||||
|
@ -108,9 +108,11 @@ export const uploadPieces = ({ file, uploadCallback, success, process, error })
|
||||
**/
|
||||
const concurrentUpload = () => {
|
||||
const startTime = new Date().getTime();
|
||||
// 设置初始化进度(避免第一份分片卡顿)
|
||||
process(0.01, 1, total, new Date().getTime() - startTime);
|
||||
concurrentExecution(chunkList, uploadFileConcurrent, (curItem) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { chunk, end } = getChunkInfo(file, curItem, chunkSize);
|
||||
const { chunk } = getChunkInfo(file, curItem, chunkSize);
|
||||
const chunkInfo = {
|
||||
chunk,
|
||||
currentChunk: curItem,
|
||||
@ -125,7 +127,7 @@ export const uploadPieces = ({ file, uploadCallback, success, process, error })
|
||||
uploaded.push(chunkInfo.currentChunk + 1);
|
||||
const sd = parseInt((uploaded.length / chunkInfo.chunkCount) * 100);
|
||||
// console.log(chunk);
|
||||
process(sd, end, total, new Date().getTime() - startTime);
|
||||
process(sd, Math.min(uploaded.length * chunkSize, total), total, new Date().getTime() - startTime);
|
||||
//
|
||||
/***
|
||||
* 创建文件上传参数
|
||||
|
Loading…
Reference in New Issue
Block a user