commit
9eff344f7e
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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; } = "";
|
||||
}
|
||||
}
|
|
@ -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">×</button>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="myModalLabel">任务编辑窗口</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</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>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue