!71 增加功能:增加任务管理可视化管理功能

Merge pull request !71 from Argo/dev
This commit is contained in:
Argo 2020-02-19 16:11:52 +08:00 committed by Gitee
commit 9eff344f7e
7 changed files with 167 additions and 86 deletions

View File

@ -1,7 +1,9 @@
using Bootstrap.DataAccess;
using Longbow;
using Longbow.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
@ -44,5 +46,53 @@ namespace Bootstrap.Admin.Controllers.Api
}
return true;
}
/// <summary>
/// 保存任务方法
/// </summary>
/// <returns></returns>
[HttpPost]
public bool Post([FromBody]TaskWidget widget)
{
// 判断 Cron 表达式
if (string.IsNullOrEmpty(widget.CronExpression)) return false;
if (_tasks.Any(t => t.Equals(widget.Name, StringComparison.OrdinalIgnoreCase))) return false;
// 加载任务执行体
// 此处可以扩展为任意 DLL 中的任意继承 ITask 接口的实体类
var taskExecutor = LgbActivator.CreateInstance<ITask>("Bootstrap.Admin", widget.TaskExecutorName);
if (taskExecutor == null) return false;
// 此处未存储到数据库中,直接送入任务中心
TaskServicesManager.Remove(widget.Name);
var expression = Longbow.Tasks.Cron.ParseCronExpression(widget.CronExpression);
TaskServicesManager.GetOrAdd(widget.Name, token => taskExecutor.Execute(token), TriggerBuilder.Build(widget.CronExpression));
return true;
}
private static IEnumerable<string> _tasks = new string[] {
"单次任务",
"周期任务",
"Cron 任务",
"超时任务",
"取消任务",
"禁用任务",
"SQL日志"
};
/// <summary>
/// 删除任务方法
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[HttpDelete]
public bool Delete([FromBody]IEnumerable<string> ids)
{
// 演示模式下禁止删除内置任务
if (DictHelper.RetrieveSystemModel() && _tasks.Any(t => ids.Any(id => id.Equals(t, StringComparison.OrdinalIgnoreCase)))) return false;
// 循环删除任务
ids.ToList().ForEach(id => TaskServicesManager.Remove(id));
return true;
}
}
}

View File

@ -4,22 +4,39 @@ using System.Collections.Generic;
namespace Bootstrap.Admin.Models
{
/// <summary>
///
/// 任务管理页面 Model 类
/// </summary>
public class TaskModel : NavigatorBarModel
{
/// <summary>
///
/// 构造函数
/// </summary>
/// <param name="controller"></param>
public TaskModel(ControllerBase controller) : base(controller)
{
Tasks = new string[] { "测试任务" };
// 此处为演示代码,具体生产环境可以从数据库配置获得
// Key 为任务名称 Value 为任务执行体 FullName
TaskExecutors = new Dictionary<string, string>
{
{ "测试任务", "Bootstrap.Admin.DefaultTaskExecutor" }
};
TaskTriggers = new Dictionary<string, string>
{
{ "每 5 秒钟执行一次", Longbow.Tasks.Cron.Secondly(5) },
{ "每 1 分钟执行一次", Longbow.Tasks.Cron.Minutely(1) }
};
}
/// <summary>
/// 获得 系统配置的所有任务
/// 获得 系统置的所有任务
/// </summary>
public IEnumerable<string> Tasks { get; }
public IDictionary<string, string> TaskExecutors { get; }
/// <summary>
/// 获得 系统内置触发器集合
/// </summary>
/// <value></value>
public IDictionary<string, string> TaskTriggers { get; }
}
}

View File

@ -0,0 +1,19 @@
using System.Threading;
using System.Threading.Tasks;
using Longbow.Tasks;
namespace Bootstrap.Admin
{
/// <summary>
/// 默认任务执行体实体类
/// </summary>
public class DefaultTaskExecutor : ITask
{
/// <summary>
/// 任务执行方法
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task Execute(CancellationToken cancellationToken) => Task.Delay(1000, cancellationToken);
}
}

View File

@ -0,0 +1,23 @@
namespace Bootstrap.Admin
{
/// <summary>
/// 任务描述类
/// </summary>
public class TaskWidget
{
/// <summary>
/// 获得/设置 任务名称
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// 获得/设置 任务执行体名称
/// </summary>
public string TaskExecutorName { get; set; } = "";
/// <summary>
/// 获得/设置 Cron 任务表达式
/// </summary>
public string CronExpression { get; set; } = "";
}
}

View File

@ -1,86 +1,61 @@
@model TaskModel
@{
ViewBag.Title = "任务管理";
Layout = "_Admin";
Layout = "_Default";
}
@section css {
<environment include="Development">
<link href="~/lib/bootstrap-table/bootstrap-table.css" rel="stylesheet" />
</environment>
<environment exclude="Development">
<link href="~/lib/bootstrap-table/bootstrap-table.min.css" rel="stylesheet" />
</environment>
<link href="~/lib/longbow-select/longbow-select.css" rel="stylesheet" />
<link href="~/css/tasks.css" rel="stylesheet" asp-append-version="true" />
}
@section javascript {
<environment include="Development">
<script src="~/lib/bootstrap-table/bootstrap-table.js"></script>
<script src="~/lib/bootstrap-table/extensions/export/bootstrap-table-export.js"></script>
<script src="~/lib/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script>
<script src="~/lib/tablexport/tableExport.js"></script>
<script src="~/lib/validate/jquery.validate.js"></script>
<script src="~/lib/validate/localization/messages_zh.js"></script>
</environment>
<environment exclude="Development">
<script src="~/lib/bootstrap-table/bootstrap-table.min.js"></script>
<script src="~/lib/bootstrap-table/extensions/export/bootstrap-table-export.min.js"></script>
<script src="~/lib/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
<script src="~/lib/tablexport/tableExport.min.js"></script>
<script src="~/lib/validate/jquery.validate.min.js"></script>
<script src="~/lib/validate/localization/messages_zh.min.js"></script>
</environment>
<script src="~/lib/longbow-checkbox/longbow-checkbox.js"></script>
<script src="~/lib/longbow-select/longbow-select.js"></script>
<script src="~/lib/longbow/longbow.dataentity.js" asp-append-version="true"></script>
<script src="~/lib/longbow/longbow.validate.js" asp-append-version="true"></script>
<script src="~/js/tasks.js" asp-append-version="true"></script>
}
@section tableButtons {
<button class='pause btn btn-sm btn-warning' asp-auth="pause"><i class='fa fa-pause-circle'></i><span>暂停</span></button>
<button class='run btn btn-sm btn-success' asp-auth="pause"><i class='fa fa-play-circle'></i><span>运行</span></button>
<button class='info btn btn-sm btn-info' asp-auth="info"><i class='fa fa-info-circle'></i><span>日志</span></button>
}
@section modal {
<div class="modal fade" id="dialogNew" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content" data-toggle="LgbValidate" data-valid-button="#btnSubmit" data-valid-modal="#dialogNew">
<div class="modal-header">
<h5 class="modal-title" id="myModalLabel">任务编辑窗口</h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<div class="modal-header">
<h5 class="modal-title" id="myModalLabel">任务编辑窗口</h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<div class="alert alert-danger" role="alert" asp-condition="@Model.IsDemo">
<span>演示系统禁止修改内置后台任务</span>
</div>
<form class="form-inline">
<div class="row">
<div class="form-group col-12">
<label class="control-label" for="taskName">任务名称</label>
<input type="text" class="form-control flex-sm-fill" id="taskName" placeholder="不可为空50字以内" maxlength="50" data-valid="true" />
</div>
<div class="modal-body">
<form class="form-inline">
<div class="row">
<input type="hidden" id="taskID" />
<div class="form-group col-12">
<label class="control-label" for="taskName">任务名称</label>
<input type="text" class="form-control flex-sm-fill" id="taskName" placeholder="不可为空50字以内" maxlength="50" data-valid="true" />
</div>
<div class="form-group col-12">
<label class="control-label" for="taskCron">Cron表达式</label>
<input type="text" class="form-control flex-sm-fill" id="taskCron" placeholder="不可为空2000字以内" maxlength="2000" data-valid="true" />
</div>
<div class="form-group col-12">
<label class="control-label" for="taskContent">内置任务</label>
<select id="taskList" data-toggle="lgbSelect">
@foreach (var name in Model.Tasks)
{
<option value="@name">@name</option>
}
</select>
</div>
</div>
</form>
<div class="form-group col-12">
<label class="control-label" for="taskCron">Cron表达式</label>
<input class="form-control" data-toggle="lgbSelect" />
<select data-toggle="lgbSelect" class="d-none" id="taskCron" data-default-val="*/5 * * * * *">
@foreach (var task in Model.TaskTriggers)
{
<option value="@task.Value">@task.Key</option>
}
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i class="fa fa-times"></i>
<span>关闭</span>
</button>
<button type="button" class="btn btn-primary" id="btnSubmit">
<i class="fa fa-save"></i>
<span>保存</span>
</button>
<div class="form-group col-12">
<label class="control-label" for="taskContent">内置任务</label>
<input class="form-control" data-toggle="lgbSelect" />
<select data-toggle="lgbSelect" class="d-none" id="taskExecutor" data-default-val="Bootstrap.Admin.DefaultTaskExecutor">
@foreach (var task in Model.TaskExecutors)
{
<option value="@task.Value">@task.Key</option>
}
</select>
</div>
</div>
</div>
</form>
</div>
}
@section customModal {
<div class="modal fade" id="dialogLog" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="taskModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content">
@ -143,10 +118,3 @@
<table></table>
</div>
</div>
<div id="tableButtons" class="d-none">
<div class='btn-group'>
<button class='pause btn btn-sm btn-danger' asp-auth="pause"><i class='fa fa-pause-circle'></i><span>暂停</span></button>
<button class='run btn btn-sm btn-success' asp-auth="pause"><i class='fa fa-play-circle'></i><span>运行</span></button>
<button class='info btn btn-sm btn-info' asp-auth="info"><i class='fa fa-info-circle'></i><span>日志</span></button>
</div>
</div>

View File

@ -37,11 +37,13 @@
url: Tasks.url,
dataBinder: {
map: {
Id: "#taskID",
Name: "#taskName"
CronExpression: "#taskCron",
Name: "#taskName",
TaskExecutorName: "#taskExecutor"
}
},
smartTable: {
idField: 'Name',
sidePagination: "client",
sortName: 'Name',
sortOrder: 'asc',

View File

@ -250,14 +250,16 @@
var idField = findIdField(op.table);
var idValue = row[idField];
var nodes = op.table.bootstrapTable('getData').filter(function (row, index, data) {
return idValue == row[op.treegridParentId];
});
if ($.isArray(nodes) && nodes.length > 0) {
$.each(nodes, function (index, element) {
data.push($.extend({}, element));
if (idValue != undefined) {
var nodes = op.table.bootstrapTable('getData').filter(function (row, index, data) {
return idValue == row[op.treegridParentId];
});
text = "本删除项含有级联子项目</br>您确定要删除 <span class='text-danger font-weight-bold'>" + row.Name + "</span> 以及子项目吗?";
if ($.isArray(nodes) && nodes.length > 0) {
$.each(nodes, function (index, element) {
data.push($.extend({}, element));
});
text = "本删除项含有级联子项目</br>您确定要删除 <span class='text-danger font-weight-bold'>" + row.Name + "</span> 以及子项目吗?";
}
}
swal($.extend({}, swalDeleteOptions, { html: text })).then(function (result) {
if (result.value) {