Commit 92ba0fdf authored by wenmo's avatar wenmo

运维首页轮询

parent 4e1fcdff
......@@ -39,20 +39,20 @@ public class JobInstanceController {
*/
@DeleteMapping
public Result deleteMul(@RequestBody JsonNode para) {
if (para.size()>0){
if (para.size() > 0) {
List<Integer> error = new ArrayList<>();
for (final JsonNode item : para){
for (final JsonNode item : para) {
Integer id = item.asInt();
if(!jobInstanceService.removeById(id)){
if (!jobInstanceService.removeById(id)) {
error.add(id);
}
}
if(error.size()==0) {
if (error.size() == 0) {
return Result.succeed("删除成功");
}else {
return Result.succeed("删除部分成功,但"+error.toString()+"删除失败,共"+error.size()+"次失败。");
} else {
return Result.succeed("删除部分成功,但" + error.toString() + "删除失败,共" + error.size() + "次失败。");
}
}else{
} else {
return Result.failed("请选择要删除的记录");
}
}
......@@ -63,7 +63,7 @@ public class JobInstanceController {
@PostMapping("/getOneById")
public Result getOneById(@RequestBody JobInstance JobInstance) throws Exception {
JobInstance = jobInstanceService.getById(JobInstance.getId());
return Result.succeed(JobInstance,"获取成功");
return Result.succeed(JobInstance, "获取成功");
}
/**
......@@ -71,6 +71,14 @@ public class JobInstanceController {
*/
@GetMapping("/getStatusCount")
public Result getStatusCount() {
return Result.succeed(jobInstanceService.getStatusCount(),"获取成功");
return Result.succeed(jobInstanceService.getStatusCount(), "获取成功");
}
/**
* 获取Job实例的所有信息
*/
@GetMapping("/getJobInfoDetail")
public Result getJobInfoDetail(@RequestParam Integer id) {
return Result.succeed(jobInstanceService.getJobInfoDetail(id), "获取成功");
}
}
package com.dlink.model;
/**
* JobInfoDetail
*
* @author wenmo
* @since 2022/3/1 19:31
**/
public class JobInfoDetail {
private Integer id;
private JobInstance instance;
private Cluster cluster;
private ClusterConfiguration clusterConfiguration;
private Task task;
private History history;
public JobInfoDetail(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public JobInstance getInstance() {
return instance;
}
public void setInstance(JobInstance instance) {
this.instance = instance;
}
public Cluster getCluster() {
return cluster;
}
public void setCluster(Cluster cluster) {
this.cluster = cluster;
}
public ClusterConfiguration getClusterConfiguration() {
return clusterConfiguration;
}
public void setClusterConfiguration(ClusterConfiguration clusterConfiguration) {
this.clusterConfiguration = clusterConfiguration;
}
public Task getTask() {
return task;
}
public void setTask(Task task) {
this.task = task;
}
public History getHistory() {
return history;
}
public void setHistory(History history) {
this.history = history;
}
}
package com.dlink.service;
import com.dlink.db.service.ISuperService;
import com.dlink.model.JobInfoDetail;
import com.dlink.model.JobInstance;
import com.dlink.model.JobInstanceStatus;
......@@ -13,4 +14,6 @@ import com.dlink.model.JobInstanceStatus;
public interface JobInstanceService extends ISuperService<JobInstance> {
JobInstanceStatus getStatusCount();
JobInfoDetail getJobInfoDetail(Integer id);
}
......@@ -3,11 +3,18 @@ package com.dlink.service.impl;
import com.dlink.assertion.Asserts;
import com.dlink.db.service.impl.SuperServiceImpl;
import com.dlink.mapper.JobInstanceMapper;
import com.dlink.model.History;
import com.dlink.model.JobInfoDetail;
import com.dlink.model.JobInstance;
import com.dlink.model.JobInstanceCount;
import com.dlink.model.JobInstanceStatus;
import com.dlink.model.JobStatus;
import com.dlink.service.ClusterConfigurationService;
import com.dlink.service.ClusterService;
import com.dlink.service.HistoryService;
import com.dlink.service.JobInstanceService;
import com.dlink.service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
......@@ -20,13 +27,23 @@ import java.util.List;
*/
@Service
public class JobInstanceServiceImpl extends SuperServiceImpl<JobInstanceMapper, JobInstance> implements JobInstanceService {
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
@Autowired
private ClusterService clusterService;
@Autowired
private ClusterConfigurationService clusterConfigurationService;
@Override
public JobInstanceStatus getStatusCount() {
List<JobInstanceCount> jobInstanceCounts = baseMapper.countStatus();
JobInstanceStatus jobInstanceStatus = new JobInstanceStatus();
Integer total = 0;
for (JobInstanceCount item : jobInstanceCounts) {
Integer counts = Asserts.isNull(item.getCounts())?0:item.getCounts();
Integer counts = Asserts.isNull(item.getCounts()) ? 0 : item.getCounts();
total += counts;
switch (JobStatus.get(item.getStatus())) {
case INITIALIZING:
......@@ -48,4 +65,20 @@ public class JobInstanceServiceImpl extends SuperServiceImpl<JobInstanceMapper,
jobInstanceStatus.setAll(total);
return jobInstanceStatus;
}
@Override
public JobInfoDetail getJobInfoDetail(Integer id) {
JobInfoDetail jobInfoDetail = new JobInfoDetail(id);
JobInstance jobInstance = getById(id);
Asserts.checkNull(jobInstance, "该任务实例不存在");
jobInfoDetail.setInstance(jobInstance);
jobInfoDetail.setTask(taskService.getTaskInfoById(jobInstance.getTaskId()));
jobInfoDetail.setCluster(clusterService.getById(jobInstance.getClusterId()));
History history = historyService.getById(jobInstance.getHistoryId());
jobInfoDetail.setHistory(history);
if (Asserts.isNotNull(history) && Asserts.isNotNull(history.getClusterConfigurationId())) {
jobInfoDetail.setClusterConfiguration(clusterConfigurationService.getClusterConfigById(history.getClusterConfigurationId()));
}
return jobInfoDetail;
}
}
......@@ -28,6 +28,12 @@ export default [
icon: 'control',
component: './DevOps',
},
{
path: '/job',
name: 'job',
component: './DevOps/JobInfo',
hideInMenu: true,
},
{
path: '/registration',
name: 'registration',
......
export type HistoryItem = {
id: number;
clusterId: number;
clusterAlias: string;
session: string;
jobId: string;
jobName: string;
jobManagerAddress: string;
statusText: string;
status: number;
statement: string;
error: string;
result: string;
config: string;
type: string;
startTime: string;
endTime: string;
taskId: number;
taskAlias: string;
};
......@@ -13,31 +13,11 @@ import styles from "./index.less";
import {Scrollbars} from 'react-custom-scrollbars';
import StudioPreview from "../StudioPreview";
import {getJobData} from "@/pages/FlinkSqlStudio/service";
import {HistoryItem} from "@/components/Studio/StudioConsole/StudioHistory/data";
const { Title, Paragraph, Text, Link } = Typography;
type HistoryItem = {
id: number;
clusterId: number;
clusterAlias: string;
session: string;
jobId: string;
jobName: string;
jobManagerAddress: string;
statusText: string;
status: number;
statement: string;
error: string;
result: string;
config: string;
type: string;
startTime: string;
endTime: string;
taskId: number;
taskAlias: string;
};
type HistoryConfig={
useSession: boolean;
session: string;
......
......@@ -61,6 +61,7 @@ export default {
'menu.studio': 'FlinkSql IDE',
'menu.flinksqlstudio': 'FlinkSQL Studio',
'menu.devops': '运维中心',
'menu.job': '作业实例',
'menu.registration.jar': 'Jar 管理',
'menu.registration.document': '文档管理',
'menu.settings': '系统设置',
......
import { Descriptions, Badge } from 'antd';
const BaseInfo = (props: any) => {
const {job} = props;
return (<Descriptions bordered>
<Descriptions.Item label="实例状态">{job?.instance.status}</Descriptions.Item>
<Descriptions.Item label="重启次数">{job?.instance.schema}</Descriptions.Item>
<Descriptions.Item label="耗时">{job?.instance.duration}</Descriptions.Item>
<Descriptions.Item label="启动时间">{job?.instance.createTime}</Descriptions.Item>
<Descriptions.Item label="更新时间">{job?.instance.updateTime}</Descriptions.Item>
<Descriptions.Item label="完成时间">{job?.instance.finishTime}</Descriptions.Item>
<Descriptions.Item label="Task" span={3}>
{}
</Descriptions.Item>
<Descriptions.Item >{}</Descriptions.Item>
</Descriptions>)
};
export default BaseInfo;
import React, {useEffect, useState} from 'react';
import { history, useLocation } from 'umi';
import { EllipsisOutlined, CheckCircleOutlined,SyncOutlined,CloseCircleOutlined,MinusCircleOutlined,ClockCircleOutlined,
FireOutlined,ClusterOutlined,RocketOutlined} from '@ant-design/icons';
import { Button, Dropdown, Menu, Tag, Space } from 'antd';
import { PageContainer } from '@ant-design/pro-layout';
import ProCard from '@ant-design/pro-card';
import {JobInfoDetail, StatusCount} from "@/pages/DevOps/data";
import {getJobInfoDetail, getStatusCount} from "@/pages/DevOps/service";
import moment from "moment";
const JobInfo = (props:any) => {
const params = useLocation();
const { } = props;
const id = params.query.id;
const [job, setJob] = useState<JobInfoDetail>();
const [time, setTime] = useState(() => Date.now());
const refreshJobInfoDetail = () => {
const res = getJobInfoDetail(id);
res.then((result)=>{
setJob(result.datas);
setTime(Date.now());
});
};
useEffect(() => {
refreshJobInfoDetail();
let dataPolling = setInterval(refreshJobInfoDetail,3000);
return () => {
clearInterval(dataPolling);
};
}, []);
const handleBack = () => {
history.goBack();
};
return (
<PageContainer
header={{
title: `${job?.instance.name}`,
ghost: true,
extra: [
<Button key="back" type="dashed" onClick={handleBack}>返回</Button>,
<Button key="flinkwebui">FlinkWebUI</Button>,
<Button key="autorestart" type="primary">智能重启</Button>,
<Button key="autostop" type="primary" danger>智能停止</Button>,
<Dropdown
key="dropdown"
trigger={['click']}
overlay={
<Menu>
<Menu.Item key="1">普通停止</Menu.Item>
<Menu.Item key="2">SavePoint停止</Menu.Item>
<Menu.Item key="3">SavePoint暂停</Menu.Item>
</Menu>
}
>
<Button key="4" style={{ padding: '0 8px' }}>
<EllipsisOutlined />
</Button>
</Dropdown>,
],
}}
content={<>
<Space size={0}>
{job?.instance.jid?(
<Tag color="blue" key={job?.instance.jid}>
<FireOutlined /> {job?.instance.jid}
</Tag>
):undefined}
{(job?.instance.status == 'FINISHED') ?
(<Tag icon={<CheckCircleOutlined />} color="success">
FINISHED
</Tag>) :
(job?.instance.status == 'RUNNING') ?
(<Tag icon={<SyncOutlined spin />} color="processing">
RUNNING
</Tag>) :
(job?.instance.status == 'FAILED') ?
(<Tag icon={<CloseCircleOutlined />} color="error">
FAILED
</Tag>) :
(job?.instance.status == 'CANCELED') ?
(<Tag icon={<MinusCircleOutlined />} color="default">
CANCELED
</Tag>) :
(job?.instance.status == 'INITIALIZING') ?
(<Tag icon={<ClockCircleOutlined />} color="default">
INITIALIZING
</Tag>) :(job?.instance.status == 'RESTARTING') ?
(<Tag icon={<ClockCircleOutlined />} color="default">
RESTARTING
</Tag>) :
(<Tag color="default">
UNKNOWEN
</Tag>)
}
{job?.history.type?(
<Tag color="blue" key={job?.history.type}>
<RocketOutlined /> {job?.history.type}
</Tag>
):undefined}
{job?.cluster.alias?(
<Tag color="green" key={job?.cluster.alias}>
<ClusterOutlined /> {job?.cluster.alias}
</Tag>
):(<Tag color="green" key='local'>
<ClusterOutlined /> 本地环境
</Tag>)}
</Space>
</>}
tabBarExtraContent={`上次更新时间:${moment(time).format('HH:mm:ss')}`}
tabList={[
{
tab: '作业总览',
key: 'base',
closable: false,
},
{
tab: '配置信息',
key: 'config',
closable: false,
},
{
tab: 'FlinkSQL',
key: 'flinksql',
closable: false,
},
{
tab: '集群信息',
key: 'cluster',
closable: false,
},
{
tab: '作业快照',
key: 'snapshot',
closable: false,
},
{
tab: '告警记录',
key: 'alert',
closable: false,
},
]}
>
<ProCard direction="column" ghost gutter={[0, 16]}>
<ProCard style={{ height: 200 }} />
<ProCard gutter={16} ghost style={{ height: 200 }}>
<ProCard colSpan={16} />
<ProCard colSpan={8} />
</ProCard>
</ProCard>
</PageContainer>
);
};
export default JobInfo;
import {Tag} from 'antd';
import { history } from 'umi';
import {
CheckCircleOutlined,
SyncOutlined, CloseCircleOutlined, MinusCircleOutlined, ClockCircleOutlined, DownOutlined
......@@ -8,11 +9,14 @@ import React, {useState} from "react";
import type { ProColumns } from '@ant-design/pro-table';
import ProTable from "@ant-design/pro-table";
import {JobInstanceTableListItem} from "@/pages/DevOps/data";
import moment from 'moment';
import {RUN_MODE} from "@/components/Studio/conf";
const url = '/api/jobInstance';
const JobInstanceTable = (props: any) => {
const {status, dispatch} = props;
const {status, activeKey, dispatch} = props;
const [time, setTime] = useState(() => Date.now());
const getColumns = () => {
const columns: ProColumns<JobInstanceTableListItem>[] = [{
......@@ -23,6 +27,38 @@ const JobInstanceTable = (props: any) => {
title: "运行模式",
dataIndex: "type",
sorter: true,
valueType: 'radio',
valueEnum: {
'': {text: '全部', status: 'ALL'},
'local': {
text: RUN_MODE.LOCAL,
status: RUN_MODE.LOCAL,
},
'standalone': {
text: RUN_MODE.STANDALONE,
status: RUN_MODE.STANDALONE,
},
'yarn-session': {
text: RUN_MODE.YARN_SESSION,
status: RUN_MODE.YARN_SESSION,
},
'yarn-per-job': {
text: RUN_MODE.YARN_PER_JOB,
status: RUN_MODE.YARN_PER_JOB,
},
'yarn-application': {
text: RUN_MODE.YARN_APPLICATION,
status: RUN_MODE.YARN_APPLICATION,
},
'kubernetes-session': {
text: RUN_MODE.KUBERNETES_SESSION,
status: RUN_MODE.KUBERNETES_SESSION,
},
'kubernetes-application': {
text: RUN_MODE.KUBERNETES_APPLICATION,
status: RUN_MODE.KUBERNETES_APPLICATION,
},
},
},{
title: "集群实例",
dataIndex: "clusterAlias",
......@@ -35,6 +71,7 @@ const JobInstanceTable = (props: any) => {
title: "状态",
dataIndex: "status",
sorter: true,
hideInSearch: true,
render: (_, row) => {
return (
<>
......@@ -72,37 +109,59 @@ const JobInstanceTable = (props: any) => {
dataIndex: "createTime",
sorter: true,
valueType: 'dateTime',
hideInSearch: true,
}, {
title: "更新时间",
dataIndex: "updateTime",
sorter: true,
valueType: 'dateTime',
hideInTable: true
hideInTable: true,
hideInSearch: true,
}, {
title: "结束时间",
dataIndex: "finishTime",
sorter: true,
valueType: 'dateTime',
hideInTable: true
hideInTable: true,
hideInSearch: true,
}, {
title: "耗时",
dataIndex: "duration",
sorter: true,
valueType: 'second',
hideInSearch: true,
},];
return columns;
};
return (
<><ProTable
request={(params, sorter, filter) => queryData(url, {...params,status, sorter: {id: 'descend'}, filter})}
request={(params, sorter, filter) => {
setTime(Date.now());
return queryData(url, {...params,status, sorter: {id: 'descend'}, filter});
}}
columns={getColumns()}
size="small"
search={false}
toolBarRender={false}
search={{
filterType: 'light',
}}
headerTitle={`上次更新时间:${moment(time).format('HH:mm:ss')}`}
polling={status==activeKey?3000:undefined}
pagination={{
pageSize: 5,
}}
onRow={ record => {
return {
onClick: event => {
history.push({
pathname: '/job',
query: {
id: record.id,
},
});
},
};
}}
/>
</>
);
......
import {ClusterTableListItem} from "@/pages/Cluster/data";
import {ClusterConfigurationTableListItem} from "@/pages/ClusterConfiguration/data";
import {HistoryItem} from "@/components/Studio/StudioConsole/StudioHistory/data";
export type JobInstanceTableListItem = {
id: number,
name: string,
......@@ -25,3 +29,12 @@ export type StatusCount = {
failed: number,
canceled: number,
}
export type JobInfoDetail = {
id: number,
instance: JobInstanceTableListItem,
cluster: ClusterTableListItem,
clusterConfiguration: ClusterConfigurationTableListItem,
task: TaskTableListItem,
history: HistoryItem
}
......@@ -20,6 +20,7 @@ const DevOps = (props:any) => {
{ key: 'CANCELED', status: 'warning', title: '停止', value: 0 },
];
const [statusCount, setStatusCount] = useState<any[]>(statusCountDefault);
const [activeKey, setActiveKey] = useState<string>('');
const refreshStatusCount = () => {
const res = getStatusCount();
......@@ -35,16 +36,21 @@ const DevOps = (props:any) => {
];
setStatusCount(items);
});
}
};
useEffect(() => {
refreshStatusCount();
let dataPolling = setInterval(refreshStatusCount,3000);
return () => {
clearInterval(dataPolling);
};
}, []);
return (
<ProCard
tabs={{
onChange: (key) => {
setActiveKey(key);
},
}}
>
......@@ -69,7 +75,7 @@ const DevOps = (props:any) => {
backgroundColor: '#fafafa',
}}
>
<JobInstanceTable status={item.key}/>
<JobInstanceTable status={item.key} activeKey={activeKey}/>
</div>
</ProCard.TabPane>
))}
......
......@@ -3,3 +3,7 @@ import {getData} from "@/components/Common/crud";
export function getStatusCount() {
return getData("api/jobInstance/getStatusCount");
}
export function getJobInfoDetail(id:number) {
return getData("api/jobInstance/getJobInfoDetail",{id});
}
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