完善计划任务日志功能

This commit is contained in:
dushixiang 2021-03-04 23:39:33 +08:00
parent 1b8ecefcfe
commit 2d06cd373f
14 changed files with 403 additions and 144 deletions

29
main.go
View File

@ -159,6 +159,9 @@ func Run() error {
if err := global.DB.AutoMigrate(&model.Job{}); err != nil { if err := global.DB.AutoMigrate(&model.Job{}); err != nil {
return err return err
} }
if err := global.DB.AutoMigrate(&model.JobLog{}); err != nil {
return err
}
if len(model.FindAllTemp()) == 0 { if len(model.FindAllTemp()) == 0 {
for i := 0; i <= 30; i++ { for i := 0; i <= 30; i++ {
@ -197,24 +200,14 @@ func Run() error {
if err := model.CreateNewJob(&job); err != nil { if err := model.CreateNewJob(&job); err != nil {
return err return err
} }
} } else {
for i := range jobs {
jobs, err = model.FindJobByFunc(model.FuncDelTimeoutSessionJob) if jobs[i].Status == model.JobStatusRunning {
if err != nil { err := model.ChangeJobStatusById(jobs[i].ID, model.JobStatusRunning)
return err if err != nil {
} return err
if jobs == nil || len(jobs) == 0 { }
job := model.Job{ }
ID: utils.UUID(),
Name: "超时会话检测",
Func: model.FuncDelTimeoutSessionJob,
Cron: "0 0 0 * * ?",
Status: model.JobStatusRunning,
Created: utils.NowJsonTime(),
Updated: utils.NowJsonTime(),
}
if err := model.CreateNewJob(&job); err != nil {
return err
} }
} }

View File

@ -60,6 +60,14 @@ func JobChangeStatusEndpoint(c echo.Context) error {
return Success(c, "") return Success(c, "")
} }
func JobExecEndpoint(c echo.Context) error {
id := c.Param("id")
if err := model.ExecJobById(id); err != nil {
return err
}
return Success(c, "")
}
func JobDeleteEndpoint(c echo.Context) error { func JobDeleteEndpoint(c echo.Context) error {
ids := c.Param("id") ids := c.Param("id")
@ -84,3 +92,22 @@ func JobGetEndpoint(c echo.Context) error {
return Success(c, item) return Success(c, item)
} }
func JobGetLogsEndpoint(c echo.Context) error {
id := c.Param("id")
items, err := model.FindJobLogs(id)
if err != nil {
return err
}
return Success(c, items)
}
func JobDeleteLogsEndpoint(c echo.Context) error {
id := c.Param("id")
if err := model.DeleteJobLogByJobId(id); err != nil {
return err
}
return Success(c, "")
}

View File

@ -150,8 +150,11 @@ func SetupRoutes() *echo.Echo {
jobs.GET("/paging", JobPagingEndpoint) jobs.GET("/paging", JobPagingEndpoint)
jobs.PUT("/:id", JobUpdateEndpoint) jobs.PUT("/:id", JobUpdateEndpoint)
jobs.POST("/:id/change-status", JobChangeStatusEndpoint) jobs.POST("/:id/change-status", JobChangeStatusEndpoint)
jobs.POST("/:id/exec", JobExecEndpoint)
jobs.DELETE("/:id", JobDeleteEndpoint) jobs.DELETE("/:id", JobDeleteEndpoint)
jobs.GET("/:id", JobGetEndpoint) jobs.GET("/:id", JobGetEndpoint)
jobs.GET("/:id/logs", JobGetLogsEndpoint)
jobs.DELETE("/:id/logs", JobDeleteLogsEndpoint)
} }
return e return e

View File

@ -1,21 +1,61 @@
package handle package handle
import ( import (
"github.com/sirupsen/logrus"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/guacd" "next-terminal/pkg/guacd"
"next-terminal/pkg/model" "next-terminal/pkg/model"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
"os" "os"
"strconv"
"time"
) )
func RunTicker() { func RunTicker() {
// 每隔一小时删除一次未使用的会话信息 // 每隔一小时删除一次未使用的会话信息
_, _ = global.Cron.AddJob("0 0 0/1 * * ?", model.DelUnUsedSessionJob{}) _, _ = global.Cron.AddFunc("0 0 0/1 * * ?", func() {
// 每隔一小时检测一次资产状态 sessions, _ := model.FindSessionByStatusIn([]string{model.NoConnect, model.Connecting})
//_, _ = global.Cron.AddJob("0 0 0/1 * * ?", model.CheckAssetStatusJob{}) if sessions != nil && len(sessions) > 0 {
now := time.Now()
for i := range sessions {
if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 {
_ = model.DeleteSessionById(sessions[i].ID)
s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port)
logrus.Infof("会话「%v」ID「%v」超过1小时未打开已删除。", s, sessions[i].ID)
}
}
}
})
// 每日凌晨删除超过时长限制的会话 // 每日凌晨删除超过时长限制的会话
//_, _ = global.Cron.AddJob("0 0 0 * * ?", model.DelTimeoutSessionJob{}) _, _ = global.Cron.AddFunc("0 0 0 * * ?", func() {
property, err := model.FindPropertyByName("session-saved-limit")
if err != nil {
return
}
if property.Value == "" || property.Value == "-" {
return
}
limit, err := strconv.Atoi(property.Value)
if err != nil {
return
}
sessions, err := model.FindOutTimeSessions(limit)
if err != nil {
return
}
if sessions != nil && len(sessions) > 0 {
var sessionIds []string
for i := range sessions {
sessionIds = append(sessionIds, sessions[i].ID)
}
err := model.DeleteSessionByIds(sessionIds)
if err != nil {
logrus.Errorf("删除离线会话失败 %v", err)
}
}
})
global.Cron.Start() global.Cron.Start()
} }

View File

@ -2,11 +2,11 @@ package model
import ( import (
"errors" "errors"
"fmt"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"next-terminal/pkg/global" "next-terminal/pkg/global"
"next-terminal/pkg/utils" "next-terminal/pkg/utils"
"strconv"
"time" "time"
) )
@ -14,20 +14,19 @@ const (
JobStatusRunning = "running" JobStatusRunning = "running"
JobStatusNotRunning = "not-running" JobStatusNotRunning = "not-running"
FuncCheckAssetStatusJob = "check-asset-status-job" FuncCheckAssetStatusJob = "check-asset-status-job"
FuncDelUnUsedSessionJob = "del-unused-session-job"
FuncDelTimeoutSessionJob = "del-timeout-session-job"
) )
type Job struct { type Job struct {
ID string `gorm:"primary_key" json:"id"` ID string `gorm:"primary_key" json:"id"`
JobId int `json:"jobId"` CronJobId int `json:"cronJobId"`
Name string `json:"name"` Name string `json:"name"`
Func string `json:"func"` Func string `json:"func"`
Cron string `json:"cron"` Cron string `json:"cron"`
Status string `json:"status"` Status string `json:"status"`
Created utils.JsonTime `json:"created"` Metadata string `json:"metadata"`
Updated utils.JsonTime `json:"updated"` Created utils.JsonTime `json:"created"`
Updated utils.JsonTime `json:"updated"`
} }
func (r *Job) TableName() string { func (r *Job) TableName() string {
@ -78,7 +77,7 @@ func CreateNewJob(o *Job) (err error) {
if err != nil { if err != nil {
return err return err
} }
o.JobId = int(jobId) o.CronJobId = int(jobId)
} }
return global.DB.Create(o).Error return global.DB.Create(o).Error
@ -93,7 +92,7 @@ func UpdateJobById(o *Job, id string) (err error) {
} }
func UpdateJonUpdatedById(id string) (err error) { func UpdateJonUpdatedById(id string) (err error) {
err = global.DB.Where("id = ?", id).Update("updated = ?", utils.NowJsonTime()).Error err = global.DB.Updates(Job{ID: id, Updated: utils.NowJsonTime()}).Error
return return
} }
@ -103,7 +102,7 @@ func ChangeJobStatusById(id, status string) (err error) {
if err != nil { if err != nil {
return err return err
} }
if status == JobStatusNotRunning { if status == JobStatusRunning {
j, err := getJob(job.ID, job.Func) j, err := getJob(job.ID, job.Func)
if err != nil { if err != nil {
return err return err
@ -112,16 +111,29 @@ func ChangeJobStatusById(id, status string) (err error) {
if err != nil { if err != nil {
return err return err
} }
job.JobId = int(entryID) job.CronJobId = int(entryID)
return global.DB.Where("id = ?", id).Update("status = ?", JobStatusRunning).Error return global.DB.Updates(Job{ID: id, Status: JobStatusRunning}).Error
} else { } else {
global.Cron.Remove(cron.EntryID(job.JobId)) global.Cron.Remove(cron.EntryID(job.CronJobId))
return global.DB.Where("id = ?", id).Update("status = ?", JobStatusNotRunning).Error return global.DB.Updates(Job{ID: id, Status: JobStatusNotRunning}).Error
} }
} }
func ExecJobById(id string) (err error) {
job, err := FindJobById(id)
if err != nil {
return err
}
j, err := getJob(id, job.Func)
if err != nil {
return err
}
j.Run()
return nil
}
func FindJobById(id string) (o Job, err error) { func FindJobById(id string) (o Job, err error) {
err = global.DB.Where("id = ?").First(&o).Error err = global.DB.Where("id = ?", id).First(&o).Error
return return
} }
@ -131,21 +143,17 @@ func DeleteJobById(id string) error {
return err return err
} }
if job.Status == JobStatusRunning { if job.Status == JobStatusRunning {
if err := ChangeJobStatusById(JobStatusNotRunning, id); err != nil { if err := ChangeJobStatusById(id, JobStatusNotRunning); err != nil {
return err return err
} }
} }
return global.DB.Where("id = ?").Delete(Job{}).Error return global.DB.Where("id = ?", id).Delete(Job{}).Error
} }
func getJob(id, function string) (job cron.Job, err error) { func getJob(id, function string) (job cron.Job, err error) {
switch function { switch function {
case FuncCheckAssetStatusJob: case FuncCheckAssetStatusJob:
job = CheckAssetStatusJob{ID: id} job = CheckAssetStatusJob{ID: id}
case FuncDelUnUsedSessionJob:
job = DelUnUsedSessionJob{ID: id}
case FuncDelTimeoutSessionJob:
job = DelTimeoutSessionJob{ID: id}
default: default:
return nil, errors.New("未识别的任务") return nil, errors.New("未识别的任务")
} }
@ -159,71 +167,38 @@ type CheckAssetStatusJob struct {
func (r CheckAssetStatusJob) Run() { func (r CheckAssetStatusJob) Run() {
assets, _ := FindAllAsset() assets, _ := FindAllAsset()
if assets != nil && len(assets) > 0 { if assets != nil && len(assets) > 0 {
msgChan := make(chan string)
for i := range assets { for i := range assets {
asset := assets[i] asset := assets[i]
active := utils.Tcping(asset.IP, asset.Port) go func() {
UpdateAssetActiveById(active, asset.ID) t1 := time.Now()
logrus.Infof("资产「%v」ID「%v」存活状态检测完成存活「%v」。", asset.Name, asset.ID, active) active := utils.Tcping(asset.IP, asset.Port)
elapsed := time.Since(t1)
msg := fmt.Sprintf("资产「%v」存活状态检测完成存活「%v」耗时「%v」", asset.Name, active, elapsed)
UpdateAssetActiveById(active, asset.ID)
logrus.Infof(msg)
msgChan <- msg
}()
} }
}
if r.ID != "" {
_ = UpdateJonUpdatedById(r.ID)
}
}
type DelUnUsedSessionJob struct { if r.ID != "" {
ID string var message = ""
} for i := 0; i < len(assets); i++ {
message += <-msgChan + "\n"
func (r DelUnUsedSessionJob) Run() {
sessions, _ := FindSessionByStatusIn([]string{NoConnect, Connecting})
if sessions != nil && len(sessions) > 0 {
now := time.Now()
for i := range sessions {
if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 {
_ = DeleteSessionById(sessions[i].ID)
s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port)
logrus.Infof("会话「%v」ID「%v」超过1小时未打开已删除。", s, sessions[i].ID)
} }
_ = UpdateJonUpdatedById(r.ID)
jobLog := JobLog{
ID: utils.UUID(),
JobId: r.ID,
Timestamp: utils.NowJsonTime(),
Message: message,
}
_ = CreateNewJobLog(&jobLog)
} }
} }
if r.ID != "" {
_ = UpdateJonUpdatedById(r.ID)
}
}
type DelTimeoutSessionJob struct {
ID string
}
func (r DelTimeoutSessionJob) Run() {
property, err := FindPropertyByName("session-saved-limit")
if err != nil {
return
}
if property.Value == "" || property.Value == "-" {
return
}
limit, err := strconv.Atoi(property.Value)
if err != nil {
return
}
sessions, err := FindOutTimeSessions(limit)
if err != nil {
return
}
if sessions != nil && len(sessions) > 0 {
var sessionIds []string
for i := range sessions {
sessionIds = append(sessionIds, sessions[i].ID)
}
err := DeleteSessionByIds(sessionIds)
if err != nil {
logrus.Errorf("删除离线会话失败 %v", err)
}
}
if r.ID != "" {
_ = UpdateJonUpdatedById(r.ID)
}
} }

30
pkg/model/job_log.go Normal file
View File

@ -0,0 +1,30 @@
package model
import (
"next-terminal/pkg/global"
"next-terminal/pkg/utils"
)
type JobLog struct {
ID string `json:"id"`
Timestamp utils.JsonTime `json:"timestamp"`
JobId string `json:"jobId"`
Message string `json:"message"`
}
func (r *JobLog) TableName() string {
return "job_logs"
}
func CreateNewJobLog(o *JobLog) error {
return global.DB.Create(o).Error
}
func FindJobLogs(jobId string) (o []JobLog, err error) {
err = global.DB.Where("job_id = ?", jobId).Order("timestamp asc").Find(&o).Error
return
}
func DeleteJobLogByJobId(jobId string) error {
return global.DB.Where("job_id = ?", jobId).Delete(JobLog{}).Error
}

View File

@ -90,6 +90,6 @@
border-radius: 5%; border-radius: 5%;
} }
.monitor .ant-modal-body{ .modal-no-padding .ant-modal-body{
padding: 0; padding: 0;
} }

View File

@ -162,6 +162,14 @@ class App extends Component {
</Menu.Item> </Menu.Item>
</SubMenu> </SubMenu>
<SubMenu key='command-manage' title='指令管理' icon={<CodeOutlined/>}>
<Menu.Item key="dynamic-command" icon={<BlockOutlined/>}>
<Link to={'/dynamic-command'}>
动态指令
</Link>
</Menu.Item>
</SubMenu>
{ {
this.state.triggerMenu && isAdmin() ? this.state.triggerMenu && isAdmin() ?
<> <>
@ -177,6 +185,9 @@ class App extends Component {
历史会话 历史会话
</Link> </Link>
</Menu.Item> </Menu.Item>
</SubMenu>
<SubMenu key='ops' title='系统运维' icon={<ControlOutlined />}>
<Menu.Item key="login-log" icon={<LoginOutlined/>}> <Menu.Item key="login-log" icon={<LoginOutlined/>}>
<Link to={'/login-log'}> <Link to={'/login-log'}>
@ -184,25 +195,9 @@ class App extends Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
</SubMenu>
<SubMenu key='ops' title='系统运维' icon={<ControlOutlined />}>
<Menu.Item key="dynamic-command" icon={<CodeOutlined/>}>
<Link to={'/dynamic-command'}>
动态指令
</Link>
</Menu.Item>
{/*<Menu.Item key="silent-command" icon={<DeploymentUnitOutlined/>}>
<Link to={'/silent-command'}>
静默指令
</Link>
</Menu.Item>*/}
<Menu.Item key="job" icon={<BlockOutlined />}> <Menu.Item key="job" icon={<BlockOutlined />}>
<Link to={'/job'}> <Link to={'/job'}>
定时任务 计划任务
</Link> </Link>
</Menu.Item> </Menu.Item>
</SubMenu> </SubMenu>

View File

@ -377,10 +377,10 @@ class Access extends Component {
let width = window.innerWidth; let width = window.innerWidth;
let height = window.innerHeight; let height = window.innerHeight;
let dpi = 96; let dpi = Math.floor(window.devicePixelRatio * 96);
if (protocol === 'ssh' || protocol === 'telnet') { // if (protocol === 'ssh' || protocol === 'telnet') {
dpi = dpi * 2; // dpi = dpi * 2;
} // }
let token = getToken(); let token = getToken();

View File

@ -0,0 +1,12 @@
.cron-log {
overflow: auto;
border: 0 none;
line-height: 23px;
padding: 15px;
margin: 0;
white-space: pre-wrap;
height: 500px;
background-color: rgb(51, 51, 51);
color: #f1f1f1;
border-radius: 0;
}

View File

@ -2,7 +2,6 @@ import React, {Component} from 'react';
import { import {
Button, Button,
Checkbox,
Col, Col,
Divider, Divider,
Dropdown, Dropdown,
@ -13,7 +12,10 @@ import {
PageHeader, PageHeader,
Row, Row,
Space, Space,
Switch,
Table, Table,
Tag,
Timeline,
Tooltip, Tooltip,
Typography Typography
} from "antd"; } from "antd";
@ -30,14 +32,14 @@ import {
} from '@ant-design/icons'; } from '@ant-design/icons';
import {itemRender} from "../../utils/utils"; import {itemRender} from "../../utils/utils";
import Logout from "../user/Logout"; import Logout from "../user/Logout";
import {hasPermission} from "../../service/permission";
import dayjs from "dayjs"; import dayjs from "dayjs";
import JobModal from "./JobModal";
import './Job.css'
const confirm = Modal.confirm; const confirm = Modal.confirm;
const {Content} = Layout; const {Content} = Layout;
const {Title, Text} = Typography; const {Title, Text} = Typography;
const {Search} = Input; const {Search} = Input;
const CheckboxGroup = Checkbox.Group;
const routes = [ const routes = [
{ {
path: '', path: '',
@ -45,7 +47,7 @@ const routes = [
}, },
{ {
path: 'job', path: 'job',
breadcrumbName: '定时任务', breadcrumbName: '计划任务',
} }
]; ];
@ -64,7 +66,9 @@ class Job extends Component {
modalVisible: false, modalVisible: false,
modalTitle: '', modalTitle: '',
modalConfirmLoading: false, modalConfirmLoading: false,
selectedRowKeys: [] selectedRow: undefined,
selectedRowKeys: [],
logs: []
}; };
componentDidMount() { componentDidMount() {
@ -255,10 +259,34 @@ class Job extends Component {
</Tooltip> </Tooltip>
); );
} }
}, {
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status, record) => {
return <Switch checkedChildren="开启" unCheckedChildren="关闭" checked={status === 'running'}
onChange={async (checked) => {
let jobStatus = checked ? 'running' : 'not-running';
let result = await request.post(`/jobs/${record['id']}/change-status?status=${jobStatus}`);
if (result['code'] === 1) {
message.success('操作成功');
await this.loadTableData();
} else {
message.error(result['message']);
}
}}
/>
}
}, { }, {
title: '任务类型', title: '任务类型',
dataIndex: 'func', dataIndex: 'func',
key: 'func' key: 'func',
render: (func, record) => {
switch (func) {
case "check-asset-status-job":
return <Tag color="green">资产状态检测</Tag>
}
}
}, { }, {
title: 'cron表达式', title: 'cron表达式',
dataIndex: 'cron', dataIndex: 'cron',
@ -275,7 +303,7 @@ class Job extends Component {
) )
} }
}, { }, {
title: '更新日期', title: '最后执行日期',
dataIndex: 'updated', dataIndex: 'updated',
key: 'updated', key: 'updated',
render: (text, record) => { render: (text, record) => {
@ -288,20 +316,38 @@ class Job extends Component {
}, { }, {
title: '操作', title: '操作',
key: 'action', key: 'action',
render: (text, record) => { render: (text, record, index) => {
const menu = ( const menu = (
<Menu> <Menu>
<Menu.Item key="0"> <Menu.Item key="0">
<Button type="text" size='small' <Button type="text" size='small'
disabled={!hasPermission(record['owner'])} onClick={() => this.showModal('更新计划任务', record)}>编辑</Button>
onClick={() => this.showModal('更新指令', record)}>编辑</Button> </Menu.Item>
<Menu.Item key="2">
<Button type="text" size='small'
onClick={async () => {
this.setState({
logVisible: true,
logPending: '正在加载...'
})
let result = await request.get(`/jobs/${record['id']}/logs`);
if (result['code'] === 1) {
this.setState({
logPending: false,
logs: result['data'],
selectedRow: record
})
}
}}>日志</Button>
</Menu.Item> </Menu.Item>
<Menu.Divider/> <Menu.Divider/>
<Menu.Item key="3"> <Menu.Item key="3">
<Button type="text" size='small' danger <Button type="text" size='small' danger
disabled={!hasPermission(record['owner'])}
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button> onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
</Menu.Item> </Menu.Item>
</Menu> </Menu>
@ -309,9 +355,26 @@ class Job extends Component {
return ( return (
<div> <div>
<Button type="link" size='small' onClick={async () => { <Button type="link" size='small' loading={this.state.items[index]['execLoading']}
onClick={async () => {
let items = this.state.items;
items[index]['execLoading'] = true;
this.setState({
items: items
});
}}>执行</Button> let result = await request.post(`/jobs/${record['id']}/exec`);
if (result['code'] === 1) {
message.success('执行成功');
await this.loadTableData();
} else {
message.error(result['message']);
items[index]['execLoading'] = false;
this.setState({
items: items
});
}
}}>执行</Button>
<Dropdown overlay={menu}> <Dropdown overlay={menu}>
<Button type="link" size='small'> <Button type="link" size='small'>
@ -441,6 +504,65 @@ class Job extends Component {
loading={this.state.loading} loading={this.state.loading}
/> />
{
this.state.modalVisible ?
<JobModal
visible={this.state.modalVisible}
title={this.state.modalTitle}
handleOk={this.handleOk}
handleCancel={this.handleCancelModal}
confirmLoading={this.state.modalConfirmLoading}
model={this.state.model}
>
</JobModal> : undefined
}
{
this.state.logVisible ?
<Modal
className='modal-no-padding'
width={window.innerWidth * 0.8}
title={'日志'}
visible={true}
maskClosable={false}
centered={true}
onOk={async () => {
let result = await request.delete(`/jobs/${this.state.selectedRow['id']}/logs`);
if (result['code'] === 1) {
this.setState({
logVisible: false,
selectedRow: undefined
})
message.success('日志清空成功');
} else {
message.error(result['message'], 10);
}
}}
onCancel={() => {
this.setState({
logVisible: false,
selectedRow: undefined
})
}}
okText='清空'
okType={'danger'}
cancelText='取消'
>
<Timeline pending={this.state.logPending} mode={'left'}>
<pre className='cron-log'>
{
this.state.logs.map(item => {
return <><Divider
orientation="left"
style={{color: 'white'}}>{item['timestamp']}</Divider>{item['message']}</>;
})
}
</pre>
</Timeline>
</Modal> : undefined
}
</Content> </Content>
</> </>
); );

View File

@ -0,0 +1,62 @@
import React from 'react';
import {Form, Input, Modal, Radio, Select} from "antd/lib/index";
const JobModal = ({title, visible, handleOk, handleCancel, confirmLoading, model}) => {
const [form] = Form.useForm();
const formItemLayout = {
labelCol: {span: 6},
wrapperCol: {span: 14},
};
return (
<Modal
title={title}
visible={visible}
maskClosable={false}
centered={true}
onOk={() => {
form
.validateFields()
.then(values => {
form.resetFields();
handleOk(values);
})
.catch(info => {
});
}}
onCancel={handleCancel}
confirmLoading={confirmLoading}
okText='确定'
cancelText='取消'
>
<Form form={form} {...formItemLayout} initialValues={model}>
<Form.Item name='id' noStyle>
<Input hidden={true}/>
</Form.Item>
<Form.Item label="任务类型" name='func' rules={[{required: true, message: '请选择任务类型'}]}>
<Select onChange={(value) => {
}}>
<Select.Option value="shell">Shell脚本</Select.Option>
<Select.Option value="check-asset-status-job">资产状态检测</Select.Option>
</Select>
</Form.Item>
<Form.Item label="任务名称" name='name' rules={[{required: true, message: '请输入任务名称'}]}>
<Input autoComplete="off" placeholder="请输入任务名称"/>
</Form.Item>
<Form.Item label="cron表达式" name='cron' rules={[{required: true, message: '请输入cron表达式'}]}>
<Input placeholder="请输入cron表达式"/>
</Form.Item>
</Form>
</Modal>
)
};
export default JobModal;

View File

@ -477,7 +477,7 @@ class OfflineSession extends Component {
{ {
this.state.playbackVisible ? this.state.playbackVisible ?
<Modal <Modal
className='monitor' className='modal-no-padding'
title="会话回放" title="会话回放"
centered={true} centered={true}
visible={this.state.playbackVisible} visible={this.state.playbackVisible}

View File

@ -473,7 +473,7 @@ class OnlineSession extends Component {
{ {
this.state.accessVisible ? this.state.accessVisible ?
<Modal <Modal
className='monitor' className='modal-no-padding'
title={this.state.sessionTitle} title={this.state.sessionTitle}
centered={true} centered={true}
maskClosable={false} maskClosable={false}