Unverified Commit 9e87c606 authored by mydq's avatar mydq Committed by GitHub

[Feature-789][admin,web] One click online and offline operation (#788)

* one click operating

* select save point restart

* onclick-operating

* onclick-operating

* feat:上下线状态明细批量执行开发

* feat:上下线状态明细批量执行开发

* import modify

* application.yml modify

* protocol header add

* doc add

* doc add

* mybatis join remove

* mybatis join remove

* mybatis join remove
parent 9887ca14
......@@ -49,6 +49,10 @@ public class SpringContextUtils implements ApplicationContextAware {
return applicationContext.getBean(name, requiredType);
}
public static <T> T getBeanByClass(Class<T> tClass) {
return applicationContext.getBean(tClass);
}
public static boolean containsBean(String name) {
return applicationContext.containsBean(name);
}
......
......@@ -24,8 +24,12 @@ import com.dlink.common.result.ProTableResult;
import com.dlink.common.result.Result;
import com.dlink.dto.TaskRollbackVersionDTO;
import com.dlink.job.JobResult;
import com.dlink.model.JobLifeCycle;
import com.dlink.model.JobStatus;
import com.dlink.model.Task;
import com.dlink.model.TaskOperatingSavepointSelect;
import com.dlink.service.TaskService;
import com.dlink.utils.TaskOneClickOperatingUtil;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -33,6 +37,8 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
......@@ -254,5 +260,75 @@ public class TaskController {
return taskService.uploadTaskJson(file);
}
/**
* 查询所有目录
*
* @return
*/
@GetMapping("/queryAllCatalogue")
public Result queryAllCatalogue() {
return taskService.queryAllCatalogue();
}
/**
* 查询对应操作的任务列表
*
* @param operating
* @return
*/
@GetMapping("/queryOnClickOperatingTask")
public Result<List<Task>> queryOnClickOperatingTask(@RequestParam("operating") Integer operating
, @RequestParam("catalogueId") Integer catalogueId) {
if (operating == null) {
return Result.failed("操作不正确");
}
switch (operating) {
case 1:
return taskService.queryOnLineTaskByDoneStatus(Arrays.asList(JobLifeCycle.RELEASE)
, JobStatus.getAllDoneStatus(), true, catalogueId);
case 2:
return taskService.queryOnLineTaskByDoneStatus(Arrays.asList(JobLifeCycle.ONLINE)
, Collections.singletonList(JobStatus.RUNNING), false, catalogueId);
default:
return Result.failed("操作不正确");
}
}
/**
* 一键操作任务
*
* @param operating
* @return
*/
@PostMapping("/onClickOperatingTask")
public Result onClickOperatingTask(@RequestBody JsonNode operating) {
if (operating == null || operating.get("operating") == null) {
return Result.failed("操作不正确");
}
switch (operating.get("operating").asInt()) {
case 1:
final JsonNode savepointSelect = operating.get("taskOperatingSavepointSelect");
return TaskOneClickOperatingUtil.oneClickOnline(TaskOneClickOperatingUtil.parseJsonNode(operating)
, TaskOperatingSavepointSelect.valueByCode(savepointSelect == null ? 0 : savepointSelect.asInt()));
case 2:
return TaskOneClickOperatingUtil.onClickOffline(TaskOneClickOperatingUtil.parseJsonNode(operating));
default:
return Result.failed("操作不正确");
}
}
/**
* 查询一键操作任务状态
*
* @return
*/
@GetMapping("/queryOneClickOperatingTaskStatus")
public Result queryOneClickOperatingTaskStatus() {
return TaskOneClickOperatingUtil.queryOneClickOperatingTaskStatus();
}
}
......@@ -23,6 +23,9 @@ package com.dlink.mapper;
import com.dlink.db.mapper.SuperMapper;
import com.dlink.model.Task;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 作业 Mapper 接口
......@@ -34,4 +37,8 @@ import org.apache.ibatis.annotations.Mapper;
public interface TaskMapper extends SuperMapper<Task> {
Integer queryAllSizeByName(String name);
List<Task> queryOnLineTaskByDoneStatus(@Param("parentIds") List<Integer> parentIds
, @Param("stepIds") List<Integer> stepIds, @Param("includeNull") boolean includeNull
, @Param("jobStatuses") List<String> jobStatuses);
}
......@@ -29,6 +29,8 @@ package com.dlink.model;
public enum CodeEnum {
SUCCESS(0),
ERROR(1),
EXCEPTION(5),
NOTLOGIN(401);
private Integer code;
......
/*
*
* 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.
*
*/
package com.dlink.result;
import com.dlink.common.result.Result;
import com.dlink.model.CodeEnum;
import com.dlink.model.Task;
import com.dlink.model.TaskOperatingSavepointSelect;
import com.dlink.model.TaskOperatingStatus;
import lombok.Data;
/**
* @author mydq
* @version 1.0
* @date 2022/7/16 21:09
**/
@Data
public class TaskOperatingResult {
private Task task;
private TaskOperatingStatus status;
private Integer code;
private String message;
private TaskOperatingSavepointSelect taskOperatingSavepointSelect;
public TaskOperatingResult(Task task) {
this.task = task;
this.status = TaskOperatingStatus.INIT;
this.taskOperatingSavepointSelect = TaskOperatingSavepointSelect.DEFAULT_CONFIG;
}
public TaskOperatingResult(Task task, TaskOperatingSavepointSelect taskOperatingSavepointSelect) {
this.task = task;
this.status = TaskOperatingStatus.INIT;
this.taskOperatingSavepointSelect = taskOperatingSavepointSelect;
}
public void parseResult(Result result) {
if (result == null) {
return;
}
if (CodeEnum.SUCCESS.getCode().equals(result.getCode())) {
this.status = TaskOperatingStatus.SUCCESS;
} else if (CodeEnum.ERROR.getCode().equals(result.getCode())) {
this.status = TaskOperatingStatus.FAIL;
}
this.code = result.getCode();
this.message = result.getMsg();
}
}
......@@ -27,10 +27,13 @@ import com.dlink.dto.TaskRollbackVersionDTO;
import com.dlink.job.JobResult;
import com.dlink.model.JobInfoDetail;
import com.dlink.model.JobInstance;
import com.dlink.model.JobLifeCycle;
import com.dlink.model.JobStatus;
import com.dlink.model.Task;
import com.dlink.result.SqlExplainResult;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.web.multipart.MultipartFile;
import com.dlink.result.TaskOperatingResult;
import java.util.List;
......@@ -93,4 +96,14 @@ public interface TaskService extends ISuperService<Task> {
String exportJsonByTaskIds(JsonNode para);
Result uploadTaskJson(MultipartFile file) throws Exception;
Result queryAllCatalogue();
Result<List<Task>> queryOnLineTaskByDoneStatus(List<JobLifeCycle> jobLifeCycle
, List<JobStatus> jobStatuses, boolean includeNull, Integer catalogueId);
void selectSavepointOnLineTask(TaskOperatingResult taskOperatingResult);
void selectSavepointOffLineTask(TaskOperatingResult taskOperatingResult);
}
/*
*
* 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.
*
*/
package com.dlink.utils;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.dlink.common.result.Result;
import com.dlink.context.SpringContextUtils;
import com.dlink.model.CodeEnum;
import com.dlink.model.JobLifeCycle;
import com.dlink.model.JobStatus;
import com.dlink.model.Task;
import com.dlink.model.TaskOperatingSavepointSelect;
import com.dlink.model.TaskOperatingStatus;
import com.dlink.result.TaskOperatingResult;
import com.dlink.service.TaskService;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* @author mydq
* @version 1.0
* @date 2022/7/16 20:26
**/
public class TaskOneClickOperatingUtil {
private static List<TaskOperatingResult> oneClickOnlineCache = new ArrayList<>(0);
private static List<TaskOperatingResult> oneClickOfflineCache = new ArrayList<>(0);
private final static AtomicBoolean oneClickOnlineThreadStatus = new AtomicBoolean(false);
private final static AtomicBoolean oneClickOfflineThreadStatus = new AtomicBoolean(false);
public static synchronized Result oneClickOnline(List<Task> tasks, TaskOperatingSavepointSelect taskOperatingSavepointSelect) {
if (oneClickOnlineThreadStatus.get() || oneClickOfflineThreadStatus.get()) {
return Result.failed("存在一键上线或者下线操作,请稍后重试");
}
final TaskService taskService = SpringContextUtils.getBeanByClass(TaskService.class);
if (CollectionUtils.isEmpty(tasks)) {
final Result<List<Task>> listResult = taskService.queryOnLineTaskByDoneStatus(Arrays.asList(JobLifeCycle.RELEASE), JobStatus.getAllDoneStatus(), true, 0);
if (CollectionUtils.isEmpty(listResult.getDatas())) {
return Result.succeed("没有需要上线的任务");
}
tasks = listResult.getDatas();
}
oneClickOnlineCache = tasks.stream().map(task -> new TaskOperatingResult(task, taskOperatingSavepointSelect)).collect(Collectors.toList());
new OneClickOperatingThread("oneClickOnlineThread", oneClickOnlineCache, oneClickOnlineThreadStatus, taskService::selectSavepointOnLineTask).start();
return Result.succeed("success");
}
public static synchronized Result onClickOffline(List<Task> tasks) {
if (oneClickOnlineThreadStatus.get() || oneClickOfflineThreadStatus.get()) {
return Result.failed("存在一键上线或者下线操作,请稍后重试");
}
final TaskService taskService = SpringContextUtils.getBeanByClass(TaskService.class);
if (CollectionUtils.isEmpty(tasks)) {
final Result<List<Task>> listResult = taskService.queryOnLineTaskByDoneStatus(Arrays.asList(JobLifeCycle.ONLINE), Collections.singletonList(JobStatus.RUNNING), false, 0);
if (CollectionUtils.isEmpty(listResult.getDatas())) {
return Result.succeed("没有需要下线的任务");
}
tasks = listResult.getDatas();
}
oneClickOfflineCache = tasks.stream().map(TaskOperatingResult::new).collect(Collectors.toList());
new OneClickOperatingThread("oneClickOfflineThread", oneClickOfflineCache, oneClickOfflineThreadStatus, taskService::selectSavepointOffLineTask).start();
return Result.succeed("success");
}
public static Result<Map<String, Object>> queryOneClickOperatingTaskStatus() {
final Map<String, Object> map = new HashMap<>(4);
map.put("online", oneClickOnlineCache);
map.put("onlineStatus", oneClickOnlineThreadStatus.get());
map.put("offline", oneClickOfflineCache);
map.put("offlineStatus", oneClickOfflineThreadStatus.get());
return Result.succeed(map);
}
public static List<Task> parseJsonNode(JsonNode operating) {
final JsonNode tasksJsonNode = operating.withArray("tasks");
if (tasksJsonNode == null || tasksJsonNode.isEmpty()) {
return null;
}
final List<Task> result = new ArrayList<>(tasksJsonNode.size());
for (JsonNode node : tasksJsonNode) {
final Task task = new Task();
task.setId(node.get("id").asInt());
task.setName(node.get("name").asText());
result.add(task);
}
return result;
}
private static class OneClickOperatingThread extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(OneClickOperatingThread.class);
private final String threadName;
private final List<TaskOperatingResult> taskOperatingResults;
private final AtomicBoolean threadStatus;
private final Consumer<TaskOperatingResult> consumer;
public OneClickOperatingThread(String threadName, List<TaskOperatingResult> taskOperatingResults, AtomicBoolean threadStatus, Consumer<TaskOperatingResult> consumer) {
super(threadName);
this.threadName = threadName;
this.threadStatus = threadStatus;
this.threadStatus.set(true);
this.taskOperatingResults = taskOperatingResults;
this.consumer = consumer;
}
@Override
public void run() {
try {
if (CollectionUtils.isEmpty(taskOperatingResults)) {
return;
}
for (TaskOperatingResult taskOperatingResult : taskOperatingResults) {
try {
taskOperatingResult.setStatus(TaskOperatingStatus.OPERATING_BEFORE);
consumer.accept(taskOperatingResult);
} catch (Throwable e) {
exceptionDealWith(taskOperatingResult, e);
}
}
} finally {
this.threadStatus.set(false);
}
}
private void exceptionDealWith(TaskOperatingResult taskOperatingResult, Throwable e) {
taskOperatingResult.setStatus(TaskOperatingStatus.EXCEPTION);
taskOperatingResult.setCode(CodeEnum.EXCEPTION.getCode());
taskOperatingResult.setMessage(ExceptionUtil.stacktraceToString(e));
LOGGER.error("[{}], taskId={}, taskName={}, operating exception", threadName
, taskOperatingResult.getTask().getId(), taskOperatingResult.getTask().getName(), e);
}
}
}
......@@ -36,4 +36,24 @@
from dlink_task
where `name` REGEXP '${name}_[0-9]$'
</select>
<select id="queryOnLineTaskByDoneStatus" resultType="com.dlink.model.Task">
select t.id as id, t.name as name
from dlink_task t
left join dlink_catalogue c on c.task_id = t.id
left join dlink_job_instance i on i.id = t.job_instance_id
where
c.parent_id in <foreach collection="parentIds" item="parentId" open="(" close=")" separator=","> #{parentId} </foreach>
and c.task_id is not null
and c.is_leaf = 1
and t.step in <foreach collection="stepIds" item="stepId" open="(" close=")" separator=","> #{stepId} </foreach>
and t.enabled = 1
<if test="includeNull == true">
and ((i.status is null) or (i.status in <foreach collection="jobStatuses" item="jobStatus" open="(" close=")" separator=","> #{jobStatus} </foreach>))
</if>
<if test="includeNull != true">
and i.status in <foreach collection="jobStatuses" item="jobStatus" open="(" close=")" separator=","> #{jobStatus} </foreach>
</if>
</select>
</mapper>
......@@ -22,6 +22,9 @@ package com.dlink.model;
import com.dlink.assertion.Asserts;
import java.util.ArrayList;
import java.util.List;
/**
* JobState
*
......@@ -122,4 +125,13 @@ public enum JobStatus {
}
}
public static List<JobStatus> getAllDoneStatus() {
final List<JobStatus> list = new ArrayList<>(4);
list.add(FAILED);
list.add(CANCELED);
list.add(FINISHED);
list.add(UNKNOWN);
return list;
}
}
/*
*
* 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.
*
*/
package com.dlink.model;
/**
* @author csz
* @version 1.0
* @date 2022/7/19 15:21
**/
public enum TaskOperatingSavepointSelect {
DEFAULT_CONFIG(0, "defaultConfig", "默认保存点"),
LATEST(1, "latest", "最新保存点");
private Integer code;
private String name;
private String message;
TaskOperatingSavepointSelect(Integer code, String name, String message) {
this.code = code;
this.name = name;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getName() {
return name;
}
public String getMessage() {
return message;
}
public static TaskOperatingSavepointSelect valueByCode(Integer code) {
for (TaskOperatingSavepointSelect savepointSelect : TaskOperatingSavepointSelect.values()) {
if (savepointSelect.getCode().equals(code)) {
return savepointSelect;
}
}
return null;
}
}
/*
*
* 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.
*
*/
package com.dlink.model;
/**
* @author mydq
* @version 1.0
* @date 2022/7/16 21:13
**/
public enum TaskOperatingStatus {
INIT(1, "init", "初始化"),
OPERATING_BEFORE(4, "operatingBefore", "操作前准备"),
TASK_STATUS_NO_DONE(8, "taskStatusNoDone", "任务不是完成状态"),
OPERATING(12, "operating", "正在操作"),
EXCEPTION(13, "exception", "异常"),
SUCCESS(16, "success", "成功"),
FAIL(20, "fail", "失败");
private Integer code;
private String name;
private String message;
TaskOperatingStatus(Integer code, String name, String message) {
this.code = code;
this.name = name;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getName() {
return name;
}
public String getMessage() {
return message;
}
}
......@@ -20,33 +20,111 @@
import {history} from 'umi';
import {queryData} from "@/components/Common/crud";
import React, {useEffect, useRef, useState} from "react";
import type {ActionType, ProColumns} from '@ant-design/pro-table';
import {useState, useRef, useEffect} from "react";
import type {ProColumns, ActionType} from '@ant-design/pro-table';
import ProTable from "@ant-design/pro-table";
import {Badge, message} from 'antd'
import {JobInstanceTableListItem} from "@/pages/DevOps/data";
import moment from 'moment';
import {RUN_MODE} from "@/components/Studio/conf";
import JobStatus from "@/components/Common/JobStatus";
import JobLifeCycle, {JOB_LIFE_CYCLE} from "@/components/Common/JobLifeCycle";
import {parseSecondStr} from "@/components/Common/function";
import OpsStatusModal from "@/pages/DevOps/OpsStatusModel/index";
import StatusDetailedModal from "@/pages/DevOps/StatusDetailedModel/index";
import {
queryOneClickOperatingTaskStatus,
onClickOperatingTask,
queryAllCatalogue
} from "@/pages/DevOps/service";
const OPS_STATUS_COLOR = {
success: 'lime',
padding: 'yellow',
}
const url = '/api/jobInstance';
const JobInstanceTable = (props: any) => {
const {status, activeKey,isHistory, dispatch} = props;
const {status, activeKey, isHistory, taskStatus} = props;
const [time, setTime] = useState(() => Date.now());
const [opsStatusVisible, setOpsStatusVisible] = useState<boolean>(false);
const [opsStatus, setOpsStatus] = useState<string>('');
const [opsStatusListTree, setOpsStatusListTree] = useState<any[]>([]);
const [statusDetailedVisible, setStatusDetailedVisible] = useState<boolean>(false);
const [statusDetailedList, setStatusDetailedList] = useState<any[]>([]);
const ref = useRef<ActionType>();
useEffect(() => {
ref?.current?.reload();
}, [isHistory]);
/**
* oGoOnline 状态上下线
* */
// eslint-disable-next-line @typescript-eslint/no-shadow
const onStatusChange = async (status: string) => {
try {
const {datas} = await queryAllCatalogue({operating: status})
setOpsStatusListTree([datas])
setOpsStatusVisible(true)
setOpsStatus(status);
} catch (e) {
console.log(e)
}
}
/**
* onOpsStatusCallBack 上下线提交操作
* */
const onOpsStatusCallBack = async (values?: any) => {
if (values) {
try {
await onClickOperatingTask(values)
message.success('操作成功')
setOpsStatusVisible(false)
} catch (e) {
console.log(e)
}
} else {
setOpsStatusVisible(false)
}
}
/**
* onOfflineDetailed 上下线明细详情
* */
// eslint-disable-next-line @typescript-eslint/no-shadow
const onStatusDetailed = async (status: string) => {
const {datas} = await queryOneClickOperatingTaskStatus()
datas.online = datas.online.map(({task, ...rest}: any) => {
return {
...task,
...rest
}
})
datas.offline = datas.offline.map(({task, ...rest}: any) => {
return {
...task,
...rest
}
})
const newStatusData = status === '1' ? datas.online : datas.offline
setStatusDetailedList(newStatusData)
setStatusDetailedVisible(true)
setOpsStatus(status);
}
/**
* onCancelStatusDetailed 上下线明细弹窗关闭回调
* */
const onCancelStatusDetailed = () => {
setStatusDetailedVisible(false)
}
const getColumns = () => {
const columns: ProColumns<JobInstanceTableListItem>[] = [{
const columns: ProColumns<JobInstanceTableListItem>[] = [{
title: "作业名",
dataIndex: "name",
sorter: true,
},{
}, {
title: "生命周期",
dataIndex: "step",
sorter: true,
......@@ -73,7 +151,7 @@ const JobInstanceTable = (props: any) => {
render: (_, row) => {
return (<JobLifeCycle step={row.step}/>);
}
},{
}, {
title: "运行模式",
dataIndex: "type",
sorter: true,
......@@ -109,11 +187,11 @@ const JobInstanceTable = (props: any) => {
status: RUN_MODE.KUBERNETES_APPLICATION,
},
},
},{
}, {
title: "集群实例",
dataIndex: "clusterAlias",
sorter: true,
},{
}, {
title: "作业ID",
dataIndex: "jid",
key: "jid",
......@@ -149,11 +227,10 @@ const JobInstanceTable = (props: any) => {
hideInSearch: true,
}, {
title: "耗时",
dataIndex: "duration",
sorter: true,
valueType: 'second',
hideInSearch: true,
render: (_, row) => {
return parseSecondStr(row.duration);
}
},];
return columns;
};
......@@ -161,9 +238,27 @@ const JobInstanceTable = (props: any) => {
return (
<><ProTable
actionRef={ref}
toolBarRender={() => [<Badge
color={taskStatus?.onlineStatus ? OPS_STATUS_COLOR.padding : OPS_STATUS_COLOR.success} text={<a
onClick={() => {
onStatusChange('1')
}}>一键上线</a>}/>,
<a
style={{color: taskStatus?.onlineStatus ? '#FF0000' : '#1E90FF'}}
onClick={() => {
onStatusDetailed('1')
}}>上线明细</a>,
<Badge color={taskStatus?.offlineStatus ? OPS_STATUS_COLOR.padding : OPS_STATUS_COLOR.success}
text={<a onClick={() => {
onStatusChange('2')
}}>一键下线</a>}/>, <a
style={{color: taskStatus?.onlineStatus ? '#FF0000' : '#1E90FF'}}
onClick={() => {
onStatusDetailed('2')
}}>下线明细</a>,]}
request={(params, sorter, filter) => {
setTime(Date.now());
return queryData(url, {...params,status,isHistory, sorter: {id: 'descend'}, filter});
return queryData(url, {...params, status, isHistory, sorter: {id: 'descend'}, filter});
}}
columns={getColumns()}
size="small"
......@@ -171,11 +266,11 @@ const JobInstanceTable = (props: any) => {
filterType: 'light',
}}
headerTitle={`上次更新时间:${moment(time).format('HH:mm:ss')}`}
polling={status==activeKey?3000:undefined}
polling={status == activeKey ? 3000 : undefined}
pagination={{
pageSize: 10,
}}
onRow={ record => {
onRow={record => {
return {
onClick: event => {
history.push({
......@@ -188,6 +283,13 @@ const JobInstanceTable = (props: any) => {
};
}}
/>
<OpsStatusModal opsStatusListTree={opsStatusListTree} opsStatusVisible={opsStatusVisible} opsStatus={opsStatus}
onOpsStatusCallBack={onOpsStatusCallBack}/>
<StatusDetailedModal opsStatus={opsStatus} statusDetailedList={statusDetailedList}
statusDetailedVisible={statusDetailedVisible}
onCancelStatusDetailed={onCancelStatusDetailed}
/>
</>
);
};
......
/*
*
* 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 React, {useState, useRef, useEffect} from 'react'
import {Modal, Card, Select, Checkbox, Form, Divider, Row, Col, TreeSelect, Button} from 'antd'
import {queryOnClickOperatingTask} from '../service'
import type {CheckboxChangeEvent} from 'antd/es/checkbox';
import type {CheckboxValueType} from 'antd/es/checkbox/Group';
interface IOpsStatusModalProps {
opsStatusVisible: boolean;
opsStatus: string;
opsStatusListTree: any;
onOpsStatusCallBack: (values?: any) => void
}
export const OpsStatusLabel = {
'1': '上线',
'0': '下线'
}
const {Option} = Select
const CheckboxGroup = Checkbox.Group;
const OpsStatusModal: React.FC<IOpsStatusModalProps> = (props): React.ReactElement => {
const {opsStatusVisible, opsStatus, opsStatusListTree, onOpsStatusCallBack} = props
const formRef = useRef<any>(null)
const [opsStatusList, setOpsStatusList] = useState<any[]>([])
const [treeValue, setTreeValue] = useState<number | null>(null);
useEffect(() => {
setTreeValue(null)
setOpsStatusList([])
formRef.current?.resetFields(['tasks','checkoutAll'])
console.log(opsStatus)
if (opsStatus === '1') {
formRef.current?.setFieldsValue({
taskOperatingSavepointSelect: '0'
})
}
}, [opsStatusVisible, opsStatus])
/**
* onCheckAllChange 全选
* */
const onCheckAllChange = (e: CheckboxChangeEvent) => {
formRef.current?.setFieldsValue({
tasks: e.target.checked ? opsStatusList.map((item: any) => item.id) : []
})
};
/**
* onChangeCheckout 选中
* */
const onChangeCheckout = (list: CheckboxValueType[]) => {
formRef.current?.setFieldsValue({
checkoutAll: list.length === opsStatusList.length ? true : false
})
};
/**
* onTreeChange 结构树选中
* */
const onTreeChange = (newValue: number) => {
setTreeValue(newValue);
};
/**
* onSubmit 提交
* */
const onSubmit = () => {
formRef.current.validateFields(['tasks', 'taskOperatingSavepointSelect']).then((values: any) => {
const {tasks, ...rest} = values
onOpsStatusCallBack({
...rest,
operating: opsStatus,
tasks: tasks.map((item: any) => {
return {
id: item,
name: opsStatusList.find((items: any) => items.id === item).name
}
})
})
})
}
/**
* onSubmitTree 树型选择提交
* */
const onSubmitTree = async () => {
try {
const {datas} = await queryOnClickOperatingTask({
operating: opsStatus,
catalogueId: treeValue
})
setOpsStatusList(datas)
} catch (e) {
console.log(e)
}
}
// options = {opsStatusList}
// value = {checkedList}
// onChange = {onChange}
return (
<Modal width={800} okText={'提交'} onCancel={() => {
onOpsStatusCallBack()
}} onOk={() => {
onSubmit()
}} title={OpsStatusLabel[opsStatus]} visible={opsStatusVisible}>
<Form ref={formRef}>
<Card>
<Row>
<Col span={8}>
<TreeSelect
showSearch
allowClear
treeDataSimpleMode
style={{width: '100%'}}
dropdownStyle={{maxHeight: 400, overflow: 'auto'}}
fieldNames={{label: 'name', value: 'id'}}
treeNodeFilterProp={'name'}
placeholder="请选择"
onChange={onTreeChange}
value={treeValue}
treeData={opsStatusListTree}
/>
</Col>
<Col push={2}>
<Button type={'primary'} onClick={() => {
onSubmitTree()
}}>查询</Button>
</Col>
</Row>
<Divider/>
<Row>
<Col>
<Form.Item name={'checkoutAll'} label={'全选'}>
<Checkbox onChange={onCheckAllChange}/>
</Form.Item>
</Col>
<Col push={15}>
{
opsStatus === '1' &&
<Form.Item name={'taskOperatingSavepointSelect'} label={' '} colon={false}
rules={[{message: '请输入', required: true}]}>
<Select style={{width: '150px'}}>
<Option value={'0'}>默认保存点</Option>
<Option value={'1'}>最新保存点</Option>
</Select>
</Form.Item>}
</Col>
</Row>
<Divider/>
<Form.Item name={'tasks'}>
<CheckboxGroup onChange={onChangeCheckout}>
<Row>
{
opsStatusList.map((item: any) => {
return <Col key={item.id} span={8}>
<Checkbox value={item.id}>{item.name}</Checkbox>
</Col>
})
}
</Row>
</CheckboxGroup>
</Form.Item>
</Card>
</Form>
</Modal>
)
}
export default OpsStatusModal
/*
*
* 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 React from 'react';
import {Modal, Table, Button, Tooltip} from 'antd'
interface IStatusDetailedModal {
statusDetailedVisible: boolean;
statusDetailedList: any;
onCancelStatusDetailed: () => void
opsStatus: string;
}
export const OpsStatusTitle = {
'1': '上线明细列表',
'0': '下线明细列表'
}
const StatusDetailedModal: React.FC<IStatusDetailedModal> = (props): React.ReactElement => {
const {statusDetailedVisible, statusDetailedList, opsStatus, onCancelStatusDetailed} = props
const columns = [{
title: '名称',
dataIndex: 'name'
}, {
title: '状态',
dataIndex: 'status'
}, {
title: '结果',
dataIndex: 'code'
}, {
title: '信息',
dataIndex: 'message',
render: (text: string) => <Tooltip overlayInnerStyle={{width: '800px'}} placement="bottom" title={text}>
<div style={{width: '150px', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis'}}>{text}</div>
</Tooltip>,
width: 150
}, {
title: '点位配置选择',
dataIndex: 'taskOperatingSavepointSelect',
}].filter((item) => {
if (item.dataIndex === 'taskOperatingSavepointSelect') {
if (opsStatus === '1') {
return true
} else {
return false
}
} else {
return true
}
})
const onFooter = () => <Button onClick={() => onCancelStatusDetailed()}>
返回
</Button>
return (
<Modal title={OpsStatusTitle[opsStatus]} width={800} onCancel={() => onCancelStatusDetailed()} footer={onFooter()}
visible={statusDetailedVisible}>
<Table rowKey={'id'} dataSource={statusDetailedList} columns={columns}/>
</Modal>)
}
export default StatusDetailedModal
......@@ -20,17 +20,16 @@
import ProCard, { StatisticCard } from '@ant-design/pro-card';
import type { StatisticProps } from '@ant-design/pro-card';
import JobInstanceTable from "./JobInstanceTable";
import {getStatusCount} from "@/pages/DevOps/service";
import JobInstanceTable from "./JobInstanceTable/index";
import {getStatusCount,queryOneClickOperatingTaskStatus} from "@/pages/DevOps/service";
import {useEffect, useState} from "react";
import {StatusCount} from "@/pages/DevOps/data";
import {JOB_STATUS} from "@/components/Common/JobStatus";
import {Form, Switch} from "antd";
import {Switch} from "antd";
const { Statistic } = StatisticCard;
const DevOps = (props:any) => {
const DevOps = () => {
const [isHistory, setIsHistory] = useState<boolean>(false);
......@@ -59,9 +58,14 @@ const DevOps = (props:any) => {
const [statusCount, setStatusCount] = useState<any[]>(statusCountDefault);
const [statusHistoryCount, setStatusHistoryCount] = useState<any[]>(statusCountDefault);
const [activeKey, setActiveKey] = useState<string>('');
const [taskStatus, setTaskStatus] = useState<any>({});
const refreshStatusCount = () => {
const res = getStatusCount();
const taskStatusRes = queryOneClickOperatingTaskStatus();
taskStatusRes.then((result)=>{
setTaskStatus(result)
})
res.then((result)=>{
const statusHistoryCountData: StatusCount = result.datas.history;
const historyItems: any = [
......@@ -135,7 +139,7 @@ const DevOps = (props:any) => {
backgroundColor: '#fafafa',
}}
>
<JobInstanceTable status={item.key} activeKey={activeKey} isHistory={isHistory}/>
<JobInstanceTable taskStatus={taskStatus} status={item.key} activeKey={activeKey} isHistory={isHistory}/>
</div>
</ProCard.TabPane>
))}
......
......@@ -18,7 +18,7 @@
*/
import {getData} from "@/components/Common/crud";
import {getData, postAll} from "@/components/Common/crud";
export function getStatusCount() {
return getData("api/jobInstance/getStatusCount");
......@@ -44,6 +44,34 @@ export function getTaskManagerInfo(address: string) {
return getData("api/jobInstance/getTaskManagerInfo", {address});
}
export function selectSavePointRestartTask(id: number,isOnLine: boolean, savePointPath: string) {
return getData("api/task/selectSavePointRestartTask", {id,isOnLine,savePointPath});
export function selectSavePointRestartTask(id: number, isOnLine: boolean, savePointPath: string) {
return getData("api/task/selectSavePointRestartTask", {id, isOnLine, savePointPath});
}
/**
* queryOneClickOperatingTaskStatus 一键操作状态查询
* */
export function queryOneClickOperatingTaskStatus() {
return getData("api/task/queryOneClickOperatingTaskStatus", {});
}
/**
* onClickOperatingTask 一键操作保存
* */
export function onClickOperatingTask(params: any) {
return postAll("api/task/onClickOperatingTask", params);
}
/**
* queryOnClickOperatingTask 查询对应操作的任务列表
* */
export function queryOnClickOperatingTask(params: { operating: string, catalogueId: number | null }) {
return getData("api/task/queryOnClickOperatingTask", params);
}
/**
* queryAllCatalogue 查询对应操作的任务列表树形
* */
export function queryAllCatalogue(params: { operating: string }) {
return getData("api/task/queryAllCatalogue", params);
}
......@@ -12,21 +12,22 @@ title: 运维中心概述
下表为运维中心各模块功能使用的简单说明:
| 模块 | 说明 |
| :------: | :------------------------------------: |
| 作业实例 | 查看及修改 FlinkSQL 的作业实例状态 |
| 作业总览 | 查看 FlinkSQL 各监控指标 |
| 集群信息 | 查看 FlinkSQL 的集群实例信息 |
| 作业快照 | |
| 异常信息 | 查看 FlinkSQL 启动及运行时的异常 |
| 作业日志 | 完整的 FlinkSQL 日志 |
| 自动调优 | - |
| 配置信息 | 查看 FlinkSQL 的作业配置 |
| FlinkSQL | 查看 FlinkSQL 的 SQL 语句 |
| 数据地图 | 查看 FlinkSQL 的字段血缘 |
| 即席查询 | |
| 历史版本 | 对比查看 FlinkSQL 作业发布后的多个版本 |
| 告警记录 | 查看 FlinkSQL 提交和发布后的告警信息 |
| 模块 | 说明 |
|:--------:|:------------------------:|
| 作业实例 | 查看及修改 FlinkSQL 的作业实例状态 |
| 作业总览 | 查看 FlinkSQL 各监控指标 |
| 集群信息 | 查看 FlinkSQL 的集群实例信息 |
| 作业快照 | |
| 异常信息 | 查看 FlinkSQL 启动及运行时的异常 |
| 作业日志 | 完整的 FlinkSQL 日志 |
| 自动调优 | - |
| 配置信息 | 查看 FlinkSQL 的作业配置 |
| FlinkSQL | 查看 FlinkSQL 的 SQL 语句 |
| 数据地图 | 查看 FlinkSQL 的字段血缘 |
| 即席查询 | |
| 历史版本 | 对比查看 FlinkSQL 作业发布后的多个版本 |
| 告警记录 | 查看 FlinkSQL 提交和发布后的告警信息 |
| 一键上下线 | 已发布的作业可以进行一键上下线 |
:::warning 注意事项
......
......@@ -83,3 +83,68 @@ title: 作业实例状态
| SavePoint 停止 | 作业触发 SavePoint 操作,并停止作业 |
| 普通停止 | 作业只停止 |
## 一键上下线功能说明
一键上下线功能,只针对发布后的作业
1、 一键上线:
(1)点击一键上线后,出现检索和操作弹窗;
(2)点击下拉框可以根据目录进行针对性的检索,检索结果出现在下面;
(3)点击全选可以进行全选;
(4)右上角,可以选择默认保存点或者最新保存点启动;
(I) 默认保存点:以studio页面,任务页面的任务内部配置的保存点策略和点位为准;
(II)最新保存点:会检索此instance的最后一次成功的保存点,进行任务保存点策略和点位的修改;
(5)点击提交,即可开始提交任务
(6)可以转向上线明细功能,进行操作结果和状态的查看;
2、 上线明细:
(1)名称:任务名称
(2)状态:
(I)INIT:初始化
(II)OPERATING_BEFORE:操作前准备,一般指正在排队等待;
(III)TASK_STATUS_NO_DONE:任务不是完成状态,任务真正执行时,状态不一致;
(IV)OPERATING:正在操作
(V)EXCEPTION:系统发生异常
(VI)SUCCESS:成功
(VII)FAIL:失败
(3)结果:
(I)0:CodeEnum.SUCCESS
(II)1:CodeEnum.ERROR
(III)5:CodeEnum.EXCEPTION
(IV)401:CodeEnum.NOTLOGIN
(4)信息: 上线结果描述
异常的情况下,打印异常截取
鼠标放到该字段对应位置,可以查看超长内容
(5)点位配置选择:
DEFAULT_CONFIG:默认配置
LATEST:最新保存点
3、 一键下线:
(1)点击一键下线后,出现检索和操作弹窗;
(2)点击下拉框可以根据目录进行针对性的检索,检索结果出现在下面;
(3)点击全选可以进行全选;
(5)点击提交,即可开始提交任务
(6)可以转向下线明细功能,进行操作结果和状态的查看;
4、 下线明细:
(1)名称:任务名称
(2)状态:
(I)INIT:初始化
(II)OPERATING_BEFORE:操作前准备,一般指正在排队等待;
(III)TASK_STATUS_NO_DONE:任务不是完成状态,任务真正执行时,状态不一致;
(IV)OPERATING:正在操作
(V)EXCEPTION:系统发生异常
(VI)SUCCESS:成功
(VII)FAIL:失败
(3)结果:
(I)0:CodeEnum.SUCCESS
(II)1:CodeEnum.ERROR
(III)5:CodeEnum.EXCEPTION
(IV)401:CodeEnum.NOTLOGIN
(4)信息: 上线结果描述
异常的情况下,打印异常截取
鼠标放到该字段对应位置,可以查看超长内容
......@@ -83,6 +83,7 @@ title: 功能
| | | 新增 即席查询 | dev |
| | | 新增 历史版本 | dev |
| | | 新增 告警记录 | 0.6.0 |
| | | 新增 一键上下线功能 | 0.6.6 |
| 注册中心 | Flink 集群实例 | 新增 外部 Flink 集群实例注册 | 0.4.0 |
| | | 新增 外部 Flink 集群实例心态检测与版本获取 | 0.4.0 |
| | | 新增 外部 Flink 集群手动一键回收 | 0.4.0 |
......
......@@ -34,7 +34,7 @@ Dinky(原 Dlink):
- 支持实时调试预览 Table 和 ChangeLog 数据及图形展示
- 支持语法逻辑检查、作业执行计划、字段级血缘分析等
- 支持 Flink 元数据、数据源元数据查询及管理
- 支持实时任务运维:作业上线下线、作业信息、集群信息、作业快照、异常信息、作业日志、数据地图、即席查询、历史版本、报警记录等
- 支持实时任务运维:作业上线下线、作业信息、集群信息、作业快照、异常信息、作业日志、数据地图、即席查询、历史版本、报警记录、一键上下线功能(发布后的作业)
- 支持作为多版本 FlinkSQL Server 的能力以及 OpenApi
- 支持易扩展的实时作业报警及报警组:钉钉、微信企业号等
- 支持完全托管的 SavePoint 启动机制:最近一次、最早一次、指定一次等
......
......@@ -27,6 +27,7 @@ title: 运维中心概述
| 即席查询 | |
| 历史版本 | 对比查看 FlinkSQL 作业发布后的多个版本 |
| 告警记录 | 查看 FlinkSQL 提交和发布后的告警信息 |
| 一键上下线 | 已发布的作业可以进行一键上下线 |
:::warning 注意事项
......
......@@ -83,3 +83,66 @@ title: 作业实例状态
| SavePoint 停止 | 作业触发 SavePoint 操作,并停止作业 |
| 普通停止 | 作业只停止 |
## 一键上下线功能说明
一键上下线功能,只针对发布后的作业
1、 一键上线:
(1)点击一键上线后,出现检索和操作弹窗;
(2)点击下拉框可以根据目录进行针对性的检索,检索结果出现在下面;
(3)点击全选可以进行全选;
(4)右上角,可以选择默认保存点或者最新保存点启动;
(I) 默认保存点:以studio页面,任务页面的任务内部配置的保存点策略和点位为准;
(II)最新保存点:会检索此instance的最后一次成功的保存点,进行任务保存点策略和点位的修改;
(5)点击提交,即可开始提交任务
(6)可以转向上线明细功能,进行操作结果和状态的查看;
2、 上线明细:
(1)名称:任务名称
(2)状态:
(I)INIT:初始化
(II)OPERATING_BEFORE:操作前准备,一般指正在排队等待;
(III)TASK_STATUS_NO_DONE:任务不是完成状态,任务真正执行时,状态不一致;
(IV)OPERATING:正在操作
(V)EXCEPTION:系统发生异常
(VI)SUCCESS:成功
(VII)FAIL:失败
(3)结果:
(I)0:CodeEnum.SUCCESS
(II)1:CodeEnum.ERROR
(III)5:CodeEnum.EXCEPTION
(IV)401:CodeEnum.NOTLOGIN
(4)信息: 上线结果描述
异常的情况下,打印异常截取
鼠标放到该字段对应位置,可以查看超长内容
(5)点位配置选择:
DEFAULT_CONFIG:默认配置
LATEST:最新保存点
3、 一键下线:
(1)点击一键下线后,出现检索和操作弹窗;
(2)点击下拉框可以根据目录进行针对性的检索,检索结果出现在下面;
(3)点击全选可以进行全选;
(5)点击提交,即可开始提交任务
(6)可以转向下线明细功能,进行操作结果和状态的查看;
4、 下线明细:
(1)名称:任务名称
(2)状态:
(I)INIT:初始化
(II)OPERATING_BEFORE:操作前准备,一般指正在排队等待;
(III)TASK_STATUS_NO_DONE:任务不是完成状态,任务真正执行时,状态不一致;
(IV)OPERATING:正在操作
(V)EXCEPTION:系统发生异常
(VI)SUCCESS:成功
(VII)FAIL:失败
(3)结果:
(I)0:CodeEnum.SUCCESS
(II)1:CodeEnum.ERROR
(III)5:CodeEnum.EXCEPTION
(IV)401:CodeEnum.NOTLOGIN
(4)信息: 上线结果描述
异常的情况下,打印异常截取
鼠标放到该字段对应位置,可以查看超长内容
\ No newline at end of file
......@@ -85,6 +85,7 @@ title: 功能
| | | 新增 即席查询 | dev |
| | | 新增 历史版本 | dev |
| | | 新增 告警记录 | 0.6.0 |
| | | 新增 一键上下线功能 | 0.6.6 |
| 注册中心 | Flink 集群实例 | 新增 外部 Flink 集群实例注册 | 0.4.0 |
| | | 新增 外部 Flink 集群实例心态检测与版本获取 | 0.4.0 |
| | | 新增 外部 Flink 集群手动一键回收 | 0.4.0 |
......
......@@ -34,7 +34,7 @@ Dinky(原 Dlink):
- 支持实时调试预览 Table 和 ChangeLog 数据及图形展示
- 支持语法逻辑检查、作业执行计划、字段级血缘分析等
- 支持 Flink 元数据、数据源元数据查询及管理
- 支持实时任务运维:作业上线下线、作业信息、集群信息、作业快照、异常信息、作业日志、数据地图、即席查询、历史版本、报警记录等
- 支持实时任务运维:作业上线下线、作业信息、集群信息、作业快照、异常信息、作业日志、数据地图、即席查询、历史版本、报警记录、一键上下线功能
- 支持作为多版本 FlinkSQL Server 的能力以及 OpenApi
- 支持易扩展的实时作业报警及报警组:钉钉、微信企业号等
- 支持完全托管的 SavePoint 启动机制:最近一次、最早一次、指定一次等
......
......@@ -35,7 +35,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot-dependencies.version>2.6.3</spring-boot-dependencies.version>
<hutool.version>5.1.4</hutool.version>
<hutool.version>5.8.4</hutool.version>
<druid-starter>1.2.8</druid-starter>
<mybatis-plus-boot-starter.version>3.5.1</mybatis-plus-boot-starter.version>
<lombok.version>1.18.16</lombok.version>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment