[Improvement][Monitor] Show master && worker Busy Or Normal Status and Show Commands table list (#15978)

* update

* test

* add monitor enhance ui

* update

* update

* update doc

* fix spotless

* update

* update

* Update dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/DataAnalysisController.java

Co-authored-by: Wenjun Ruan <wenjun@apache.org>

* Update dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/DataAnalysisController.java

Co-authored-by: Wenjun Ruan <wenjun@apache.org>

* Update dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ErrorCommandMapper.java

Co-authored-by: Wenjun Ruan <wenjun@apache.org>

* Update dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ErrorCommandMapper.xml

Co-authored-by: Wenjun Ruan <wenjun@apache.org>

* Update dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/CommandMapper.java

Co-authored-by: Wenjun Ruan <wenjun@apache.org>

* Update dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ErrorCommandMapper.xml

Co-authored-by: Wenjun Ruan <wenjun@apache.org>

* update

* fix spotless

* update

---------

Co-authored-by: Wenjun Ruan <wenjun@apache.org>
This commit is contained in:
旺阳 2024-05-15 13:58:59 +08:00 committed by GitHub
parent 4f4a13da02
commit 0e5cb664bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 1268 additions and 188 deletions

View File

@ -16,6 +16,12 @@
![worker](../../../img/new_ui/dev/monitor/worker.png)
### Alert Server
- Mainly related to alert server information.
![alert-server](../../../img/new_ui/dev/monitor/alert-server.png)
### Database
- Mainly the health status of the DB.
@ -26,18 +32,17 @@
### Statistics
![statistics](../../../img/new_ui/dev/monitor/statistics.png)
![Command Statistics List](../../../img/new_ui/dev/monitor/command-list.png)
| **Parameter** | **Description** |
|----------------------------------------|----------------------------------------------------|
| Number of commands wait to be executed | Statistics of the `t_ds_command` table data. |
| The number of failed commands | Statistics of the `t_ds_error_command` table data. |
| Number of tasks wait to run | Count the data of `task_queue` in the ZooKeeper. |
| Number of tasks wait to be killed | Count the data of `task_kill` in the ZooKeeper. |
Shows the command list in the system. Data is from the `t_ds_command` table.
![Failure Command Statistics List](../../../img/new_ui/dev/monitor/failure-command-list.png)
Shows the failure command list in the system. Data is from the `t_ds_error_command` table.
### Audit Log
The audit log provides information about who accesses the system and the operations made to the system and record related
time, which strengthen the security of the system and maintenance.
![audit-log](../../../img/new_ui/dev/monitor/audit-log.jpg)
![audit-log](../../../img/new_ui/dev/monitor/audit-log.png)

View File

@ -16,6 +16,12 @@
![worker](../../../img/new_ui/dev/monitor/worker.png)
### Alert Server
- 主要是 alert server 的相关信息。
![alert-server](../../../img/new_ui/dev/monitor/alert-server.png)
### Database
- 主要是 DB 的健康状况
@ -26,15 +32,16 @@
### Statistics
![statistics](../../../img/new_ui/dev/monitor/statistics.png)
![Command Statistics List](../../../img/new_ui/dev/monitor/command-list.png)
- 待执行命令数:统计 t_ds_command 表的数据
- 执行失败的命令数:统计 t_ds_error_command 表的数据
- 待运行任务数:统计 Zookeeper 中 task_queue 的数据
- 待杀死任务数:统计 Zookeeper 中 task_kill 的数据
展示系统中的命令列表,数据来自`t_ds_command`表。
![Failure Command Statistics List](../../../img/new_ui/dev/monitor/failure-command-list.png)
展示系统中的失败命令列表,数据来自`t_ds_error_command`表。
### 审计日志
审计日志的记录提供了有关谁访问了系统,以及他或她在给定时间段内执行了哪些操作的信息,对于维护安全都很有用。
审计日志的记录提供了有关谁访问了系统,以及他或她在给定时间段内执行了哪些操作的信息,对于维护安全都很有用。
![audit-log](../../../img/new_ui/dev/monitor/audit-log.jpg)
![audit-log](../../../img/new_ui/dev/monitor/audit-log.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 139 KiB

View File

@ -20,17 +20,21 @@ package org.apache.dolphinscheduler.api.controller;
import static org.apache.dolphinscheduler.api.enums.Status.COMMAND_STATE_COUNT_ERROR;
import static org.apache.dolphinscheduler.api.enums.Status.COUNT_PROCESS_DEFINITION_USER_ERROR;
import static org.apache.dolphinscheduler.api.enums.Status.COUNT_PROCESS_INSTANCE_STATE_ERROR;
import static org.apache.dolphinscheduler.api.enums.Status.LIST_PAGING_ALERT_GROUP_ERROR;
import static org.apache.dolphinscheduler.api.enums.Status.QUEUE_COUNT_ERROR;
import static org.apache.dolphinscheduler.api.enums.Status.TASK_INSTANCE_STATE_COUNT_ERROR;
import org.apache.dolphinscheduler.api.dto.CommandStateCount;
import org.apache.dolphinscheduler.api.exceptions.ApiException;
import org.apache.dolphinscheduler.api.service.DataAnalysisService;
import org.apache.dolphinscheduler.api.utils.PageInfo;
import org.apache.dolphinscheduler.api.utils.Result;
import org.apache.dolphinscheduler.api.vo.TaskInstanceCountVO;
import org.apache.dolphinscheduler.api.vo.WorkflowDefinitionCountVO;
import org.apache.dolphinscheduler.api.vo.WorkflowInstanceCountVO;
import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.dao.entity.Command;
import org.apache.dolphinscheduler.dao.entity.ErrorCommand;
import org.apache.dolphinscheduler.dao.entity.User;
import java.util.List;
@ -148,4 +152,54 @@ public class DataAnalysisController extends BaseController {
Map<String, Integer> stringIntegerMap = dataAnalysisService.countQueueState(loginUser);
return Result.success(stringIntegerMap);
}
/**
* command queue
*
* @param loginUser login user
* @return queue state count
*/
@Operation(summary = "listPendingCommands", description = "LIST_PENDING_COMMANDS")
@Parameters({
@Parameter(name = "searchVal", description = "SEARCH_VAL", schema = @Schema(implementation = String.class)),
@Parameter(name = "pageNo", description = "PAGE_NO", required = true, schema = @Schema(implementation = int.class, example = "1")),
@Parameter(name = "pageSize", description = "PAGE_SIZE", required = true, schema = @Schema(implementation = int.class, example = "20"))
})
@GetMapping("/listCommand")
@ResponseStatus(HttpStatus.OK)
@ApiException(LIST_PAGING_ALERT_GROUP_ERROR)
public Result<PageInfo<Command>> listPaging(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
@RequestParam(value = "projectCode", required = false) Long projectCode,
@RequestParam("pageNo") Integer pageNo,
@RequestParam("pageSize") Integer pageSize) {
checkPageParams(pageNo, pageSize);
PageInfo<Command> commandPageInfo =
dataAnalysisService.listPendingCommands(loginUser, projectCode, pageNo, pageSize);
return Result.success(commandPageInfo);
}
/**
* error command
*
* @param loginUser login user
* @return queue state count
*/
@Operation(summary = "listErrorCommand", description = "LIST_ERROR_COMMAND_LIST_PAGING_NOTES")
@Parameters({
@Parameter(name = "searchVal", description = "SEARCH_VAL", schema = @Schema(implementation = String.class)),
@Parameter(name = "pageNo", description = "PAGE_NO", required = true, schema = @Schema(implementation = int.class, example = "1")),
@Parameter(name = "pageSize", description = "PAGE_SIZE", required = true, schema = @Schema(implementation = int.class, example = "20"))
})
@GetMapping("/listErrorCommand")
@ResponseStatus(HttpStatus.OK)
@ApiException(LIST_PAGING_ALERT_GROUP_ERROR)
public Result<PageInfo<ErrorCommand>> listErrorCommand(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
@RequestParam(value = "projectCode", required = false) Long projectCode,
@RequestParam("pageNo") Integer pageNo,
@RequestParam("pageSize") Integer pageSize) {
checkPageParams(pageNo, pageSize);
PageInfo<ErrorCommand> errorCommandPageInfo =
dataAnalysisService.listErrorCommand(loginUser, projectCode, pageNo, pageSize);
return Result.success(errorCommandPageInfo);
}
}

View File

@ -18,7 +18,6 @@
package org.apache.dolphinscheduler.api.controller;
import static org.apache.dolphinscheduler.api.enums.Status.LIST_MASTERS_ERROR;
import static org.apache.dolphinscheduler.api.enums.Status.LIST_WORKERS_ERROR;
import static org.apache.dolphinscheduler.api.enums.Status.QUERY_DATABASE_STATE_ERROR;
import org.apache.dolphinscheduler.api.exceptions.ApiException;
@ -26,15 +25,16 @@ import org.apache.dolphinscheduler.api.service.MonitorService;
import org.apache.dolphinscheduler.api.utils.Result;
import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.common.model.Server;
import org.apache.dolphinscheduler.common.model.WorkerServerModel;
import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.dolphinscheduler.dao.plugin.api.monitor.DatabaseMetrics;
import org.apache.dolphinscheduler.registry.api.enums.RegistryNodeType;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
@ -56,35 +56,19 @@ public class MonitorController extends BaseController {
private MonitorService monitorService;
/**
* master list
* server list
*
* @param loginUser login user
* @return master list
* @return server list
*/
@Operation(summary = "listMaster", description = "MASTER_LIST_NOTES")
@GetMapping(value = "/masters")
@Operation(summary = "listServer", description = "SERVER_LIST_NOTES")
@GetMapping(value = "/{nodeType}")
@ResponseStatus(HttpStatus.OK)
@ApiException(LIST_MASTERS_ERROR)
public Result<List<Server>> listMaster(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser) {
List<Server> servers = monitorService.queryMaster(loginUser);
public Result<List<Server>> listServer(@PathVariable("nodeType") RegistryNodeType nodeType) {
List<Server> servers = monitorService.listServer(nodeType);
return Result.success(servers);
}
/**
* worker list
*
* @param loginUser login user
* @return worker information list
*/
@Operation(summary = "listWorker", description = "WORKER_LIST_NOTES")
@GetMapping(value = "/workers")
@ResponseStatus(HttpStatus.OK)
@ApiException(LIST_WORKERS_ERROR)
public Result<List<WorkerServerModel>> listWorker(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser) {
List<WorkerServerModel> workerServerModels = monitorService.queryWorker(loginUser);
return Result.success(workerServerModels);
}
/**
* query database state
*

View File

@ -21,9 +21,12 @@ import org.apache.dolphinscheduler.api.dto.CommandStateCount;
import org.apache.dolphinscheduler.api.dto.DefineUserDto;
import org.apache.dolphinscheduler.api.dto.TaskCountDto;
import org.apache.dolphinscheduler.api.dto.project.StatisticsStateRequest;
import org.apache.dolphinscheduler.api.utils.PageInfo;
import org.apache.dolphinscheduler.api.vo.TaskInstanceCountVO;
import org.apache.dolphinscheduler.api.vo.WorkflowDefinitionCountVO;
import org.apache.dolphinscheduler.api.vo.WorkflowInstanceCountVO;
import org.apache.dolphinscheduler.dao.entity.Command;
import org.apache.dolphinscheduler.dao.entity.ErrorCommand;
import org.apache.dolphinscheduler.dao.entity.User;
import java.util.List;
@ -117,4 +120,7 @@ public interface DataAnalysisService {
*/
TaskCountDto countOneTaskStates(User loginUser, Long taskCode);
PageInfo<Command> listPendingCommands(User loginUser, Long projectCode, Integer pageNo, Integer pageSize);
PageInfo<ErrorCommand> listErrorCommand(User loginUser, Long projectCode, Integer pageNo, Integer pageSize);
}

View File

@ -18,9 +18,9 @@
package org.apache.dolphinscheduler.api.service;
import org.apache.dolphinscheduler.common.model.Server;
import org.apache.dolphinscheduler.common.model.WorkerServerModel;
import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.dolphinscheduler.dao.plugin.api.monitor.DatabaseMetrics;
import org.apache.dolphinscheduler.registry.api.enums.RegistryNodeType;
import java.util.List;
@ -38,20 +38,10 @@ public interface MonitorService {
List<DatabaseMetrics> queryDatabaseState(User loginUser);
/**
* query master list
* query server list
*
* @param loginUser login user
* @return master information list
* @param nodeType RegistryNodeType
* @return server information list
*/
List<Server> queryMaster(User loginUser);
/**
* query worker list
*
* @param loginUser login user
* @return worker information list
*/
List<WorkerServerModel> queryWorker(User loginUser);
List<Server> getServerListFromRegistry(boolean isMaster);
List<Server> listServer(RegistryNodeType nodeType);
}

View File

@ -27,14 +27,18 @@ import org.apache.dolphinscheduler.api.enums.Status;
import org.apache.dolphinscheduler.api.exceptions.ServiceException;
import org.apache.dolphinscheduler.api.service.DataAnalysisService;
import org.apache.dolphinscheduler.api.service.ProjectService;
import org.apache.dolphinscheduler.api.utils.PageInfo;
import org.apache.dolphinscheduler.api.vo.TaskInstanceCountVO;
import org.apache.dolphinscheduler.api.vo.WorkflowDefinitionCountVO;
import org.apache.dolphinscheduler.api.vo.WorkflowInstanceCountVO;
import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.common.enums.AuthorizationType;
import org.apache.dolphinscheduler.common.enums.CommandType;
import org.apache.dolphinscheduler.common.enums.UserType;
import org.apache.dolphinscheduler.common.utils.DateUtils;
import org.apache.dolphinscheduler.dao.entity.Command;
import org.apache.dolphinscheduler.dao.entity.CommandCount;
import org.apache.dolphinscheduler.dao.entity.ErrorCommand;
import org.apache.dolphinscheduler.dao.entity.ExecuteStatusCount;
import org.apache.dolphinscheduler.dao.entity.ProcessDefinition;
import org.apache.dolphinscheduler.dao.entity.Project;
@ -71,6 +75,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
/**
@ -380,6 +386,66 @@ public class DataAnalysisServiceImpl extends BaseServiceImpl implements DataAnal
return new TaskCountDto(executeStatusCounts);
}
@Override
public PageInfo<Command> listPendingCommands(User loginUser, Long projectCode, Integer pageNo, Integer pageSize) {
Page<Command> page = new Page<>(pageNo, pageSize);
if (loginUser.getUserType().equals(UserType.ADMIN_USER)) {
IPage<Command> commandIPage = commandMapper.queryCommandPage(page);
return PageInfo.of(commandIPage);
}
List<Long> workflowDefinitionCodes = getAuthDefinitionCodes(loginUser, projectCode);
if (workflowDefinitionCodes.isEmpty()) {
return PageInfo.of(pageNo, pageSize);
}
IPage<Command> commandIPage =
commandMapper.queryCommandPageByIds(page, new ArrayList<>(workflowDefinitionCodes));
return PageInfo.of(commandIPage);
}
@Override
public PageInfo<ErrorCommand> listErrorCommand(User loginUser, Long projectCode, Integer pageNo, Integer pageSize) {
Page<ErrorCommand> page = new Page<>(pageNo, pageSize);
if (loginUser.getUserType().equals(UserType.ADMIN_USER)) {
IPage<ErrorCommand> commandIPage = errorCommandMapper.queryErrorCommandPage(page);
return PageInfo.of(commandIPage);
}
List<Long> workflowDefinitionCodes = getAuthDefinitionCodes(loginUser, projectCode);
if (workflowDefinitionCodes.isEmpty()) {
return PageInfo.of(pageNo, pageSize);
}
IPage<ErrorCommand> commandIPage =
errorCommandMapper.queryErrorCommandPageByIds(page, new ArrayList<>(workflowDefinitionCodes));
return PageInfo.of(commandIPage);
}
private List<Long> getAuthDefinitionCodes(User loginUser, Long projectCode) {
Set<Integer> projectIds = resourcePermissionCheckService
.userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, loginUser.getId(), log);
if (CollectionUtils.isEmpty(projectIds)) {
return Collections.emptyList();
}
List<Long> projectCodes = projectMapper.selectBatchIds(projectIds)
.stream()
.map(Project::getCode)
.collect(Collectors.toList());
if (projectCode != null) {
if (!projectCodes.contains(projectCode)) {
return Collections.emptyList();
}
projectCodes = Collections.singletonList(projectCode);
}
return processDefinitionMapper.queryDefinitionCodeListByProjectCodes(projectCodes);
}
/**
* statistics the process definition quantities of a certain person
* <p>

View File

@ -90,6 +90,7 @@ import org.apache.dolphinscheduler.extract.master.transportor.StreamingTaskTrigg
import org.apache.dolphinscheduler.extract.master.transportor.StreamingTaskTriggerResponse;
import org.apache.dolphinscheduler.extract.master.transportor.WorkflowInstanceStateChangeEvent;
import org.apache.dolphinscheduler.plugin.task.api.TaskConstants;
import org.apache.dolphinscheduler.registry.api.enums.RegistryNodeType;
import org.apache.dolphinscheduler.service.command.CommandService;
import org.apache.dolphinscheduler.service.cron.CronUtils;
import org.apache.dolphinscheduler.service.exceptions.CronParseException;
@ -289,7 +290,7 @@ public class ExecutorServiceImpl extends BaseServiceImpl implements ExecutorServ
private void checkMasterExists() {
// check master server exists
List<Server> masterServers = monitorService.getServerListFromRegistry(true);
List<Server> masterServers = monitorService.listServer(RegistryNodeType.MASTER);
// no master
if (masterServers.isEmpty()) {
@ -1142,7 +1143,7 @@ public class ExecutorServiceImpl extends BaseServiceImpl implements ExecutorServ
checkValidTenant(tenantCode);
checkMasterExists();
// todo dispatch improvement
List<Server> masterServerList = monitorService.getServerListFromRegistry(true);
List<Server> masterServerList = monitorService.listServer(RegistryNodeType.MASTER);
Server server = masterServerList.get(0);
StreamingTaskTriggerRequest taskExecuteStartMessage = new StreamingTaskTriggerRequest();

View File

@ -19,7 +19,6 @@ package org.apache.dolphinscheduler.api.service.impl;
import org.apache.dolphinscheduler.api.service.MonitorService;
import org.apache.dolphinscheduler.common.model.Server;
import org.apache.dolphinscheduler.common.model.WorkerServerModel;
import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.dolphinscheduler.dao.plugin.api.monitor.DatabaseMetrics;
import org.apache.dolphinscheduler.dao.plugin.api.monitor.DatabaseMonitor;
@ -27,7 +26,6 @@ import org.apache.dolphinscheduler.registry.api.RegistryClient;
import org.apache.dolphinscheduler.registry.api.enums.RegistryNodeType;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@ -35,7 +33,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* monitor service impl
@ -61,48 +58,8 @@ public class MonitorServiceImpl extends BaseServiceImpl implements MonitorServic
return Lists.newArrayList(databaseMonitor.getDatabaseMetrics());
}
/**
* query master list
*
* @param loginUser login user
* @return master information list
*/
@Override
public List<Server> queryMaster(User loginUser) {
return registryClient.getServerList(RegistryNodeType.MASTER);
public List<Server> listServer(RegistryNodeType nodeType) {
return registryClient.getServerList(nodeType);
}
/**
* query worker list
*
* @param loginUser login user
* @return worker information list
*/
@Override
public List<WorkerServerModel> queryWorker(User loginUser) {
return registryClient.getServerList(RegistryNodeType.WORKER)
.stream()
.map((Server server) -> {
WorkerServerModel model = new WorkerServerModel();
model.setId(server.getId());
model.setHost(server.getHost());
model.setPort(server.getPort());
model.setZkDirectories(Sets.newHashSet(server.getZkDirectory()));
model.setResInfo(server.getResInfo());
model.setCreateTime(server.getCreateTime());
model.setLastHeartbeatTime(server.getLastHeartbeatTime());
return model;
})
.collect(Collectors.toList());
}
@Override
public List<Server> getServerListFromRegistry(boolean isMaster) {
return isMaster
? registryClient.getServerList(RegistryNodeType.MASTER)
: registryClient.getServerList(RegistryNodeType.WORKER);
}
}

View File

@ -41,8 +41,7 @@ public class MonitorControllerTest extends AbstractControllerTest {
@Test
public void testListMaster() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/monitor/masters")
MvcResult mvcResult = mockMvc.perform(get("/monitor/MASTER")
.header(SESSION_ID, sessionId)
/* .param("type", ResourceType.FILE.name()) */)
.andExpect(status().isOk())
@ -59,7 +58,24 @@ public class MonitorControllerTest extends AbstractControllerTest {
@Test
public void testListWorker() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/monitor/workers")
MvcResult mvcResult = mockMvc.perform(get("/monitor/WORKER")
.header(SESSION_ID, sessionId)
/* .param("type", ResourceType.FILE.name()) */)
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class);
result.getCode().equals(Status.SUCCESS.getCode());
Assertions.assertEquals(Status.SUCCESS.getCode(), result.getCode().intValue());
logger.info(mvcResult.getResponse().getContentAsString());
}
@Test
public void testListAlert() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/monitor/ALERT_SERVER")
.header(SESSION_ID, sessionId)
/* .param("type", ResourceType.FILE.name()) */)
.andExpect(status().isOk())
@ -76,8 +92,7 @@ public class MonitorControllerTest extends AbstractControllerTest {
@Test
public void testQueryDatabaseState() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/monitor/databases")
.header(SESSION_ID, sessionId)
/* .param("type", ResourceType.FILE.name()) */)
.header(SESSION_ID, sessionId))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();

View File

@ -67,6 +67,7 @@ import org.apache.dolphinscheduler.dao.mapper.TaskDefinitionMapper;
import org.apache.dolphinscheduler.dao.mapper.TaskGroupQueueMapper;
import org.apache.dolphinscheduler.dao.mapper.TenantMapper;
import org.apache.dolphinscheduler.dao.repository.ProcessInstanceDao;
import org.apache.dolphinscheduler.registry.api.enums.RegistryNodeType;
import org.apache.dolphinscheduler.service.command.CommandService;
import org.apache.dolphinscheduler.service.process.ProcessService;
import org.apache.dolphinscheduler.service.process.TriggerRelationService;
@ -242,7 +243,7 @@ public class ExecuteFunctionServiceTest {
Mockito.when(processService.getTenantForProcess(tenantCode, userId)).thenReturn(tenantCode);
doReturn(1).when(commandService).createCommand(argThat(c -> c.getId() == null));
doReturn(0).when(commandService).createCommand(argThat(c -> c.getId() != null));
Mockito.when(monitorService.getServerListFromRegistry(true)).thenReturn(getMasterServersList());
Mockito.when(monitorService.listServer(RegistryNodeType.MASTER)).thenReturn(getMasterServersList());
Mockito.when(processService.findProcessInstanceDetailById(processInstanceId))
.thenReturn(Optional.ofNullable(processInstance));
Mockito.when(processService.findProcessDefinition(1L, 1)).thenReturn(this.processDefinition);
@ -498,7 +499,7 @@ public class ExecuteFunctionServiceTest {
@Test
public void testNoMasterServers() {
Mockito.when(monitorService.getServerListFromRegistry(true)).thenReturn(new ArrayList<>());
Mockito.when(monitorService.listServer(RegistryNodeType.MASTER)).thenReturn(new ArrayList<>());
Assertions.assertThrows(ServiceException.class, () -> executorService.execProcessInstance(
loginUser,

View File

@ -98,13 +98,13 @@ public class MonitorServiceTest {
public void testQueryMaster() {
mockPermissionCheck(ApiFuncIdentificationConstant.MONITOR_MASTER_VIEW, true);
Mockito.when(registryClient.getServerList(RegistryNodeType.MASTER)).thenReturn(getServerList());
assertDoesNotThrow(() -> monitorService.queryMaster(user));
assertDoesNotThrow(() -> monitorService.listServer(RegistryNodeType.MASTER));
}
@Test
public void testQueryWorker() {
Mockito.when(registryClient.getServerList(RegistryNodeType.WORKER)).thenReturn(getServerList());
AssertionsHelper.assertDoesNotThrow(() -> monitorService.queryWorker(user));
AssertionsHelper.assertDoesNotThrow(() -> monitorService.listServer(RegistryNodeType.WORKER));
}
@Test

View File

@ -26,6 +26,8 @@ import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
/**
* command mapper interface
@ -50,7 +52,7 @@ public interface CommandMapper extends BaseMapper<Command> {
*
* @return
*/
List<Command> queryCommandPage(@Param("limit") int limit, @Param("offset") int offset);
IPage<Command> queryCommandPage(Page<Command> page);
List<Command> queryCommandByIdSlot(@Param("currentSlotIndex") int currentSlotIndex,
@Param("totalSlot") int totalSlot,
@ -58,4 +60,7 @@ public interface CommandMapper extends BaseMapper<Command> {
@Param("fetchNumber") int fetchNum);
void deleteByWorkflowInstanceIds(@Param("workflowInstanceIds") List<Integer> workflowInstanceIds);
IPage<Command> queryCommandPageByIds(Page<Command> page,
@Param("workflowDefinitionCodes") List<Long> workflowDefinitionCodes);
}

View File

@ -26,6 +26,8 @@ import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
/**
* error command mapper interface
@ -43,4 +45,9 @@ public interface ErrorCommandMapper extends BaseMapper<ErrorCommand> {
@Param("startTime") Date startTime,
@Param("endTime") Date endTime,
@Param("projectCodes") List<Long> projectCodes);
IPage<ErrorCommand> queryErrorCommandPage(Page<ErrorCommand> page);
IPage<ErrorCommand> queryErrorCommandPageByIds(Page<ErrorCommand> page,
@Param("workflowDefinitionCodes") List<Long> workflowDefinitionCodes);
}

View File

@ -188,4 +188,5 @@ public interface ProcessDefinitionMapper extends BaseMapper<ProcessDefinition> {
* @return project ids list
*/
List<Integer> listProjectIds();
List<Long> queryDefinitionCodeListByProjectCodes(@Param("projectCodes") List<Long> projectCodes);
}

View File

@ -34,10 +34,22 @@
group by cmd.command_type
</select>
<select id="queryCommandPage" resultType="org.apache.dolphinscheduler.dao.entity.Command">
select *
select
*
from t_ds_command
where 1 = 1
order by process_instance_priority, id asc
</select>
<select id="queryCommandPageByIds" resultType="org.apache.dolphinscheduler.dao.entity.Command">
select
*
from t_ds_command
where process_definition_code in
<foreach item="id" index="index" collection="workflowDefinitionCodes" open="(" separator="," close=")">
#{id}
</foreach>
order by process_instance_priority, id asc
limit #{limit} offset #{offset}
</select>
<select id="queryCommandByIdSlot" resultType="org.apache.dolphinscheduler.dao.entity.Command">

View File

@ -33,4 +33,23 @@
</if>
group by cmd.command_type
</select>
<select id="queryErrorCommandPage" resultType="org.apache.dolphinscheduler.dao.entity.ErrorCommand">
select
*
from t_ds_error_command
order by process_instance_priority, id asc
</select>
<select id="queryErrorCommandPageByIds" resultType="org.apache.dolphinscheduler.dao.entity.ErrorCommand">
select
*
from t_ds_error_command
where process_definition_code in
<foreach item="id" index="index" collection="workflowDefinitionCodes" open="(" separator="," close=")">
#{id}
</foreach>
order by process_instance_priority, id asc
</select>
</mapper>

View File

@ -208,4 +208,14 @@
SELECT DISTINCT(id) as project_id
FROM t_ds_project
</select>
<select id="queryDefinitionCodeListByProjectCodes" resultType="java.lang.Long">
select
code
from t_ds_process_definition
where project_code in
<foreach collection="projectCodes" index="index" item="i" open="(" separator="," close=")">
#{i}
</foreach>
</select>
</mapper>

View File

@ -42,6 +42,7 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Lists;
/**
@ -135,7 +136,7 @@ public class CommandMapperTest extends BaseDaoTest {
createCommand(CommandType.START_PROCESS, processDefinition.getCode());
List<Command> actualCommand = commandMapper.queryCommandPage(1, 0);
List<Command> actualCommand = commandMapper.selectList(new QueryWrapper<>());
Assertions.assertNotNull(actualCommand);
}

View File

@ -37,5 +37,4 @@ public enum RegistryNodeType {
private final String name;
private final String registryPath;
}

View File

@ -255,6 +255,10 @@ export function useDataList() {
label: t('menu.worker'),
key: '/monitor/worker'
},
{
label: t('menu.alert_server'),
key: '/monitor/alert_server'
},
{
label: t('menu.db'),
key: '/monitor/db'

View File

@ -40,6 +40,7 @@ export default {
service_manage: 'Service Manage',
master: 'Master',
worker: 'Worker',
alert_server: 'Alert Server',
db: 'DB',
statistical_manage: 'Statistical Manage',
statistics: 'Statistics',

View File

@ -35,6 +35,7 @@ export default {
memory_usage: 'Memory Usage',
disk_available: 'Disk Available',
load_average: 'Load Average',
thread_pool_usage: 'Thread Pool Usage',
create_time: 'Create Time',
last_heartbeat_time: 'Last Heartbeat Time',
directory_detail: 'Directory Detail',
@ -44,6 +45,11 @@ export default {
worker_no_data_result_desc:
'Currently, there are no worker nodes exist, please create a worker node and refresh this page'
},
alert_server: {
alert_server_no_data_result_title: 'No Alert Server Nodes Exist',
alert_server_no_data_result_desc:
'Currently, there are no alert server nodes exist, please create a alert server node and refresh this page'
},
db: {
health_state: 'Health State',
max_connections: 'Max Connections',

View File

@ -41,6 +41,7 @@ export default {
service_manage: '服务管理',
master: 'Master',
worker: 'Worker',
alert_server: 'Alert Server',
db: 'DB',
statistical_manage: '统计管理',
statistics: 'Statistics',

View File

@ -35,6 +35,7 @@ export default {
memory_usage: '内存使用量',
disk_available: '磁盘可用容量',
load_average: '平均负载量',
thread_pool_usage: '线程池使用量',
create_time: '创建时间',
last_heartbeat_time: '最后心跳时间',
directory_detail: '目录详情',
@ -44,6 +45,11 @@ export default {
worker_no_data_result_desc:
'目前没有任何Worker节点请先创建Worker节点再访问该页面'
},
alert_server: {
alert_server_no_data_result_title: 'Alert Server节点不存在',
alert_server_no_data_result_desc:
'目前没有任何Alert Server节点请先创建Alert Server节点再访问该页面'
},
db: {
health_state: '健康状态',
max_connections: '最大连接数',

View File

@ -51,6 +51,17 @@ export default {
auth: []
}
},
{
path: '/monitor/alert_server',
name: 'servers-alert-server',
component: components['monitor-servers-alert_server'],
meta: {
title: '服务管理-Alert Server',
activeMenu: 'monitor',
showSide: true,
auth: []
}
},
{
path: '/monitor/db',
name: 'servers-db',

View File

@ -16,6 +16,7 @@
*/
import { axios } from '@/service/service'
import type { ServerNodeType } from './types'
export function queryDatabaseState(): any {
return axios({
@ -37,3 +38,10 @@ export function listWorker(): any {
method: 'get'
})
}
export function listMonitorServerNode(nodeType: ServerNodeType): any {
return axios({
url: `/monitor/${nodeType}`,
method: 'get'
})
}

View File

@ -25,7 +25,9 @@ interface DatabaseRes {
date: string
}
interface MasterNode {
type ServerNodeType = 'MASTER' | 'WORKER' | 'ALERT_SERVER'
interface ServerNode {
id: number
host: string
port: number
@ -35,14 +37,23 @@ interface MasterNode {
lastHeartbeatTime: string
}
interface WorkerNode {
id: number
host: string
port: number
zkDirectories: Array<string>
resInfo: string
createTime: string
lastHeartbeatTime: string
interface MasterNode extends ServerNode {
serverStatus?: 'NORMAL' | 'BUZY'
}
export { DatabaseRes, MasterNode, WorkerNode }
interface WorkerNode extends ServerNode {
serverStatus?: 'NORMAL' | 'BUZY'
workerHostWeight?: number
threadPoolUsage?: number
}
interface AlertNode extends MasterNode {}
export {
DatabaseRes,
MasterNode,
WorkerNode,
ServerNodeType,
ServerNode,
AlertNode
}

View File

@ -16,7 +16,7 @@
*/
import { axios } from '@/service/service'
import { CodeReq, StateReq } from './types'
import { ListReq, CodeReq, StateReq } from './types'
export function countCommandState(): any {
return axios({
@ -55,3 +55,19 @@ export function countTaskState(params: StateReq): any {
params
})
}
export function queryListCommandPaging(params: ListReq): any {
return axios({
url: '/projects/analysis/listCommand',
method: 'get',
params
})
}
export function queryListErrorCommandPaging(params: ListReq): any {
return axios({
url: '/projects/analysis/listErrorCommand',
method: 'get',
params
})
}

View File

@ -66,6 +66,12 @@ interface CommandStateRes {
commandState: string
}
interface ListReq {
pageNo: number
pageSize: number
searchVal?: string
}
export {
CodeReq,
StateReq,
@ -73,5 +79,6 @@ export {
WorkflowInstanceCountVo,
TaskInstanceCountVo,
TaskQueueRes,
CommandStateRes
CommandStateRes,
ListReq
}

View File

@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@mixin base {
font-size: 5vw;
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.card {
@include base;
}
.load-average {
@include base;
color: var(--n-color-target);
}
.link-btn {
color: var(--n-color-target);
cursor: pointer;
&:hover {
color: var(--n-color-target);
opacity: 0.8;
}
}

View File

@ -0,0 +1,189 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, onMounted, ref, toRefs } from 'vue'
import { NGrid, NGi, NCard, NNumberAnimation, NSpace, NTag } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { useServerNode } from './use-server-node'
import styles from './index.module.scss'
import Card from '@/components/card'
import Result from '@/components/result'
import Gauge from '@/components/chart/modules/Gauge'
import NodeModal from './node-modal'
import type { Ref } from 'vue'
import type { RowData } from 'naive-ui/es/data-table/src/interface'
import type { AlertNode } from '@/service/modules/monitor/types'
import { capitalize } from 'lodash'
const alertServer = defineComponent({
name: 'alertServer',
setup() {
const showModalRef = ref(false)
const { t } = useI18n()
const { variables, getTableData } = useServerNode()
const zkDirectoryRef: Ref<Array<RowData>> = ref([])
const clickDetails = (zkDirectories: string) => {
zkDirectoryRef.value = [{ directory: zkDirectories, index: 1 }]
showModalRef.value = true
}
const onConfirmModal = () => {
showModalRef.value = false
}
onMounted(() => {
getTableData()
})
return {
t,
...toRefs(variables),
clickDetails,
onConfirmModal,
showModalRef,
zkDirectoryRef
}
},
render() {
const { t, clickDetails, onConfirmModal, showModalRef, zkDirectoryRef } =
this
const renderNodeServerStatusTag = (item: AlertNode) => {
const serverStatus = JSON.parse(item.resInfo)?.serverStatus
if (!serverStatus) return ''
return (
<NTag type={serverStatus === 'NORMAL' ? 'info' : 'warning'}>
{capitalize(serverStatus)}
</NTag>
)
}
return this.data.length < 1 ? (
<Result
title={t('monitor.alert_server.alert_server_no_data_result_title')}
description={t('monitor.alert_server.alert_server_no_data_result_desc')}
status={'info'}
size={'medium'}
/>
) : (
<>
<NSpace vertical size={25}>
{this.data.map((item: AlertNode) => {
return (
<NSpace vertical>
<NCard>
<NSpace
justify='space-between'
style={{
'line-height': '28px'
}}
>
<NSpace>
{renderNodeServerStatusTag(item)}
<span>{`${t('monitor.master.host')}: ${
item ? item.host : ' - '
}`}</span>
<span
class={styles['link-btn']}
onClick={() => clickDetails(item.zkDirectory)}
>
{t('monitor.master.directory_detail')}
</span>
</NSpace>
<NSpace>
<span>{`${t('monitor.master.create_time')}: ${
item ? item.createTime : ' - '
}`}</span>
<span>{`${t('monitor.master.last_heartbeat_time')}: ${
item ? item.lastHeartbeatTime : ' - '
}`}</span>
</NSpace>
</NSpace>
</NCard>
<NGrid x-gap='12' cols='4'>
<NGi>
<Card title={t('monitor.master.cpu_usage')}>
<div class={styles.card}>
{item && (
<Gauge
data={(
JSON.parse(item.resInfo).cpuUsage * 100
).toFixed(2)}
/>
)}
</div>
</Card>
</NGi>
<NGi>
<Card title={t('monitor.master.memory_usage')}>
<div class={styles.card}>
{item && (
<Gauge
data={(
JSON.parse(item.resInfo).memoryUsage * 100
).toFixed(2)}
/>
)}
</div>
</Card>
</NGi>
<NGi>
<Card title={t('monitor.master.disk_available')}>
<div class={[styles.card, styles['load-average']]}>
{item && (
<NNumberAnimation
precision={2}
from={0}
to={JSON.parse(item.resInfo).diskAvailable}
/>
)}
</div>
</Card>
</NGi>
<NGi>
<Card title={t('monitor.master.load_average')}>
<div class={[styles.card, styles['load-average']]}>
{item && (
<NNumberAnimation
precision={2}
from={0}
to={JSON.parse(item.resInfo).loadAverage}
/>
)}
</div>
</Card>
</NGi>
</NGrid>
</NSpace>
)
})}
</NSpace>
<NodeModal
showModal={showModalRef}
data={zkDirectoryRef}
onConfirmModal={onConfirmModal}
></NodeModal>
</>
)
}
})
export default alertServer

View File

@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import { NDataTable } from 'naive-ui'
import Modal from '@/components/modal'
import type { PropType } from 'vue'
import type {
RowData,
TableColumns
} from 'naive-ui/es/data-table/src/interface'
const props = {
showModal: {
type: Boolean as PropType<boolean>,
default: false
},
data: {
type: Array as PropType<Array<RowData>>,
default: () => []
}
}
const NodeModal = defineComponent({
props,
emits: ['confirmModal'],
setup(props, ctx) {
const { t } = useI18n()
const columnsRef: TableColumns<any> = [
{ title: '#', key: 'index', render: (row, index) => index + 1 },
{ title: t('monitor.master.directory'), key: 'directory' }
]
const onConfirm = () => {
ctx.emit('confirmModal')
}
return { t, columnsRef, onConfirm }
},
render() {
const { t, columnsRef, onConfirm } = this
return (
<Modal
title={t('monitor.master.directory_detail')}
show={this.showModal}
cancelShow={false}
onConfirm={onConfirm}
>
{{
default: () => (
<NDataTable
columns={columnsRef}
data={this.data}
striped
size={'small'}
/>
)
}}
</Modal>
)
}
})
export default NodeModal

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { reactive } from 'vue'
import { useAsyncState } from '@vueuse/core'
import { listMonitorServerNode } from '@/service/modules/monitor'
import type { AlertNode } from '@/service/modules/monitor/types'
export function useServerNode() {
const variables = reactive({
data: []
})
const getTableData = () => {
const { state } = useAsyncState(
listMonitorServerNode('ALERT_SERVER').then((res: Array<AlertNode>) => {
variables.data = res as any
}),
[]
)
return state
}
return { variables, getTableData }
}

View File

@ -16,7 +16,7 @@
*/
import { defineComponent, onMounted, ref, toRefs } from 'vue'
import { NGrid, NGi, NCard, NNumberAnimation, NSpace } from 'naive-ui'
import { NGrid, NGi, NCard, NNumberAnimation, NSpace, NTag } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { useMaster } from './use-master'
import styles from './index.module.scss'
@ -27,6 +27,7 @@ import MasterModal from './master-modal'
import type { Ref } from 'vue'
import type { RowData } from 'naive-ui/es/data-table/src/interface'
import type { MasterNode } from '@/service/modules/monitor/types'
import { capitalize } from 'lodash'
const master = defineComponent({
name: 'master',
@ -62,6 +63,18 @@ const master = defineComponent({
const { t, clickDetails, onConfirmModal, showModalRef, zkDirectoryRef } =
this
const renderNodeServerStatusTag = (item: MasterNode) => {
const serverStatus = JSON.parse(item.resInfo)?.serverStatus
if (!serverStatus) return ''
return (
<NTag type={serverStatus === 'NORMAL' ? 'info' : 'warning'}>
{capitalize(serverStatus)}
</NTag>
)
}
return this.data.length < 1 ? (
<Result
title={t('monitor.master.master_no_data_result_title')}
@ -76,8 +89,15 @@ const master = defineComponent({
return (
<NSpace vertical>
<NCard>
<NSpace justify='space-between'>
<NSpace
justify='space-between'
style={{
'line-height': '28px'
}}
>
<NSpace>
{renderNodeServerStatusTag(item)}
<span>{`${t('monitor.master.host')}: ${
item ? item.host : ' - '
}`}</span>

View File

@ -17,7 +17,7 @@
import { reactive } from 'vue'
import { useAsyncState } from '@vueuse/core'
import { listMaster } from '@/service/modules/monitor'
import { listMonitorServerNode } from '@/service/modules/monitor'
import type { MasterNode } from '@/service/modules/monitor/types'
export function useMaster() {
@ -26,7 +26,7 @@ export function useMaster() {
})
const getTableMaster = () => {
const { state } = useAsyncState(
listMaster().then((res: Array<MasterNode>) => {
listMonitorServerNode('MASTER').then((res: Array<MasterNode>) => {
variables.data = res as any
}),
[]

View File

@ -16,7 +16,7 @@
*/
import { defineComponent, onMounted, ref, toRefs } from 'vue'
import { NGrid, NGi, NCard, NNumberAnimation, NSpace } from 'naive-ui'
import { NGrid, NGi, NCard, NNumberAnimation, NSpace, NTag } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { useWorker } from './use-worker'
import styles from './index.module.scss'
@ -27,6 +27,7 @@ import WorkerModal from './worker-modal'
import type { Ref } from 'vue'
import type { RowData } from 'naive-ui/es/data-table/src/interface'
import type { WorkerNode } from '@/service/modules/monitor/types'
import { capitalize } from 'lodash'
const worker = defineComponent({
name: 'worker',
@ -36,12 +37,8 @@ const worker = defineComponent({
const { variables, getTableWorker } = useWorker()
const zkDirectoryRef: Ref<Array<RowData>> = ref([])
const clickDetails = (zkDirectories: Array<string>) => {
zkDirectoryRef.value = zkDirectories.map((zkItem) => {
return {
directory: zkItem
}
})
const clickDetails = (zkDirectories: string) => {
zkDirectoryRef.value = [{ directory: zkDirectories, index: 1 }]
showModalRef.value = true
}
@ -66,6 +63,18 @@ const worker = defineComponent({
const { t, clickDetails, onConfirmModal, showModalRef, zkDirectoryRef } =
this
const renderNodeServerStatusTag = (item: WorkerNode) => {
const serverStatus = JSON.parse(item.resInfo)?.serverStatus
if (!serverStatus) return ''
return (
<NTag type={serverStatus === 'NORMAL' ? 'info' : 'warning'}>
{capitalize(serverStatus)}
</NTag>
)
}
return this.data.length < 1 ? (
<Result
title={t('monitor.worker.worker_no_data_result_title')}
@ -80,14 +89,21 @@ const worker = defineComponent({
return (
<NSpace vertical>
<NCard>
<NSpace justify='space-between'>
<NSpace
justify='space-between'
style={{
'line-height': '28px'
}}
>
<NSpace>
{renderNodeServerStatusTag(item)}
<span>{`${t('monitor.worker.host')}: ${
item ? item.host : ' - '
}`}</span>
<span
class={styles['link-btn']}
onClick={() => clickDetails(item.zkDirectories)}
onClick={() => clickDetails(item.zkDirectory)}
>
{t('monitor.worker.directory_detail')}
</span>
@ -102,7 +118,7 @@ const worker = defineComponent({
</NSpace>
</NSpace>
</NCard>
<NGrid x-gap='12' cols='4'>
<NGrid x-gap='12' cols='5'>
<NGi>
<Card title={t('monitor.worker.cpu_usage')}>
<div class={styles.card}>
@ -155,6 +171,33 @@ const worker = defineComponent({
</div>
</Card>
</NGi>
<NGi>
<Card title={t('monitor.worker.thread_pool_usage')}>
<div
class={[styles.card, styles['load-average']]}
style={{
'font-size': '90px'
}}
>
{item && (
<>
<NNumberAnimation
precision={0}
from={0}
to={JSON.parse(item.resInfo).threadPoolUsage}
/>
/
<NNumberAnimation
precision={0}
from={0}
to={JSON.parse(item.resInfo).workerHostWeight}
/>
</>
)}
</div>
</Card>
</NGi>
</NGrid>
</NSpace>
)

View File

@ -17,7 +17,7 @@
import { reactive } from 'vue'
import { useAsyncState } from '@vueuse/core'
import { listWorker } from '@/service/modules/monitor'
import { listMonitorServerNode } from '@/service/modules/monitor'
import type { WorkerNode } from '@/service/modules/monitor/types'
export function useWorker() {
@ -27,7 +27,7 @@ export function useWorker() {
const getTableWorker = () => {
const { state } = useAsyncState(
listWorker().then((res: Array<WorkerNode>) => {
listMonitorServerNode('WORKER').then((res: Array<WorkerNode>) => {
variables.data = res as any
}),
[]

View File

@ -15,58 +15,44 @@
* limitations under the License.
*/
import { defineComponent, ref } from 'vue'
import { NGrid, NGi, NNumberAnimation } from 'naive-ui'
import { useStatistics } from './use-statistics'
import { useI18n } from 'vue-i18n'
import Card from '@/components/card'
import styles from './index.module.scss'
import { defineComponent } from 'vue'
import { NGrid, NGi, NTabs, NTabPane, NCard } from 'naive-ui'
import ListCommandTable from './list-command-table'
import ListErrorCommandTable from './list-error-command-table'
const statistics = defineComponent({
name: 'statistics',
setup() {
const { t } = useI18n()
const { getStatistics } = useStatistics()
const statisticsRef = ref(getStatistics())
setup() {},
return { t, statisticsRef }
},
render() {
const { t, statisticsRef } = this
return (
<NGrid x-gap='12' y-gap='8' cols='2' responsive='screen'>
<NGrid x-gap='12' y-gap='8' cols='1' responsive='screen'>
<NGi>
<Card
title={t(
'monitor.statistics.command_number_of_waiting_for_running'
)}
>
<div class={styles.connections}>
{statisticsRef.command.length > 0 && (
<NNumberAnimation
from={0}
to={statisticsRef.command
.map((item) => item.normalCount)
.reduce((prev, next) => prev + next)}
/>
)}
</div>
</Card>
</NGi>
<NGi>
<Card title={t('monitor.statistics.failure_command_number')}>
<div class={styles.connections}>
{statisticsRef.command.length > 0 && (
<NNumberAnimation
from={0}
to={statisticsRef.command
.map((item) => item.errorCount)
.reduce((prev, next) => prev + next)}
/>
)}
</div>
</Card>
<NCard>
<NTabs
type='card'
animated
pane-style={{
padding: '0px',
border: 'none'
}}
>
<NTabPane
name='command'
tab='Command Statistics List'
display-directiv='show'
>
<ListCommandTable></ListCommandTable>
</NTabPane>
<NTabPane
name='command-error'
tab='Failure Command Statistics List'
display-directiv='show'
>
<ListErrorCommandTable></ListErrorCommandTable>
</NTabPane>
</NTabs>
</NCard>
</NGi>
</NGrid>
)

View File

@ -0,0 +1,233 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
defineComponent,
onMounted,
toRefs,
watch,
reactive,
ref,
h
} from 'vue'
import { NSpace, NDataTable, NPagination } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Card from '@/components/card'
import {
COLUMN_WIDTH_CONFIG,
calculateTableWidth,
DefaultTableWidth
} from '@/common/column-width-config'
import { useAsyncState } from '@vueuse/core'
import { queryListCommandPaging } from '@/service/modules/projects-analysis'
const ListCommandTable = defineComponent({
name: 'list-command-table',
setup() {
const { t } = useI18n()
const variables = reactive({
columns: [],
tableWidth: DefaultTableWidth,
tableData: [],
page: ref(1),
pageSize: ref(10),
userName: ref(null),
totalPage: ref(1),
loadingRef: ref(false)
})
const createColumns = (variables: any) => {
variables.columns = [
{
title: 'ID',
key: 'id',
...COLUMN_WIDTH_CONFIG['index']
},
{
title: 'Command Type',
key: 'commandType',
...COLUMN_WIDTH_CONFIG['userName']
},
{
title: 'Command Param',
key: 'commandParam',
...COLUMN_WIDTH_CONFIG['linkName']
},
{
title: 'Task Info',
key: 'id',
width: 300,
render: (row: any) => {
return h('div', [
`Definition Code${row.processDefinitionCode} `,
h('br'),
`Definition Version${row.processDefinitionVersion} `,
h('br'),
`Instance Id${row.processInstanceId} `,
h('br'),
`Instance Priority${row.processInstancePriority} `
])
}
},
{
title: 'Task Params',
key: 'id',
width: 300,
render: (row: any) => {
return h('div', [
`DryRun${row.dryRun} `,
h('br'),
`Environment Code${row.environmentCode} `,
h('br'),
`Failure Strategy${row.failureStrategy} `,
h('br'),
`Task Depend Type${row.taskDependType} `
])
}
},
{
title: 'Worker Info',
key: 'id',
width: 220,
render: (row: any) => {
return h('div', [
`Worker Group${row.workerGroup} `,
h('br'),
`Tenant Code${row.tenantCode} `,
h('br'),
`Test Flag${row.testFlag} `
])
}
},
{
title: 'Warning Info',
key: 'id',
width: 200,
render: (row: any) => {
return h('div', [
`Warning Group Id${row.warningGroupId} `,
h('br'),
`Warning Type${row.warningType} `
])
}
},
{
title: 'Executor Id',
key: 'executorId',
...COLUMN_WIDTH_CONFIG['type']
},
{
title: 'Time',
key: 'startTime',
width: 280,
render: (row: any) => {
return h('div', [
`Start Time${row.startTime} `,
h('br'),
`Update Time${row.updateTime} `,
h('br'),
`Schedule Time${row.scheduleTime} `
])
}
}
]
if (variables.tableWidth) {
variables.tableWidth = calculateTableWidth(variables.columns)
}
}
const getTableData = () => {
if (variables.loadingRef) return
variables.loadingRef = true
const data = {
pageSize: variables.pageSize,
pageNo: variables.page
}
const { state } = useAsyncState(
queryListCommandPaging(data).then((res: any) => {
variables.totalPage = res.totalPage
variables.tableData = res.totalList
variables.loadingRef = false
}),
{}
)
return state
}
const onUpdatePageSize = () => {
variables.page = 1
getTableData()
}
onMounted(() => {
createColumns(variables)
getTableData()
})
watch(useI18n().locale, () => {
createColumns(variables)
})
return {
t,
...toRefs(variables),
getTableData,
onUpdatePageSize
}
},
render() {
const { getTableData, onUpdatePageSize, loadingRef } = this
return (
<NSpace vertical>
<Card>
<NSpace vertical>
<NDataTable
size={'small'}
loading={loadingRef}
columns={this.columns}
scrollX={this.tableWidth}
data={this.tableData}
/>
<NSpace justify='center'>
<NPagination
v-model:page={this.page}
v-model:page-size={this.pageSize}
page-count={this.totalPage}
show-size-picker
page-sizes={[10, 30, 50]}
show-quick-jumper
onUpdatePage={getTableData}
onUpdatePageSize={onUpdatePageSize}
/>
</NSpace>
</NSpace>
</Card>
</NSpace>
)
}
})
export default ListCommandTable

View File

@ -0,0 +1,237 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
defineComponent,
onMounted,
toRefs,
watch,
reactive,
ref,
h
} from 'vue'
import { NSpace, NDataTable, NPagination } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Card from '@/components/card'
import {
COLUMN_WIDTH_CONFIG,
calculateTableWidth,
DefaultTableWidth
} from '@/common/column-width-config'
import { useAsyncState } from '@vueuse/core'
import { queryListErrorCommandPaging } from '@/service/modules/projects-analysis'
const ListErrorCommandTable = defineComponent({
name: 'list-error-command-table',
setup() {
const { t } = useI18n()
const variables = reactive({
columns: [],
tableWidth: DefaultTableWidth,
tableData: [],
page: ref(1),
pageSize: ref(10),
userName: ref(null),
totalPage: ref(1),
loadingRef: ref(false)
})
const createColumns = (variables: any) => {
variables.columns = [
{
title: 'ID',
key: 'id',
...COLUMN_WIDTH_CONFIG['index']
},
{
title: 'Command Type',
key: 'commandType',
...COLUMN_WIDTH_CONFIG['userName']
},
{
title: 'Command Param',
key: 'commandParam',
...COLUMN_WIDTH_CONFIG['linkName']
},
{
title: 'Task Info',
key: 'id',
width: 300,
render: (row: any) => {
return h('div', [
`Definition Code${row.processDefinitionCode} `,
h('br'),
`Definition Version${row.processDefinitionVersion} `,
h('br'),
`Instance Id${row.processInstanceId} `,
h('br'),
`Instance Priority${row.processInstancePriority} `
])
}
},
{
title: 'Task Params',
key: 'id',
width: 300,
render: (row: any) => {
return h('div', [
`DryRun${row.dryRun} `,
h('br'),
`Environment Code${row.environmentCode} `,
h('br'),
`Failure Strategy${row.failureStrategy} `,
h('br'),
`Task Depend Type${row.taskDependType} `
])
}
},
{
title: 'Worker Info',
key: 'id',
width: 220,
render: (row: any) => {
return h('div', [
`Worker Group${row.workerGroup} `,
h('br'),
`Tenant Code${row.tenantCode} `,
h('br'),
`Test Flag${row.testFlag} `
])
}
},
{
title: 'Warning Info',
key: 'id',
width: 200,
render: (row: any) => {
return h('div', [
`Warning Group Id${row.warningGroupId} `,
h('br'),
`Warning Type${row.warningType} `
])
}
},
{
title: 'Message',
key: 'message',
...COLUMN_WIDTH_CONFIG['linkName']
},
{
title: 'Executor Id',
key: 'executorId',
...COLUMN_WIDTH_CONFIG['type']
},
{
title: 'Time',
key: 'startTime',
width: 280,
render: (row: any) => {
return h('div', [
`Start Time${row.startTime || '-'} `,
h('br'),
`Update Time${row.updateTime || '-'} `,
h('br'),
`Schedule Time${row.scheduleTime || '-'} `
])
}
}
]
if (variables.tableWidth) {
variables.tableWidth = calculateTableWidth(variables.columns)
}
}
const getTableData = () => {
if (variables.loadingRef) return
variables.loadingRef = true
const data = {
pageSize: variables.pageSize,
pageNo: variables.page
}
const { state } = useAsyncState(
queryListErrorCommandPaging(data).then((res: any) => {
variables.totalPage = res.totalPage
variables.tableData = res.totalList
variables.loadingRef = false
}),
{}
)
return state
}
const onUpdatePageSize = () => {
variables.page = 1
getTableData()
}
onMounted(() => {
createColumns(variables)
getTableData()
})
watch(useI18n().locale, () => {
createColumns(variables)
})
return {
t,
...toRefs(variables),
getTableData,
onUpdatePageSize
}
},
render() {
const { getTableData, onUpdatePageSize, loadingRef } = this
return (
<NSpace vertical>
<Card>
<NSpace vertical>
<NDataTable
loading={loadingRef}
columns={this.columns}
scrollX={this.tableWidth}
data={this.tableData}
/>
<NSpace justify='center'>
<NPagination
v-model:page={this.page}
v-model:page-size={this.pageSize}
page-count={this.totalPage}
show-size-picker
page-sizes={[10, 30, 50]}
show-quick-jumper
onUpdatePage={getTableData}
onUpdatePageSize={onUpdatePageSize}
/>
</NSpace>
</NSpace>
</Card>
</NSpace>
)
}
})
export default ListErrorCommandTable