diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 91106d3f..3231d1a9 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -1,6 +1,10 @@ name: Java CI -on: [push] +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] jobs: build: @@ -8,10 +12,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Build with Maven - run: mvn -B package --file pom.xml + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B package --file pom.xml diff --git a/README.md b/README.md index ba876bd6..dbeb4a10 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ - - + + @@ -46,10 +46,23 @@ XXL-JOB is an open source and free project, with its ongoing development made po XXL-JOB 是一个开源且免费项目,其正在进行的开发完全得益于支持者的支持。开源不易,[前往赞助项目开发](https://www.xuxueli.com/page/donate.html ) -
金牌赞助方
-
- - +

金牌赞助方

+ + + + + +
+ + +
+ 阿里云 提供云上托管 XXL-JOB +
+
+ + + +
@@ -93,13 +106,15 @@ XXL-JOB 是一个开源且免费项目,其正在进行的开发完全得益于 - 28、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; - 29、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等; - 30、全异步:任务调度流程全异步化设计实现,如异步调度、异步运行、异步回调等,有效对密集调度进行流量削峰,理论上支持任意时长任务的运行; -- 31、跨语言:调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案; +- 31、跨语言/OpenAPI:调度中心与执行器提供语言无关的 OpenApi(RESTful 格式),第三方任意语言可据此对接调度中心或者实现执行器,实现多语言支持。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案; - 32、国际化:调度中心支持国际化设置,提供中文、英文两种可选语言,默认为中文; - 33、容器化:提供官方docker镜像,并实时更新推送dockerhub,进一步实现产品开箱即用; - 34、线程池隔离:调度线程池进行隔离拆分,慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性; - 35、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色; - 36、权限控制:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作; - +- 37、AI任务:原生提供AI执行器,并内置多个AI任务Handler,与spring-ai、ollama、dify等集成打通,支持快速开发AI类任务。 +- 38、审计日志:记录任务操作敏感信息,用于系统监控、审计和安全分析,可快速追溯异常行为以及定位排查问题。 +- 39、优雅停机:调度中心停机,检测时间轮非空时主动等待调度完成;客户端停机,检测存在运行中任务时,停止接收新任务并主动等待任务执行完成; ## Development 于2015年中,我在github上创建XXL-JOB项目仓库并提交第一个commit,随之进行系统结构设计,UI选型,交互设计…… @@ -827,6 +842,41 @@ XXL-JOB 是一个开源且免费项目,其正在进行的开发完全得益于 - 686、广州博依特智能信息科技有限公司 - 687、河南宠呦呦信息技术有限公司 - 688、陕西星邑空间技术有限公司 + - 689、广东西欧克实业有限公司 + - 690、唱吧麦颂KTV + - 691、联通云 + - 692、北京爱话本科技有限公司 + - 693、北京起创科技有限公司 + - 694、平安证券【平安证券】 + - 695、合肥中科类脑智能技术有限公司 + - 696、南京同仁堂健康产业有限公司【同仁堂】 + - 697、铜仁市碧江区智惠加油站 + - 698、惟客数据 + - 699、凤凰新闻【凤凰新闻】 + - 700、深圳王力智能 + - 701、返利网数字科技股份有限公司 + - 702、上海阜能信息科技有限公司 + - 703、深圳市极能超电数字科技有限公司 + - 704、海目星激光科技集团股份有限公司 + - 705、安克创新科技股份有限公司【安克】 + - 706、大庆点神科技有限公司 + - 707、浙江零跑科技股份有限公司【零跑】 + - 708、成都成电金盘健康数据技术有限公司 + - 709、成都极米科技股份有限公司【极米】 + - 710、顺德职业技术大学 + - 711、中邮证券有限责任公司【中邮证券】 + - 712、志豪链云科技有限公司 + - 713、湖南万鲸科技有限公司 + - 714、广州万表 + - 715、再惠(上海)网络科技有限公司 + - 716、上海爱诚裕信息科技有限公司 + - 717、杭州迈瑞数字科技有限公司 + - 718、广州串联网络科技有限公司 + - 719、上海乐研化学试剂 + - 720、智现未来 + - 721、大庆点神科技有限公司 + - 722、深圳市中科环球科技有限公司 + - 723、江苏金箭车业制造有限公司 - …… > 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。 diff --git a/doc/XXL-JOB-English-Documentation.md b/doc/XXL-JOB-English-Documentation.md index 792f3ead..051e195b 100644 --- a/doc/XXL-JOB-English-Documentation.md +++ b/doc/XXL-JOB-English-Documentation.md @@ -1,10 +1,10 @@ ## 《Distributed task scheduling framework XXL-JOB》 -[![Actions Status](https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg)](https://github.com/xuxueli/xxl-job/actions) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/) +[![Build Status](https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg)](https://github.com/xuxueli/xxl-job/actions) +[![Maven Central](https://img.shields.io/maven-central/v/com.xuxueli/xxl-job-core)](https://central.sonatype.com/artifact/com.xuxueli/xxl-job-core) [![GitHub release](https://img.shields.io/github/release/xuxueli/xxl-job.svg)](https://github.com/xuxueli/xxl-job/releases) [![GitHub stars](https://img.shields.io/github/stars/xuxueli/xxl-job)](https://github.com/xuxueli/xxl-job/) -[![Docker Status](https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin)](https://hub.docker.com/r/xuxueli/xxl-job-admin/) +[![Docker pulls](https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin)](https://hub.docker.com/r/xuxueli/xxl-job-admin/) [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0.html) [![donate](https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat)](https://www.xuxueli.com/page/donate.html) @@ -335,7 +335,452 @@ So far, XXL-JOB has access to a number of companies online product line, access - 275、卡思优派人力资源集团 - 276、南京观为智慧软件科技有限公司 - 277、杭州城市大脑科技有限公司 - - 278、猿辅导 + - 278、猿辅导【猿辅导】 + - 279、洛阳健创网络科技有限公司 + - 280、魔力耳朵 + - 281、亿阳信通 + - 282、上海招鲤科技有限公司 + - 283、四川商旅无忧科技服务有限公司 + - 284、UU跑腿 + - 285、北京老虎证券【老虎证券】 + - 286、悠活省吧(北京)网络科技有限公司 + - 287、F5未来商店 + - 288、深圳环阳通信息技术有限公司 + - 289、遠傳電信 + - 290、作业帮(北京)教育科技有限公司【作业帮】 + - 291、成都科鸿智信科技有限公司 + - 292、北京木屋时代科技有限公司 + - 293、大学通(哈尔滨)科技有限责任公司 + - 294、浙江华坤道威数据科技有限公司 + - 295、吉祥航空【吉祥航空】 + - 296、南京圆周网络科技有限公司 + - 297、广州市洋葱omall电子商务 + - 298、天津联物科技有限公司 + - 299、跑哪儿科技(北京)有限公司 + - 300、深圳市美西西餐饮有限公司(喜茶) + - 301、平安不动产有限公司【平安】 + - 302、江苏中海昇物联科技有限公司 + - 303、湖南牙医帮科技有限公司 + - 304、重庆民航凯亚信息技术有限公司(易通航) + - 305、递易(上海)智能科技有限公司 + - 306、亚朵 + - 307、浙江新课堂教育股份有限公司 + - 308、北京蜂创科技有限公司 + - 309、德一智慧城市信息系统有限公司 + - 310、北京翼点科技有限公司 + - 311、湖南智数新维度信息科技有限公司 + - 312、北京玖扬博文文化发展有限公司 + - 313、上海宇珩信息科技有限公司 + - 314、全景智联(武汉)科技有限公司 + - 315、天津易客满国际物流有限公司 + - 316、南京爱福路汽车科技有限公司 + - 317、我房旅居集团 + - 318、湛江亲邻科技有限公司 + - 319、深圳市姜科网络有限公司 + - 320、青岛日日顺物流有限公司 + - 321、南京太川信息技术有限公司 + - 322、美图之家科技优先公司【美图】 + - 323、南京太川信息技术有限公司 + - 324、众薪科技(北京)有限公司 + - 325、武汉安安物联科技有限公司 + - 326、北京智客朗道网络科技有限公司 + - 327、深圳市超级猩猩健身管理管理有限公司 + - 328、重庆达志科技有限公司 + - 329、上海享评信息科技有限公司 + - 330、薪得付信息科技 + - 331、跟谁学 + - 332、中道(苏州)旅游网络科技有限公司 + - 333、广州小卫科技有限公司 + - 334、上海非码网络科技有限公司 + - 335、途家网网络技术(北京)有限公司【途家】 + - 336、广州辉凡信息科技有限公司 + - 337、天维尔信息科技股份有限公司 + - 338、上海极豆科技有限公司 + - 339、苏州触达信息技术有限公司 + - 340、北京热云科技有限公司 + - 341、中智企服(北京)科技有限公司 + - 342、易联云计算(杭州)有限责任公司 + - 343、青岛航空股份有限公司【青岛航空】 + - 344、山西博睿通科技有限公司 + - 345、网易杭州网络有限公司【网易】 + - 346、北京果果乐学科技有限公司 + - 347、百望股份有限公司 + - 348、中保金服(深圳)科技有限公司 + - 349、天津运友物流科技股份有限公司 + - 350、广东创能科技股份有限公司 + - 351、上海倚博信息科技有限公司 + - 352、深圳百果园实业(集团)股份有限公司 + - 353、广州细刻网络科技有限公司 + - 354、武汉鸿业众创科技有限公司 + - 355、金锡科技(广州)有限公司 + - 356、易瑞国际电子商务有限公司 + - 357、奇点云 + - 358、中视信息科技有限公司 + - 359、开源项目:datax-web + - 360、云知声智能科技股份有限公司 + - 361、开源项目:bboss + - 362、成都深驾科技有限公司 + - 363、FunPlus【趣加】 + - 364、杭州创匠信科技有限公司 + - 365、龙匠(北京)科技发展有限公司 + - 366、广州一链通互联网科技有限公司 + - 367、上海星艾网络科技有限公司 + - 368、虎博网络技术(上海)有限公司 + - 369、青岛优米信息技术有限公司 + - 370、八维通科技有限公司 + - 371、烟台合享智星数据科技有限公司 + - 372、东吴证券股份有限公司 + - 373、中通云仓股份有限公司【中通】 + - 374、北京加菲猫科技有限公司 + - 375、北京匠心演绎科技有限公司 + - 376、宝贝走天下 + - 377、厦门众库科技有限公司 + - 378、海通证券数据中心 + - 389、湖南快乐通宝小额贷款有限公司 + - 380、浙江大华技术股份有限公司 + - 381、杭州魔筷科技有限公司 + - 382、青岛掌讯通区块链科技有限公司 + - 383、新大陆金融科技 + - 384、常州玺拓软件科技有限公司 + - 385、北京正保网格教育科技有限公司 + - 386、统一企业(中国)投资有限公司【统一】 + - 387、微革网络科技有限公司 + - 388、杭州融易算科技有限公司 + - 399、青岛上啥班网络科技有限公司 + - 390、京东酒世界 + - 391、杭州爱博仕科技有限公司 + - 392、五星金服控股有限公司 + - 393、福建乐摩物联科技有限公司 + - 394、百炼智能科技有限公司 + - 395、山东能源数智云科技有限公司 + - 396、招商局能源运输股份有限公司 + - 397、三一集团【三一】 + - 398、东巴文(深圳)健康管理有限公司 + - 399、索易软件 + - 400、深圳市宁远科技有限公司 + - 401、熙牛医疗 + - 402、南京智鹤电子科技有限公司 + - 403、嘀嗒出行【嘀嗒出行】 + - 404、广州虎牙信息科技有限公司【虎牙】 + - 405、广州欧莱雅百库网络科技有限公司【欧莱雅】 + - 406、微微科技有限公司 + - 407、我爱我家房地产经纪有限公司【我爱我家】 + - 408、九号发现 + - 409、薪人薪事 + - 410、武汉氪细胞网络技术有限公司 + - 411、广州市斯凯奇商业有限公司 + - 412、微淼商学院 + - 413、杭州车盛科技有限公司 + - 414、深兰科技(上海)有限公司 + - 415、安徽中科美络信息技术有限公司 + - 416、比亚迪汽车工业有限公司【比亚迪】 + - 417、湖南小桔信息技术有限公司 + - 418、安徽科大国创软件科技有限公司 + - 419、克而瑞 + - 420、陕西云基华海信息技术有限公司 + - 421、安徽深宁科技有限公司 + - 422、广东康爱多数字健康有限公司 + - 423、嘉里电子商务 + - 424、上海时代光华教育发展有限公司 + - 425、CityDo + - 426、上海禹知信息科技有限公司 + - 427、广东智瑞科技有限公司 + - 428、西安爱铭网络科技有限公司 + - 429、心医国际数字医疗系统(大连)有限公司 + - 430、乐其电商 + - 431、锐达科技 + - 432、天津长城滨银汽车金融有限公司 + - 433、代码网 + - 434、东莞市东城乔伦软件开发工作室 + - 435、浙江百应科技有限公司 + - 436、上海力爱帝信息技术有限公司(Red E) + - 437、云徙科技有限公司 + - 438、北京康智乐思网络科技有限公司【大姨吗APP】 + - 439、安徽开元瞬视科技有限公司 + - 440、立方 + - 441、厦门纵行科技 + - 442、乐山-菲尼克斯半导体有限公司 + - 443、武汉光谷联合集团有限公司 + - 444、上海金仕达软件科技有限公司 + - 445、深圳易世通达科技有限公司 + - 446、爱动超越人工智能科技(北京)有限责任公司 + - 447、迪普信(北京)科技有限公司 + - 448、掌站科技(北京)有限公司 + - 449、深圳市华云中盛股份有限公司 + - 450、上海原圈科技有限公司 + - 451、广州赞赏信息科技有限公司 + - 452、Amber Group + - 453、德威国际货运代理(上海)公司 + - 454、浙江杰夫兄弟智慧科技有限公司 + - 455、信也科技 + - 456、开思时代科技(深圳)有限公司 + - 457、大连槐德科技有限公司 + - 458、同程生活 + - 459、松果出行 + - 460、企鹅杏仁集团 + - 461、宁波科云信息科技有限公司 + - 462、上海格蓝威驰信息科技有限公司 + - 463、杭州趣淘鲸科技有限公司 + - 464、湖州市数字惠民科技有限公司 + - 465、乐普(北京)医疗器械股份有限公司 + - 466、广州市晴川高新技术开发有限公司 + - 467、山西缇客科技有限公司 + - 468、徐州卡西穆电子商务有限公司 + - 469、格创东智科技有限公司 + - 470、世纪龙信息网络有限责任公司 + - 471、邦道科技有限公司 + - 472、河南中盟新云科技股份有限公司 + - 473、横琴人寿保险有限公司 + - 474、上海海隆华钟信息技术有限公司 + - 475、上海久湛 + - 476、上海仙豆智能机器人有限公司 + - 477、广州汇尚网络科技有限公司 + - 478、深圳市阿卡索资讯股份有限公司 + - 479、青岛佳家康健康管理有限责任公司 + - 480、蓝城兄弟 + - 481、成都天府通金融服务股份有限公司 + - 482、深圳云镖网络科技有限公司 + - 483、上海影创科技 + - 484、成都艾拉物联 + - 485、北京客邻尚品网络技术有限公司 + - 486、IT实战联盟 + - 487、杭州尤拉夫科技有限公司 + - 488、中大检测(湖南)股份有限公司 + - 489、江苏电老虎工业互联网股份有限公司 + - 490、上海助通信息科技有限公司 + - 491、北京符节科技有限公司 + - 492、杭州英祐科技有限公司 + - 493、江苏电老虎工业互联网股份有限公司 + - 494、深圳市点猫科技有限公司 + - 495、杭州天音 + - 496、深圳市二十一科技互联网有限公司 + - 497、海南海口翎度科技 + - 498、北京小趣智品科技有限公司 + - 499、广州石竹计算机软件有限公司 + - 500、深圳市惟客数据科技有限公司 + - 501、中国医疗器械有限公司 + - 502、上海云谦科技有限公司 + - 503、上海磐农信息科技有限公司 + - 504、广州领航食品有限公司 + - 505、青岛掌讯通区块链科技有限公司 + - 506、北京新网数码信息技术有限公司 + - 507、超体信息科技(深圳)有限公司 + - 508、长沙店帮手信息科技有限公司 + - 509、上海助弓装饰工程有限公司 + - 510、杭州寻联网络科技有限公司 + - 511、成都大淘客科技有限公司 + - 512、松果出行 + - 513、深圳市唤梦科技有限公司 + - 514、上汽集团商用车技术中心 + - 515、北京中航讯科技股份有限公司 + - 516、北龙中网(北京)科技有限责任公司 + - 517、前海超级前台(深圳)信息技术有限公司 + - 518、上海中商网络股份有限公司 + - 519、上海助通信息科技有限公司 + - 520、宁波聚臻智能科技有限公司 + - 521、上海零动数码科技股份有限公司 + - 522、浙江学海教育科技有限公司 + - 523、聚学云(山东)信息技术有限公司 + - 524、多氟多新材料股份有限公司 + - 525、智慧眼科技股份有限公司 + - 526、广东智通人才连锁股份有限公司 + - 527、世纪开元智印互联科技集团股份有限公司 + - 528、北京理想汽车【理想汽车】 + - 529、巽逸科技(重庆)有限公司 + - 530、义乌购电子商务有限公司 + - 531、深圳市珂莱蒂尔服饰有限公司 + - 532、江西国泰利民信息科技有限公司 + - 533、广西广电大数据科技有限公司 + - 534、杭州艾麦科技有限公司 + - 535、广州小滴科技有限公司 + - 536、佳缘科技股份有限公司 + - 537、上海深擎信息科技有限公司 + - 538、武商网 + - 539、福建民本信息科技有限公司 + - 540、杭州惠合信息科技有限公司 + - 541、厦门爱立得科技有限公司 + - 542、成都拟合未来科技有限公司 + - 543、宁波聚臻智能科技有限公司 + - 544、广东百慧科技有限公司 + - 545、笨马网络 + - 546、深圳市信安数字科技有限公司 + - 547、深圳市思乐数据技术有限公司 + - 548、四川绿源集科技有限公司 + - 549、湖南云医链生物科技有限公司 + - 550、杭州源诚科技有限公司 + - 551、北京开课吧科技有限公司 + - 552、北京多来点信息技术有限公司 + - 553、JEECG BOOT低代码开发平台 + - 554、苏州同元软控信息技术有限公司 + - 555、江苏大泰信息技术有限公司 + - 556、北京大禹汇智 + - 557、北京盛哲科技有限公司 + - 558、广州钛动科技有限公司 + - 559、北京大禹汇智科技有限公司 + - 560、湖南鼎翰文化股份有限公司 + - 561、苏州安软信息科技有限公司 + - 562、芒果tv + - 563、上海艺赛旗软件股份有限公司 + - 564、中盈优创资讯科技有限公司 + - 565、乐乎公寓 + - 566、启明信息 + - 567、苏州安软 + - 568、南京富金的软件科技有限公司 + - 569、深圳市新科聚合网络技术有限公司 + - 570、你好现在(北京)科技股份有限公司 + - 571、360考试宝典 + - 572、北京一零科技有限公司 + - 573、厦门星纵信息 + - 574、Dalligent Solusi Indonesia + - 575、深圳华普物联科技有限公司 + - 576、深圳行健自动化股份有限公司 + - 577、深圳市富融信息科技服务有限公司 + - 578、蓝鸟云 + - 579、上海澎博财经资讯有限公司 + - 580、北京小鸦科技有限公司 + - 581、杭州盈泉云科技有限公司 + - 582、惟客数据 + - 583、GOSO香蜜闺秀 + - 584、普乐师(上海)数字科技有限公司 + - 585、西安市雁塔区咖北堂网络科技部 + - 586、宁波聚臻智能科技有限公司 + - 587、普乐师数字科技有限公司 + - 588、江苏蟹联网科技有限公司 + - 589、杭州未智科技有限公司 + - 590、安吉智行物流有限公司 + - 591、华生大家居集团有限公司 + - 592、美心食品(广州)有限公司 + - 593、货拉拉【货拉拉APP】 + - 594、杭州思韬瑞科技有限公司 + - 595、杭州玖融科技有限公司 + - 596、北京优海网络科技有限公司 + - 597、浙江大维高新技术股份有限公司 + - 598、粤港澳大湾区数字经济研究院 + - 599、普康(杭州)健康科技有限公司 + - 600、华西证券股份有限公司【华西证券】 + - 601、杭州海康机器人股份有限公司【海康】 + - 602、河南宸邦信息技术有限公司 + - 603、成都次元节点网络科技有限公司 + - 604、富士康科技集团【富士康】 + - 605、青岛东软载波科技股份有限公司 + - 606、小菊快跑科技有限公司 + - 607、视源股份 + - 608、宁波聚臻智能科技有限公司 + - 609、阔天科技有限公司 + - 610、网宿科技有限公司 + - 611、南京梵鼎信息技术有限公司 + - 612、房天下【房天下】 + - 613、特瓦特能源科技有限公司 + - 614、拓迪智能科技有限公司 + - 615、东软集团【东软】 + - 616、开普云 + - 617、领课网络 + - 618、南京特维软件有限公司 + - 619、福建易联众保睿通信息科技有限公司 + - 620、浙江核心同花顺金融科技有限公司【同花顺】 + - 621、浙江博观瑞思科技有限公司 + - 622、北京新美互通科技有限公司 + - 623、北京有生博大软件股份有限公司 + - 624、时代中国 + - 625、鱼泡网 + - 626、一粒方糖(安徽)科技有限公司 + - 627、北京外研在线数字科技有限公司 + - 628、德电(中国)通信技术有限公司 + - 629、杭州寻联网络科技有限公司 + - 630、橙联(中国)有限公司 + - 631、北京承启通科技有限公司 + - 632、银联数据服务有限公司【银联】 + - 633、上海晶确科技有限公司 + - 634、亚信科技有限公司 + - 635、福建新航物联网科技有限公司 + - 636、上扬软件 + - 637、深蓝汽车科技有限公司 + - 638、南昌节点汇智科技有限公司 + - 639、锐明技术 + - 640、再造再生健康科技有限公司 + - 641、华宝证券 + - 642、卓正医疗 + - 643、深圳湛信科技 + - 644、陕西鑫众为软件有限公司 + - 645、深圳市润农科技有限公司 + - 646、庚商教育智能科技有限公司 + - 647、杭州祎声科技 + - 648、四川久远银海软件股份有限公司 + - 649、GeeFox极狐低代码 + - 650、浙江和仁科技股份有限公司 + - 651、宁波聚臻智能科技有限公司 + - 652、福建福昕软件开发股份有限公司【福昕】 + - 653、广州中长康达信息技术有限公司 + - 654、武汉趣改信息科技有限公司 + - 655、北京华夏思源科技发展有限公司 + - 656、宁波关关通科技有限公司 + - 657、青岛吕氏餐饮有限公司 + - 658、杭州乐刻网络科技有限公司 + - 659、上海红瓦信息科技有限公司 + - 660、陕西旅小宝信息科技有限公司 + - 661、中科卓恒(大连)科技有限公司 + - 662、北京华益精点生物技术有限公司 + - 663、马士基(中国)航运有限公司【马士基】 + - 664、陕西美咚网络科技有限公司 + - 665、山东新北洋信息技术股份有限公司 + - 666、福建中瑞文化发展集团有限公司 + - 667、黑龙江省建工集团有限责任公司【黑龙江省建工】 + - 668、志信能达安全科技(广州)有限公司 + - 669、重庆开源共创科技有限公司 + - 670、华泰人寿保险股份有限公司【华泰人寿】 + - 671、成都盘古纵横集团 + - 672、北京果果乐学科技有限公司 + - 673、北京凌云空间科技有限公司 + - 674、临工重机股份有限公司 + - 675、上海热风时尚管理集团【热风】 + - 676、HashKey Exchange + - 677、傲基(深圳)跨境商务股份有限公司 + - 678、青岛文达通科技股份有限公司 + - 679、杭州普罗云科技有限公司 + - 680、浙江云鹭科技有限公司 + - 681、中山市芯宏柿网络科技有限公司 + - 682、深圳市家家顺物联科技 + - 683、重庆斑西科技有限公司 + - 684、福建省泰古信息技术有限公司 + - 685、贵阳永青仪电科技有限公司 + - 686、广州博依特智能信息科技有限公司 + - 687、河南宠呦呦信息技术有限公司 + - 688、陕西星邑空间技术有限公司 + - 689、广东西欧克实业有限公司 + - 690、唱吧麦颂KTV + - 691、联通云 + - 692、北京爱话本科技有限公司 + - 693、北京起创科技有限公司 + - 694、平安证券【平安证券】 + - 695、合肥中科类脑智能技术有限公司 + - 696、南京同仁堂健康产业有限公司【同仁堂】 + - 697、铜仁市碧江区智惠加油站 + - 698、惟客数据 + - 699、凤凰新闻【凤凰新闻】 + - 700、深圳王力智能 + - 701、返利网数字科技股份有限公司 + - 702、上海阜能信息科技有限公司 + - 703、深圳市极能超电数字科技有限公司 + - 704、海目星激光科技集团股份有限公司 + - 705、安克创新科技股份有限公司【安克】 + - 706、大庆点神科技有限公司 + - 707、浙江零跑科技股份有限公司【零跑】 + - 708、成都成电金盘健康数据技术有限公司 + - 709、成都极米科技股份有限公司【极米】 + - 710、顺德职业技术大学 + - 711、中邮证券有限责任公司【中邮证券】 + - 712、志豪链云科技有限公司 + - 713、湖南万鲸科技有限公司 + - 714、广州万表 + - 715、再惠(上海)网络科技有限公司 + - 716、上海爱诚裕信息科技有限公司 + - 717、杭州迈瑞数字科技有限公司 + - 718、广州串联网络科技有限公司 + - 719、上海乐研化学试剂 + - 720、智现未来 + - 721、大庆点神科技有限公司 + - 722、深圳市中科环球科技有限公司 + - 723、江苏金箭车业制造有限公司 - …… > The company that access and use this product is welcome to register at the [address](https://github.com/xuxueli/xxl-job/issues/1 ), only for product promotion. @@ -361,7 +806,7 @@ Source repository address | Release Download com.xuxueli xxl-job-core - 1.8.2 + ${version} ``` @@ -370,11 +815,8 @@ Source repository address | Release Download - [Gitter](https://gitter.im/xuxueli/xxl-job) ### 1.5 Environment -- JDK:1.7+ -- Servlet/JSP Spec:3.1/2.3 -- Tomcat:8.5.x/Jetty9.2.x -- Spring-boot:1.5.x/Spring4.x -- Mysql:5.6+ +- JDK:1.8+ +- Mysql:5.7+ - Maven:3+ @@ -1003,7 +1445,7 @@ The scheduling center provides API services for executors and business parties t 3. Executor registration remove services; 4. Triggers a single execution service, and support the task to be triggered according to the business event; -The scheduling center API service location: com.xxl.job.core.biz.AdminBiz.java +The scheduling center API service location: com.xxl.job.core.openapi.AdminBiz.java The scheduling center API service requests reference code:com.xxl.job.adminbiz.AdminBizTest.java @@ -1215,17 +1657,34 @@ Tips: V1.3.x release has been published , enter the maintenance phase, branch a - 2,Standardize project directory for extend multi executors; - 3,add JFinal type executor sample project; -### TODO LIST -- 1,Task privilege management:control privilege on executor, check privilege on core operations; -- 2,Task slice routing:using consistent Hash algorithm to calculate slice order as stable as possible, even if there is fluctuation in the registration machine will not cause large fluctuations in the order of slice. Currently using IP natural sorting can meet the demand,to be determined; -- 3,Failure retry optimization:The current failure to retry logic is execute the request logic once again after the scheduled request fails。The optimization point is retry for both scheduling and execution failures, retry a full schedule when retrying,This may lead schedule failure to an infinite loop,to be determined; -- 4,write file when callback failed,read the log when viewing the log,callback confirm after rebooting; -- 5,Task dependency,flow chart,child task + aggregation task,log of each node; -- 6,Scheduled task priority; -- 7,Remove quartz dependencies and rewrite scheduld module:insert the next execution record into delayqueue when add or resume task, schedule center cluster compete distributed lock,successful nodes bulk load expired delayqueue data and batch execution; -- 8,springboot and docker image,and push docker image to the central warehouse,further realize product out of the box; -- 9,globalization:schedule center interface and Official documents,add English version; -- 10,executor removal:notify schedule center and remove the corresponding execute node when executor is destroyed, improve the timeliness of executor state recognized; +### 6.43 version v3.3.2 Release Notes[2026-01-01] +- 1、【Optimization】Graceful Shutdown: When the scheduling center shuts down, it actively waits for scheduling completion when detecting non-empty time wheels; when the client shuts down, it stops receiving new tasks and actively waits for task execution completion when detecting running tasks; +- 2、【New Feature】Docker Compose Configuration: Added Docker Compose configuration to support one-click configuration and startup of scheduling center clusters; + +
+ Docker Compose startup steps: + + ``` + // Download XXL-JOB + git clone --branch "$(curl -s https://api.github.com/repos/xuxueli/xxl-job/releases/latest | jq -r .tag_name)" https://github.com/xuxueli/xxl-job.git + // Build XXL-JOB + mvn clean package -Dmaven.test.skip=true + // Start XXL-JOB + MYSQL_PATH={自定义数据库持久化目录} docker compose up -d + // Stop XXL-JOB + docker compose down + ``` +
+ +- 3、【Optimization】 Scheduling Center Operation Experience: Table interaction adjusted to single-row selection mode; disabled pagination cycling; optimized pagination limit text; +- 4、【Optimization】 Scheduling Thread Transaction Commit Logic: Adjusted to avoid thread abnormal exit under edge conditions, enhancing robustness; +- 5、【Optimization】 Scheduling Log List Sorting Logic: Optimized for improved readability; +- 6、【Optimization】 Scheduling Center OpenAPI Communication Token: Adjusted to optional instead of required; merged PR-3892; +- 7、【Optimization】 Executor Detail Interface Permissions: Adjusted to support regular users viewing registered nodes; merged PR-3882; +- 8、【Optimization】 Task Parameter LogDateTime Generation Logic: Adjusted to ensure consistency for the same batch of scheduling in sharding broadcast scenarios; +- 9、【Upgrade】 Maven Dependencies: Upgraded multiple dependencies to newer versions, such as spring, netty, xxl-sso, xxl-tool, etc.; +- 10、【Optimization】 Unified Project Dependency Management Structure: Unified dependency versions to parent pom for improved maintainability; + ## 7. Other diff --git a/doc/XXL-JOB官方文档.md b/doc/XXL-JOB官方文档.md index 6984a1ad..b43749c3 100644 --- a/doc/XXL-JOB官方文档.md +++ b/doc/XXL-JOB官方文档.md @@ -1,10 +1,10 @@ ## 《分布式任务调度平台XXL-JOB》 -[![Actions Status](https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg)](https://github.com/xuxueli/xxl-job/actions) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/) +[![Build Status](https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg)](https://github.com/xuxueli/xxl-job/actions) +[![Maven Central](https://img.shields.io/maven-central/v/com.xuxueli/xxl-job-core)](https://central.sonatype.com/artifact/com.xuxueli/xxl-job-core) [![GitHub release](https://img.shields.io/github/release/xuxueli/xxl-job.svg)](https://github.com/xuxueli/xxl-job/releases) [![GitHub stars](https://img.shields.io/github/stars/xuxueli/xxl-job)](https://github.com/xuxueli/xxl-job/) -[![Docker Status](https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin)](https://hub.docker.com/r/xuxueli/xxl-job-admin/) +[![Docker pulls](https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin)](https://hub.docker.com/r/xuxueli/xxl-job-admin/) [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0.html) [![donate](https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat)](https://www.xuxueli.com/page/donate.html) @@ -51,12 +51,16 @@ XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅 - 28、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; - 29、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等; - 30、全异步:任务调度流程全异步化设计实现,如异步调度、异步运行、异步回调等,有效对密集调度进行流量削峰,理论上支持任意时长任务的运行; -- 31、跨语言:调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案; +- 31、跨语言/OpenAPI:调度中心与执行器提供语言无关的 OpenApi(RESTful 格式),第三方任意语言可据此对接调度中心或者实现执行器,实现多语言支持。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案; - 32、国际化:调度中心支持国际化设置,提供中文、英文两种可选语言,默认为中文; - 33、容器化:提供官方docker镜像,并实时更新推送dockerhub,进一步实现产品开箱即用; - 34、线程池隔离:调度线程池进行隔离拆分,慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性; - 35、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色; - 36、权限控制:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作; +- 37、AI任务:原生提供AI执行器,并内置多个AI任务Handler,与spring-ai、ollama、dify等集成打通,支持快速开发AI类任务。 +- 38、审计日志:记录任务操作敏感信息,用于系统监控、审计和安全分析,可快速追溯异常行为以及定位排查问题。 +- 39、优雅停机:调度中心停机,检测时间轮非空时主动等待调度完成;客户端停机,检测存在运行中任务时,停止接收新任务并主动等待任务执行完成; + ### 1.4 发展 于2015年中,我在github上创建XXL-JOB项目仓库并提交第一个commit,随之进行系统结构设计,UI选型,交互设计…… @@ -90,7 +94,7 @@ XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅 于2021-12-06,XXL-JOB参与"[2021年度OSC中国开源项目评选](https://www.oschina.net/project/top_cn_2021) "评比,在当时已录入的一万多个开源项目中角逐,最终当选"最受欢迎项目"。 > 我司大众点评目前已接入XXL-JOB,内部别名《Ferrari》(Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。 -据最新统计, 自2016-01-21接入至2017-12-01期间,该系统已调度约100万次,表现优异。新接入应用推荐使用最新版本,因为经过数十个版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。 +> 据最新统计, 自2016-01-21接入至2017-12-01期间,该系统已调度约100万次,表现优异。新接入应用推荐使用最新版本,因为经过数十个版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。 至今,XXL-JOB已接入多家公司的线上产品线,接入场景如电商业务,O2O业务和大数据作业等,截止最新统计时间为止,XXL-JOB已接入的公司包括不限于: @@ -782,7 +786,41 @@ XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅 - 686、广州博依特智能信息科技有限公司 - 687、河南宠呦呦信息技术有限公司 - 688、陕西星邑空间技术有限公司 - + - 689、广东西欧克实业有限公司 + - 690、唱吧麦颂KTV + - 691、联通云 + - 692、北京爱话本科技有限公司 + - 693、北京起创科技有限公司 + - 694、平安证券【平安证券】 + - 695、合肥中科类脑智能技术有限公司 + - 696、南京同仁堂健康产业有限公司【同仁堂】 + - 697、铜仁市碧江区智惠加油站 + - 698、惟客数据 + - 699、凤凰新闻【凤凰新闻】 + - 700、深圳王力智能 + - 701、返利网数字科技股份有限公司 + - 702、上海阜能信息科技有限公司 + - 703、深圳市极能超电数字科技有限公司 + - 704、海目星激光科技集团股份有限公司 + - 705、安克创新科技股份有限公司【安克】 + - 706、大庆点神科技有限公司 + - 707、浙江零跑科技股份有限公司【零跑】 + - 708、成都成电金盘健康数据技术有限公司 + - 709、成都极米科技股份有限公司【极米】 + - 710、顺德职业技术大学 + - 711、中邮证券有限责任公司【中邮证券】 + - 712、志豪链云科技有限公司 + - 713、湖南万鲸科技有限公司 + - 714、广州万表 + - 715、再惠(上海)网络科技有限公司 + - 716、上海爱诚裕信息科技有限公司 + - 717、杭州迈瑞数字科技有限公司 + - 718、广州串联网络科技有限公司 + - 719、上海乐研化学试剂 + - 720、智现未来 + - 721、大庆点神科技有限公司 + - 722、深圳市中科环球科技有限公司 + - 723、江苏金箭车业制造有限公司 - …… > 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。 @@ -803,7 +841,7 @@ XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅 --- | --- [https://github.com/xuxueli/xxl-job](https://github.com/xuxueli/xxl-job) | [Download](https://github.com/xuxueli/xxl-job/releases) [http://gitee.com/xuxueli0323/xxl-job](http://gitee.com/xuxueli0323/xxl-job) | [Download](http://gitee.com/xuxueli0323/xxl-job/releases) -[https://gitcode.com/xuxueli0323/xxl-job](https://gitcode.com/xuxueli0323/xxl-job) | [Download](https://gitcode.com/xuxueli0323/xxl-job/tags) +[https://gitcode.com/xuxueli/xxl-job](https://gitcode.com/xuxueli/xxl-job) | [Download](https://gitcode.com/xuxueli/xxl-job/tags) #### 中央仓库地址 @@ -819,9 +857,9 @@ XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅 ### 1.6 环境 -- Maven3+ -- Jdk1.8+ -- Mysql8.0+ +- Maven:3+ +- Jdk:17+ (说明:版本3.x及以上要求Jdk17+;版本2.x及以下支持Jdk1.8) +- Mysql:8.0+ ## 二、快速入门 @@ -888,8 +926,11 @@ xxl.job.timeout=3 xxl.job.i18n=zh_CN ## 调度线程池最大线程配置【必填】 -xxl.job.triggerpool.fast.max=200 -xxl.job.triggerpool.slow.max=100 +xxl.job.triggerpool.fast.max=300 +xxl.job.triggerpool.slow.max=200 + +### 调度触发后,批量更新任务批次数量【必填】 +xxl.job.schedule.batchsize=100 ### 调度中心日志表数据保存天数 [必填]:过期日志自动清理;限制大于等于7时生效,否则, 如-1,关闭自动清理功能; xxl.job.logretentiondays=30 @@ -921,19 +962,28 @@ xxl.job.logretentiondays=30 - 下载镜像 ``` -// Docker地址:https://hub.docker.com/r/xuxueli/xxl-job-admin/ (建议指定版本号) -docker pull xuxueli/xxl-job-admin +/** +* Docker地址:https://hub.docker.com/r/xuxueli/xxl-job-admin/ +* 建议指定版本号拉取镜像; +*/ +docker pull xuxueli/xxl-job-admin:{指定版本} ``` - 创建容器并运行 ``` /** -* 如需自定义 mysql 等配置,可通过 "-e PARAMS" 指定,参数格式 PARAMS="--key=value --key2=value2" ; -* 配置项参考文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties -* 如需自定义 JVM内存参数 等配置,可通过 "-e JAVA_OPTS" 指定,参数格式 JAVA_OPTS="-Xmx512m" ; +* 如需自定义 “项目配置文件” 中配置项,比如 mysql 配置,可通过 "-e PARAMS" 指定,参数格式: -e PARAMS="--key=value --key2=value2"; +* (配置项参考文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties) +* 如需自定义 “JVM内存参数”,可通过 "-e JAVA_OPTS" 指定,参数格式: -e JAVA_OPTS="-Xmx512m" +* 如需自定义 “日志文件目录”,可通过 "-e LOG_HOME" 指定,参数格式: -e LOG_HOME=/data/applogs */ -docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai" -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin -d xuxueli/xxl-job-admin:{指定版本} +docker run -d \ +-e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai" \ +-p 8080:8080 \ +-v /tmp:/data/applogs \ +--name xxl-job-admin \ +xuxueli/xxl-job-admin:{指定版本} ``` @@ -957,18 +1007,18 @@ docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_jo ``` ### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册; xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin - ### 调度中心通讯TOKEN [选填]:非空时启用; xxl.job.admin.accessToken=default_token - ### 调度中心通讯超时时间[选填],单位秒;默认3s; xxl.job.admin.timeout=3 +### 执行器启用开关 [选填]:默认开启,关闭时不进行执行器初始化; +xxl.job.executor.enabled=true ### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册 xxl.job.executor.appname=xxl-job-executor-sample ### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。 xxl.job.executor.address= -### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"; +### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯使用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"; xxl.job.executor.ip= ### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口; xxl.job.executor.port=9999 @@ -976,6 +1026,8 @@ xxl.job.executor.port=9999 xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler ### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能; xxl.job.executor.logretentiondays=30 +### 任务扫描排除路径 [选填] :任务扫描时忽略指定包路径下的Bean;支持配置包路径前缀,多个逗号分隔; +xxl.job.executor.excludedpackage=org.springframework,spring ``` #### 步骤三:执行器组件配置 @@ -1004,7 +1056,7 @@ public XxlJobSpringExecutor xxlJobExecutor() { ``` #### 步骤四:部署执行器项目: -如果已经正确进行上述配置,可将执行器项目编译打部署,系统提供多种执行器Sample示例项目,选择其中一个即可,各自的部署方式如下。 +如果已经正确进行上述配置,可将执行器项目编译打包部署,系统提供多种执行器Sample示例项目,选择其中一个即可,各自的部署方式如下。 xxl-job-executor-sample-springboot:项目编译打包成springboot类型的可执行JAR包,命令启动即可; xxl-job-executor-sample-frameless:项目编译打包成JAR包,命令启动即可; @@ -1035,8 +1087,8 @@ public XxlJobSpringExecutor xxlJobExecutor() { #### 步骤二:“GLUE模式(Java)” 任务开发: -请点击任务右侧 “GLUE” 按钮,进入 “GLUE编辑器开发界面” ,见下图。“GLUE模式(Java)” 运行模式的任务默认已经初始化了示例任务代码,即打印Hello World。 -( “GLUE模式(Java)” 运行模式的任务实际上是一段继承自IJobHandler的Java类代码,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务,详细介绍请查看第三章节) +请点击任务右侧 “GLUE IDE” 按钮,进入 “GLUE编辑器开发界面” ,见下图。“GLUE模式(Java)” 运行模式的任务默认已经初始化了示例任务代码,即打印Hello World。 +( “GLUE模式(Java)” 运行模式的任务实际上是一段继承自IJobHandler的Java类代码,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器中的其他服务,详细介绍请查看第三章节) ![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_Fgql.png "在这里输入图片标题") @@ -1055,6 +1107,7 @@ public XxlJobSpringExecutor xxlJobExecutor() { ![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_eYrv.png "在这里输入图片标题") + ## 三、任务详解 ### 配置属性详细说明: @@ -1078,7 +1131,7 @@ public XxlJobSpringExecutor xxlJobExecutor() { 任务配置: - 运行模式: BEAN模式:任务以JobHandler方式维护在执行器端;需要结合 "JobHandler" 属性匹配执行器中任务; - GLUE模式(Java):任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自IJobHandler的Java类代码并 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务; + GLUE模式(Java):任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自IJobHandler的Java类代码并以 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器中的其他服务; GLUE模式(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "shell" 脚本; GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本; GLUE模式(PHP):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "php" 脚本; @@ -1154,7 +1207,7 @@ Bean模式任务,支持基于方法的开发方式,每个任务对应一个 4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果; ``` -// 可参考Sample示例执行器中的 "com.xxl.job.executor.service.jobhandler.SampleXxlJob" ,如下: +// 可参考Sample示例执行器中的 "com.xxl.job.executor.jobhandler.SampleXxlJob" ,如下: @XxlJob("demoJobHandler") public void demoJobHandler() throws Exception { XxlJobHelper.log("XXL-JOB, Hello World."); @@ -1166,18 +1219,106 @@ public void demoJobHandler() throws Exception { ![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAsz.png "在这里输入图片标题") -#### 原生内置Bean模式任务 -为方便用户参考与快速实用,示例执行器内原生提供多个Bean模式任务Handler,可以直接配置实用,如下: +#### 原生内置Bean模式任务(通用执行器) +为方便用户参考与快速使用,提供 “通用执行器” 并内置多个Bean模式任务Handler,可以直接配置使用,如下: -- demoJobHandler:简单示例任务,任务内部模拟耗时任务逻辑,用户可在线体验Rolling Log等功能; -- shardingJobHandler:分片示例任务,任务内部模拟处理分片参数,可参考熟悉分片任务; -- httpJobHandler:通用HTTP任务Handler;业务方只需要提供HTTP链接等信息即可,不限制语言、平台。示例任务入参如下: - ``` - url: http://www.xxx.com - method: get 或 post - data: post-data - ``` -- commandJobHandler:通用命令行任务Handler;业务方只需要提供命令行即可;如 “pwd”命令; +**通用执行器说明:** +- AppName:xxl-job-executor-sample +- 执行器代码: + - xxl-job-executor-sample-springboot:springboot版本 + - xxl-job-executor-sample-frameless:无框架版本 + +**执行器内置任务列表:** +- a、demoJobHandler:简单示例任务,任务内部模拟耗时任务逻辑,用户可在线体验Rolling Log等功能; +- b、shardingJobHandler:分片示例任务,任务内部模拟处理分片参数,可参考熟悉分片任务; +- c、httpJobHandler:通用HTTP任务Handler;业务方只需要提供HTTP链接等信息即可,不限制语言、平台。任务入参示例如下: + +``` +// 1、简单示例: +{ + "url": "http://www.baidu.com", + "method": "GET", + "data": "hello world" +} + +// 2、完整参数示例: +{ + "url": "http://www.baidu.com", // 请求URL + "method": "POST", // 请求方法,支持:GET、POST、HEAD、OPTIONS、PUT、DELETE、TRACE + "contentType": "application/json", // 请求内容类型,支持:application/json、application/x-www-form-urlencoded、application/xml、text/html、text/xml、text/plain + "headers": { // 请求Header,key-value结构 + "header01": "value01" + }, + "cookies": { // 请求Cookie,key-value结构 + "cookie01": "value01" + }, + "timeout": 3000, // 请求超时时间,默认 3000;单位:毫秒; + "data": "request body data", // 请求Body数据,仅针对 POST 请求有效 + "form": { // 请求Form数据,仅针对 GET 请求有效 + "key01": "value01" + }, + "auth": "auth data" // 请求认证信息, 通过Basic Auth方式认证 +} +``` + +- d、commandJobHandler:通用命令行任务Handler;业务方只需要提供命令行即可,命令及参数之间通过空格隔开;如任务参数 "ls la" 或 "pwd" 将会执行命令并输出数据; + +#### 原生内置Bean模式任务(AI执行器) +为方便用户参考与快速使用,提供 “AI执行器” 并内置多个Bean模式 AI任务Handler,与spring-ai、ollama、dify等集成打通,支持快速开发AI类任务,如下: + +**AI执行器说明:** +- AppName:xxl-job-executor-sample-ai +- 执行器代码:xxl-job-executor-sample-springboot-ai + +**执行器内置任务列表:** + +- a、ollamaJobHandler: OllamaChat任务,支持自定义prompt、input等输入信息。示例任务入参如下: +``` +{ + "input": "{输入信息,必填信息}", + "prompt": "{模型prompt,可选信息}", + "model": "{模型实现,如qwen3.5:2b,可选信息}" +} +``` + +- b、difyWorkflowJobHandler:DifyWorkflow 任务,支持自定义inputs、user、baseUrl、apiKey 等输入信息,示例参数如下; +``` +{ + "inputs":{ // inputs 为dify工作流任务参数;参数不固定,结合各自 workflow 自行定义。 + "input":"{用户输入信息}" // 该参数为示例变量,需要 workflow 的“开始”节点 自定义参数 “input”,可自行调整或删除。 + }, + "user": "xxl-job", // 用户标识,选填 + "baseUrl": "http://localhost/v1", // Dify应用的 访问API 地址,需要从 Dify 系统获取; + "apiKey": "xxx" // Dify应用的 API-Key,需要从 Dify 系统获取; +} +``` + +- c、openClawJobHandler: OpenClaw任务,支持自定义prompt、input等输入信息。示例任务入参如下: +``` +{ + "input": "{输入信息,必填信息}", + "prompt": "{模型prompt,可选信息}" +} +``` + +- 依赖1:参考 [Ollama本地化部署大模型](https://www.xuxueli.com/blog/?blog=./notebook/13-AI/%E4%BD%BF%E7%94%A8Ollama%E6%9C%AC%E5%9C%B0%E5%8C%96%E9%83%A8%E7%BD%B2DeepSeek.md) ,执行器示例部署“qwen2.5:1.5b”模型,也可自定选择其他模型版本。 +- 依赖2:参考 [使用DeepSeek与Dify搭建AI助手](https://www.xuxueli.com/blog/?blog=./notebook/13-AI/%E4%BD%BF%E7%94%A8DeepSeek%E4%B8%8EDify%E6%90%AD%E5%BB%BAAI%E5%8A%A9%E6%89%8B.md),执行器示例新建Dify DifyWork应用,并在开始节点添加“input”参数,可结合实际情况调整。 +- 依赖3:启动示例 “AI执行器” 相关配置文件说明如下: + +``` +// ollama 配置 +spring.ai.ollama.base-url=http://localhost:11434 +spring.ai.ollama.chat.enabled=true +// Model模型配置;注意,此处配置模型版本、必须本地先通过ollama进行安装运行。 +spring.ai.ollama.chat.options.model=qwen2.5:1.5b +spring.ai.ollama.chat.options.temperature=0.8 + +// dify 配置;选择相关 workflow 应用,切换 “访问API” 页面获取 url 地址信息. +dify.base-url=http://localhost/v1 +// dify api-key;选择相关 workflow 应用并进入 “访问API” 页面,右上角 “API 密钥” 入口获取 api-key。 +dify.api-key={自行获取并修改} + +``` ### 3.3 GLUE模式(Java) @@ -1189,7 +1330,7 @@ public void demoJobHandler() throws Exception { ![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_tJOq.png "在这里输入图片标题") #### 步骤二:开发任务代码: -选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。 +选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑器中)。 版本回溯功能(支持30个版本的版本回溯):在GLUE任务的Web IDE界面,选择右上角下拉框“版本回溯”,会列出该GLUE的更新历史,选择相应版本即可显示该版本代码,保存后GLUE代码即回退到对应的历史版本; @@ -1201,7 +1342,7 @@ public void demoJobHandler() throws Exception { 参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Shell)"; #### 步骤二:开发任务代码: -选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。 +选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑器中)。 该模式的任务实际上是一段 "shell" 脚本; @@ -1213,7 +1354,7 @@ public void demoJobHandler() throws Exception { 参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Python)"; #### 步骤二:开发任务代码: -选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。 +选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑器中)。 该模式的任务实际上是一段 "python" 脚本; @@ -1225,7 +1366,7 @@ public void demoJobHandler() throws Exception { 参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(NodeJS)"; #### 步骤二:开发任务代码: -选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。 +选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑器中)。 该模式的任务实际上是一段 "nodeJS" 脚本; @@ -1258,7 +1399,7 @@ public void demoJobHandler() throws Exception { 注册方式:调度中心获取执行器地址的方式; 自动注册:执行器自动进行执行器注册,调度中心通过底层注册表可以动态发现执行器机器地址; 手动录入:人工手动录入执行器的地址信息,多地址逗号分隔,供调度中心使用; - 机器地址:"注册方式"为"手动录入"时有效,支持人工维护执行器的地址信息; + 机器地址:"注册方式"为"手动录入"时有效,支持人工维护执行器的地址信息;注册地址格式可参考“http://127.0.0.1:9999/”,为执行器内嵌服务地址; ### 4.2 新建任务 进入任务管理界面,点击“新增任务”按钮,在弹出的“新增任务”界面配置任务属性后保存即可。详情页参考章节 "三、任务详解"。 @@ -1284,7 +1425,7 @@ public void demoJobHandler() throws Exception { ![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAhX.png "在这里输入图片标题") ### 4.7 查看调度日志 -点击“日志”按钮,可以查看任务历史调度日志。在历史调入日志界面可查看每次任务调度的调度结果、执行结果等,点击“执行日志”按钮可查看执行器完整日志。 +点击“日志”按钮,可以查看任务历史调度日志。在历史调度日志界面可查看每次任务调度的调度结果、执行结果等,点击“执行日志”按钮可查看执行器完整日志。 ![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAhX.png "在这里输入图片标题") @@ -1356,6 +1497,7 @@ try{ ![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_1002.png "在这里输入图片标题") + ## 五、总体设计 ### 5.1 源码目录介绍 - /doc :文档资料 @@ -1381,7 +1523,7 @@ XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据 #### 5.3.1 设计思想 将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。 -将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。 +将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中的业务逻辑。 因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性; @@ -1401,7 +1543,7 @@ XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据 #### 5.4.1 quartz的不足 Quartz作为开源作业调度中的佼佼者,是作业调度的首选。但是集群环境中Quartz采用API的方式对任务进行管理,从而可以避免上述问题,但是同样存在以下问题: -- 问题一:调用API的的方式操作任务,不人性化; +- 问题一:调用API的方式操作任务,不人性化; - 问题二:需要持久化业务QuartzJobBean到底层数据表中,系统侵入性相当严重。 - 问题三:调度逻辑和QuartzJobBean耦合在同一个项目中,这将导致一个问题,在调度任务数量逐渐增多,同时调度任务逻辑逐渐加重的情况下,此时调度系统的性能将大大受限于业务; - 问题四:quartz底层以“抢占式”获取DB锁并由抢占成功节点负责运行任务,会导致节点负载悬殊非常大;而XXL-JOB通过执行器实现“协同分配式”运行任务,充分发挥集群优势,负载各节点均衡。 @@ -1414,7 +1556,7 @@ XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一 XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。 #### 5.4.3 调度中心HA(集群) -基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。 +基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。 #### 5.4.4 调度线程池 调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。 @@ -1458,7 +1600,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback 调度中心每次进行任务调度,都会记录一条任务日志,任务日志主要包括以下三部分内容: - 任务信息:包括“执行器地址”、“JobHandler”和“执行参数”等属性,点击任务ID按钮可查看,根据这些参数,可以精确的定位任务执行的具体机器和任务代码; -- 调度信息:包括“调度时间”、“调度结果”和“调度日志”等,根据这些参数,可以了解“调度中心”发起调度请求时具体情况。 +- 调度信息:包括“调度时间”、“调度结果”和“调度日志”等,根据这些参数,可以了解“调度中心”发起调度请求时的具体情况。 - 执行信息:包括“执行时间”、“执行结果”和“执行日志”等,根据这些参数,可以了解在“执行器”端任务执行的具体情况; 调度日志,针对单次调度,属性说明如下: @@ -1493,7 +1635,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback 得益于上述两点优化,理论上默认配置下的调度中心,单机能够支撑 5000 任务并发运行稳定运行; -实际场景中,由于调度中心与执行器网络ping延迟不同、DB读写耗时不同、任务调度密集程度不同,会导致任务量上限会上下波动。 +实际场景中,由于调度中心与执行器网络ping延迟不同、DB读写耗时不同、任务调度密集程度不同,会导致任务量上限上下波动。 如若需要支撑更多的任务量,可以通过 "调大调度线程数" 、"降低调度中心与执行器ping延迟" 和 "提升机器配置" 几种方式优化。 @@ -1562,7 +1704,7 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过 ### 5.8 任务执行结果 自v1.6.2之后,任务执行结果通过 "IJobHandler" 的返回值 "ReturnT" 进行判断; -当返回值符合 "ReturnT.code == ReturnT.SUCCESS_CODE" 时表示任务执行成功,否则表示任务执行失败,而且可以通过 "ReturnT.msg" 回调错误信息给调度中心; +当返回值符合 "ReturnT#code == 200" 时表示任务执行成功,否则表示任务执行失败,而且可以通过 "ReturnT#msg" 回调错误信息给调度中心; 从而,在任务逻辑中可以方便的控制任务执行结果; ### 5.9 分片广播 & 动态分片 @@ -1635,7 +1777,7 @@ echo "分片总数 total = $3" ### 5.15 跨语言 XXL-JOB是一个跨语言的任务调度平台,主要体现在如下几个方面: -- 1、RESTful API:调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器。(可参考章节 “调度中心/执行器 RESTful API” ) +- 1、OpenApi(RESTful 格式):调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器,实现多语言支持。(可参考章节 “调度中心/执行器 RESTful API” ) - 2、多任务模式:提供Java、Python、PHP……等十来种任务模式,可参考章节 “5.5 任务 "运行模式" ”;理论上可扩展任意语言任务模式; - 2、提供基于HTTP的任务Handler(Bean任务,JobHandler="httpJobHandler");业务方只需要提供HTTP链接等相关信息即可,不限制语言、平台;(可参考章节 “原生内置Bean模式任务” ) @@ -1644,10 +1786,30 @@ XXL-JOB是一个跨语言的任务调度平台,主要体现在如下几个方 ### 5.17 调度中心Docker镜像构建 可以通过以下命令快速构建调度中心,并启动运行; + ``` +/** +* build package +*/ mvn clean package -docker build -t xuxueli/xxl-job-admin:{version} ./xxl-job-admin -docker run --name xxl-job-admin -p 8080:8080 -d xuxueli/xxl-job-admin + +/** +* build docker image +*/ +docker build -t xuxueli/xxl-job-admin:{指定版本} ./xxl-job-admin + +/** +* 如需自定义 “项目配置文件” 中配置项,比如 mysql 配置,可通过 "-e PARAMS" 指定,参数格式: -e PARAMS="--key=value --key2=value2"; +* (配置项参考文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties) +* 如需自定义 “JVM内存参数”,可通过 "-e JAVA_OPTS" 指定,参数格式: -e JAVA_OPTS="-Xmx512m" +* 如需自定义 “日志文件目录”,可通过 "-e LOG_HOME" 指定,参数格式: -e LOG_HOME=/data/applogs +*/ +docker run -d \ +-e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai" \ +-p 8080:8080 \ +-v /tmp:/data/applogs \ +--name xxl-job-admin \ +xuxueli/xxl-job-admin:{指定版本} ``` ### 5.20 避免任务重复执行 @@ -1655,8 +1817,8 @@ docker run --name xxl-job-admin -p 8080:8080 -d xuxueli/xxl-job-admin 针对上述情况,可以通过结合 "单机路由策略(如:第一台、一致性哈希)" + "阻塞策略(如:单机串行、丢弃后续调度)" 来规避,最终避免任务重复执行。 ### 5.21 命令行任务 -原生提供通用命令行任务Handler(Bean任务,"CommandJobHandler");业务方只需要提供命令行即可; -如任务参数 "pwd" 将会执行命令并输出数据; +原生提供通用命令行任务Handler(Bean任务,"CommandJobHandler");业务方只需要提供命令行即可,命令及参数之间通过空格隔开; +如任务参数 "ls la" 或 "pwd" 将会执行命令并输出数据; ### 5.22 日志自动清理 XXL-JOB日志主要包含如下两部分,均支持日志自动清理,说明如下: @@ -1668,13 +1830,48 @@ XXL-JOB日志主要包含如下两部分,均支持日志自动清理,说明 针对该问题,调度中心提供内置组件进行处理,逻辑为:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败; +### 5.24 Docker Compose 快速部署 +支持通过 Docker Compose 方式部署并启动 XXL-JOB,包括:数据库、调度中心、示例执行器。 -## 六、调度中心/执行器 RESTful API +- 第一步:克隆 XXL-JOB +``` +git clone --branch "$(curl -s https://api.github.com/repos/xuxueli/xxl-job/releases/latest | jq -r .tag_name)" https://github.com/xuxueli/xxl-job.git +``` + +- 第二步:构建 XXL-JOB +``` +// 注意:如下命令需要在项目仓库根目录执行 +mvn clean package -Dmaven.test.skip=true +``` + +- 第三步:配置 XXL-JOB +``` +// 注意:前往docker目录,自定义 .env 配置;如修改 MYSQL_PATH 配置设置Mysql数据持久化目录; +cd ./docker +cat .env +``` + +- 第四步:启动 XXL-JOB +``` +// 启动 +docker compose up -d + +// 停止 +docker compose down +``` + +### 5.25 优雅停机 +针对任务调度场景,优雅停机包括调度中心与执行器两部分: +- 调度中心优雅停机:调度中心停机时,如果检测到时间轮非空则主动等待等待一段时间,等待调度处理完成;否则立即停机; +- 执行器优雅停机:执行器停机时,如果检测到存在运行中任务,在停止接收新任务后会主动等待一段时间,等待任务执行完成;否则立即停机; + + +## 六、调度中心/执行器 OpenApi XXL-JOB 目标是一种跨平台、跨语言的任务调度规范和协议。 针对Java应用,可以直接通过官方提供的调度中心与执行器,方便快速的接入和使用调度中心,可以参考上文 “快速入门” 章节。 -针对非Java应用,可借助 XXL-JOB 的标准 RESTful API 方便的实现多语言支持。 +针对非Java应用,可借助 XXL-JOB 的标准 OpenApi(RESTful API) 方便的实现多语言支持。 - 调度中心 RESTful API: - 说明:调度中心提供给执行器使用的API;不局限于官方执行器使用,第三方可使用该API来实现执行器; @@ -1687,7 +1884,7 @@ XXL-JOB 目标是一种跨平台、跨语言的任务调度规范和协议。 ### 6.1 调度中心 RESTful API -API服务位置:com.xxl.job.core.biz.AdminBiz ( com.xxl.job.admin.controller.JobApiController ) +API服务位置:com.xxl.job.core.openapi.AdminBiz ( com.xxl.job.admin.controller.JobApiController ) API服务请求参考代码:com.xxl.job.adminbiz.AdminBizTest #### a、任务回调 @@ -1769,7 +1966,7 @@ Header: ### 6.2 执行器 RESTful API -API服务位置:com.xxl.job.core.biz.ExecutorBiz +API服务位置:com.xxl.job.core.openapi.ExecutorBiz API服务请求参考代码:com.xxl.job.executorbiz.ExecutorBizTest #### a、心跳检测 @@ -1831,7 +2028,7 @@ Header: "jobId":1, // 任务ID "executorHandler":"demoJobHandler", // 任务标识 "executorParams":"demoJobHandler", // 任务参数 - "executorBlockStrategy":"COVER_EARLY", // 任务阻塞策略,可选值参考 com.xxl.job.core.enums.ExecutorBlockStrategyEnum + "executorBlockStrategy":"COVER_EARLY", // 任务阻塞策略,可选值参考 com.xxl.job.core.constant.ExecutorBlockStrategyEnum "executorTimeout":0, // 任务超时时间,单位秒,大于零时生效 "logId":1, // 本次调度日志ID "logDateTime":1586629003729, // 本次调度日志时间 @@ -2215,7 +2412,7 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段 - 6、任务状态优化,仅运行状态"NORMAL"任务关联至quartz,降低quartz底层数据存储与调度压力; - 7、任务状态规范:新增任务默认停止状态,任务更新时保持任务状态不变; - 8、IP获取逻辑优化,优先遍历网卡来获取可用IP; -- 9、任务新增的API服务接口返回任务ID,方便调用方实用; +- 9、任务新增的API服务接口返回任务ID,方便调用方使用; - 10、组件化优化,移除对 spring 的依赖:非spring应用选用 "XxlJobExecutor" 、spring应用选用 "XxlJobSpringExecutor" 作为执行器组件; - 11、任务RollingLog展示逻辑优化,修复超时任务无法查看的问题; - 12、多项UI组件升级到最新版本,如:CodeMirror、Echarts、Jquery 等; @@ -2312,7 +2509,7 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段 @XxlJob("demoJobHandler") public ReturnT execute(String param) { XxlJobLogger.log("hello world"); - return ReturnT.SUCCESS; + return ReturnT.ofSuccess(); } ``` - 2、移除commons-exec,采用原生方式实现,降低第三方依赖; @@ -2418,7 +2615,7 @@ public void execute() { - 6、【修复】漏洞修复,包括 "CVE-2024-42681" 子任务越权漏洞修复、"CVE-2023-33779" 任务API越权问题修复; - 7、【升级】多个项目依赖升级至较新稳定版本,涉及netty、groovy、gson、springboot、mybatis等; -### 7.36 版本 v2.5.0 Release Notes[2024-01-11] +### 7.36 版本 v2.5.0 Release Notes[2025-01-11] - 1、【优化】框架基础守护线程异常处理逻辑优化,避免极端情况下因Error导致调度终止问题; - 2、【优化】底层通讯超时时间支持自定义,默认3秒,缓解网络抖动导致任务通讯超时问题;可参考 xxl-job-admin 和 samples 示例代码自行配置; - 3、【修复】调度中心快慢线程池优化拒绝策略,避免因默认AbortPolicy导致调度结果丢失问题; @@ -2435,19 +2632,224 @@ public void execute() { - 14、【升级】多个项目依赖升级至较新稳定版本,涉及netty、slf4j、junit等; **备注:** -- a、本次升级数据模型及通讯协议向前兼容,v2.4.*代码和系统可无缝升级;建议对比新旧版本数据库初始化脚本补充索引; -- b、计划下个大版本升级 v3.0,将会基于 jdk17 与 springboot3.x 构建;版本v2.5.x将会继续维护,问题及漏洞将会及时跟进修复。 +- a、本次升级数据模型及通讯协议向前兼容,v2.4.*代码和系统可无缝升级(该版本优化了“xxl_job_log”表索引,建议低版本参考调整); +- b、版本v2.5.x为基于jdk8的最后的大版本,将会长期持续维护,问题及漏洞将会及时跟进修复。 +- c、下个大版本(v3.0)将会基于 jdk17 与 springboot3.x 构建; + +### 7.37 版本 v3.0.0 Release Notes[2025-02-07] +- 1、【升级】调度中心升级至 SpringBoot3 + JDK17; +- 2、【升级】Docker镜像升级,镜像构建基于JDK17; +- 3、【优化】IP获取逻辑优化,优先遍历网卡来获取可用IP; +- 4、【优化】通用命令行任务(“commandJobHandler”)优化,支持多参数执行,命令及参数之间通过空格隔开;如任务参数 "ls la" 或 "pwd" 将会执行命令并输出数据; +- 5、【优化】通用HTTP任务(httpJobHandler)优化,任务参数格式调整为json格式; +- 6、【升级】多个项目依赖升级至较新稳定版本,涉及 netty、groovy、spring/springboot 等; +**备注:** +- a、本次升级数据模型及通讯协议向前兼容,v2.4.*及后续版本可无缝升级; +- b、版本3.x开始要求Jdk17;版本2.x及以下支持Jdk1.8。如对Jdk版本有诉求,可选择接入不同版本; + +### 7.38 版本 v3.1.0 Release Notes[2025-05-01] +- 1、【新增】新增提供 “AI执行器” 并内置多个Bean模式 AI任务Handler,与spring-ai、ollama、dify等集成打通,支持快速开发AI类任务。 + - AppName:xxl-job-executor-sample-ai + - 执行器代码:xxl-job-executor-sample-springboot-ai + - 执行器初始化脚本:执行参考SQL脚本,或自行人工创建: + ``` + INSERT INTO `xxl_job_group`(`app_name`, `title`, `address_type`, `address_list`, `update_time`) + VALUES ('xxl-job-executor-sample-ai', 'AI执行器Sample', 0, NULL, now()); + ``` +- 2、【新增】新增多个 Bean模式 AI任务Handler,如 ollamaJobHandler、difyWorkflowJobHandler 等,支持快速集成开发AI任务。任务配置可参考 [AI执行器](https://www.xuxueli.com/xxl-job/#原生内置Bean模式任务(AI执行器)) + - a、ollamaJobHandler: OllamaChat任务,支持自定义prompt、input等输入信息。 + - b、difyWorkflowJobHandler:DifyWorkflow 任务,支持自定义inputs、user、baseUrl、apiKey等输入信息。 +- 3、【修复】合并PR-3708、PR-3704,解决固定速度调度模式下,下次计算执行时间小概率(间隔超长时)不准问题。 +- 4、【修复】任务操作逻辑优化,修复边界情况下逻辑中断问题 (ISSUE-2081)。 +- 5、【修复】调度中心Cron前端组件优化,解决week配置与后端兼容性问题 (ISSUE-2220)。 +- 6、【修复】任务RollingLog权限逻辑调整:修复非管理员账号越权访问问题 (ISSUE-3705)。 +- 7、【优化】Glue IDE调整,版本回溯支持查看修改时间; +- 8、【优化】任务RollingLog调整,XSS过滤支持白名单排出,提升日志易读性; +- 9、【优化】执行器日志文件保存天数(logretentiondays)调整,最小保留时间调整至3天。 +- 10、【升级】多个项目依赖升级至较新稳定版本,涉及 gson、groovy、spring/springboot、mysql 等; + +### 7.39 版本 v3.1.1 Release Notes[2025-06-23] +- 1、【调整】AI任务(difyWorkflowJobHandler)优化:针对 “baseUrl、apiKey” 等Dify配置信息,从执行器侧文件类配置调整至调度中心“任务参数”动态配置,支持多Dify应用集成并提升研发效率; +- 2、【优化】合并PR-2417,修复任务管理时JobHandler录入空格问题; +- 3、【优化】合并PR-2504,规避SQL注入问题; +- 4、【升级】多个项目依赖升级至较新稳定版本,涉及 netty、spring/springboot、groovy 等; + +### 7.40 版本 v3.2.0 Release Notes[2025-08-24] +- 1、【强化】AI任务(ollamaJobHandler)优化:针对 “model” 模型配置信息,从执行器侧文件类配置调整至调度中心“任务参数”动态配置,支持集成多模型、并结合任务动态配置切换。 +- 2、【安全】登录认证重构:密码加密算法从Md5改为Sha256;登录态改为登录后动态随机生成;提升系统安全性;(需要针对用户表进行字段调整,同时需要重新初始化密码信息;相关SQL脚本如下) + ``` + // 1、用户表password字段需要调整长度,执行如下命令 + ALTER TABLE xxl_job_user + MODIFY COLUMN `password` varchar(100) NOT NULL COMMENT '密码加密信息'; + ALTER TABLE xxl_job_user + ADD COLUMN `token` varchar(100) DEFAULT NULL COMMENT '登录token'; + + // 2、存量用户密码需要修改,可执行如下命令将密码初始化 “123456”;也可以自行通过 “SHA256Tool.sha256” 工具生成其他初始化密码; + UPDATE xxl_job_user t SET t.password = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92' WHERE t.username = {用户名}; + ``` +- 3、【强化】GLUE模式(Python) 扩展,支持 "GLUE(Python3)" 与 "GLUE(Python2)" 两种模式,分别支持 python3/2 多版本; +- 4、【强化】调度中心系统日志调整,支持启动时指定 -DLOG_HOME 参数自定义日志位置;同时优化日志格式提升易读性; +- 5、【优化】任务Bean扫描规则调整,过滤冗余不必要扫描,避免系统组件提前初始化; +- 6、【优化】登录信息页面空值处理优化,避免空值影响ftl渲染; +- 7、【优化】异常页面处理逻辑优化,新增兜底落地页配置; +- 8、【重构】ReturnT 重构,简化代码结构,提升API易用性以及可维护性; +- 9、【重构】项目结构重构,提升可维护性与易读性; +- 10、【修复】漏洞修复(CVE-2025-7787),针对 httpJobHandler 支持配置URL白名单限制,防止服务器端请求伪造(SSRF)攻击。 +- 11、【修复】合并PR-3738,修复拼写问题; +- 12、【修复】合并PR-3506,修复小概率情况下任务重复调度问题; +- 13、【修复】合并PR-3747,修复异常情况下资源泄漏风险; +- 14、【修复】IDOR越权问题修复,提升任务操作及日志管理安全性; +- 15、【升级】升级多项maven依赖至较新版本,如 netty、groovy、mybatis、spring、spring-ai、dify 等; + +### 7.41 版本 v3.3.0 Release Notes[2025-11-29] +- 1、【新增】执行器新增“任务扫描排除路径”配置项(xxl.job.executor.excludedpackage),任务扫描时忽略指定包路径下的任务; +- 2、【优化】执行器任务Bean扫描逻辑调整,优化懒加载Bean检测及过滤机制,避免提前初始化类问题; +- 3、【新增】合并PR-3840,执行器支持通过XxlJobHelper获取任务触发时间戳;XxlJobHelper组件完善,支持通过“XxlJobHelper.getLogId/getLogDateTime/getLogFileName”方法获取执行日志相关信息; +- 4、【升级】调度中心UI框架升级,统一交互组件,支持多主题、多标签与局部渲染等,升级UI组件及性能; +- 5、【优化】调度时间轮组件强化,保障不重不漏:调度时间轮单刻度数据去重,避免极端情况下任务重复执行;时间轮转动时校验临近刻度,避免极端情况下遗漏刻度; +- 6、【优化】调度任务锁逻辑优化,事务SQL下沉至Mapper层统一管理,并增加测试用例,提升代码可读性以及可维护性; +- 7、【优化】调度快慢线程池默认配置上调,提升默认配置单机负载;调度预读任务数计算系数下调,降低事务颗粒度,提升性能及稳定性; +- 8、【性能】调度中心调整资源加载逻辑,移除不必要的拦截器,提升页面加载性能; +- 9、【优化】优化日志列表页面展示逻辑,新增展示“日志ID”与“任务名称”信息; +- 10、【优化】报表统计SQL优化,修复小概率情况下查询null值问题;报表初始化SQL优化,修复小概率情况增改竞争问题; +- 11、【优化】优日志报告与清理逻辑,增加清理过期日志的异常捕获,避免线程异常退出; +- 12、【优化】任务回调失败日志读写磁盘逻辑优化,解决极端情况下大文件读写内存问题; +- 13、【升级】Http通讯组件升级,基于接口代理方式重构通讯组件,提升组件性能及扩展性; +- 14、【重构】规范API交互协议,通用响应结构体调整为Response,调度中心API统一为Response封装数据; + (注意:响应结构体从ReturnT升级为Response,其中属性值“content”会调整为“data”,通过openapi交互场景需要关注) +- 15、【重构】调度过期策略、调度类型策略逻辑重构,代码组件化拆分并完善日志,提升健壮性及可维护性; +- 16、【重构】调度中心底层组件重构,组件初始化以及销毁逻辑统一处理,任务触发及和回调逻辑优化,避免资源泄漏风险; +- 17、【重构】调度中心底层组件模块化拆分,移除组件单例以及静态代码逻辑,提升组件可维护性; +- 18、【重构】重构Rolling日志读写逻辑,解决边界条件下异常情况,优化读写性能; +- 19、【修复】脚本任务process销毁逻辑优化,解决风险情况下脚本进程无法终止问题; +- 20、【修复】合并PR-2369,修复脚本任务参数取值问题; +- 21、【新增】任务审计日志,记录任务操作敏感日志信息,如任务新建/更新/删除/启停/触发以及GLUE代码更新等,用于系统监控、审计和安全分析,可快速追溯异常行为以及定位排查问题等。 + (当前任务审计日志以Info级别输出在系统日志中,可通过关键词 "xxl-job operation log:" 检索过滤) +- 22、【强化】通用HTTP任务(httpJobHandler)强化,支持更丰富请求参数设置,完整参数示例如下: + +
+ 完整参数示例参考: + + ``` + { + "url": "http://www.baidu.com", + "method": "POST", + "contentType": "application/json", + "headers": { + "header01": "value01" + }, + "cookies": { + "cookie01": "value01" + }, + "timeout": 3000, + "data": "request body data", + "form": { + "key01": "value01" + }, + "auth": "auth data" + } + ``` +
+ +- 23、【优化】调度组件日志完善,提升边界情况下问题定位效率; +- 24、【升级】升级多项maven依赖至较新版本,如 netty、groovy、springboot、spring-ai、dify、mybatis、xxl-sso 等; + +**备注:** +- a、本次升级数据模型向前兼容,v3.2.*版本可直接升级不需要进行数据库表调整; +- b、本次升级针对客户端rollinglog依赖字段做规范约束,如不关注该功能 v2.4.* 及后续版本客户端不需要升级/可兼容,否则需要升级客户端版本; + +### 7.42 版本 v3.3.1 Release Notes[2025-12-06] +- 1、【新增】新增“执行器启用开关”配置项(xxl.job.executor.enabled),默认开启,关闭时不进行执行器初始化; +- 2、【修复】调度组件事务代码调整,修复DB超时等小概率情况下调度终止问题; +- 3、【修复】合并PR-3869,修复底层通讯超时设置无效问题; +- 4、【优化】执行器删除逻辑优化,删除时一并清理注册表数据,避免小概率情况下注册数据堆积(ISSUE-3669); +- 5、【升级】调度中心升级至 SpringBoot4;升级多项maven依赖至较新版本,如 mybatis、groovy 等; + +### 7.43 版本 v3.3.2 Release Notes[2026-01-01] +- 1、【优化】优雅停机:调度中心停机,检测时间轮非空时主动等待调度完成;客户端停机,检测存在运行中任务时,停止接收新任务并主动等待任务执行完成; +- 2、【新增】新增 Docker Compose 配置,支持一键配置启动调度中心集群; + +
+ Docker Compose启动步骤: + + ``` + // 下载 XXL-JOB + git clone --branch "$(curl -s https://api.github.com/repos/xuxueli/xxl-job/releases/latest | jq -r .tag_name)" https://github.com/xuxueli/xxl-job.git + // 构建 XXL-JOB + mvn clean package -Dmaven.test.skip=true + // 配置 XXL-JOB(前往docker目录,自定义 .env) + cd ./docker + cat .env + // 启动 XXL-JOB + docker compose up -d + // 停止 XXL-JOB + docker compose down + ``` +
+ +- 3、【优化】调度中心操作体验优化:表格交互调整为单行选中模式;禁用分页循环;优化分页限制文案; +- 4、【优化】调度线程事务提交逻辑调整,避免边界条件下线程异常退出,增强健壮性; +- 5、【优化】调度日志列表排序逻辑优化,提升易读性; +- 6、【优化】调度中心OpenAPI通讯token调整为非必填;合并PR-3892; +- 7、【优化】执行器详情接口权限调整,支持普通用户查看注册节点;合并PR-3882; +- 8、【优化】任务参数LogDateTime生成逻辑调整,分片广播场景下保障同一批调度一致; +- 9、【升级】升级多项maven依赖至较新版本,如 spring、netty、xxl-sso、xxl-tool 等; +- 10、【优化】统一项目依赖管理结构,依赖版本统一到父级pom提升可维护性; + +### 7.44 版本 v3.4.0 Release Notes[2026-04-05] +- 1、【新增】AI执行器集成OpenClaw: 新增“openClawJobHandler”内置AI任务,与OpenClaw集成打通,支持快速开发AI类任务; +- 2、【增强】任务调度后分批合并更新:高频调度场景可百倍降低SQL操作合并执行,提升调度性能; +(任务调度后批量合并更新配置“xxl.job.schedule.batchsize”) +- 3、【优化】调度日志支持执行器维度查看,提升体验;新增调度日志索引,提升查询性能; +- 4、【优化】一致性哈希路由算法优化,重构哈希环逻辑提升代码简洁性; +- 5、【优化】Cron解析工具优化,解决day-of-month使用L时会跳过非31天的月份问题; +- 6、【优化】执行器注册表主键调整为long数据类型,防止大规模执行器集群注册数据溢出; +- 7、【优化】任务参数长度调整,最长支持2048字符; +- 8、【优化】执行器名称长度调整,最长支持64字符; +- 9、【修复】固定间隔模式调度策略调整,修复小概率下触发时间偏差问题; +- 10、【调整】Docker基础镜像调整为eclipse-temurin; +- 11、【优化】父POM依赖配置优化,移除容易配置;合并PR-3926; +- 12、【优化】调度组件触发判断优化,合并PR-2502; +- 13、【优化】调度日志调整,完善日志参数信息,合并PR-2761; +- 14、【重构】代码重构优化:I18N国际化、属性加载、报表SQL等逻辑重构,合并PR-2888、PR-3006、PR-3027、PR-3198、PR-3285; +- 15、【重构】告警组件初始化重构,提升代码可维护性,合并PR-2903; +- 16、【升级】升级多项maven依赖至较新版本; + +**备注:** +数据库升级脚本: +``` +-- 任务日志表:添加索引 +create index I_jobgroup on xxl_job_log (job_group); + +-- 执行器表:修改字段长度 +alter table xxl_job_group + modify title varchar(64) not null comment '执行器名称'; + +-- 执行器注册表:修改自增ID类型 +alter table xxl_job_registry + modify id bigint(20) NOT NULL AUTO_INCREMENT; + +-- 任务表:修改字段长度 +alter table xxl_job_info + modify executor_param text null comment '任务参数'; + +-- 日志表:修改字段长度 +alter table xxl_job_log + modify executor_param text null comment '任务参数'; +``` + +### 7.45 版本 v3.4.1 Release Notes[ING] +- 1、【TODO】调度中心OpenAPI完善,提供任务管理能力;封装Agent Skill并推送ClawHub; +- 2、【TODO】AccessToken升级:执行器维度隔离,支持线上化配置;升级双端OpenApi,适配AccessToken升级; -### 7.37 版本 v3.0.0 Release Notes[规划中] -- 14、[规划中]登陆态Token生成逻辑优化,混淆登陆时间属性,降低token泄漏风险; -- 15、[规划中]升级springboot3.x,解决2.x老版本漏洞类问题。注意,springboot3.x依赖jdk17; ### TODO LIST - 1、调度隔离:调度中心针对不同执行器,各自维护不同的调度和远程触发组件。 - 2、任务优先级:调度与执行阶段按照优先级分配资源。 - 3、多数据库支持,DAO层通过JPA实现,不限制数据库类型。 -- 4、执行器Log清理功能:调度中心Log删除时同步删除执行器中的Log文件; +- 4、OpenApi: + - 执行器Log文件清理:支持调度中心远程删除执行器中指定任务的Log文件; - 5、性能优化:任务、执行器数据全量本地缓存;新增消息表广播通知; - 6、DAG流程任务 - 子任务:废弃 @@ -2456,10 +2858,8 @@ public void execute() { - 分片任务:全部完成后才会出发后置节点; - 配置并列的"a-b、b-c"路径列表,构成串行、并行、dag任务流程,"dagre-d3"绘图;任务依赖,流程图,子任务+会签任务,各节点日志;支持根据成功、失败选择分支; - 7、任务标签:方便搜索; -- 8、告警增强: - - 邮件告警:支持自定义标题、模板格式; - - webhook告警:支持自定义告警URL、请求体格式; -- 9、安全强化:AccessToken动态生成、动态启停;控制调度、回调; +- 8、GLUE 模式 Web Ide 版本对比功能; +- 9、自定义失败重试时间间隔; - 10、任务导入导出工具,灵活支持版本升级、迁移等场景。 - 11、任务日志重构:一次调度只记录一条主任务,维护起止时间和状态。 - 普通任务:只记录一条主任务; @@ -2467,13 +2867,27 @@ public void execute() { - 重试任务:失败时,新增主任务。所有调度记录,包括入口调度和重试调度,均挂载主任务上。 - 12、分片任务:全部完成后才会出发后置节点; - 13、日期过滤:支持多个时间段排除; -- 13、GLUE 模式 Web Ide 版本对比功能; - 14、提供执行器Docker镜像; - 15、脚本任务,支持数据参数,新版本仅支持单参数不支持需要兼容; - 17、批量调度:调度请求入queue,调度线程批量获取调度请求并发起远程调度;提高线程效率; - 18、执行器端口复用,复用容器端口提供通讯服务; -- 19、自定义失败重试时间间隔; -- 20、安全功能增强,通讯加密参数改用加密数据避免AccessToken明文, 降低token泄漏风险; +- 19、安全功能增强,通讯加密参数改用加密数据避免AccessToken明文, 降低token泄漏风险; +- 20、告警增强: + - 邮件告警:支持自定义标题、模板格式; + - webhook告警:支持自定义告警URL、请求体格式; +- 21、公共告警策略:执行器维度设置多告警策略,任务勾选启用;待评估任务或执行器维度; +- 20、日志策略: + - 调度日志:全局配置:废弃; 新增“调度日志策略”:任务维度自定义,保留3天、7天、1个月、3个月、一年、永久; + - 执行日志:新增“执行RollingLog开关”:任务维度自定义,支持:RollingLog、普通日志(slf4j输出)、关闭(不输出); +- 21、AccessToken:废弃全局配置;支持在线管理,动态生成、动态启停; +- 22、任务管理OpenAPI; +- 23、调度中心启动参数线上配置:告警发送邮箱、Token,支持线上配置生效,修改不需重启机器; +- 24、执行器内嵌server切换tomcat,精简依赖; +- 25、日志策略新增: + - 调度日志策略:任务级设置,最少保留1天。 + - 执行日志策略:可选 RollingLog、slf4jLog; + - 清理逻辑,性能重构。 + ## 八、其他 diff --git a/doc/XXL-JOB架构图.key b/doc/XXL-JOB架构图.key index 16076575..c4b8c850 100755 Binary files a/doc/XXL-JOB架构图.key and b/doc/XXL-JOB架构图.key differ diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql index 4b15476b..c27a5fd8 100644 --- a/doc/db/tables_xxl_job.sql +++ b/doc/db/tables_xxl_job.sql @@ -7,6 +7,34 @@ use `xxl_job`; SET NAMES utf8mb4; +## —————————————————————— job group and registry —————————————————— + +CREATE TABLE `xxl_job_group` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `app_name` varchar(64) NOT NULL COMMENT '执行器AppName', + `title` varchar(64) NOT NULL COMMENT '执行器名称', + `address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入', + `address_list` text COMMENT '执行器地址列表,多地址逗号分隔', + `update_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +CREATE TABLE `xxl_job_registry` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `registry_group` varchar(50) NOT NULL, + `registry_key` varchar(255) NOT NULL, + `registry_value` varchar(255) NOT NULL, + `update_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `i_g_k_v` (`registry_group`, `registry_key`, `registry_value`) USING BTREE +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +## —————————————————————— job info —————————————————— + CREATE TABLE `xxl_job_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -20,8 +48,8 @@ CREATE TABLE `xxl_job_info` `schedule_conf` varchar(128) DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型', `misfire_strategy` varchar(50) NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略', `executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略', - `executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler', - `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数', + `executor_handler` varchar(255) DEFAULT NULL COMMENT '任务handler', + `executor_param` text DEFAULT NULL COMMENT '任务参数', `executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略', `executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒', `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数', @@ -37,28 +65,43 @@ CREATE TABLE `xxl_job_info` ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; +CREATE TABLE `xxl_job_logglue` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `job_id` int(11) NOT NULL COMMENT '任务,主键ID', + `glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型', + `glue_source` mediumtext COMMENT 'GLUE源代码', + `glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注', + `add_time` datetime DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +## —————————————————————— job log and report —————————————————— + CREATE TABLE `xxl_job_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `job_group` int(11) NOT NULL COMMENT '执行器主键ID', - `job_id` int(11) NOT NULL COMMENT '任务,主键ID', + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `job_group` int(11) NOT NULL COMMENT '执行器主键ID', + `job_id` int(11) NOT NULL COMMENT '任务,主键ID', `executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址', - `executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler', - `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数', - `executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2', - `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数', + `executor_handler` varchar(255) DEFAULT NULL COMMENT '任务handler', + `executor_param` text DEFAULT NULL COMMENT '任务参数', + `executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '任务分片参数,格式如 1/2', + `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数', `trigger_time` datetime DEFAULT NULL COMMENT '调度-时间', - `trigger_code` int(11) NOT NULL COMMENT '调度-结果', - `trigger_msg` text COMMENT '调度-日志', + `trigger_code` int(11) NOT NULL COMMENT '调度-结果', + `trigger_msg` text COMMENT '调度-日志', `handle_time` datetime DEFAULT NULL COMMENT '执行-时间', - `handle_code` int(11) NOT NULL COMMENT '执行-状态', - `handle_msg` text COMMENT '执行-日志', - `alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败', + `handle_code` int(11) NOT NULL COMMENT '执行-状态', + `handle_msg` text COMMENT '执行-日志', + `alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败', PRIMARY KEY (`id`), KEY `I_trigger_time` (`trigger_time`), KEY `I_handle_code` (`handle_code`), - KEY `I_jobid_jobgroup` (`job_id`,`job_group`), - KEY `I_job_id` (`job_id`) + KEY `I_jobgroup` (`job_group`), + KEY `I_jobid` (`job_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; @@ -75,48 +118,23 @@ CREATE TABLE `xxl_job_log_report` ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -CREATE TABLE `xxl_job_logglue` -( - `id` int(11) NOT NULL AUTO_INCREMENT, - `job_id` int(11) NOT NULL COMMENT '任务,主键ID', - `glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型', - `glue_source` mediumtext COMMENT 'GLUE源代码', - `glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注', - `add_time` datetime DEFAULT NULL, - `update_time` datetime DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4; +## —————————————————————— lock —————————————————— -CREATE TABLE `xxl_job_registry` +CREATE TABLE `xxl_job_lock` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `registry_group` varchar(50) NOT NULL, - `registry_key` varchar(255) NOT NULL, - `registry_value` varchar(255) NOT NULL, - `update_time` datetime DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `i_g_k_v` (`registry_group`, `registry_key`, `registry_value`) USING BTREE + `lock_name` varchar(50) NOT NULL COMMENT '锁名称', + PRIMARY KEY (`lock_name`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -CREATE TABLE `xxl_job_group` -( - `id` int(11) NOT NULL AUTO_INCREMENT, - `app_name` varchar(64) NOT NULL COMMENT '执行器AppName', - `title` varchar(12) NOT NULL COMMENT '执行器名称', - `address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入', - `address_list` text COMMENT '执行器地址列表,多地址逗号分隔', - `update_time` datetime DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4; +## —————————————————————— user —————————————————— CREATE TABLE `xxl_job_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '账号', - `password` varchar(50) NOT NULL COMMENT '密码', + `password` varchar(100) NOT NULL COMMENT '密码加密信息', + `token` varchar(100) DEFAULT NULL COMMENT '登录token', `role` tinyint(4) NOT NULL COMMENT '角色:0-普通用户、1-管理员', `permission` varchar(255) DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割', PRIMARY KEY (`id`), @@ -124,33 +142,49 @@ CREATE TABLE `xxl_job_user` ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -CREATE TABLE `xxl_job_lock` -( - `lock_name` varchar(50) NOT NULL COMMENT '锁名称', - PRIMARY KEY (`lock_name`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4; - -## —————————————————————— init data —————————————————— +## —————————————————————— for default data —————————————————— INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`, `update_time`) -VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL, '2018-11-03 22:21:31'); + VALUES (1, 'xxl-job-executor-sample', '通用执行器Sample', 0, NULL, now()), + (2, 'xxl-job-executor-sample-ai', 'AI执行器Sample', 0, NULL, now()); INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) -VALUES (1, 1, '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'CRON', '0 0 0 * * ? *', +VALUES (1, 1, '示例任务01', now(), now(), 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', - '2018-11-03 22:21:31', ''); + now(), ''), + (2, 2, 'Ollama示例任务', now(), now(), 'XXL', '', 'NONE', '', + 'DO_NOTHING', 'FIRST', 'ollamaJobHandler', '{ + "input": "慢SQL问题分析思路", + "prompt": "你是一个研发工程师,擅长解决技术类问题。", + "model": "qwen3.5:2b" +}', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', + now(), ''), + (3, 2, 'Dify示例任务', now(), now(), 'XXL', '', 'NONE', '', + 'DO_NOTHING', 'FIRST', 'difyWorkflowJobHandler', '{ + "inputs":{ + "input":"查询班级各学科前三名" + }, + "user": "xxl-job", + "baseUrl": "http://localhost/v1", + "apiKey": "app-OUVgNUOQRIMokfmuJvBJoUTN" +}', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', + now(), ''), + (4, 2, 'OpenClaw示例任务', now(), now(), 'XXL', '', 'NONE', '', + 'DO_NOTHING', 'FIRST', 'openClawJobHandler', '{ + "input": "查看下上海今天得天气,给出出游建议", + "prompt": "你是一个出游助手,擅长做旅游规划" +}', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', + now(), ''); INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) -VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL); +VALUES (1, 'admin', '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92', 1, NULL); INSERT INTO `xxl_job_lock` (`lock_name`) VALUES ('schedule_lock'); commit; - diff --git a/doc/images/img_6yC0.png b/doc/images/img_6yC0.png index 01bf573f..0e68e78d 100644 Binary files a/doc/images/img_6yC0.png and b/doc/images/img_6yC0.png differ diff --git a/doc/images/img_Fgql.png b/doc/images/img_Fgql.png index f8840516..1af75b4a 100644 Binary files a/doc/images/img_Fgql.png and b/doc/images/img_Fgql.png differ diff --git a/doc/images/img_Hr2T.png b/doc/images/img_Hr2T.png index 4b5a73c2..3a467871 100644 Binary files a/doc/images/img_Hr2T.png and b/doc/images/img_Hr2T.png differ diff --git a/doc/images/img_Qohm.png b/doc/images/img_Qohm.png index 4fb24e47..5eba6cc3 100644 Binary files a/doc/images/img_Qohm.png and b/doc/images/img_Qohm.png differ diff --git a/doc/images/img_ZAsz.png b/doc/images/img_ZAsz.png index bbb83eca..2737afa1 100644 Binary files a/doc/images/img_ZAsz.png and b/doc/images/img_ZAsz.png differ diff --git a/doc/images/img_o8HQ.png b/doc/images/img_o8HQ.png index 46b5bd0a..abb8b7d6 100644 Binary files a/doc/images/img_o8HQ.png and b/doc/images/img_o8HQ.png differ diff --git a/doc/images/img_tJOq.png b/doc/images/img_tJOq.png index f3d00625..b0bc0ad6 100644 Binary files a/doc/images/img_tJOq.png and b/doc/images/img_tJOq.png differ diff --git a/docker/.env b/docker/.env new file mode 100644 index 00000000..37d1605a --- /dev/null +++ b/docker/.env @@ -0,0 +1,9 @@ +# docker-compose env + +# xxl-job, admin +XXL_JOB_ADMIN_PORT=8080 +XXL_JOB_ADMIN_CONTEXT_PATH=/xxl-job-admin + +# xxl-job, database +MYSQL_ROOT_PASSWORD=root_pwd +MYSQL_PATH=/Users/admin/program/docker/instance/mysql \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000..a3380aad --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,75 @@ +# docker-compose version +version: '3.8' + +services: + mysql: + image: mysql:8.4 + container_name: xxl-job-mysql + environment: + # 1、数据库密码设置,需要与Admin中配置一致: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + # 2、数据库实例名称,需要与Admin中配置一致; + MYSQL_DATABASE: xxl_job + ports: + - "3306:3306" + volumes: + # 说明:仅数据库首次初始化时执行; + - ../doc/db/tables_xxl_job.sql:/docker-entrypoint-initdb.d/tables_xxl_job.sql:ro + # 3、数据库持久化目录位置,建议自定义: + - ${MYSQL_PATH}/conf:/etc/mysql/conf.d + - ${MYSQL_PATH}/logs:/var/log/mysql + - ${MYSQL_PATH}/data:/var/lib/mysql + command: >- + --character-set-server=utf8mb4 + --collation-server=utf8mb4_unicode_ci + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + timeout: 20s + retries: 10 + networks: + - xxl-job-network + + xxl-job-admin: + # 4、本地Build设置,如果期望使用推动DockerHub的镜像,可以注释当前启用的image、build配置,并启用如下设置版本的image配置; + #image: xuxueli/xxl-job-admin:{version} + image: xuxueli/xxl-job-admin:local + build: + context: ../xxl-job-admin + dockerfile: Dockerfile + container_name: xxl-job-admin + environment: + # 5、数据库密码设置,需要与上文Mysql中保持一致: + PARAMS: >- + --spring.datasource.url=jdbc:mysql://mysql:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai + --spring.datasource.username=root + --spring.datasource.password=${MYSQL_ROOT_PASSWORD} + --server.port=${XXL_JOB_ADMIN_PORT} + --server.servlet.context-path=${XXL_JOB_ADMIN_CONTEXT_PATH} + ports: + - "${XXL_JOB_ADMIN_PORT}:${XXL_JOB_ADMIN_PORT}" + depends_on: + mysql: + condition: service_healthy + networks: + - xxl-job-network + + xxl-job-executor-sample-springboot: + image: xuxueli/xxl-job-executor-sample-springboot:local + build: + context: ./xxl-job-executor-samples/xxl-job-executor-sample-springboot + dockerfile: Dockerfile + container_name: xxl-job-executor-sample-springboot + environment: + PARAMS: >- + --xxl.job.admin.addresses=http://xxl-job-admin:${XXL_JOB_ADMIN_PORT}${XXL_JOB_ADMIN_CONTEXT_PATH:-/} + ports: + - "9999:9999" + depends_on: + xxl-job-admin: + condition: service_started + networks: + - xxl-job-network + +networks: + xxl-job-network: + driver: bridge diff --git a/pom.xml b/pom.xml index 7805cb52..efbe7f0b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.xuxueli xxl-job - 2.5.0 + 3.4.0 pom ${project.artifactId} @@ -21,35 +21,166 @@ UTF-8 UTF-8 UTF-8 - 1.8 - 1.8 + 17 + 17 true - 3.3.1 - 3.11.2 - 3.2.7 + 3.15.0 + 3.4.0 + 3.12.0 + 3.2.8 + 0.10.0 - 2.0.16 - 5.11.4 - 1.3.2 - - 4.1.116.Final - 2.11.0 + 2.0.17 + 6.0.3 + + + 3.0.0 + - 5.3.39 - 2.7.18 - - 2.3.2 - 9.1.0 - - 4.0.24 + 4.0.5 + 7.0.6 + + 4.0.1 + 9.6.0 + + + 4.2.12.Final + + 5.0.5 + + + 2.4.0 + + 2.5.0 + + 2.13.2 + + + 2.0.0-M4 + + 1.2.5 - - - - + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + + org.slf4j + slf4j-reload4j + ${slf4j-api.version} + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + + + + + jakarta.annotation + jakarta.annotation-api + ${jakarta.annotation-api.version} + + + + + io.netty + netty-codec-http + ${netty.version} + + + + com.google.code.gson + gson + ${gson.version} + + + + + com.xuxueli + xxl-tool + ${xxl-tool.version} + + + + + org.apache.groovy + groovy + ${groovy.version} + + + + + org.springframework + spring-context + ${spring.version} + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot-starter.version} + + + + com.mysql + mysql-connector-j + ${mysql-connector-j.version} + + + + com.xuxueli + xxl-job-core + ${project.parent.version} + + + + + com.xuxueli + xxl-sso-core + ${xxl-sso.version} + + + + + org.springframework.ai + spring-ai-starter-model-ollama + ${spring-ai.version} + + + org.springframework.ai + spring-ai-starter-model-openai + ${spring-ai.version} + + + + io.github.imfangs + dify-java-client + ${dify-java-client.version} + + + + @@ -57,7 +188,6 @@ https://opensource.org/licenses/GPL-3.0 - master https://github.com/xuxueli/xxl-job.git @@ -73,6 +203,11 @@ + + + + + @@ -85,6 +220,17 @@ + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${maven.compiler.source} + ${maven.compiler.target} + true + + org.apache.maven.plugins @@ -133,18 +279,25 @@ + + + org.sonatype.central + central-publishing-maven-plugin + ${central-publishing-maven-plugin.version} + true + + central + + xxl-job-admin + xxl-job-executor-samples + xxl-job-executor-sample-frameless + xxl-job-executor-sample-springboot + xxl-job-executor-sample-springboot-ai + + + - - - oss - https://oss.sonatype.org/content/repositories/snapshots/ - - - oss - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - diff --git a/xxl-job-admin/Dockerfile b/xxl-job-admin/Dockerfile index dc195371..d599b077 100644 --- a/xxl-job-admin/Dockerfile +++ b/xxl-job-admin/Dockerfile @@ -1,11 +1,22 @@ -FROM openjdk:8-jre-slim +# base image +#FROM openjdk:21-jdk-slim +FROM eclipse-temurin:17-jre + +# maintainer MAINTAINER xuxueli +# set params ENV PARAMS="" +# set timezone ENV TZ=PRC RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone +# copy jar ADD target/xxl-job-admin-*.jar /app.jar -ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /app.jar $PARAMS"] \ No newline at end of file +# command +# log home: -e LOG_HOME=/data/applogs +# jvm options: -e JAVA_OPTS="-Xms128m -Xmx128m" +# app params: -e PARAMS="--server.port=8080" +ENTRYPOINT ["sh","-c","java ${LOG_HOME:+-DLOG_HOME=$LOG_HOME} -jar $JAVA_OPTS /app.jar $PARAMS"] \ No newline at end of file diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml index 16b36523..28299ca4 100644 --- a/xxl-job-admin/pom.xml +++ b/xxl-job-admin/pom.xml @@ -4,7 +4,7 @@ com.xuxueli xxl-job - 2.5.0 + 3.4.0 xxl-job-admin jar @@ -13,26 +13,14 @@ true - - - - org.springframework.boot - spring-boot-starter-parent - ${spring-boot.version} - pom - import - - - - - + org.springframework.boot spring-boot-starter-web - + org.springframework.boot spring-boot-starter-test @@ -44,37 +32,38 @@ org.springframework.boot spring-boot-starter-freemarker - org.springframework.boot spring-boot-starter-mail - org.springframework.boot spring-boot-starter-actuator - + org.mybatis.spring.boot mybatis-spring-boot-starter - ${mybatis-spring-boot-starter.version} com.mysql mysql-connector-j - ${mysql-connector-j.version} com.xuxueli xxl-job-core - ${project.parent.version} + + + + + com.xuxueli + xxl-sso-core @@ -94,25 +83,6 @@ - - - diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/constant/Consts.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/constant/Consts.java new file mode 100644 index 00000000..640350ac --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/constant/Consts.java @@ -0,0 +1,7 @@ +package com.xxl.job.admin.constant; + +public class Consts { + + public static final String ADMIN_ROLE = "ADMIN"; + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/constant/TriggerStatus.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/constant/TriggerStatus.java new file mode 100644 index 00000000..de13e009 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/constant/TriggerStatus.java @@ -0,0 +1,32 @@ +package com.xxl.job.admin.constant; + +public enum TriggerStatus { + + STOPPED(0, "stopped"), + RUNNING(1, "running"); + + private int value; + private String desc; + + TriggerStatus(int value, String desc) { + this.value = value; + this.desc = desc; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java deleted file mode 100644 index ca965107..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.xxl.job.admin.controller; - -import com.xxl.job.admin.controller.annotation.PermissionLimit; -import com.xxl.job.admin.service.impl.LoginService; -import com.xxl.job.admin.service.XxlJobService; -import com.xxl.job.core.biz.model.ReturnT; -import org.springframework.beans.propertyeditors.CustomDateEditor; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.bind.annotation.InitBinder; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Map; - -/** - * index controller - * @author xuxueli 2015-12-19 16:13:16 - */ -@Controller -public class IndexController { - - @Resource - private XxlJobService xxlJobService; - @Resource - private LoginService loginService; - - - @RequestMapping("/") - public String index(Model model) { - - Map dashboardMap = xxlJobService.dashboardInfo(); - model.addAllAttributes(dashboardMap); - - return "index"; - } - - @RequestMapping("/chartInfo") - @ResponseBody - public ReturnT> chartInfo(Date startDate, Date endDate) { - ReturnT> chartInfo = xxlJobService.chartInfo(startDate, endDate); - return chartInfo; - } - - @RequestMapping("/toLogin") - @PermissionLimit(limit=false) - public ModelAndView toLogin(HttpServletRequest request, HttpServletResponse response,ModelAndView modelAndView) { - if (loginService.ifLogin(request, response) != null) { - modelAndView.setView(new RedirectView("/",true,false)); - return modelAndView; - } - return new ModelAndView("login"); - } - - @RequestMapping(value="login", method=RequestMethod.POST) - @ResponseBody - @PermissionLimit(limit=false) - public ReturnT loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){ - boolean ifRem = (ifRemember!=null && ifRemember.trim().length()>0 && "on".equals(ifRemember))?true:false; - return loginService.login(request, response, userName, password, ifRem); - } - - @RequestMapping(value="logout", method=RequestMethod.POST) - @ResponseBody - @PermissionLimit(limit=false) - public ReturnT logout(HttpServletRequest request, HttpServletResponse response){ - return loginService.logout(request, response); - } - - @RequestMapping("/help") - public String help() { - - /*if (!PermissionInterceptor.ifLogin(request)) { - return "redirect:/toLogin"; - }*/ - - return "help"; - } - - @InitBinder - public void initBinder(WebDataBinder binder) { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - dateFormat.setLenient(false); - binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true)); - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java deleted file mode 100644 index aa51e739..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.xxl.job.admin.controller; - -import com.xxl.job.admin.controller.annotation.PermissionLimit; -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.core.biz.AdminBiz; -import com.xxl.job.core.biz.model.HandleCallbackParam; -import com.xxl.job.core.biz.model.RegistryParam; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.util.GsonTool; -import com.xxl.job.core.util.XxlJobRemotingUtil; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import java.util.List; - -/** - * Created by xuxueli on 17/5/10. - */ -@Controller -@RequestMapping("/api") -public class JobApiController { - - @Resource - private AdminBiz adminBiz; - - /** - * api - * - * @param uri - * @param data - * @return - */ - @RequestMapping("/{uri}") - @ResponseBody - @PermissionLimit(limit=false) - public ReturnT api(HttpServletRequest request, @PathVariable("uri") String uri, @RequestBody(required = false) String data) { - - // valid - if (!"POST".equalsIgnoreCase(request.getMethod())) { - return new ReturnT(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support."); - } - if (uri==null || uri.trim().length()==0) { - return new ReturnT(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty."); - } - if (XxlJobAdminConfig.getAdminConfig().getAccessToken()!=null - && XxlJobAdminConfig.getAdminConfig().getAccessToken().trim().length()>0 - && !XxlJobAdminConfig.getAdminConfig().getAccessToken().equals(request.getHeader(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN))) { - return new ReturnT(ReturnT.FAIL_CODE, "The access token is wrong."); - } - - // services mapping - if ("callback".equals(uri)) { - List callbackParamList = GsonTool.fromJson(data, List.class, HandleCallbackParam.class); - return adminBiz.callback(callbackParamList); - } else if ("registry".equals(uri)) { - RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class); - return adminBiz.registry(registryParam); - } else if ("registryRemove".equals(uri)) { - RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class); - return adminBiz.registryRemove(registryParam); - } else { - return new ReturnT(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found."); - } - - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobCodeController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobCodeController.java deleted file mode 100644 index a0886da8..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobCodeController.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.xxl.job.admin.controller; - -import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobLogGlue; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.admin.dao.XxlJobInfoDao; -import com.xxl.job.admin.dao.XxlJobLogGlueDao; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.glue.GlueTypeEnum; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import java.util.Date; -import java.util.List; - -/** - * job code controller - * @author xuxueli 2015-12-19 16:13:16 - */ -@Controller -@RequestMapping("/jobcode") -public class JobCodeController { - - @Resource - private XxlJobInfoDao xxlJobInfoDao; - @Resource - private XxlJobLogGlueDao xxlJobLogGlueDao; - - @RequestMapping - public String index(HttpServletRequest request, Model model, int jobId) { - XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId); - List jobLogGlues = xxlJobLogGlueDao.findByJobId(jobId); - - if (jobInfo == null) { - throw new RuntimeException(I18nUtil.getString("jobinfo_glue_jobid_unvalid")); - } - if (GlueTypeEnum.BEAN == GlueTypeEnum.match(jobInfo.getGlueType())) { - throw new RuntimeException(I18nUtil.getString("jobinfo_glue_gluetype_unvalid")); - } - - // valid permission - PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup()); - - // Glue类型-字典 - model.addAttribute("GlueTypeEnum", GlueTypeEnum.values()); - - model.addAttribute("jobInfo", jobInfo); - model.addAttribute("jobLogGlues", jobLogGlues); - return "jobcode/jobcode.index"; - } - - @RequestMapping("/save") - @ResponseBody - public ReturnT save(HttpServletRequest request, int id, String glueSource, String glueRemark) { - // valid - if (glueRemark==null) { - return new ReturnT(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_remark")) ); - } - if (glueRemark.length()<4 || glueRemark.length()>100) { - return new ReturnT(500, I18nUtil.getString("jobinfo_glue_remark_limit")); - } - XxlJobInfo existsJobInfo = xxlJobInfoDao.loadById(id); - if (existsJobInfo == null) { - return new ReturnT(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid")); - } - - // valid permission - PermissionInterceptor.validJobGroupPermission(request, existsJobInfo.getJobGroup()); - - // update new code - existsJobInfo.setGlueSource(glueSource); - existsJobInfo.setGlueRemark(glueRemark); - existsJobInfo.setGlueUpdatetime(new Date()); - - existsJobInfo.setUpdateTime(new Date()); - xxlJobInfoDao.update(existsJobInfo); - - // log old code - XxlJobLogGlue xxlJobLogGlue = new XxlJobLogGlue(); - xxlJobLogGlue.setJobId(existsJobInfo.getId()); - xxlJobLogGlue.setGlueType(existsJobInfo.getGlueType()); - xxlJobLogGlue.setGlueSource(glueSource); - xxlJobLogGlue.setGlueRemark(glueRemark); - - xxlJobLogGlue.setAddTime(new Date()); - xxlJobLogGlue.setUpdateTime(new Date()); - xxlJobLogGlueDao.save(xxlJobLogGlue); - - // remove code backup more than 30 - xxlJobLogGlueDao.removeOld(existsJobInfo.getId(), 30); - - return ReturnT.SUCCESS; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java deleted file mode 100644 index 8e0c5a4d..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.xxl.job.admin.controller; - -import com.xxl.job.admin.controller.annotation.PermissionLimit; -import com.xxl.job.admin.core.model.XxlJobGroup; -import com.xxl.job.admin.core.model.XxlJobRegistry; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.admin.dao.XxlJobGroupDao; -import com.xxl.job.admin.dao.XxlJobInfoDao; -import com.xxl.job.admin.dao.XxlJobRegistryDao; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.enums.RegistryConfig; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import java.util.*; - -/** - * job group controller - * @author xuxueli 2016-10-02 20:52:56 - */ -@Controller -@RequestMapping("/jobgroup") -public class JobGroupController { - - @Resource - public XxlJobInfoDao xxlJobInfoDao; - @Resource - public XxlJobGroupDao xxlJobGroupDao; - @Resource - private XxlJobRegistryDao xxlJobRegistryDao; - - @RequestMapping - @PermissionLimit(adminuser = true) - public String index(Model model) { - return "jobgroup/jobgroup.index"; - } - - @RequestMapping("/pageList") - @ResponseBody - @PermissionLimit(adminuser = true) - public Map pageList(HttpServletRequest request, - @RequestParam(required = false, defaultValue = "0") int start, - @RequestParam(required = false, defaultValue = "10") int length, - String appname, String title) { - - // page query - List list = xxlJobGroupDao.pageList(start, length, appname, title); - int list_count = xxlJobGroupDao.pageListCount(start, length, appname, title); - - // package result - Map maps = new HashMap(); - maps.put("recordsTotal", list_count); // 总记录数 - maps.put("recordsFiltered", list_count); // 过滤后的总记录数 - maps.put("data", list); // 分页列表 - return maps; - } - - @RequestMapping("/save") - @ResponseBody - @PermissionLimit(adminuser = true) - public ReturnT save(XxlJobGroup xxlJobGroup){ - - // valid - if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) { - return new ReturnT(500, (I18nUtil.getString("system_please_input")+"AppName") ); - } - if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) { - return new ReturnT(500, I18nUtil.getString("jobgroup_field_appname_length") ); - } - if (xxlJobGroup.getAppname().contains(">") || xxlJobGroup.getAppname().contains("<")) { - return new ReturnT(500, "AppName"+I18nUtil.getString("system_unvalid") ); - } - if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) { - return new ReturnT(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) ); - } - if (xxlJobGroup.getTitle().contains(">") || xxlJobGroup.getTitle().contains("<")) { - return new ReturnT(500, I18nUtil.getString("jobgroup_field_title")+I18nUtil.getString("system_unvalid") ); - } - if (xxlJobGroup.getAddressType()!=0) { - if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) { - return new ReturnT(500, I18nUtil.getString("jobgroup_field_addressType_limit") ); - } - if (xxlJobGroup.getAddressList().contains(">") || xxlJobGroup.getAddressList().contains("<")) { - return new ReturnT(500, I18nUtil.getString("jobgroup_field_registryList")+I18nUtil.getString("system_unvalid") ); - } - - String[] addresss = xxlJobGroup.getAddressList().split(","); - for (String item: addresss) { - if (item==null || item.trim().length()==0) { - return new ReturnT(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") ); - } - } - } - - // process - xxlJobGroup.setUpdateTime(new Date()); - - int ret = xxlJobGroupDao.save(xxlJobGroup); - return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL; - } - - @RequestMapping("/update") - @ResponseBody - @PermissionLimit(adminuser = true) - public ReturnT update(XxlJobGroup xxlJobGroup){ - // valid - if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) { - return new ReturnT(500, (I18nUtil.getString("system_please_input")+"AppName") ); - } - if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) { - return new ReturnT(500, I18nUtil.getString("jobgroup_field_appname_length") ); - } - if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) { - return new ReturnT(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) ); - } - if (xxlJobGroup.getAddressType() == 0) { - // 0=自动注册 - List registryList = findRegistryByAppName(xxlJobGroup.getAppname()); - String addressListStr = null; - if (registryList!=null && !registryList.isEmpty()) { - Collections.sort(registryList); - addressListStr = ""; - for (String item:registryList) { - addressListStr += item + ","; - } - addressListStr = addressListStr.substring(0, addressListStr.length()-1); - } - xxlJobGroup.setAddressList(addressListStr); - } else { - // 1=手动录入 - if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) { - return new ReturnT(500, I18nUtil.getString("jobgroup_field_addressType_limit") ); - } - String[] addresss = xxlJobGroup.getAddressList().split(","); - for (String item: addresss) { - if (item==null || item.trim().length()==0) { - return new ReturnT(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") ); - } - } - } - - // process - xxlJobGroup.setUpdateTime(new Date()); - - int ret = xxlJobGroupDao.update(xxlJobGroup); - return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL; - } - - private List findRegistryByAppName(String appnameParam){ - HashMap> appAddressMap = new HashMap>(); - List list = xxlJobRegistryDao.findAll(RegistryConfig.DEAD_TIMEOUT, new Date()); - if (list != null) { - for (XxlJobRegistry item: list) { - if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) { - String appname = item.getRegistryKey(); - List registryList = appAddressMap.get(appname); - if (registryList == null) { - registryList = new ArrayList(); - } - - if (!registryList.contains(item.getRegistryValue())) { - registryList.add(item.getRegistryValue()); - } - appAddressMap.put(appname, registryList); - } - } - } - return appAddressMap.get(appnameParam); - } - - @RequestMapping("/remove") - @ResponseBody - @PermissionLimit(adminuser = true) - public ReturnT remove(int id){ - - // valid - int count = xxlJobInfoDao.pageListCount(0, 10, id, -1, null, null, null); - if (count > 0) { - return new ReturnT(500, I18nUtil.getString("jobgroup_del_limit_0") ); - } - - List allList = xxlJobGroupDao.findAll(); - if (allList.size() == 1) { - return new ReturnT(500, I18nUtil.getString("jobgroup_del_limit_1") ); - } - - int ret = xxlJobGroupDao.remove(id); - return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL; - } - - @RequestMapping("/loadById") - @ResponseBody - @PermissionLimit(adminuser = true) - public ReturnT loadById(int id){ - XxlJobGroup jobGroup = xxlJobGroupDao.load(id); - return jobGroup!=null?new ReturnT(jobGroup):new ReturnT(ReturnT.FAIL_CODE, null); - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobInfoController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobInfoController.java deleted file mode 100644 index 77bb4b28..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobInfoController.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.xxl.job.admin.controller; - -import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; -import com.xxl.job.admin.core.exception.XxlJobException; -import com.xxl.job.admin.core.model.XxlJobGroup; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobUser; -import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum; -import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum; -import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum; -import com.xxl.job.admin.core.thread.JobScheduleHelper; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.admin.dao.XxlJobGroupDao; -import com.xxl.job.admin.service.XxlJobService; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; -import com.xxl.job.core.glue.GlueTypeEnum; -import com.xxl.job.core.util.DateUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import java.util.*; - -/** - * index controller - * @author xuxueli 2015-12-19 16:13:16 - */ -@Controller -@RequestMapping("/jobinfo") -public class JobInfoController { - private static Logger logger = LoggerFactory.getLogger(JobInfoController.class); - - @Resource - private XxlJobGroupDao xxlJobGroupDao; - @Resource - private XxlJobService xxlJobService; - - @RequestMapping - public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "-1") int jobGroup) { - - // 枚举-字典 - model.addAttribute("ExecutorRouteStrategyEnum", ExecutorRouteStrategyEnum.values()); // 路由策略-列表 - model.addAttribute("GlueTypeEnum", GlueTypeEnum.values()); // Glue类型-字典 - model.addAttribute("ExecutorBlockStrategyEnum", ExecutorBlockStrategyEnum.values()); // 阻塞处理策略-字典 - model.addAttribute("ScheduleTypeEnum", ScheduleTypeEnum.values()); // 调度类型 - model.addAttribute("MisfireStrategyEnum", MisfireStrategyEnum.values()); // 调度过期策略 - - // 执行器列表 - List jobGroupList_all = xxlJobGroupDao.findAll(); - - // filter group - List jobGroupList = PermissionInterceptor.filterJobGroupByRole(request, jobGroupList_all); - if (jobGroupList==null || jobGroupList.size()==0) { - throw new XxlJobException(I18nUtil.getString("jobgroup_empty")); - } - - model.addAttribute("JobGroupList", jobGroupList); - model.addAttribute("jobGroup", jobGroup); - - return "jobinfo/jobinfo.index"; - } - - @RequestMapping("/pageList") - @ResponseBody - public Map pageList(@RequestParam(required = false, defaultValue = "0") int start, - @RequestParam(required = false, defaultValue = "10") int length, - int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) { - - return xxlJobService.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author); - } - - @RequestMapping("/add") - @ResponseBody - public ReturnT add(HttpServletRequest request, XxlJobInfo jobInfo) { - // valid permission - PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup()); - - // opt - XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); - return xxlJobService.add(jobInfo, loginUser); - } - - @RequestMapping("/update") - @ResponseBody - public ReturnT update(HttpServletRequest request, XxlJobInfo jobInfo) { - // valid permission - PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup()); - - // opt - XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); - return xxlJobService.update(jobInfo, loginUser); - } - - @RequestMapping("/remove") - @ResponseBody - public ReturnT remove(int id) { - return xxlJobService.remove(id); - } - - @RequestMapping("/stop") - @ResponseBody - public ReturnT pause(int id) { - return xxlJobService.stop(id); - } - - @RequestMapping("/start") - @ResponseBody - public ReturnT start(int id) { - return xxlJobService.start(id); - } - - @RequestMapping("/trigger") - @ResponseBody - public ReturnT triggerJob(HttpServletRequest request, int id, String executorParam, String addressList) { - // login user - XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); - // trigger - return xxlJobService.trigger(loginUser, id, executorParam, addressList); - } - - @RequestMapping("/nextTriggerTime") - @ResponseBody - public ReturnT> nextTriggerTime(String scheduleType, String scheduleConf) { - - XxlJobInfo paramXxlJobInfo = new XxlJobInfo(); - paramXxlJobInfo.setScheduleType(scheduleType); - paramXxlJobInfo.setScheduleConf(scheduleConf); - - List result = new ArrayList<>(); - try { - Date lastTime = new Date(); - for (int i = 0; i < 5; i++) { - lastTime = JobScheduleHelper.generateNextValidTime(paramXxlJobInfo, lastTime); - if (lastTime != null) { - result.add(DateUtil.formatDateTime(lastTime)); - } else { - break; - } - } - } catch (Exception e) { - logger.error("nextTriggerTime error. scheduleType = {}, scheduleConf= {}", scheduleType, scheduleConf, e); - return new ReturnT>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) + e.getMessage()); - } - return new ReturnT>(result); - - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java deleted file mode 100644 index 221e2771..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java +++ /dev/null @@ -1,250 +0,0 @@ -package com.xxl.job.admin.controller; - -import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; -import com.xxl.job.admin.core.complete.XxlJobCompleter; -import com.xxl.job.admin.core.exception.XxlJobException; -import com.xxl.job.admin.core.model.XxlJobGroup; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobLog; -import com.xxl.job.admin.core.scheduler.XxlJobScheduler; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.admin.dao.XxlJobGroupDao; -import com.xxl.job.admin.dao.XxlJobInfoDao; -import com.xxl.job.admin.dao.XxlJobLogDao; -import com.xxl.job.core.biz.ExecutorBiz; -import com.xxl.job.core.biz.model.KillParam; -import com.xxl.job.core.biz.model.LogParam; -import com.xxl.job.core.biz.model.LogResult; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.util.DateUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.util.HtmlUtils; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * index controller - * @author xuxueli 2015-12-19 16:13:16 - */ -@Controller -@RequestMapping("/joblog") -public class JobLogController { - private static Logger logger = LoggerFactory.getLogger(JobLogController.class); - - @Resource - private XxlJobGroupDao xxlJobGroupDao; - @Resource - public XxlJobInfoDao xxlJobInfoDao; - @Resource - public XxlJobLogDao xxlJobLogDao; - - @RequestMapping - public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "0") Integer jobId) { - - // 执行器列表 - List jobGroupList_all = xxlJobGroupDao.findAll(); - - // filter group - List jobGroupList = PermissionInterceptor.filterJobGroupByRole(request, jobGroupList_all); - if (jobGroupList==null || jobGroupList.size()==0) { - throw new XxlJobException(I18nUtil.getString("jobgroup_empty")); - } - - model.addAttribute("JobGroupList", jobGroupList); - - // 任务 - if (jobId > 0) { - XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId); - if (jobInfo == null) { - throw new RuntimeException(I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_unvalid")); - } - - model.addAttribute("jobInfo", jobInfo); - - // valid permission - PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup()); - } - - return "joblog/joblog.index"; - } - - @RequestMapping("/getJobsByGroup") - @ResponseBody - public ReturnT> getJobsByGroup(int jobGroup){ - List list = xxlJobInfoDao.getJobsByGroup(jobGroup); - return new ReturnT>(list); - } - - @RequestMapping("/pageList") - @ResponseBody - public Map pageList(HttpServletRequest request, - @RequestParam(required = false, defaultValue = "0") int start, - @RequestParam(required = false, defaultValue = "10") int length, - int jobGroup, int jobId, int logStatus, String filterTime) { - - // valid permission - PermissionInterceptor.validJobGroupPermission(request, jobGroup); // 仅管理员支持查询全部;普通用户仅支持查询有权限的 jobGroup - - // parse param - Date triggerTimeStart = null; - Date triggerTimeEnd = null; - if (filterTime!=null && filterTime.trim().length()>0) { - String[] temp = filterTime.split(" - "); - if (temp.length == 2) { - triggerTimeStart = DateUtil.parseDateTime(temp[0]); - triggerTimeEnd = DateUtil.parseDateTime(temp[1]); - } - } - - // page query - List list = xxlJobLogDao.pageList(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus); - int list_count = xxlJobLogDao.pageListCount(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus); - - // package result - Map maps = new HashMap(); - maps.put("recordsTotal", list_count); // 总记录数 - maps.put("recordsFiltered", list_count); // 过滤后的总记录数 - maps.put("data", list); // 分页列表 - return maps; - } - - @RequestMapping("/logDetailPage") - public String logDetailPage(int id, Model model){ - - // base check - ReturnT logStatue = ReturnT.SUCCESS; - XxlJobLog jobLog = xxlJobLogDao.load(id); - if (jobLog == null) { - throw new RuntimeException(I18nUtil.getString("joblog_logid_unvalid")); - } - - model.addAttribute("triggerCode", jobLog.getTriggerCode()); - model.addAttribute("handleCode", jobLog.getHandleCode()); - model.addAttribute("logId", jobLog.getId()); - return "joblog/joblog.detail"; - } - - @RequestMapping("/logDetailCat") - @ResponseBody - public ReturnT logDetailCat(long logId, int fromLineNum){ - try { - // valid - XxlJobLog jobLog = xxlJobLogDao.load(logId); // todo, need to improve performance - if (jobLog == null) { - return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_logid_unvalid")); - } - - // log cat - ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(jobLog.getExecutorAddress()); - ReturnT logResult = executorBiz.log(new LogParam(jobLog.getTriggerTime().getTime(), logId, fromLineNum)); - - // is end - if (logResult.getContent()!=null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) { - if (jobLog.getHandleCode() > 0) { - logResult.getContent().setEnd(true); - } - } - - // fix xss - if (logResult.getContent()!=null && StringUtils.hasText(logResult.getContent().getLogContent())) { - String newLogContent = logResult.getContent().getLogContent(); - newLogContent = HtmlUtils.htmlEscape(newLogContent, "UTF-8"); - logResult.getContent().setLogContent(newLogContent); - } - - return logResult; - } catch (Exception e) { - logger.error(e.getMessage(), e); - return new ReturnT(ReturnT.FAIL_CODE, e.getMessage()); - } - } - - @RequestMapping("/logKill") - @ResponseBody - public ReturnT logKill(int id){ - // base check - XxlJobLog log = xxlJobLogDao.load(id); - XxlJobInfo jobInfo = xxlJobInfoDao.loadById(log.getJobId()); - if (jobInfo==null) { - return new ReturnT(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid")); - } - if (ReturnT.SUCCESS_CODE != log.getTriggerCode()) { - return new ReturnT(500, I18nUtil.getString("joblog_kill_log_limit")); - } - - // request of kill - ReturnT runResult = null; - try { - ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress()); - runResult = executorBiz.kill(new KillParam(jobInfo.getId())); - } catch (Exception e) { - logger.error(e.getMessage(), e); - runResult = new ReturnT(500, e.getMessage()); - } - - if (ReturnT.SUCCESS_CODE == runResult.getCode()) { - log.setHandleCode(ReturnT.FAIL_CODE); - log.setHandleMsg( I18nUtil.getString("joblog_kill_log_byman")+":" + (runResult.getMsg()!=null?runResult.getMsg():"")); - log.setHandleTime(new Date()); - XxlJobCompleter.updateHandleInfoAndFinish(log); - return new ReturnT(runResult.getMsg()); - } else { - return new ReturnT(500, runResult.getMsg()); - } - } - - @RequestMapping("/clearLog") - @ResponseBody - public ReturnT clearLog(HttpServletRequest request, int jobGroup, int jobId, int type){ - // valid permission - PermissionInterceptor.validJobGroupPermission(request, jobGroup); - - // opt - Date clearBeforeTime = null; - int clearBeforeNum = 0; - if (type == 1) { - clearBeforeTime = DateUtil.addMonths(new Date(), -1); // 清理一个月之前日志数据 - } else if (type == 2) { - clearBeforeTime = DateUtil.addMonths(new Date(), -3); // 清理三个月之前日志数据 - } else if (type == 3) { - clearBeforeTime = DateUtil.addMonths(new Date(), -6); // 清理六个月之前日志数据 - } else if (type == 4) { - clearBeforeTime = DateUtil.addYears(new Date(), -1); // 清理一年之前日志数据 - } else if (type == 5) { - clearBeforeNum = 1000; // 清理一千条以前日志数据 - } else if (type == 6) { - clearBeforeNum = 10000; // 清理一万条以前日志数据 - } else if (type == 7) { - clearBeforeNum = 30000; // 清理三万条以前日志数据 - } else if (type == 8) { - clearBeforeNum = 100000; // 清理十万条以前日志数据 - } else if (type == 9) { - clearBeforeNum = 0; // 清理所有日志数据 - } else { - return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_clean_type_unvalid")); - } - - List logIds = null; - do { - logIds = xxlJobLogDao.findClearLogIds(jobGroup, jobId, clearBeforeTime, clearBeforeNum, 1000); - if (logIds!=null && logIds.size()>0) { - xxlJobLogDao.clearLog(logIds); - } - } while (logIds!=null && logIds.size()>0); - - return ReturnT.SUCCESS; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobUserController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobUserController.java deleted file mode 100644 index 32b607c3..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobUserController.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.xxl.job.admin.controller; - -import com.xxl.job.admin.controller.annotation.PermissionLimit; -import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; -import com.xxl.job.admin.core.model.XxlJobGroup; -import com.xxl.job.admin.core.model.XxlJobUser; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.admin.dao.XxlJobGroupDao; -import com.xxl.job.admin.dao.XxlJobUserDao; -import com.xxl.job.core.biz.model.ReturnT; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.util.DigestUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author xuxueli 2019-05-04 16:39:50 - */ -@Controller -@RequestMapping("/user") -public class JobUserController { - - @Resource - private XxlJobUserDao xxlJobUserDao; - @Resource - private XxlJobGroupDao xxlJobGroupDao; - - @RequestMapping - @PermissionLimit(adminuser = true) - public String index(Model model) { - - // 执行器列表 - List groupList = xxlJobGroupDao.findAll(); - model.addAttribute("groupList", groupList); - - return "user/user.index"; - } - - @RequestMapping("/pageList") - @ResponseBody - @PermissionLimit(adminuser = true) - public Map pageList(@RequestParam(required = false, defaultValue = "0") int start, - @RequestParam(required = false, defaultValue = "10") int length, - String username, int role) { - - // page list - List list = xxlJobUserDao.pageList(start, length, username, role); - int list_count = xxlJobUserDao.pageListCount(start, length, username, role); - - // filter - if (list!=null && list.size()>0) { - for (XxlJobUser item: list) { - item.setPassword(null); - } - } - - // package result - Map maps = new HashMap(); - maps.put("recordsTotal", list_count); // 总记录数 - maps.put("recordsFiltered", list_count); // 过滤后的总记录数 - maps.put("data", list); // 分页列表 - return maps; - } - - @RequestMapping("/add") - @ResponseBody - @PermissionLimit(adminuser = true) - public ReturnT add(XxlJobUser xxlJobUser) { - - // valid username - if (!StringUtils.hasText(xxlJobUser.getUsername())) { - return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_username") ); - } - xxlJobUser.setUsername(xxlJobUser.getUsername().trim()); - if (!(xxlJobUser.getUsername().length()>=4 && xxlJobUser.getUsername().length()<=20)) { - return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" ); - } - // valid password - if (!StringUtils.hasText(xxlJobUser.getPassword())) { - return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_password") ); - } - xxlJobUser.setPassword(xxlJobUser.getPassword().trim()); - if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) { - return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" ); - } - // md5 password - xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes())); - - // check repeat - XxlJobUser existUser = xxlJobUserDao.loadByUserName(xxlJobUser.getUsername()); - if (existUser != null) { - return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("user_username_repeat") ); - } - - // write - xxlJobUserDao.save(xxlJobUser); - return ReturnT.SUCCESS; - } - - @RequestMapping("/update") - @ResponseBody - @PermissionLimit(adminuser = true) - public ReturnT update(HttpServletRequest request, XxlJobUser xxlJobUser) { - - // avoid opt login seft - XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); - if (loginUser.getUsername().equals(xxlJobUser.getUsername())) { - return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit")); - } - - // valid password - if (StringUtils.hasText(xxlJobUser.getPassword())) { - xxlJobUser.setPassword(xxlJobUser.getPassword().trim()); - if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) { - return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" ); - } - // md5 password - xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes())); - } else { - xxlJobUser.setPassword(null); - } - - // write - xxlJobUserDao.update(xxlJobUser); - return ReturnT.SUCCESS; - } - - @RequestMapping("/remove") - @ResponseBody - @PermissionLimit(adminuser = true) - public ReturnT remove(HttpServletRequest request, int id) { - - // avoid opt login seft - XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); - if (loginUser.getId() == id) { - return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit")); - } - - xxlJobUserDao.delete(id); - return ReturnT.SUCCESS; - } - - @RequestMapping("/updatePwd") - @ResponseBody - public ReturnT updatePwd(HttpServletRequest request, String password, String oldPassword){ - - // valid - if (oldPassword==null || oldPassword.trim().length()==0){ - return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd")); - } - if (password==null || password.trim().length()==0){ - return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd")); - } - password = password.trim(); - if (!(password.length()>=4 && password.length()<=20)) { - return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" ); - } - - // md5 password - String md5OldPassword = DigestUtils.md5DigestAsHex(oldPassword.getBytes()); - String md5Password = DigestUtils.md5DigestAsHex(password.getBytes()); - - // valid old pwd - XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); - XxlJobUser existUser = xxlJobUserDao.loadByUserName(loginUser.getUsername()); - if (!md5OldPassword.equals(existUser.getPassword())) { - return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("change_pwd_field_oldpwd") + I18nUtil.getString("system_unvalid")); - } - - // write new - existUser.setPassword(md5Password); - xxlJobUserDao.update(existUser); - - return ReturnT.SUCCESS; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/annotation/PermissionLimit.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/annotation/PermissionLimit.java deleted file mode 100644 index 379efd46..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/annotation/PermissionLimit.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.xxl.job.admin.controller.annotation; - - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 权限限制 - * @author xuxueli 2015-12-12 18:29:02 - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface PermissionLimit { - - /** - * 登录拦截 (默认拦截) - */ - boolean limit() default true; - - /** - * 要求管理员权限 - * - * @return - */ - boolean adminuser() default false; - -} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/base/IndexController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/base/IndexController.java new file mode 100644 index 00000000..3b7537b5 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/base/IndexController.java @@ -0,0 +1,127 @@ +package com.xxl.job.admin.controller.base; + +import com.xxl.job.admin.constant.Consts; +import com.xxl.job.admin.model.dto.XxlBootResourceDTO; +import com.xxl.job.admin.service.XxlJobService; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.sso.core.annotation.XxlSso; +import com.xxl.sso.core.helper.XxlSsoHelper; +import com.xxl.sso.core.model.LoginInfo; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.propertyeditors.CustomDateEditor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +/** + * index controller + * + * @author xuxueli 2015-12-19 16:13:16 + */ +@Controller +public class IndexController { + + @Resource + private XxlJobService xxlJobService; + + /** + * index + */ + @RequestMapping("/") + @XxlSso + public String index(HttpServletRequest request, Model model) { + + // menu resource + List resourceList = findResourceList(request); + model.addAttribute("resourceList", resourceList); + + return "base/index"; + } + + /** + * fill menu data + */ + private List findResourceList(HttpServletRequest request){ + // login check + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + // init menu-list + List resourceDTOList = Arrays.asList( + new XxlBootResourceDTO(1, 0, I18nUtil.getString("job_dashboard_name"),1, "", "/dashboard", "fa-home", 1, 0, null), + new XxlBootResourceDTO(2, 0, I18nUtil.getString("jobinfo_name"),1, "", "/jobinfo", " fa-clock-o", 2, 0, null), + new XxlBootResourceDTO(3, 0, I18nUtil.getString("joblog_name"),1, "", "/joblog", " fa-database", 3, 0, null), + new XxlBootResourceDTO(4, 0, I18nUtil.getString("jobgroup_name"),1, Consts.ADMIN_ROLE, "/jobgroup", " fa-cloud", 4, 0,null), + new XxlBootResourceDTO(5, 0, I18nUtil.getString("user_manage"),1, Consts.ADMIN_ROLE, "/user", "fa-users", 5, 0, null), + new XxlBootResourceDTO(9, 0, I18nUtil.getString("admin_help"),1, "", "/help", "fa-book", 6, 0, null) + ); + + // filter by role + if (!XxlSsoHelper.hasRole(loginInfoResponse.getData(), Consts.ADMIN_ROLE).isSuccess()) { + resourceDTOList = resourceDTOList.stream() + .filter(resourceDTO -> StringTool.isBlank(resourceDTO.getPermission() )) // normal user had no permission + .collect(Collectors.toList()); + } + resourceDTOList.stream().sorted(Comparator.comparing(XxlBootResourceDTO::getOrder)).toList(); + return resourceDTOList; + } + + /** + * dashboard + */ + @RequestMapping("/dashboard") + @XxlSso + public String dashboard(HttpServletRequest request, Model model) { + + Map dashboardMap = xxlJobService.dashboardInfo(); + model.addAllAttributes(dashboardMap); + + return "base/dashboard"; + } + + @RequestMapping("/chartInfo") + @ResponseBody + public Response> chartInfo(@RequestParam("startDate") Date startDate, @RequestParam("endDate") Date endDate) { + Response> chartInfo = xxlJobService.chartInfo(startDate, endDate); + return chartInfo; + } + + /** + * help + */ + @RequestMapping("/help") + @XxlSso + public String help() { + return "base/help"; + } + + @RequestMapping(value = "/errorpage") + @XxlSso(login = false) + public ModelAndView errorPage(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) { + + String exceptionMsg = "HTTP Status Code: "+response.getStatus(); + + mv.addObject("exceptionMsg", exceptionMsg); + mv.setViewName("common/common.errorpage"); + return mv; + } + + @InitBinder + public void initBinder(WebDataBinder binder) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setLenient(false); + binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true)); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/base/LoginController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/base/LoginController.java new file mode 100644 index 00000000..fcdc344a --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/base/LoginController.java @@ -0,0 +1,124 @@ +package com.xxl.job.admin.controller.base; + +import com.xxl.job.admin.mapper.XxlJobUserMapper; +import com.xxl.job.admin.model.XxlJobUser; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.sso.core.annotation.XxlSso; +import com.xxl.sso.core.helper.XxlSsoHelper; +import com.xxl.sso.core.model.LoginInfo; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.crypto.Sha256Tool; +import com.xxl.tool.id.UUIDTool; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; + +/** + * index controller + * @author xuxueli 2015-12-19 16:13:16 + */ +@Controller +@RequestMapping("/auth") +public class LoginController { + + @Resource + private XxlJobUserMapper xxlJobUserMapper; + + @RequestMapping("/login") + @XxlSso(login = false) + public ModelAndView login(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) { + + // xxl-sso, logincheck + Response loginInfoResponse = XxlSsoHelper.loginCheckWithCookie(request, response); + + if (loginInfoResponse.isSuccess()) { + modelAndView.setView(new RedirectView("/",true,false)); + return modelAndView; + } + return new ModelAndView("base/login"); + } + + @RequestMapping(value="/doLogin", method=RequestMethod.POST) + @ResponseBody + @XxlSso(login=false) + public Response doLogin(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){ + + // param + boolean ifRem = StringTool.isNotBlank(ifRemember) && "on".equals(ifRemember); + if (StringTool.isBlank(userName) || StringTool.isBlank(password)){ + return Response.ofFail( I18nUtil.getString("login_param_empty") ); + } + + // valid user、status + XxlJobUser xxlJobUser = xxlJobUserMapper.loadByUserName(userName); + if (xxlJobUser == null) { + return Response.ofFail( I18nUtil.getString("login_param_invalid") ); + } + + // valid passowrd + String passwordHash = Sha256Tool.sha256(password); + if (!passwordHash.equals(xxlJobUser.getPassword())) { + return Response.ofFail( I18nUtil.getString("login_param_invalid") ); + } + + // xxl-sso, do login + LoginInfo loginInfo = new LoginInfo(String.valueOf(xxlJobUser.getId()), UUIDTool.getSimpleUUID()); + Response result= XxlSsoHelper.loginWithCookie(loginInfo, response, ifRem); + + return Response.of(result.getCode(), result.getMsg()); + } + + @RequestMapping(value="/logout", method=RequestMethod.POST) + @ResponseBody + @XxlSso(login=false) + public Response logout(HttpServletRequest request, HttpServletResponse response){ + + // xxl-sso, do logout + Response result = XxlSsoHelper.logoutWithCookie(request, response); + + return Response.of(result.getCode(), result.getMsg()); + } + + @RequestMapping("/updatePwd") + @ResponseBody + @XxlSso + public Response updatePwd(HttpServletRequest request, String oldPassword, String password){ + + // valid + if (oldPassword==null || oldPassword.trim().isEmpty()){ + return Response.ofFail(I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd")); + } + if (password==null || password.trim().isEmpty()){ + return Response.ofFail(I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd")); + } + password = password.trim(); + if (!(password.length()>=4 && password.length()<=20)) { + return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" ); + } + + // md5 password + String oldPasswordHash = Sha256Tool.sha256(oldPassword); + String passwordHash = Sha256Tool.sha256(password); + + // valid old pwd + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + XxlJobUser existUser = xxlJobUserMapper.loadByUserName(loginInfoResponse.getData().getUserName()); + if (!oldPasswordHash.equals(existUser.getPassword())) { + return Response.ofFail(I18nUtil.getString("change_pwd_field_oldpwd") + I18nUtil.getString("system_invalid")); + } + + // write new + existUser.setPassword(passwordHash); + xxlJobUserMapper.update(existUser); + + return Response.ofSuccess(); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobCodeController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobCodeController.java new file mode 100644 index 00000000..04e7f78c --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobCodeController.java @@ -0,0 +1,117 @@ +package com.xxl.job.admin.controller.biz; + +import com.xxl.job.admin.mapper.XxlJobInfoMapper; +import com.xxl.job.admin.mapper.XxlJobLogGlueMapper; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobLogGlue; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.admin.util.JobGroupPermissionUtil; +import com.xxl.job.core.glue.GlueTypeEnum; +import com.xxl.sso.core.model.LoginInfo; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.json.GsonTool; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.Date; +import java.util.List; + +/** + * job code controller + * @author xuxueli 2015-12-19 16:13:16 + */ +@Controller +@RequestMapping("/jobcode") +public class JobCodeController { + private static final Logger logger = LoggerFactory.getLogger(JobCodeController.class); + + @Resource + private XxlJobInfoMapper xxlJobInfoMapper; + @Resource + private XxlJobLogGlueMapper xxlJobLogGlueMapper; + + @RequestMapping + public String index(HttpServletRequest request, Model model, @RequestParam("jobId") int jobId) { + XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(jobId); + List jobLogGlues = xxlJobLogGlueMapper.findByJobId(jobId); + + if (jobInfo == null) { + throw new RuntimeException(I18nUtil.getString("jobinfo_glue_jobid_invalid")); + } + if (GlueTypeEnum.BEAN == GlueTypeEnum.match(jobInfo.getGlueType())) { + throw new RuntimeException(I18nUtil.getString("jobinfo_glue_gluetype_invalid")); + } + + // valid jobGroup permission + JobGroupPermissionUtil.validJobGroupPermission(request, jobInfo.getJobGroup()); + + // Glue类型-字典 + model.addAttribute("GlueTypeEnum", GlueTypeEnum.values()); + + model.addAttribute("jobInfo", jobInfo); + model.addAttribute("jobLogGlues", jobLogGlues); + return "biz/job.code"; + } + + @RequestMapping("/save") + @ResponseBody + public Response save(HttpServletRequest request, + @RequestParam("id") int id, + @RequestParam("glueSource") String glueSource, + @RequestParam("glueRemark") String glueRemark) { + + // valid + if (StringTool.isBlank(glueSource)) { + return Response.ofFail( (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_source")) ); + } + if (glueRemark==null) { + return Response.ofFail( (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_remark")) ); + } + if (glueRemark.length()<4 || glueRemark.length()>100) { + return Response.ofFail(I18nUtil.getString("jobinfo_glue_remark_limit")); + } + XxlJobInfo existsJobInfo = xxlJobInfoMapper.loadById(id); + if (existsJobInfo == null) { + return Response.ofFail( I18nUtil.getString("jobinfo_glue_jobid_invalid")); + } + + // valid jobGroup permission + LoginInfo loginInfo = JobGroupPermissionUtil.validJobGroupPermission(request, existsJobInfo.getJobGroup()); + + // update new code + existsJobInfo.setGlueSource(glueSource); + existsJobInfo.setGlueRemark(glueRemark); + existsJobInfo.setGlueUpdatetime(new Date()); + + existsJobInfo.setUpdateTime(new Date()); + xxlJobInfoMapper.update(existsJobInfo); + + // log old code + XxlJobLogGlue xxlJobLogGlue = new XxlJobLogGlue(); + xxlJobLogGlue.setJobId(existsJobInfo.getId()); + xxlJobLogGlue.setGlueType(existsJobInfo.getGlueType()); + xxlJobLogGlue.setGlueSource(glueSource); + xxlJobLogGlue.setGlueRemark(glueRemark); + + xxlJobLogGlue.setAddTime(new Date()); + xxlJobLogGlue.setUpdateTime(new Date()); + xxlJobLogGlueMapper.save(xxlJobLogGlue); + + // remove code backup more than 30 + xxlJobLogGlueMapper.removeOld(existsJobInfo.getId(), 30); + + // write operation log + logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}", + loginInfo.getUserName(), "jobcode-update", GsonTool.toJson(xxlJobLogGlue)); + return Response.ofSuccess(); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobGroupController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobGroupController.java new file mode 100644 index 00000000..52a83d98 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobGroupController.java @@ -0,0 +1,225 @@ +package com.xxl.job.admin.controller.biz; + +import com.xxl.job.admin.constant.Consts; +import com.xxl.job.admin.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobRegistry; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.admin.mapper.XxlJobGroupMapper; +import com.xxl.job.admin.mapper.XxlJobInfoMapper; +import com.xxl.job.admin.mapper.XxlJobRegistryMapper; +import com.xxl.job.core.constant.Const; +import com.xxl.job.core.constant.RegistType; +import com.xxl.sso.core.annotation.XxlSso; +import com.xxl.tool.core.CollectionTool; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.http.HttpTool; +import com.xxl.tool.response.PageModel; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.*; + +/** + * job group controller + * @author xuxueli 2016-10-02 20:52:56 + */ +@Controller +@RequestMapping("/jobgroup") +public class JobGroupController { + + @Resource + public XxlJobInfoMapper xxlJobInfoMapper; + @Resource + public XxlJobGroupMapper xxlJobGroupMapper; + @Resource + private XxlJobRegistryMapper xxlJobRegistryMapper; + + @RequestMapping + @XxlSso(role = Consts.ADMIN_ROLE) + public String index(Model model) { + return "biz/group.list"; + } + + @RequestMapping("/pageList") + @ResponseBody + @XxlSso(role = Consts.ADMIN_ROLE) + public Response> pageList(@RequestParam(required = false, defaultValue = "0") int offset, + @RequestParam(required = false, defaultValue = "10") int pagesize, + String appname, + String title) { + + // page query + List list = xxlJobGroupMapper.pageList(offset, pagesize, appname, title); + int list_count = xxlJobGroupMapper.pageListCount(offset, pagesize, appname, title); + + // package result + PageModel pageModel = new PageModel<>(); + pageModel.setData(list); + pageModel.setTotal(list_count); + + return Response.ofSuccess(pageModel); + } + + @RequestMapping("/insert") + @ResponseBody + @XxlSso(role = Consts.ADMIN_ROLE) + public Response insert(XxlJobGroup xxlJobGroup){ + + // valid + if (StringTool.isBlank(xxlJobGroup.getAppname())) { + return Response.ofFail((I18nUtil.getString("system_please_input")+"AppName") ); + } + if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) { + return Response.ofFail( I18nUtil.getString("jobgroup_field_appname_length") ); + } + if (xxlJobGroup.getAppname().contains(">") || xxlJobGroup.getAppname().contains("<")) { + return Response.ofFail( "AppName"+I18nUtil.getString("system_invalid") ); + } + if (StringTool.isBlank(xxlJobGroup.getTitle())) { + return Response.ofFail((I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) ); + } + if (xxlJobGroup.getTitle().contains(">") || xxlJobGroup.getTitle().contains("<")) { + return Response.ofFail(I18nUtil.getString("jobgroup_field_title")+I18nUtil.getString("system_invalid") ); + } + if (xxlJobGroup.getAddressType()!=0) { + if (StringTool.isBlank(xxlJobGroup.getAddressList())) { + return Response.ofFail( I18nUtil.getString("jobgroup_field_addressType_limit") ); + } + if (xxlJobGroup.getAddressList().contains(">") || xxlJobGroup.getAddressList().contains("<")) { + return Response.ofFail(I18nUtil.getString("jobgroup_field_registryList")+I18nUtil.getString("system_invalid") ); + } + + String[] addresss = xxlJobGroup.getAddressList().split(","); + for (String item: addresss) { + if (StringTool.isBlank(item)) { + return Response.ofFail( I18nUtil.getString("jobgroup_field_registryList_invalid") ); + } + if (!(HttpTool.isHttp(item) || HttpTool.isHttps(item))) { + return Response.ofFail( I18nUtil.getString("jobgroup_field_registryList_invalid")+"[2]" ); + } + } + } + + // process + xxlJobGroup.setUpdateTime(new Date()); + + int ret = xxlJobGroupMapper.save(xxlJobGroup); + return (ret>0)?Response.ofSuccess():Response.ofFail(); + } + + @RequestMapping("/update") + @ResponseBody + @XxlSso(role = Consts.ADMIN_ROLE) + public Response update(XxlJobGroup xxlJobGroup){ + // valid + if (StringTool.isBlank(xxlJobGroup.getAppname())) { + return Response.ofFail((I18nUtil.getString("system_please_input")+"AppName") ); + } + if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) { + return Response.ofFail( I18nUtil.getString("jobgroup_field_appname_length") ); + } + if (StringTool.isBlank(xxlJobGroup.getTitle())) { + return Response.ofFail( (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) ); + } + if (xxlJobGroup.getAddressType() == 0) { + // 0=自动注册 + List registryList = findRegistryByAppName(xxlJobGroup.getAppname()); + String addressListStr = null; + if (CollectionTool.isNotEmpty(registryList)) { + Collections.sort(registryList); + addressListStr = String.join(",", registryList); + } + xxlJobGroup.setAddressList(addressListStr); + } else { + // 1=手动录入 + if (StringTool.isBlank(xxlJobGroup.getAddressList())) { + return Response.ofFail( I18nUtil.getString("jobgroup_field_addressType_limit") ); + } + String[] addresss = xxlJobGroup.getAddressList().split(","); + for (String item: addresss) { + if (StringTool.isBlank(item)) { + return Response.ofFail(I18nUtil.getString("jobgroup_field_registryList_invalid") ); + } + if (!(HttpTool.isHttp(item) || HttpTool.isHttps(item))) { + return Response.ofFail( I18nUtil.getString("jobgroup_field_registryList_invalid")+"[2]" ); + } + } + } + + // process + xxlJobGroup.setUpdateTime(new Date()); + + int ret = xxlJobGroupMapper.update(xxlJobGroup); + return (ret>0)?Response.ofSuccess():Response.ofFail(); + } + + private List findRegistryByAppName(String appnameParam){ + HashMap> appAddressMap = new HashMap<>(); + List list = xxlJobRegistryMapper.findAll(Const.DEAD_TIMEOUT, new Date()); + if (CollectionTool.isNotEmpty(list)) { + for (XxlJobRegistry item: list) { + if (!RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) { + continue; + } + + String appname = item.getRegistryKey(); + List registryList = appAddressMap.computeIfAbsent(appname, k -> new ArrayList<>()); + + if (!registryList.contains(item.getRegistryValue())) { + registryList.add(item.getRegistryValue()); + } + } + } + return appAddressMap.get(appnameParam); + } + + @RequestMapping("/delete") + @ResponseBody + @XxlSso(role = Consts.ADMIN_ROLE) + public Response delete(@RequestParam("ids[]") List ids){ + + // parse id + if (CollectionTool.isEmpty(ids) || ids.size()!=1) { + return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data")); + } + int id = ids.get(0); + + // valid repeat operation + XxlJobGroup xxlJobGroup = xxlJobGroupMapper.load(id); + if (xxlJobGroup == null) { + return Response.ofSuccess(); + } + + // whether exists job + int count = xxlJobInfoMapper.pageListCount(0, 10, id, -1, null, null, null); + if (count > 0) { + return Response.ofFail( I18nUtil.getString("jobgroup_del_limit_0") ); + } + + // whether only exists one group + List allList = xxlJobGroupMapper.findAll(); + if (allList.size() == 1) { + return Response.ofFail( I18nUtil.getString("jobgroup_del_limit_1") ); + } + + // remove group + int ret = xxlJobGroupMapper.remove(id); + // remove registry-data + xxlJobRegistryMapper.removeByRegistryGroupAndKey(RegistType.EXECUTOR.name(), xxlJobGroup.getAppname()); + return (ret>0)?Response.ofSuccess():Response.ofFail(); + } + + @RequestMapping("/loadById") + @ResponseBody + //@XxlSso(role = Consts.ADMIN_ROLE) // open to default user, support show registry nodes + public Response loadById(@RequestParam("id") int id){ + XxlJobGroup jobGroup = xxlJobGroupMapper.load(id); + return jobGroup!=null?Response.ofSuccess(jobGroup):Response.ofFail(); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobInfoController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobInfoController.java new file mode 100644 index 00000000..d611456c --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobInfoController.java @@ -0,0 +1,211 @@ +package com.xxl.job.admin.controller.biz; + +import com.xxl.job.admin.mapper.XxlJobGroupMapper; +import com.xxl.job.admin.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.scheduler.exception.XxlJobException; +import com.xxl.job.admin.scheduler.misfire.MisfireStrategyEnum; +import com.xxl.job.admin.scheduler.route.ExecutorRouteStrategyEnum; +import com.xxl.job.admin.scheduler.type.ScheduleTypeEnum; +import com.xxl.job.admin.service.XxlJobService; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.admin.util.JobGroupPermissionUtil; +import com.xxl.job.core.constant.ExecutorBlockStrategyEnum; +import com.xxl.job.core.glue.GlueTypeEnum; +import com.xxl.sso.core.helper.XxlSsoHelper; +import com.xxl.sso.core.model.LoginInfo; +import com.xxl.tool.core.CollectionTool; +import com.xxl.tool.core.DateTool; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.response.PageModel; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * index controller + * @author xuxueli 2015-12-19 16:13:16 + */ +@Controller +@RequestMapping("/jobinfo") +public class JobInfoController { + private static Logger logger = LoggerFactory.getLogger(JobInfoController.class); + + @Resource + private XxlJobGroupMapper xxlJobGroupMapper; + @Resource + private XxlJobService xxlJobService; + + @RequestMapping + public String index(HttpServletRequest request, Model model, @RequestParam(value = "jobGroup", required = false, defaultValue = "-1") int jobGroup) { + + // 枚举-字典 + model.addAttribute("ExecutorRouteStrategyEnum", ExecutorRouteStrategyEnum.values()); // 路由策略-列表 + model.addAttribute("GlueTypeEnum", GlueTypeEnum.values()); // Glue类型-字典 + model.addAttribute("ExecutorBlockStrategyEnum", ExecutorBlockStrategyEnum.values()); // 阻塞处理策略-字典 + model.addAttribute("ScheduleTypeEnum", ScheduleTypeEnum.values()); // 调度类型 + model.addAttribute("MisfireStrategyEnum", MisfireStrategyEnum.values()); // 调度过期策略 + + // 执行器列表 + List jobGroupListTotal = xxlJobGroupMapper.findAll(); + + // filter group + List jobGroupList = JobGroupPermissionUtil.filterJobGroupByPermission(request, jobGroupListTotal); + if (CollectionTool.isEmpty(jobGroupList)) { + throw new XxlJobException(I18nUtil.getString("jobgroup_empty")); + } + + // parse jobGroup + if (!(CollectionTool.isNotEmpty(jobGroupList) + && jobGroupList.stream().map(XxlJobGroup::getId).toList().contains(jobGroup))) { + jobGroup = -1; + } + + model.addAttribute("JobGroupList", jobGroupList); + model.addAttribute("jobGroup", jobGroup); + + return "biz/job.list"; + } + + @RequestMapping("/pageList") + @ResponseBody + public Response> pageList(HttpServletRequest request, + @RequestParam(required = false, defaultValue = "0") int offset, + @RequestParam(required = false, defaultValue = "10") int pagesize, + @RequestParam int jobGroup, + @RequestParam int triggerStatus, + @RequestParam String jobDesc, + @RequestParam String executorHandler, + @RequestParam String author) { + + // valid jobGroup permission + JobGroupPermissionUtil.validJobGroupPermission(request, jobGroup); + + // page + return xxlJobService.pageList(offset, pagesize, jobGroup, triggerStatus, jobDesc, executorHandler, author); + } + + @RequestMapping("/insert") + @ResponseBody + public Response add(HttpServletRequest request, XxlJobInfo jobInfo) { + // valid permission + LoginInfo loginInfo = JobGroupPermissionUtil.validJobGroupPermission(request, jobInfo.getJobGroup()); + + // opt + return xxlJobService.add(jobInfo, loginInfo); + } + + @RequestMapping("/update") + @ResponseBody + public Response update(HttpServletRequest request, XxlJobInfo jobInfo) { + // valid permission + LoginInfo loginInfo = JobGroupPermissionUtil.validJobGroupPermission(request, jobInfo.getJobGroup()); + + // opt + return xxlJobService.update(jobInfo, loginInfo); + } + + @RequestMapping("/delete") + @ResponseBody + public Response delete(HttpServletRequest request, @RequestParam("ids[]") List ids) { + + // valid + if (CollectionTool.isEmpty(ids) || ids.size()!=1) { + return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data")); + } + + // invoke + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + return xxlJobService.remove(ids.get(0), loginInfoResponse.getData()); + } + + @RequestMapping("/stop") + @ResponseBody + public Response pause(HttpServletRequest request, @RequestParam("ids[]") List ids) { + + // valid + if (CollectionTool.isEmpty(ids) || ids.size()!=1) { + return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data")); + } + + // invoke + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + return xxlJobService.stop(ids.get(0), loginInfoResponse.getData()); + } + + @RequestMapping("/start") + @ResponseBody + public Response start(HttpServletRequest request, @RequestParam("ids[]") List ids) { + + // valid + if (CollectionTool.isEmpty(ids) || ids.size()!=1) { + return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data")); + } + + // invoke + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + return xxlJobService.start(ids.get(0), loginInfoResponse.getData()); + } + + @RequestMapping("/trigger") + @ResponseBody + public Response triggerJob(HttpServletRequest request, + @RequestParam("id") int id, + @RequestParam("executorParam") String executorParam, + @RequestParam("addressList") String addressList) { + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + return xxlJobService.trigger(loginInfoResponse.getData(), id, executorParam, addressList); + } + + @RequestMapping("/nextTriggerTime") + @ResponseBody + public Response> nextTriggerTime(@RequestParam("scheduleType") String scheduleType, + @RequestParam("scheduleConf") String scheduleConf) { + + // valid + if (StringTool.isBlank(scheduleType) || StringTool.isBlank(scheduleConf)) { + return Response.ofSuccess(new ArrayList<>()); + } + + // param + XxlJobInfo paramXxlJobInfo = new XxlJobInfo(); + paramXxlJobInfo.setScheduleType(scheduleType); + paramXxlJobInfo.setScheduleConf(scheduleConf); + + // generate + List result = new ArrayList<>(); + try { + Date lastTime = new Date(); + for (int i = 0; i < 5; i++) { + + // generate next trigger time + ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(paramXxlJobInfo.getScheduleType(), ScheduleTypeEnum.NONE); + lastTime = scheduleTypeEnum.getScheduleType().generateNextTriggerTime(paramXxlJobInfo, lastTime); + + // collect data + if (lastTime != null) { + result.add(DateTool.formatDateTime(lastTime)); + } else { + break; + } + } + } catch (Exception e) { + logger.error(">>>>>>>>>>> nextTriggerTime error. scheduleType = {}, scheduleConf= {}, error:{} ", scheduleType, scheduleConf, e.getMessage()); + return Response.ofFail((I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) + e.getMessage()); + } + return Response.ofSuccess(result); + + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobLogController.java new file mode 100644 index 00000000..a756abb6 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobLogController.java @@ -0,0 +1,324 @@ +package com.xxl.job.admin.controller.biz; + +import com.xxl.job.admin.mapper.XxlJobGroupMapper; +import com.xxl.job.admin.mapper.XxlJobInfoMapper; +import com.xxl.job.admin.mapper.XxlJobLogMapper; +import com.xxl.job.admin.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobLog; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.exception.XxlJobException; +import com.xxl.job.admin.service.XxlJobService; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.admin.util.JobGroupPermissionUtil; +import com.xxl.job.core.context.XxlJobContext; +import com.xxl.job.core.openapi.ExecutorBiz; +import com.xxl.job.core.openapi.model.KillRequest; +import com.xxl.job.core.openapi.model.LogRequest; +import com.xxl.job.core.openapi.model.LogResult; +import com.xxl.tool.core.CollectionTool; +import com.xxl.tool.core.DateTool; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.response.PageModel; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.util.HtmlUtils; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * index controller + * @author xuxueli 2015-12-19 16:13:16 + */ +@Controller +@RequestMapping("/joblog") +public class JobLogController { + private static final Logger logger = LoggerFactory.getLogger(JobLogController.class); + + @Resource + private XxlJobGroupMapper xxlJobGroupMapper; + @Resource + public XxlJobInfoMapper xxlJobInfoMapper; + @Resource + public XxlJobLogMapper xxlJobLogMapper; + @Autowired + private XxlJobService xxlJobService; + + @RequestMapping + public String index(HttpServletRequest request, + Model model, + @RequestParam(value = "jobGroup", required = false, defaultValue = "0") Integer jobGroup, + @RequestParam(value = "jobId", required = false, defaultValue = "0") Integer jobId) { + + // 1、init JobGroupList + // find all jobGroup + List jobGroupListTotal = xxlJobGroupMapper.findAll(); + + // filter JobGroupList + List jobGroupList = JobGroupPermissionUtil.filterJobGroupByPermission(request, jobGroupListTotal); + if (CollectionTool.isEmpty(jobGroupList)) { + throw new XxlJobException(I18nUtil.getString("jobgroup_empty")); + } + List jobGroupIds = jobGroupList.stream().map(XxlJobGroup::getId).toList(); + + // 2、check jobId + if (jobId > 0) { + // valid jobId + XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(jobId); + if (jobInfo == null) { + throw new RuntimeException(I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_invalid")); + } + // valid jobGroup + jobGroup = jobInfo.getJobGroup(); + } + + // 3、init jobGroup, default first 1 + if (!jobGroupIds.contains(jobGroup)) { + jobGroup = jobGroupList.get(0).getId(); + } + + // 4、init jobInfoList + List jobInfoList = xxlJobInfoMapper.getJobsByGroup(jobGroup); + List jobIds = jobInfoList.stream().map(XxlJobInfo::getId).toList(); + + // 5、init JobId, default 0 + if (!jobIds.contains(jobId)) { + jobId = 0; + } + + // write + model.addAttribute("JobGroupList", jobGroupList); + model.addAttribute("jobInfoList", jobInfoList); + model.addAttribute("jobGroup", jobGroup); + model.addAttribute("jobId", jobId); + + return "biz/log.list"; + } + + @RequestMapping("/pageList") + @ResponseBody + public Response> pageList(HttpServletRequest request, + @RequestParam(required = false, defaultValue = "0") int offset, + @RequestParam(required = false, defaultValue = "10") int pagesize, + @RequestParam int jobGroup, + @RequestParam int jobId, + @RequestParam int logStatus, + @RequestParam String filterTime) { + + // valid jobGroup permission + JobGroupPermissionUtil.validJobGroupPermission(request, jobGroup); + + // valid jobId + /*if (jobId < 1) { + return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("jobinfo_job")); + }*/ + + // parse param + Date triggerTimeStart = null; + Date triggerTimeEnd = null; + if (StringTool.isNotBlank(filterTime)) { + String[] temp = filterTime.split(" - "); + if (temp.length == 2) { + triggerTimeStart = DateTool.parseDateTime(temp[0]); + triggerTimeEnd = DateTool.parseDateTime(temp[1]); + } + } + + // page query + List list = xxlJobLogMapper.pageList(offset, pagesize, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus); + int list_count = xxlJobLogMapper.pageListCount(offset, pagesize, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus); + + // package result + PageModel pageModel = new PageModel<>(); + pageModel.setData(list); + pageModel.setTotal(list_count); + + return Response.ofSuccess(pageModel); + } + + /** + * filter xss tag + */ + private String filter(String originData){ + + // exclude tag + Map excludeTagMap = new HashMap(); + excludeTagMap.put("
", "###TAG_BR###"); + excludeTagMap.put("", "###TAG_BOLD###"); + excludeTagMap.put("", "###TAG_BOLD_END###"); + + // replace + for (String key : excludeTagMap.keySet()) { + String value = excludeTagMap.get(key); + originData = originData.replaceAll(key, value); + } + + // htmlEscape + originData = HtmlUtils.htmlEscape(originData, "UTF-8"); + + // replace back + for (String key : excludeTagMap.keySet()) { + String value = excludeTagMap.get(key); + originData = originData.replaceAll(value, key); + } + + return originData; + } + + @RequestMapping("/logKill") + @ResponseBody + public Response logKill(HttpServletRequest request, @RequestParam("id") long id){ + // base check + XxlJobLog log = xxlJobLogMapper.load(id); + XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(log.getJobId()); + if (jobInfo==null) { + return Response.ofFail(I18nUtil.getString("jobinfo_glue_jobid_invalid")); + } + if (XxlJobContext.HANDLE_CODE_SUCCESS != log.getTriggerCode()) { + return Response.ofFail( I18nUtil.getString("joblog_kill_log_limit")); + } + + // valid JobGroup permission + JobGroupPermissionUtil.validJobGroupPermission(request, jobInfo.getJobGroup()); + + // request of kill + Response runResult = null; + try { + ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(log.getExecutorAddress()); + runResult = executorBiz.kill(new KillRequest(jobInfo.getId())); + } catch (Exception e) { + logger.error(e.getMessage(), e); + runResult = Response.ofFail( e.getMessage()); + } + + if (XxlJobContext.HANDLE_CODE_SUCCESS == runResult.getCode()) { + log.setHandleCode(XxlJobContext.HANDLE_CODE_FAIL); + log.setHandleMsg( I18nUtil.getString("joblog_kill_log_byman")+":" + (runResult.getMsg()!=null?runResult.getMsg():"")); + log.setHandleTime(new Date()); + XxlJobAdminBootstrap.getInstance().getJobCompleter().complete(log); + return Response.ofSuccess(runResult.getMsg()); + } else { + return Response.ofFail(runResult.getMsg()); + } + } + + @RequestMapping("/clearLog") + @ResponseBody + public Response clearLog(HttpServletRequest request, + @RequestParam("jobGroup") int jobGroup, + @RequestParam("jobId") int jobId, + @RequestParam("type") int type){ + // valid JobGroup permission + JobGroupPermissionUtil.validJobGroupPermission(request, jobGroup); + + // valid jobId + if (jobId < 1) { + return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("jobinfo_job")); + } + + // opt + Date clearBeforeTime = null; + int clearBeforeNum = 0; + if (type == 1) { + clearBeforeTime = DateTool.addMonths(new Date(), -1); // 清理一个月之前日志数据 + } else if (type == 2) { + clearBeforeTime = DateTool.addMonths(new Date(), -3); // 清理三个月之前日志数据 + } else if (type == 3) { + clearBeforeTime = DateTool.addMonths(new Date(), -6); // 清理六个月之前日志数据 + } else if (type == 4) { + clearBeforeTime = DateTool.addYears(new Date(), -1); // 清理一年之前日志数据 + } else if (type == 5) { + clearBeforeNum = 1000; // 清理一千条以前日志数据 + } else if (type == 6) { + clearBeforeNum = 10000; // 清理一万条以前日志数据 + } else if (type == 7) { + clearBeforeNum = 30000; // 清理三万条以前日志数据 + } else if (type == 8) { + clearBeforeNum = 100000; // 清理十万条以前日志数据 + } else if (type == 9) { + clearBeforeNum = 0; // 清理所有日志数据 + } else { + return Response.ofFail(I18nUtil.getString("joblog_clean_type_invalid")); + } + + List logIds = null; + do { + logIds = xxlJobLogMapper.findClearLogIds(jobGroup, jobId, clearBeforeTime, clearBeforeNum, 1000); + if (logIds!=null && !logIds.isEmpty()) { + xxlJobLogMapper.clearLog(logIds); + } + } while (logIds!=null && !logIds.isEmpty()); + + return Response.ofSuccess(); + } + + @RequestMapping("/logDetailPage") + public String logDetailPage(HttpServletRequest request, @RequestParam("id") long id, Model model){ + + // base check + XxlJobLog jobLog = xxlJobLogMapper.load(id); + if (jobLog == null) { + throw new RuntimeException(I18nUtil.getString("joblog_logid_invalid")); + } + + // valid permission + JobGroupPermissionUtil.validJobGroupPermission(request, jobLog.getJobGroup()); + + // load jobInfo + XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(jobLog.getJobId()); + + // data + model.addAttribute("triggerCode", jobLog.getTriggerCode()); + model.addAttribute("handleCode", jobLog.getHandleCode()); + model.addAttribute("logId", jobLog.getId()); + model.addAttribute("jobInfo", jobInfo); + return "biz/log.detail"; + } + + @RequestMapping("/logDetailCat") + @ResponseBody + public Response logDetailCat(@RequestParam("logId") long logId, @RequestParam("fromLineNum") int fromLineNum){ + try { + // valid + XxlJobLog jobLog = xxlJobLogMapper.load(logId); // todo, need to improve performance + if (jobLog == null) { + return Response.ofFail(I18nUtil.getString("joblog_logid_invalid")); + } + + // log cat + ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(jobLog.getExecutorAddress()); + Response logResult = executorBiz.log(new LogRequest(jobLog.getTriggerTime().getTime(), logId, fromLineNum)); + + // is end + if (logResult.getData()!=null && logResult.getData().getFromLineNum() > logResult.getData().getToLineNum()) { + if (jobLog.getHandleCode() > 0) { + logResult.getData().setEnd(true); + } + } + + // fix xss + if (logResult.getData()!=null && StringTool.isNotBlank(logResult.getData().getLogContent())) { + String newLogContent = filter(logResult.getData().getLogContent()); + logResult.getData().setLogContent(newLogContent); + } + + return logResult; + } catch (Exception e) { + logger.error("logId({}) logDetailCat error: {}", logId, e.getMessage(), e); + return Response.ofFail(e.getMessage()); + } + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobUserController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobUserController.java new file mode 100644 index 00000000..3bd06fde --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/biz/JobUserController.java @@ -0,0 +1,200 @@ +package com.xxl.job.admin.controller.biz; + +import com.xxl.job.admin.constant.Consts; +import com.xxl.job.admin.mapper.XxlJobGroupMapper; +import com.xxl.job.admin.mapper.XxlJobUserMapper; +import com.xxl.job.admin.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobUser; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.sso.core.annotation.XxlSso; +import com.xxl.sso.core.helper.XxlSsoHelper; +import com.xxl.sso.core.model.LoginInfo; +import com.xxl.tool.core.CollectionTool; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.crypto.Sha256Tool; +import com.xxl.tool.response.PageModel; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author xuxueli 2019-05-04 16:39:50 + */ +@Controller +@RequestMapping("/user") +public class JobUserController { + + @Resource + private XxlJobUserMapper xxlJobUserMapper; + @Resource + private XxlJobGroupMapper xxlJobGroupMapper; + + @RequestMapping + @XxlSso(role = Consts.ADMIN_ROLE) + public String index(Model model) { + + // 执行器列表 + List groupList = xxlJobGroupMapper.findAll(); + model.addAttribute("groupList", groupList); + + return "biz/user.list"; + } + + @RequestMapping("/pageList") + @ResponseBody + @XxlSso(role = Consts.ADMIN_ROLE) + public Response> pageList(@RequestParam(required = false, defaultValue = "0") int offset, + @RequestParam(required = false, defaultValue = "10") int pagesize, + @RequestParam String username, + @RequestParam int role) { + + // page list + List list = xxlJobUserMapper.pageList(offset, pagesize, username, role); + int list_count = xxlJobUserMapper.pageListCount(offset, pagesize, username, role); + + // filter + if (list!=null && !list.isEmpty()) { + for (XxlJobUser item: list) { + item.setPassword(null); + } + } + + // package result + PageModel pageModel = new PageModel<>(); + pageModel.setData(list); + pageModel.setTotal(list_count); + + return Response.ofSuccess(pageModel); + } + + @RequestMapping("/insert") + @ResponseBody + @XxlSso(role = Consts.ADMIN_ROLE) + public Response insert(XxlJobUser xxlJobUser) { + + // valid username + if (StringTool.isBlank(xxlJobUser.getUsername())) { + return Response.ofFail(I18nUtil.getString("system_please_input")+I18nUtil.getString("user_username") ); + } + xxlJobUser.setUsername(xxlJobUser.getUsername().trim()); + if (!(xxlJobUser.getUsername().length()>=4 && xxlJobUser.getUsername().length()<=20)) { + return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" ); + } + // valid password + if (StringTool.isBlank(xxlJobUser.getPassword())) { + return Response.ofFail(I18nUtil.getString("system_please_input")+I18nUtil.getString("user_password") ); + } + xxlJobUser.setPassword(xxlJobUser.getPassword().trim()); + if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) { + return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" ); + } + // md5 password + String passwordHash = Sha256Tool.sha256(xxlJobUser.getPassword()); + xxlJobUser.setPassword(passwordHash); + + // check repeat + XxlJobUser existUser = xxlJobUserMapper.loadByUserName(xxlJobUser.getUsername()); + if (existUser != null) { + return Response.ofFail( I18nUtil.getString("user_username_repeat") ); + } + + // write + xxlJobUserMapper.save(xxlJobUser); + return Response.ofSuccess(); + } + + @RequestMapping("/update") + @ResponseBody + @XxlSso(role = Consts.ADMIN_ROLE) + public Response update(HttpServletRequest request, XxlJobUser xxlJobUser) { + + // avoid opt login seft + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + if (loginInfoResponse.getData().getUserName().equals(xxlJobUser.getUsername())) { + return Response.ofFail(I18nUtil.getString("user_update_loginuser_limit")); + } + + // valid password + if (StringTool.isNotBlank(xxlJobUser.getPassword())) { + xxlJobUser.setPassword(xxlJobUser.getPassword().trim()); + if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) { + return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" ); + } + // md5 password + String passwordHash = Sha256Tool.sha256(xxlJobUser.getPassword()); + xxlJobUser.setPassword(passwordHash); + } else { + xxlJobUser.setPassword(null); + } + + // write + xxlJobUserMapper.update(xxlJobUser); + return Response.ofSuccess(); + } + + @RequestMapping("/delete") + @ResponseBody + @XxlSso(role = Consts.ADMIN_ROLE) + public Response delete(HttpServletRequest request, @RequestParam("ids[]") List ids) { + + // valid + if (CollectionTool.isEmpty(ids) || ids.size()!=1) { + return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data")); + } + + // avoid opt login seft + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + if (ids.contains(Integer.parseInt(loginInfoResponse.getData().getUserId()))) { + return Response.ofFail(I18nUtil.getString("user_update_loginuser_limit")); + } + + xxlJobUserMapper.delete(ids.get(0)); + return Response.ofSuccess(); + } + + /*@RequestMapping("/updatePwd") + @ResponseBody + public Response updatePwd(HttpServletRequest request, + @RequestParam("password") String password, + @RequestParam("oldPassword") String oldPassword){ + + // valid + if (oldPassword==null || oldPassword.trim().isEmpty()){ + return Response.ofFail(I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd")); + } + if (password==null || password.trim().isEmpty()){ + return Response.ofFail(I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd")); + } + password = password.trim(); + if (!(password.length()>=4 && password.length()<=20)) { + return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" ); + } + + // md5 password + String oldPasswordHash = Sha256Tool.sha256(oldPassword); + String passwordHash = Sha256Tool.sha256(password); + + // valid old pwd + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + XxlJobUser existUser = xxlJobUserMapper.loadByUserName(loginInfoResponse.getData().getUserName()); + if (!oldPasswordHash.equals(existUser.getPassword())) { + return Response.ofFail(I18nUtil.getString("change_pwd_field_oldpwd") + I18nUtil.getString("system_invalid")); + } + + // write new + existUser.setPassword(passwordHash); + xxlJobUserMapper.update(existUser); + + return Response.ofSuccess(); + }*/ + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java deleted file mode 100644 index 930b9e8a..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.xxl.job.admin.controller.interceptor; - -import com.xxl.job.admin.core.util.FtlUtil; -import com.xxl.job.admin.core.util.I18nUtil; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.AsyncHandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.HashMap; - -/** - * push cookies to model as cookieMap - * - * @author xuxueli 2015-12-12 18:09:04 - */ -@Component -public class CookieInterceptor implements AsyncHandlerInterceptor { - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, - ModelAndView modelAndView) throws Exception { - - // cookie - if (modelAndView!=null && request.getCookies()!=null && request.getCookies().length>0) { - HashMap cookieMap = new HashMap(); - for (Cookie ck : request.getCookies()) { - cookieMap.put(ck.getName(), ck); - } - modelAndView.addObject("cookieMap", cookieMap); - } - - // static method - if (modelAndView != null) { - modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName())); - } - - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java deleted file mode 100644 index c088edee..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.xxl.job.admin.controller.interceptor; - -import com.xxl.job.admin.controller.annotation.PermissionLimit; -import com.xxl.job.admin.core.model.XxlJobGroup; -import com.xxl.job.admin.core.model.XxlJobUser; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.admin.service.impl.LoginService; -import org.springframework.stereotype.Component; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.AsyncHandlerInterceptor; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * 权限拦截 - * - * @author xuxueli 2015-12-12 18:09:04 - */ -@Component -public class PermissionInterceptor implements AsyncHandlerInterceptor { - - @Resource - private LoginService loginService; - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - - if (!(handler instanceof HandlerMethod)) { - return true; // proceed with the next interceptor - } - - // if need login - boolean needLogin = true; - boolean needAdminuser = false; - HandlerMethod method = (HandlerMethod)handler; - PermissionLimit permission = method.getMethodAnnotation(PermissionLimit.class); - if (permission!=null) { - needLogin = permission.limit(); - needAdminuser = permission.adminuser(); - } - - if (needLogin) { - XxlJobUser loginUser = loginService.ifLogin(request, response); - if (loginUser == null) { - response.setStatus(302); - response.setHeader("location", request.getContextPath()+"/toLogin"); - return false; - } - if (needAdminuser && loginUser.getRole()!=1) { - throw new RuntimeException(I18nUtil.getString("system_permission_limit")); - } - request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser); // set loginUser, with request - } - - return true; // proceed with the next interceptor - } - - - // -------------------- permission tool -------------------- - - /** - * get loginUser - * - * @param request - * @return - */ - public static XxlJobUser getLoginUser(HttpServletRequest request){ - XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); // get loginUser, with request - return loginUser; - } - - /** - * valid permission by JobGroup - * - * @param request - * @param jobGroup - */ - public static void validJobGroupPermission(HttpServletRequest request, int jobGroup) { - XxlJobUser loginUser = getLoginUser(request); - if (!loginUser.validPermission(jobGroup)) { - throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]"); - } - } - - /** - * filter XxlJobGroup by role - * - * @param request - * @param jobGroupList_all - * @return - */ - public static List filterJobGroupByRole(HttpServletRequest request, List jobGroupList_all){ - List jobGroupList = new ArrayList<>(); - if (jobGroupList_all!=null && jobGroupList_all.size()>0) { - XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request); - if (loginUser.getRole() == 1) { - jobGroupList = jobGroupList_all; - } else { - List groupIdStrs = new ArrayList<>(); - if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) { - groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(",")); - } - for (XxlJobGroup groupItem:jobGroupList_all) { - if (groupIdStrs.contains(String.valueOf(groupItem.getId()))) { - jobGroupList.add(groupItem); - } - } - } - } - return jobGroupList; - } - - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/WebMvcConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/WebMvcConfig.java deleted file mode 100644 index 0be6ba66..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/WebMvcConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.xxl.job.admin.controller.interceptor; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import javax.annotation.Resource; - -/** - * web mvc config - * - * @author xuxueli 2018-04-02 20:48:20 - */ -@Configuration -public class WebMvcConfig implements WebMvcConfigurer { - - @Resource - private PermissionInterceptor permissionInterceptor; - @Resource - private CookieInterceptor cookieInterceptor; - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(permissionInterceptor).addPathPatterns("/**"); - registry.addInterceptor(cookieInterceptor).addPathPatterns("/**"); - } - -} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java deleted file mode 100644 index b48f469b..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.xxl.job.admin.core.conf; - -import com.xxl.job.admin.core.alarm.JobAlarmer; -import com.xxl.job.admin.core.scheduler.XxlJobScheduler; -import com.xxl.job.admin.dao.*; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import javax.sql.DataSource; -import java.util.Arrays; - -/** - * xxl-job config - * - * @author xuxueli 2017-04-28 - */ - -@Component -public class XxlJobAdminConfig implements InitializingBean, DisposableBean { - - private static XxlJobAdminConfig adminConfig = null; - public static XxlJobAdminConfig getAdminConfig() { - return adminConfig; - } - - - // ---------------------- XxlJobScheduler ---------------------- - - private XxlJobScheduler xxlJobScheduler; - - @Override - public void afterPropertiesSet() throws Exception { - adminConfig = this; - - xxlJobScheduler = new XxlJobScheduler(); - xxlJobScheduler.init(); - } - - @Override - public void destroy() throws Exception { - xxlJobScheduler.destroy(); - } - - - // ---------------------- XxlJobScheduler ---------------------- - - // conf - @Value("${xxl.job.i18n}") - private String i18n; - - @Value("${xxl.job.accessToken}") - private String accessToken; - - @Value("${xxl.job.timeout}") - private int timeout; - - @Value("${spring.mail.from}") - private String emailFrom; - - @Value("${xxl.job.triggerpool.fast.max}") - private int triggerPoolFastMax; - - @Value("${xxl.job.triggerpool.slow.max}") - private int triggerPoolSlowMax; - - @Value("${xxl.job.logretentiondays}") - private int logretentiondays; - - // dao, service - - @Resource - private XxlJobLogDao xxlJobLogDao; - @Resource - private XxlJobInfoDao xxlJobInfoDao; - @Resource - private XxlJobRegistryDao xxlJobRegistryDao; - @Resource - private XxlJobGroupDao xxlJobGroupDao; - @Resource - private XxlJobLogReportDao xxlJobLogReportDao; - @Resource - private JavaMailSender mailSender; - @Resource - private DataSource dataSource; - @Resource - private JobAlarmer jobAlarmer; - - - public String getI18n() { - if (!Arrays.asList("zh_CN", "zh_TC", "en").contains(i18n)) { - return "zh_CN"; - } - return i18n; - } - - public String getAccessToken() { - return accessToken; - } - - public int getTimeout() { - return timeout; - } - - public String getEmailFrom() { - return emailFrom; - } - - public int getTriggerPoolFastMax() { - if (triggerPoolFastMax < 200) { - return 200; - } - return triggerPoolFastMax; - } - - public int getTriggerPoolSlowMax() { - if (triggerPoolSlowMax < 100) { - return 100; - } - return triggerPoolSlowMax; - } - - public int getLogretentiondays() { - if (logretentiondays < 7) { - return -1; // Limit greater than or equal to 7, otherwise close - } - return logretentiondays; - } - - public XxlJobLogDao getXxlJobLogDao() { - return xxlJobLogDao; - } - - public XxlJobInfoDao getXxlJobInfoDao() { - return xxlJobInfoDao; - } - - public XxlJobRegistryDao getXxlJobRegistryDao() { - return xxlJobRegistryDao; - } - - public XxlJobGroupDao getXxlJobGroupDao() { - return xxlJobGroupDao; - } - - public XxlJobLogReportDao getXxlJobLogReportDao() { - return xxlJobLogReportDao; - } - - public JavaMailSender getMailSender() { - return mailSender; - } - - public DataSource getDataSource() { - return dataSource; - } - - public JobAlarmer getJobAlarmer() { - return jobAlarmer; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java deleted file mode 100644 index de4d7afb..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.xxl.job.admin.core.route.strategy; - -import com.xxl.job.admin.core.route.ExecutorRouter; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; - -import java.util.List; - -/** - * Created by xuxueli on 17/3/10. - */ -public class ExecutorRouteFirst extends ExecutorRouter { - - @Override - public ReturnT route(TriggerParam triggerParam, List addressList){ - return new ReturnT(addressList.get(0)); - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java deleted file mode 100644 index 4ff3cf6b..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.xxl.job.admin.core.route.strategy; - -import com.xxl.job.admin.core.route.ExecutorRouter; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; - -import java.util.List; - -/** - * Created by xuxueli on 17/3/10. - */ -public class ExecutorRouteLast extends ExecutorRouter { - - @Override - public ReturnT route(TriggerParam triggerParam, List addressList) { - return new ReturnT(addressList.get(addressList.size()-1)); - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java deleted file mode 100644 index 5ea4a384..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.xxl.job.admin.core.route.strategy; - -import com.xxl.job.admin.core.route.ExecutorRouter; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; - -import java.util.List; -import java.util.Random; - -/** - * Created by xuxueli on 17/3/10. - */ -public class ExecutorRouteRandom extends ExecutorRouter { - - private static Random localRandom = new Random(); - - @Override - public ReturnT route(TriggerParam triggerParam, List addressList) { - String address = addressList.get(localRandom.nextInt(addressList.size())); - return new ReturnT(address); - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java deleted file mode 100644 index 0b9b4a9c..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.xxl.job.admin.core.scheduler; - -import com.xxl.job.admin.core.util.I18nUtil; - -/** - * @author xuxueli 2020-10-29 21:11:23 - */ -public enum MisfireStrategyEnum { - - /** - * do nothing - */ - DO_NOTHING(I18nUtil.getString("misfire_strategy_do_nothing")), - - /** - * fire once now - */ - FIRE_ONCE_NOW(I18nUtil.getString("misfire_strategy_fire_once_now")); - - private String title; - - MisfireStrategyEnum(String title) { - this.title = title; - } - - public String getTitle() { - return title; - } - - public static MisfireStrategyEnum match(String name, MisfireStrategyEnum defaultItem){ - for (MisfireStrategyEnum item: MisfireStrategyEnum.values()) { - if (item.name().equals(name)) { - return item; - } - } - return defaultItem; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java deleted file mode 100644 index aa334fda..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.xxl.job.admin.core.scheduler; - -import com.xxl.job.admin.core.util.I18nUtil; - -/** - * @author xuxueli 2020-10-29 21:11:23 - */ -public enum ScheduleTypeEnum { - - NONE(I18nUtil.getString("schedule_type_none")), - - /** - * schedule by cron - */ - CRON(I18nUtil.getString("schedule_type_cron")), - - /** - * schedule by fixed rate (in seconds) - */ - FIX_RATE(I18nUtil.getString("schedule_type_fix_rate")), - - /** - * schedule by fix delay (in seconds), after the last time - */ - /*FIX_DELAY(I18nUtil.getString("schedule_type_fix_delay"))*/; - - private String title; - - ScheduleTypeEnum(String title) { - this.title = title; - } - - public String getTitle() { - return title; - } - - public static ScheduleTypeEnum match(String name, ScheduleTypeEnum defaultItem){ - for (ScheduleTypeEnum item: ScheduleTypeEnum.values()) { - if (item.name().equals(name)) { - return item; - } - } - return defaultItem; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java deleted file mode 100644 index 8a0f4a06..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.xxl.job.admin.core.scheduler; - -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.thread.*; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.core.biz.ExecutorBiz; -import com.xxl.job.core.biz.client.ExecutorBizClient; -import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * @author xuxueli 2018-10-28 00:18:17 - */ - -public class XxlJobScheduler { - private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class); - - - public void init() throws Exception { - // init i18n - initI18n(); - - // admin trigger pool start - JobTriggerPoolHelper.toStart(); - - // admin registry monitor run - JobRegistryHelper.getInstance().start(); - - // admin fail-monitor run - JobFailMonitorHelper.getInstance().start(); - - // admin lose-monitor run ( depend on JobTriggerPoolHelper ) - JobCompleteHelper.getInstance().start(); - - // admin log report start - JobLogReportHelper.getInstance().start(); - - // start-schedule ( depend on JobTriggerPoolHelper ) - JobScheduleHelper.getInstance().start(); - - logger.info(">>>>>>>>> init xxl-job admin success."); - } - - - public void destroy() throws Exception { - - // stop-schedule - JobScheduleHelper.getInstance().toStop(); - - // admin log report stop - JobLogReportHelper.getInstance().toStop(); - - // admin lose-monitor stop - JobCompleteHelper.getInstance().toStop(); - - // admin fail-monitor stop - JobFailMonitorHelper.getInstance().toStop(); - - // admin registry stop - JobRegistryHelper.getInstance().toStop(); - - // admin trigger pool stop - JobTriggerPoolHelper.toStop(); - - } - - // ---------------------- I18n ---------------------- - - private void initI18n(){ - for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) { - item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name()))); - } - } - - // ---------------------- executor-client ---------------------- - private static ConcurrentMap executorBizRepository = new ConcurrentHashMap(); - public static ExecutorBiz getExecutorBiz(String address) throws Exception { - // valid - if (address==null || address.trim().length()==0) { - return null; - } - - // load-cache - address = address.trim(); - ExecutorBiz executorBiz = executorBizRepository.get(address); - if (executorBiz != null) { - return executorBiz; - } - - // set-cache - executorBiz = new ExecutorBizClient(address, - XxlJobAdminConfig.getAdminConfig().getAccessToken(), - XxlJobAdminConfig.getAdminConfig().getTimeout()); - - executorBizRepository.put(address, executorBiz); - return executorBiz; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java deleted file mode 100644 index df33cc3a..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.xxl.job.admin.core.thread; - -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.model.XxlJobLogReport; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * job log report helper - * - * @author xuxueli 2019-11-22 - */ -public class JobLogReportHelper { - private static Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class); - - private static JobLogReportHelper instance = new JobLogReportHelper(); - public static JobLogReportHelper getInstance(){ - return instance; - } - - - private Thread logrThread; - private volatile boolean toStop = false; - public void start(){ - logrThread = new Thread(new Runnable() { - - @Override - public void run() { - - // last clean log time - long lastCleanLogTime = 0; - - - while (!toStop) { - - // 1、log-report refresh: refresh log report in 3 days - try { - - for (int i = 0; i < 3; i++) { - - // today - Calendar itemDay = Calendar.getInstance(); - itemDay.add(Calendar.DAY_OF_MONTH, -i); - itemDay.set(Calendar.HOUR_OF_DAY, 0); - itemDay.set(Calendar.MINUTE, 0); - itemDay.set(Calendar.SECOND, 0); - itemDay.set(Calendar.MILLISECOND, 0); - - Date todayFrom = itemDay.getTime(); - - itemDay.set(Calendar.HOUR_OF_DAY, 23); - itemDay.set(Calendar.MINUTE, 59); - itemDay.set(Calendar.SECOND, 59); - itemDay.set(Calendar.MILLISECOND, 999); - - Date todayTo = itemDay.getTime(); - - // refresh log-report every minute - XxlJobLogReport xxlJobLogReport = new XxlJobLogReport(); - xxlJobLogReport.setTriggerDay(todayFrom); - xxlJobLogReport.setRunningCount(0); - xxlJobLogReport.setSucCount(0); - xxlJobLogReport.setFailCount(0); - - Map triggerCountMap = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLogReport(todayFrom, todayTo); - if (triggerCountMap!=null && triggerCountMap.size()>0) { - int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCount"))):0; - int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))):0; - int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))):0; - int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc; - - xxlJobLogReport.setRunningCount(triggerDayCountRunning); - xxlJobLogReport.setSucCount(triggerDayCountSuc); - xxlJobLogReport.setFailCount(triggerDayCountFail); - } - - // do refresh - int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().update(xxlJobLogReport); - if (ret < 1) { - XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().save(xxlJobLogReport); - } - } - - } catch (Throwable e) { - if (!toStop) { - logger.error(">>>>>>>>>>> xxl-job, job log report thread error:{}", e); - } - } - - // 2、log-clean: switch open & once each day - if (XxlJobAdminConfig.getAdminConfig().getLogretentiondays()>0 - && System.currentTimeMillis() - lastCleanLogTime > 24*60*60*1000) { - - // expire-time - Calendar expiredDay = Calendar.getInstance(); - expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminConfig.getAdminConfig().getLogretentiondays()); - expiredDay.set(Calendar.HOUR_OF_DAY, 0); - expiredDay.set(Calendar.MINUTE, 0); - expiredDay.set(Calendar.SECOND, 0); - expiredDay.set(Calendar.MILLISECOND, 0); - Date clearBeforeTime = expiredDay.getTime(); - - // clean expired log - List logIds = null; - do { - logIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findClearLogIds(0, 0, clearBeforeTime, 0, 1000); - if (logIds!=null && logIds.size()>0) { - XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().clearLog(logIds); - } - } while (logIds!=null && logIds.size()>0); - - // update clean time - lastCleanLogTime = System.currentTimeMillis(); - } - - try { - TimeUnit.MINUTES.sleep(1); - } catch (Throwable e) { - if (!toStop) { - logger.error(e.getMessage(), e); - } - } - - } - - logger.info(">>>>>>>>>>> xxl-job, job log report thread stop"); - - } - }); - logrThread.setDaemon(true); - logrThread.setName("xxl-job, admin JobLogReportHelper"); - logrThread.start(); - } - - public void toStop(){ - toStop = true; - // interrupt and wait - logrThread.interrupt(); - try { - logrThread.join(); - } catch (Throwable e) { - logger.error(e.getMessage(), e); - } - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java deleted file mode 100644 index 34a898f3..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.xxl.job.admin.core.util; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * Cookie.Util - * - * @author xuxueli 2015-12-12 18:01:06 - */ -public class CookieUtil { - - // 默认缓存时间,单位/秒, 2H - private static final int COOKIE_MAX_AGE = Integer.MAX_VALUE; - // 保存路径,根路径 - private static final String COOKIE_PATH = "/"; - - /** - * 保存 - * - * @param response - * @param key - * @param value - * @param ifRemember - */ - public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) { - int age = ifRemember?COOKIE_MAX_AGE:-1; - set(response, key, value, null, COOKIE_PATH, age, true); - } - - /** - * 保存 - * - * @param response - * @param key - * @param value - * @param maxAge - */ - private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) { - Cookie cookie = new Cookie(key, value); - if (domain != null) { - cookie.setDomain(domain); - } - cookie.setPath(path); - cookie.setMaxAge(maxAge); - cookie.setHttpOnly(isHttpOnly); - response.addCookie(cookie); - } - - /** - * 查询value - * - * @param request - * @param key - * @return - */ - public static String getValue(HttpServletRequest request, String key) { - Cookie cookie = get(request, key); - if (cookie != null) { - return cookie.getValue(); - } - return null; - } - - /** - * 查询Cookie - * - * @param request - * @param key - */ - private static Cookie get(HttpServletRequest request, String key) { - Cookie[] arr_cookie = request.getCookies(); - if (arr_cookie != null && arr_cookie.length > 0) { - for (Cookie cookie : arr_cookie) { - if (cookie.getName().equals(key)) { - return cookie; - } - } - } - return null; - } - - /** - * 删除Cookie - * - * @param request - * @param response - * @param key - */ - public static void remove(HttpServletRequest request, HttpServletResponse response, String key) { - Cookie cookie = get(request, key); - if (cookie != null) { - set(response, key, "", null, COOKIE_PATH, 0, true); - } - } - -} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java deleted file mode 100644 index e90af434..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.xxl.job.admin.core.util; - -import freemarker.ext.beans.BeansWrapper; -import freemarker.ext.beans.BeansWrapperBuilder; -import freemarker.template.Configuration; -import freemarker.template.TemplateHashModel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * ftl util - * - * @author xuxueli 2018-01-17 20:37:48 - */ -public class FtlUtil { - private static Logger logger = LoggerFactory.getLogger(FtlUtil.class); - - private static BeansWrapper wrapper = new BeansWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build(); //BeansWrapper.getDefaultInstance(); - - public static TemplateHashModel generateStaticModel(String packageName) { - try { - TemplateHashModel staticModels = wrapper.getStaticModels(); - TemplateHashModel fileStatics = (TemplateHashModel) staticModels.get(packageName); - return fileStatics; - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - return null; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java deleted file mode 100644 index 772a96ec..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.xxl.job.admin.core.util; - -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.EncodedResource; -import org.springframework.core.io.support.PropertiesLoaderUtils; - -import java.io.IOException; -import java.text.MessageFormat; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -/** - * i18n util - * - * @author xuxueli 2018-01-17 20:39:06 - */ -public class I18nUtil { - private static Logger logger = LoggerFactory.getLogger(I18nUtil.class); - - private static Properties prop = null; - public static Properties loadI18nProp(){ - if (prop != null) { - return prop; - } - try { - // build i18n prop - String i18n = XxlJobAdminConfig.getAdminConfig().getI18n(); - String i18nFile = MessageFormat.format("i18n/message_{0}.properties", i18n); - - // load prop - Resource resource = new ClassPathResource(i18nFile); - EncodedResource encodedResource = new EncodedResource(resource,"UTF-8"); - prop = PropertiesLoaderUtils.loadProperties(encodedResource); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return prop; - } - - /** - * get val of i18n key - * - * @param key - * @return - */ - public static String getString(String key) { - return loadI18nProp().getProperty(key); - } - - /** - * get mult val of i18n mult key, as json - * - * @param keys - * @return - */ - public static String getMultString(String... keys) { - Map map = new HashMap(); - - Properties prop = loadI18nProp(); - if (keys!=null && keys.length>0) { - for (String key: keys) { - map.put(key, prop.getProperty(key)); - } - } else { - for (String key: prop.stringPropertyNames()) { - map.put(key, prop.getProperty(key)); - } - } - - String json = JacksonUtil.writeValueAsString(map); - return json; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java deleted file mode 100644 index 4f4ea3cc..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.xxl.job.admin.core.util; - -import com.fasterxml.jackson.core.JsonGenerationException; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -/** - * Jackson util - * - * 1、obj need private and set/get; - * 2、do not support inner class; - * - * @author xuxueli 2015-9-25 18:02:56 - */ -public class JacksonUtil { - private static Logger logger = LoggerFactory.getLogger(JacksonUtil.class); - - private final static ObjectMapper objectMapper = new ObjectMapper(); - public static ObjectMapper getInstance() { - return objectMapper; - } - - /** - * bean、array、List、Map --> json - * - * @param obj - * @return json string - * @throws Exception - */ - public static String writeValueAsString(Object obj) { - try { - return getInstance().writeValueAsString(obj); - } catch (JsonGenerationException e) { - logger.error(e.getMessage(), e); - } catch (JsonMappingException e) { - logger.error(e.getMessage(), e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * string --> bean、Map、List(array) - * - * @param jsonStr - * @param clazz - * @return obj - * @throws Exception - */ - public static T readValue(String jsonStr, Class clazz) { - try { - return getInstance().readValue(jsonStr, clazz); - } catch (JsonParseException e) { - logger.error(e.getMessage(), e); - } catch (JsonMappingException e) { - logger.error(e.getMessage(), e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } - - /** - * string --> List... - * - * @param jsonStr - * @param parametrized - * @param parameterClasses - * @param - * @return - */ - public static T readValue(String jsonStr, Class parametrized, Class... parameterClasses) { - try { - JavaType javaType = getInstance().getTypeFactory().constructParametricType(parametrized, parameterClasses); - return getInstance().readValue(jsonStr, javaType); - } catch (JsonParseException e) { - logger.error(e.getMessage(), e); - } catch (JsonMappingException e) { - logger.error(e.getMessage(), e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - return null; - } -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java deleted file mode 100644 index fbab0613..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.xxl.job.admin.core.util; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * local cache tool - * - * @author xuxueli 2018-01-22 21:37:34 - */ -public class LocalCacheUtil { - - private static ConcurrentMap cacheRepository = new ConcurrentHashMap(); // 类型建议用抽象父类,兼容性更好; - private static class LocalCacheData{ - private String key; - private Object val; - private long timeoutTime; - - public LocalCacheData() { - } - - public LocalCacheData(String key, Object val, long timeoutTime) { - this.key = key; - this.val = val; - this.timeoutTime = timeoutTime; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public Object getVal() { - return val; - } - - public void setVal(Object val) { - this.val = val; - } - - public long getTimeoutTime() { - return timeoutTime; - } - - public void setTimeoutTime(long timeoutTime) { - this.timeoutTime = timeoutTime; - } - } - - - /** - * set cache - * - * @param key - * @param val - * @param cacheTime - * @return - */ - public static boolean set(String key, Object val, long cacheTime){ - - // clean timeout cache, before set new cache (avoid cache too much) - cleanTimeoutCache(); - - // set new cache - if (key==null || key.trim().length()==0) { - return false; - } - if (val == null) { - remove(key); - } - if (cacheTime <= 0) { - remove(key); - } - long timeoutTime = System.currentTimeMillis() + cacheTime; - LocalCacheData localCacheData = new LocalCacheData(key, val, timeoutTime); - cacheRepository.put(localCacheData.getKey(), localCacheData); - return true; - } - - /** - * remove cache - * - * @param key - * @return - */ - public static boolean remove(String key){ - if (key==null || key.trim().length()==0) { - return false; - } - cacheRepository.remove(key); - return true; - } - - /** - * get cache - * - * @param key - * @return - */ - public static Object get(String key){ - if (key==null || key.trim().length()==0) { - return null; - } - LocalCacheData localCacheData = cacheRepository.get(key); - if (localCacheData!=null && System.currentTimeMillis()=localCacheData.getTimeoutTime()) { - cacheRepository.remove(key); - } - } - } - return true; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobGroupMapper.java similarity index 89% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobGroupMapper.java index b608d9fb..f220f2e2 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobGroupMapper.java @@ -1,6 +1,6 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobGroup; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -10,7 +10,7 @@ import java.util.List; * Created by xuxueli on 16/9/30. */ @Mapper -public interface XxlJobGroupDao { +public interface XxlJobGroupMapper { public List findAll(); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobInfoMapper.java similarity index 86% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobInfoMapper.java index ac0019d5..b2919fca 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobInfoMapper.java @@ -1,6 +1,6 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobInfo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -12,7 +12,7 @@ import java.util.List; * @author xuxueli 2016-1-12 18:03:45 */ @Mapper -public interface XxlJobInfoDao { +public interface XxlJobInfoMapper { public List pageList(@Param("offset") int offset, @Param("pagesize") int pagesize, @@ -61,5 +61,12 @@ public interface XxlJobInfoDao { */ public int scheduleUpdate(XxlJobInfo xxlJobInfo); + /** + * batch update job info + * + * @param jobInfoList + * @return + */ + public int scheduleBatchUpdate(@Param("list") List jobInfoList); } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLockMapper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLockMapper.java new file mode 100644 index 00000000..27d2cbbc --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLockMapper.java @@ -0,0 +1,18 @@ +package com.xxl.job.admin.mapper; + +import org.apache.ibatis.annotations.Mapper; + +/** + * job lock + * + * @author xuxueli 2016-1-12 18:03:45 + */ +@Mapper +public interface XxlJobLockMapper { + + /** + * get schedule lock + */ + String scheduleLock(); + +} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLogGlueMapper.java similarity index 78% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLogGlueMapper.java index 3028aed2..42b39ee0 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLogGlueMapper.java @@ -1,6 +1,6 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobLogGlue; +import com.xxl.job.admin.model.XxlJobLogGlue; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -11,7 +11,7 @@ import java.util.List; * @author xuxueli 2016-5-19 18:04:56 */ @Mapper -public interface XxlJobLogGlueDao { +public interface XxlJobLogGlueMapper { public int save(XxlJobLogGlue xxlJobLogGlue); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLogMapper.java similarity index 94% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLogMapper.java index 62fa3b4f..82ea690b 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLogMapper.java @@ -1,6 +1,6 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobLog; +import com.xxl.job.admin.model.XxlJobLog; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -13,7 +13,7 @@ import java.util.Map; * @author xuxueli 2016-1-12 18:03:06 */ @Mapper -public interface XxlJobLogDao { +public interface XxlJobLogMapper { // exist jobId not use jobGroup, not exist use jobGroup public List pageList(@Param("offset") int offset, diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLogReportMapper.java similarity index 57% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLogReportMapper.java index f4b3dc81..26f19684 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobLogReportMapper.java @@ -1,6 +1,6 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobLogReport; +import com.xxl.job.admin.model.XxlJobLogReport; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -12,11 +12,13 @@ import java.util.List; * @author xuxueli 2019-11-22 */ @Mapper -public interface XxlJobLogReportDao { +public interface XxlJobLogReportMapper { - public int save(XxlJobLogReport xxlJobLogReport); + /*public int save(XxlJobLogReport xxlJobLogReport); - public int update(XxlJobLogReport xxlJobLogReport); + public int update(XxlJobLogReport xxlJobLogReport);*/ + + public int saveOrUpdate(XxlJobLogReport xxlJobLogReport); public List queryLogReport(@Param("triggerDayFrom") Date triggerDayFrom, @Param("triggerDayTo") Date triggerDayTo); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobRegistryMapper.java similarity index 84% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobRegistryMapper.java index 234d3845..6bcf87aa 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobRegistryMapper.java @@ -1,6 +1,6 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobRegistry; +import com.xxl.job.admin.model.XxlJobRegistry; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -11,7 +11,7 @@ import java.util.List; * Created by xuxueli on 16/9/30. */ @Mapper -public interface XxlJobRegistryDao { +public interface XxlJobRegistryMapper { public List findDead(@Param("timeout") int timeout, @Param("nowTime") Date nowTime); @@ -40,4 +40,7 @@ public interface XxlJobRegistryDao { @Param("registryKey") String registryKey, @Param("registryValue") String registryValue); + public int removeByRegistryGroupAndKey(@Param("registryGroup") String registryGroup, + @Param("registryKey") String registryKey); + } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobUserMapper.java similarity index 73% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobUserMapper.java index e8404947..c70e02be 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/XxlJobUserMapper.java @@ -1,6 +1,7 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobUser; +import com.xxl.job.admin.model.XxlJobUser; +import com.xxl.tool.response.Response; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @@ -9,7 +10,7 @@ import java.util.List; * @author xuxueli 2019-05-04 16:44:59 */ @Mapper -public interface XxlJobUserDao { +public interface XxlJobUserMapper { public List pageList(@Param("offset") int offset, @Param("pagesize") int pagesize, @@ -22,10 +23,14 @@ public interface XxlJobUserDao { public XxlJobUser loadByUserName(@Param("username") String username); + public XxlJobUser loadById(@Param("id") int id); + public int save(XxlJobUser xxlJobUser); public int update(XxlJobUser xxlJobUser); public int delete(@Param("id") int id); + public int updateToken(@Param("id") int id, @Param("token") String token); + } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobGroup.java similarity index 88% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobGroup.java index dde4b399..57f7b773 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobGroup.java @@ -1,4 +1,6 @@ -package com.xxl.job.admin.core.model; +package com.xxl.job.admin.model; + +import com.xxl.tool.core.StringTool; import java.util.ArrayList; import java.util.Arrays; @@ -20,8 +22,8 @@ public class XxlJobGroup { // registry list private List registryList; // 执行器地址列表(系统注册) public List getRegistryList() { - if (addressList!=null && addressList.trim().length()>0) { - registryList = new ArrayList(Arrays.asList(addressList.split(","))); + if (StringTool.isNotBlank(addressList)) { + registryList = new ArrayList<>(Arrays.asList(addressList.split(","))); } return registryList; } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobInfo.java similarity index 90% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobInfo.java index e47b6dc6..5a3b80de 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobInfo.java @@ -1,4 +1,4 @@ -package com.xxl.job.admin.core.model; +package com.xxl.job.admin.model; import java.util.Date; @@ -20,25 +20,25 @@ public class XxlJobInfo { private String author; // 负责人 private String alarmEmail; // 报警邮件 - private String scheduleType; // 调度类型 + private String scheduleType; // 调度类型:ScheduleTypeEnum private String scheduleConf; // 调度配置,值含义取决于调度类型 - private String misfireStrategy; // 调度过期策略 + private String misfireStrategy; // 调度过期策略:MisfireStrategyEnum - private String executorRouteStrategy; // 执行器路由策略 + private String executorRouteStrategy; // 执行器路由策略:ExecutorRouteStrategyEnum private String executorHandler; // 执行器,任务Handler名称 private String executorParam; // 执行器,任务参数 - private String executorBlockStrategy; // 阻塞处理策略 + private String executorBlockStrategy; // 阻塞处理策略:ExecutorBlockStrategyEnum private int executorTimeout; // 任务执行超时时间,单位秒 private int executorFailRetryCount; // 失败重试次数 - private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum + private String glueType; // GLUE类型:GlueTypeEnum private String glueSource; // GLUE源代码 private String glueRemark; // GLUE备注 private Date glueUpdatetime; // GLUE更新时间 private String childJobId; // 子任务ID,多个逗号分隔 - private int triggerStatus; // 调度状态:0-停止,1-运行 + private int triggerStatus; // 调度状态:TriggerStatus private long triggerLastTime; // 上次调度时间 private long triggerNextTime; // 下次调度时间 diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobLog.java similarity index 98% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobLog.java index 7d3072aa..d98d3675 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobLog.java @@ -1,4 +1,4 @@ -package com.xxl.job.admin.core.model; +package com.xxl.job.admin.model; import java.util.Date; diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobLogGlue.java similarity index 91% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobLogGlue.java index 7b1ca461..310ee41e 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobLogGlue.java @@ -1,75 +1,75 @@ -package com.xxl.job.admin.core.model; - -import java.util.Date; - -/** - * xxl-job log for glue, used to track job code process - * @author xuxueli 2016-5-19 17:57:46 - */ -public class XxlJobLogGlue { - - private int id; - private int jobId; // 任务主键ID - private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum - private String glueSource; - private String glueRemark; - private Date addTime; - private Date updateTime; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public int getJobId() { - return jobId; - } - - public void setJobId(int jobId) { - this.jobId = jobId; - } - - public String getGlueType() { - return glueType; - } - - public void setGlueType(String glueType) { - this.glueType = glueType; - } - - public String getGlueSource() { - return glueSource; - } - - public void setGlueSource(String glueSource) { - this.glueSource = glueSource; - } - - public String getGlueRemark() { - return glueRemark; - } - - public void setGlueRemark(String glueRemark) { - this.glueRemark = glueRemark; - } - - public Date getAddTime() { - return addTime; - } - - public void setAddTime(Date addTime) { - this.addTime = addTime; - } - - public Date getUpdateTime() { - return updateTime; - } - - public void setUpdateTime(Date updateTime) { - this.updateTime = updateTime; - } - -} +package com.xxl.job.admin.model; + +import java.util.Date; + +/** + * xxl-job log for glue, used to track job code process + * @author xuxueli 2016-5-19 17:57:46 + */ +public class XxlJobLogGlue { + + private int id; + private int jobId; // 任务主键ID + private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum + private String glueSource; + private String glueRemark; + private Date addTime; + private Date updateTime; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getJobId() { + return jobId; + } + + public void setJobId(int jobId) { + this.jobId = jobId; + } + + public String getGlueType() { + return glueType; + } + + public void setGlueType(String glueType) { + this.glueType = glueType; + } + + public String getGlueSource() { + return glueSource; + } + + public void setGlueSource(String glueSource) { + this.glueSource = glueSource; + } + + public String getGlueRemark() { + return glueRemark; + } + + public void setGlueRemark(String glueRemark) { + this.glueRemark = glueRemark; + } + + public Date getAddTime() { + return addTime; + } + + public void setAddTime(Date addTime) { + this.addTime = addTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobLogReport.java similarity index 80% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobLogReport.java index e58ff1a9..92faada2 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobLogReport.java @@ -1,17 +1,18 @@ -package com.xxl.job.admin.core.model; +package com.xxl.job.admin.model; import java.util.Date; public class XxlJobLogReport { private int id; - private Date triggerDay; private int runningCount; private int sucCount; private int failCount; + private Date updateTime; + public int getId() { return id; } @@ -51,4 +52,13 @@ public class XxlJobLogReport { public void setFailCount(int failCount) { this.failCount = failCount; } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobRegistry.java similarity index 89% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobRegistry.java index 924d6d33..84aefdf1 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobRegistry.java @@ -1,4 +1,4 @@ -package com.xxl.job.admin.core.model; +package com.xxl.job.admin.model; import java.util.Date; @@ -7,17 +7,17 @@ import java.util.Date; */ public class XxlJobRegistry { - private int id; + private long id; private String registryGroup; private String registryKey; private String registryValue; private Date updateTime; - public int getId() { + public long getId() { return id; } - public void setId(int id) { + public void setId(long id) { this.id = id; } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobUser.java similarity index 62% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobUser.java index db17327a..9c7b8506 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/XxlJobUser.java @@ -1,6 +1,4 @@ -package com.xxl.job.admin.core.model; - -import org.springframework.util.StringUtils; +package com.xxl.job.admin.model; /** * @author xuxueli 2019-05-04 16:43:12 @@ -10,8 +8,9 @@ public class XxlJobUser { private int id; private String username; // 账号 private String password; // 密码 + private String token; // 登录token private int role; // 角色:0-普通用户、1-管理员 - private String permission; // 权限:执行器ID列表,多个逗号分割 + private String permission; // 权限:执行器ID列表,多个逗号分割 public int getId() { return id; @@ -37,6 +36,14 @@ public class XxlJobUser { this.password = password; } + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + public int getRole() { return role; } @@ -53,21 +60,4 @@ public class XxlJobUser { this.permission = permission; } - // plugin - public boolean validPermission(int jobGroup){ - if (this.role == 1) { - return true; - } else { - if (StringUtils.hasText(this.permission)) { - for (String permissionItem : this.permission.split(",")) { - if (String.valueOf(jobGroup).equals(permissionItem)) { - return true; - } - } - } - return false; - } - - } - } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/model/dto/XxlBootResourceDTO.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/dto/XxlBootResourceDTO.java new file mode 100644 index 00000000..a77370aa --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/model/dto/XxlBootResourceDTO.java @@ -0,0 +1,185 @@ +package com.xxl.job.admin.model.dto; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * XxlBootResource DTO + * + * Created by xuxueli on 2024-08-04 + */ +public class XxlBootResourceDTO implements Serializable { + private static final long serialVersionUID = 42L; + + /** + * 资源ID + */ + private int id; + + /** + * 父节点ID + */ + private int parentId; + + /** + * 名称 + */ + private String name; + + /** + * 类型:0-目录, 1-菜单, 2-按钮 + */ + private int type; + + /** + * 权限标识 + */ + private String permission; + + /** + * 菜单地址 + */ + private String url; + + /** + * ICON + */ + private String icon; + + /** + * 顺序 + */ + private int order; + + /** + * 状态:0-正常、1-禁用 + */ + private int status; + + /** + * 新增时间 + */ + private Date addTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * child data + */ + private List children; + + public XxlBootResourceDTO() { + } + public XxlBootResourceDTO(int id, int parentId, String name, int type, String permission, String url, String icon, int order, int status, List children) { + this.id = id; + this.parentId = parentId; + this.name = name; + this.type = type; + this.permission = permission; + this.url = url; + this.icon = icon; + this.order = order; + this.status = status; + this.children = children; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getParentId() { + return parentId; + } + + public void setParentId(int parentId) { + this.parentId = parentId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getPermission() { + return permission; + } + + public void setPermission(String permission) { + this.permission = permission; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public Date getAddTime() { + return addTime; + } + + public void setAddTime(Date addTime) { + this.addTime = addTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarm.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/alarm/JobAlarm.java similarity index 63% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarm.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/alarm/JobAlarm.java index 4165ff3a..d7f61773 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarm.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/alarm/JobAlarm.java @@ -1,7 +1,7 @@ -package com.xxl.job.admin.core.alarm; +package com.xxl.job.admin.scheduler.alarm; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobLog; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobLog; /** * @author xuxueli 2020-01-19 diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarmer.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/alarm/JobAlarmer.java similarity index 59% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarmer.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/alarm/JobAlarmer.java index 797dc900..3a37ba26 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarmer.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/alarm/JobAlarmer.java @@ -1,50 +1,34 @@ -package com.xxl.job.admin.core.alarm; +package com.xxl.job.admin.scheduler.alarm; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobLog; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobLog; +import com.xxl.tool.core.CollectionTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.ArrayList; import java.util.List; -import java.util.Map; +/** + * xxl-job alarmer + * + * @author xuxueli 17/7/13. + */ @Component -public class JobAlarmer implements ApplicationContextAware, InitializingBean { - private static Logger logger = LoggerFactory.getLogger(JobAlarmer.class); +public class JobAlarmer { + private static final Logger logger = LoggerFactory.getLogger(JobAlarmer.class); - private ApplicationContext applicationContext; + @Autowired private List jobAlarmList; - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public void afterPropertiesSet() throws Exception { - Map serviceBeanMap = applicationContext.getBeansOfType(JobAlarm.class); - if (serviceBeanMap != null && serviceBeanMap.size() > 0) { - jobAlarmList = new ArrayList(serviceBeanMap.values()); - } - } - /** * job alarm - * - * @param info - * @param jobLog - * @return */ public boolean alarm(XxlJobInfo info, XxlJobLog jobLog) { boolean result = false; - if (jobAlarmList!=null && jobAlarmList.size()>0) { + if (CollectionTool.isNotEmpty(jobAlarmList)) { result = true; // success means all-success for (JobAlarm alarm: jobAlarmList) { boolean resultItem = false; @@ -62,4 +46,22 @@ public class JobAlarmer implements ApplicationContextAware, InitializingBean { return result; } + /* + // implements SmartInitializingSingleton + // implements ApplicationContextAware, InitializingBean + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void afterPropertiesSet() throws Exception { + Map serviceBeanMap = applicationContext.getBeansOfType(JobAlarm.class); + if (MapTool.isNotEmpty(serviceBeanMap)) { + jobAlarmList = new ArrayList<>(serviceBeanMap.values()); + } + }*/ + } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/impl/EmailJobAlarm.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/alarm/impl/EmailJobAlarm.java similarity index 78% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/impl/EmailJobAlarm.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/alarm/impl/EmailJobAlarm.java index 16e52184..fdbab03e 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/impl/EmailJobAlarm.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/alarm/impl/EmailJobAlarm.java @@ -1,18 +1,18 @@ -package com.xxl.job.admin.core.alarm.impl; - -import com.xxl.job.admin.core.alarm.JobAlarm; -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.model.XxlJobGroup; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobLog; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.core.biz.model.ReturnT; +package com.xxl.job.admin.scheduler.alarm.impl; + +import com.xxl.job.admin.scheduler.alarm.JobAlarm; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobLog; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.core.context.XxlJobContext; +import jakarta.mail.internet.MimeMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; -import javax.mail.internet.MimeMessage; import java.text.MessageFormat; import java.util.Arrays; import java.util.HashSet; @@ -37,19 +37,19 @@ public class EmailJobAlarm implements JobAlarm { boolean alarmResult = true; // send monitor email - if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) { + if (info!=null && info.getAlarmEmail()!=null && !info.getAlarmEmail().trim().isEmpty()) { // alarmContent String alarmContent = "Alarm Job LogId=" + jobLog.getId(); - if (jobLog.getTriggerCode() != ReturnT.SUCCESS_CODE) { + if (jobLog.getTriggerCode() != XxlJobContext.HANDLE_CODE_SUCCESS) { alarmContent += "
TriggerMsg=
" + jobLog.getTriggerMsg(); } - if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) { + if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != XxlJobContext.HANDLE_CODE_SUCCESS) { alarmContent += "
HandleCode=" + jobLog.getHandleMsg(); } // email info - XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(Integer.valueOf(info.getJobGroup())); + XxlJobGroup group = XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().load(Integer.valueOf(info.getJobGroup())); String personal = I18nUtil.getString("admin_name_full"); String title = I18nUtil.getString("jobconf_monitor"); String content = MessageFormat.format(loadEmailJobAlarmTemplate(), @@ -63,15 +63,15 @@ public class EmailJobAlarm implements JobAlarm { // make mail try { - MimeMessage mimeMessage = XxlJobAdminConfig.getAdminConfig().getMailSender().createMimeMessage(); + MimeMessage mimeMessage = XxlJobAdminBootstrap.getInstance().getMailSender().createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); - helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailFrom(), personal); + helper.setFrom(XxlJobAdminBootstrap.getInstance().getEmailFrom(), personal); helper.setTo(email); helper.setSubject(title); helper.setText(content, true); - XxlJobAdminConfig.getAdminConfig().getMailSender().send(mimeMessage); + XxlJobAdminBootstrap.getInstance().getMailSender().send(mimeMessage); } catch (Exception e) { logger.error(">>>>>>>>>>> xxl-job, job fail alarm email send error, JobLogId:{}", jobLog.getId(), e); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/complete/XxlJobCompleter.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/complete/JobCompleter.java similarity index 52% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/complete/XxlJobCompleter.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/complete/JobCompleter.java index 6b615ac2..e11541a9 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/complete/XxlJobCompleter.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/complete/JobCompleter.java @@ -1,60 +1,79 @@ -package com.xxl.job.admin.core.complete; - -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobLog; -import com.xxl.job.admin.core.thread.JobTriggerPoolHelper; -import com.xxl.job.admin.core.trigger.TriggerTypeEnum; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.core.biz.model.ReturnT; +package com.xxl.job.admin.scheduler.complete; + +import com.xxl.job.admin.mapper.XxlJobInfoMapper; +import com.xxl.job.admin.mapper.XxlJobLogMapper; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobLog; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum; +import com.xxl.job.admin.util.I18nUtil; import com.xxl.job.core.context.XxlJobContext; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; import java.text.MessageFormat; /** + * xxl-job job log complete + * * @author xuxueli 2020-10-30 20:43:10 */ -public class XxlJobCompleter { - private static Logger logger = LoggerFactory.getLogger(XxlJobCompleter.class); +@Component +public class JobCompleter { + private static final Logger logger = LoggerFactory.getLogger(JobCompleter.class); + + + @Resource + private XxlJobInfoMapper xxlJobInfoMapper; + @Resource + private XxlJobLogMapper xxlJobLogMapper; + /** - * common fresh handle entrance (limit only once) - * - * @param xxlJobLog - * @return + * complate job (limit only once) */ - public static int updateHandleInfoAndFinish(XxlJobLog xxlJobLog) { + public int complete(XxlJobLog xxlJobLog) { - // finish - finishJob(xxlJobLog); + // 1、process child-job + processChildJob(xxlJobLog); // text最大64kb 避免长度过长 if (xxlJobLog.getHandleMsg().length() > 15000) { xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg().substring(0, 15000) ); } - // fresh handle - return XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateHandleInfo(xxlJobLog); + // 2、fix_delay trigger next + // on the way + + // 3、update job handle-info + return xxlJobLogMapper.updateHandleInfo(xxlJobLog); } /** * do somethind to finish job */ - private static void finishJob(XxlJobLog xxlJobLog){ + private void processChildJob(XxlJobLog xxlJobLog){ // 1、handle success, to trigger child job String triggerChildMsg = null; if (XxlJobContext.HANDLE_CODE_SUCCESS == xxlJobLog.getHandleCode()) { - XxlJobInfo xxlJobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(xxlJobLog.getJobId()); - if (xxlJobInfo!=null && xxlJobInfo.getChildJobId()!=null && xxlJobInfo.getChildJobId().trim().length()>0) { - triggerChildMsg = "

>>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_child_run") +"<<<<<<<<<<<
"; + XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(xxlJobLog.getJobId()); + // process child job + if (xxlJobInfo!=null && StringTool.isNotBlank(xxlJobInfo.getChildJobId())) { + triggerChildMsg = "

>>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_child_run") +"<<<<<<<<<<<
"; String[] childJobIds = xxlJobInfo.getChildJobId().split(","); for (int i = 0; i < childJobIds.length; i++) { - int childJobId = (childJobIds[i]!=null && childJobIds[i].trim().length()>0 && isNumeric(childJobIds[i]))?Integer.valueOf(childJobIds[i]):-1; + + // process eath child + int childJobId = (StringTool.isNotBlank(childJobIds[i]) && StringTool.isNumeric(childJobIds[i])) + ?Integer.parseInt(childJobIds[i]) + :-1; if (childJobId > 0) { // valid if (childJobId == xxlJobLog.getJobId()) { @@ -63,15 +82,15 @@ public class XxlJobCompleter { } // trigger child job - JobTriggerPoolHelper.trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null, null); - ReturnT triggerChildResult = ReturnT.SUCCESS; + XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null, null); + Response triggerChildResult = Response.ofSuccess(); // add msg triggerChildMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg1"), (i+1), childJobIds.length, childJobIds[i], - (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")), + (triggerChildResult.isSuccess()?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")), triggerChildResult.getMsg()); } else { triggerChildMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg2"), @@ -84,22 +103,20 @@ public class XxlJobCompleter { } } - if (triggerChildMsg != null) { + // 2、append trigger-child message + if (StringTool.isNotBlank(triggerChildMsg)) { xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg() + triggerChildMsg ); } - // 2、fix_delay trigger next - // on the way - } - private static boolean isNumeric(String str){ + /*private static boolean isNumeric(String str){ try { int result = Integer.valueOf(str); return true; } catch (NumberFormatException e) { return false; } - } + }*/ } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/config/XxlJobAdminBootstrap.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/config/XxlJobAdminBootstrap.java new file mode 100644 index 00000000..05d13a12 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/config/XxlJobAdminBootstrap.java @@ -0,0 +1,312 @@ +package com.xxl.job.admin.scheduler.config; + +import com.xxl.job.admin.mapper.*; +import com.xxl.job.admin.scheduler.alarm.JobAlarmer; +import com.xxl.job.admin.scheduler.complete.JobCompleter; +import com.xxl.job.admin.scheduler.thread.*; +import com.xxl.job.admin.scheduler.trigger.JobTrigger; +import com.xxl.job.core.constant.Const; +import com.xxl.job.core.openapi.ExecutorBiz; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.http.HttpTool; +import jakarta.annotation.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; + +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * xxl-job config + * + * @author xuxueli 2017-04-28 + */ + +@Component +public class XxlJobAdminBootstrap implements InitializingBean, DisposableBean { + private static final Logger logger = LoggerFactory.getLogger(XxlJobAdminBootstrap.class); + + // ---------------------- instance ---------------------- + + private static XxlJobAdminBootstrap adminConfig = null; + public static XxlJobAdminBootstrap getInstance() { + return adminConfig; + } + + + // ---------------------- start / stop ---------------------- + + @Override + public void afterPropertiesSet() throws Exception { + // init instance + adminConfig = this; + + // start + doStart(); + } + + @Override + public void destroy() throws Exception { + // stop + doStop(); + } + + // job module + private JobTriggerPoolHelper jobTriggerPoolHelper; + private JobRegistryHelper jobRegistryHelper; + private JobFailAlarmMonitorHelper jobFailAlarmMonitorHelper; + private JobCompleteHelper jobCompleteHelper; + private JobLogReportHelper jobLogReportHelper; + private JobScheduleHelper jobScheduleHelper; + + public JobTriggerPoolHelper getJobTriggerPoolHelper() { + return jobTriggerPoolHelper; + } + public JobRegistryHelper getJobRegistryHelper() { + return jobRegistryHelper; + } + public JobCompleteHelper getJobCompleteHelper() { + return jobCompleteHelper; + } + + /** + * do start + */ + private void doStart() throws Exception { + // trigger-pool start + jobTriggerPoolHelper = new JobTriggerPoolHelper(); + jobTriggerPoolHelper.start(); + + // registry monitor start + jobRegistryHelper = new JobRegistryHelper(); + jobRegistryHelper.start(); + + // fail-alarm monitor start + jobFailAlarmMonitorHelper = new JobFailAlarmMonitorHelper(); + jobFailAlarmMonitorHelper.start(); + + // job complate start ( depend on JobTriggerPoolHelper ) for callback and result-lost + jobCompleteHelper = new JobCompleteHelper(); + jobCompleteHelper.start(); + + // log-report start + jobLogReportHelper = new JobLogReportHelper(); + jobLogReportHelper.start(); + + // job-schedule start ( depend on JobTriggerPoolHelper ) + jobScheduleHelper = new JobScheduleHelper(); + jobScheduleHelper.start(); + + logger.info(">>>>>>>>> xxl-job admin start success."); + } + + /** + * do stop + */ + private void doStop(){ + // job-schedule stop + jobScheduleHelper.stop(); + + // log-report stop + jobLogReportHelper.stop(); + + // job complate stop + jobCompleteHelper.stop(); + + // fail-alarm monitor stop + jobFailAlarmMonitorHelper.stop(); + + // registry monitor stop + jobRegistryHelper.stop(); + + // trigger-pool stop + jobTriggerPoolHelper.stop(); + + logger.info(">>>>>>>>> xxl-job admin stopped."); + } + + + // ---------------------- executor-client ---------------------- + + private static ConcurrentMap executorBizRepository = new ConcurrentHashMap(); + public static ExecutorBiz getExecutorBiz(String address) throws Exception { + // valid + if (StringTool.isBlank(address)) { + return null; + } + + // load-cache + address = address.trim(); + ExecutorBiz executorBiz = executorBizRepository.get(address); + if (executorBiz != null) { + return executorBiz; + } + + // set-cache + executorBiz = HttpTool.createClient() + .url(address) + .timeout(XxlJobAdminBootstrap.getInstance().getTimeout() * 1000) + .header(Const.XXL_JOB_ACCESS_TOKEN, XxlJobAdminBootstrap.getInstance().getAccessToken()) + .proxy(ExecutorBiz.class); + executorBizRepository.put(address, executorBiz); + return executorBiz; + } + + + // ---------------------- field ---------------------- + + // conf + @Value("${xxl.job.i18n}") + private String i18n; + + @Value("${xxl.job.accessToken}") + private String accessToken; + + @Value("${xxl.job.timeout}") + private int timeout; + + @Value("${spring.mail.from}") + private String emailFrom; + + @Value("${xxl.job.triggerpool.fast.max}") + private int triggerPoolFastMax; + + @Value("${xxl.job.triggerpool.slow.max}") + private int triggerPoolSlowMax; + + @Value("${xxl.job.schedule.batchsize}") + private int scheduleBatchSize; + + @Value("${xxl.job.logretentiondays}") + private int logretentiondays; + + // service, mapper + @Resource + private XxlJobLogMapper xxlJobLogMapper; + @Resource + private XxlJobInfoMapper xxlJobInfoMapper; + @Resource + private XxlJobRegistryMapper xxlJobRegistryMapper; + @Resource + private XxlJobGroupMapper xxlJobGroupMapper; + @Resource + private XxlJobLogReportMapper xxlJobLogReportMapper; + @Resource + private XxlJobLockMapper xxlJobLockMapper; + @Resource + private JavaMailSender mailSender; + /*@Resource + private DataSource dataSource;*/ + @Resource + private PlatformTransactionManager transactionManager; + @Resource + private JobAlarmer jobAlarmer; + @Resource + private JobTrigger jobTrigger; + @Resource + private JobCompleter jobCompleter; + + + public String getI18n() { + if (!Arrays.asList("zh_CN", "zh_TC", "en").contains(i18n)) { + return "zh_CN"; + } + return i18n; + } + + public String getAccessToken() { + return accessToken; + } + + public int getTimeout() { + return timeout; + } + + public String getEmailFrom() { + return emailFrom; + } + + public int getTriggerPoolFastMax() { + if (triggerPoolFastMax < 200) { + return 200; + } + return triggerPoolFastMax; + } + + public int getTriggerPoolSlowMax() { + if (triggerPoolSlowMax < 100) { + return 100; + } + return triggerPoolSlowMax; + } + + public int getScheduleBatchSize() { + if (!(scheduleBatchSize >=50 && scheduleBatchSize <= 500)) { + return 100; + } + return scheduleBatchSize; + } + + public int getLogretentiondays() { + if (logretentiondays < 3) { + return -1; // Limit greater than or equal to 3, otherwise close + } + return logretentiondays; + } + + public XxlJobLogMapper getXxlJobLogMapper() { + return xxlJobLogMapper; + } + + public XxlJobInfoMapper getXxlJobInfoMapper() { + return xxlJobInfoMapper; + } + + public XxlJobRegistryMapper getXxlJobRegistryMapper() { + return xxlJobRegistryMapper; + } + + public XxlJobGroupMapper getXxlJobGroupMapper() { + return xxlJobGroupMapper; + } + + public XxlJobLogReportMapper getXxlJobLogReportMapper() { + return xxlJobLogReportMapper; + } + + public XxlJobLockMapper getXxlJobLockMapper() { + return xxlJobLockMapper; + } + + public JavaMailSender getMailSender() { + return mailSender; + } + + /*public DataSource getDataSource() { + return dataSource; + }*/ + + public PlatformTransactionManager getTransactionManager() { + return transactionManager; + } + + public JobAlarmer getJobAlarmer() { + return jobAlarmer; + } + + public JobTrigger getJobTrigger() { + return jobTrigger; + } + + public JobCompleter getJobCompleter() { + return jobCompleter; + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/cron/CronExpression.java similarity index 81% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/cron/CronExpression.java index 42f9918e..65e5b607 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/cron/CronExpression.java @@ -1,22 +1,23 @@ +package com.xxl.job.admin.scheduler.cron; /* * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. - * - * Licensed 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 + * Copyright IBM Corp. 2024, 2025 + * + * Licensed 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. - * + * + * Borrowed from quartz v2.5.2 */ -package com.xxl.job.admin.core.cron; - import java.io.Serializable; import java.text.ParseException; import java.util.Calendar; @@ -25,145 +26,153 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TimeZone; import java.util.TreeSet; /** - * Provides a parser and evaluator for unix-like cron expressions. Cron + * Provides a parser and evaluator for unix-like cron expressions. Cron * expressions provide the ability to specify complex time combinations such as - * "At 8:00am every Monday through Friday" or "At 1:30am every - * last Friday of the month". - *

+ * "At 8:00am every Monday through Friday" or "At 1:30am every + * last Friday of the month". + *

* Cron expressions are comprised of 6 required fields and one optional field * separated by white space. The fields respectively are described as follows: - * - * + *

+ *
+ * * - * - * - * - * - * + * + * + * + * + * * * - * - * - * + * + * + * + * + * * * - * - * - * + * + * + * + * + * * * - * - * - * + * + * + * + * + * * * - * - * - * + * + * + * + * + * * * - * - * - * + * + * + * + * + * * * - * - * - * + * + * + * + * + * * * - * - * - * + * + * + * + * + * * *
Examples of cron expressions and their meanings.
Field Name Allowed Values Allowed Special CharactersField Name Allowed Values Allowed Special Characters
Seconds  - * 0-59  - * , - * /Seconds 0-59 , - * /
Minutes  - * 0-59  - * , - * /Minutes 0-59 , - * /
Hours  - * 0-23  - * , - * /Hours 0-23 , - * /
Day-of-month  - * 1-31  - * , - * ? / L WDay-of-month 1-31 , - * ? / L W
Month  - * 0-11 or JAN-DEC  - * , - * /Month 1-12 or JAN-DEC , - * /
Day-of-Week  - * 1-7 or SUN-SAT  - * , - * ? / L #Day-of-Week 1-7 or SUN-SAT , - * ? / L #
Year (Optional)  - * empty, 1970-2199  - * , - * /Year (Optional) empty, 1970-2199 , - * /
- *

- * The '*' character is used to specify all values. For example, "*" + *

+ * The '*' character is used to specify all values. For example, "*" * in the minute field means "every minute". - *

+ *

+ *

* The '?' character is allowed for the day-of-month and day-of-week fields. It * is used to specify 'no specific value'. This is useful when you need to * specify something in one of the two fields, but not the other. - *

+ *

* The '-' character is used to specify ranges For example "10-12" in * the hour field means "the hours 10, 11 and 12". - *

+ *

* The ',' character is used to specify additional values. For example * "MON,WED,FRI" in the day-of-week field means "the days Monday, * Wednesday, and Friday". - *

+ *

+ *

* The '/' character is used to specify increments. For example "0/15" - * in the seconds field means "the seconds 0, 15, 30, and 45". And + * in the seconds field means "the seconds 0, 15, 30, and 45". And * "5/15" in the seconds field means "the seconds 5, 20, 35, and * 50". Specifying '*' before the '/' is equivalent to specifying 0 is * the value to start with. Essentially, for each field in the expression, there - * is a set of numbers that can be turned on or off. For seconds and minutes, + * is a set of numbers that can be turned on or off. For seconds and minutes, * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn * on every "nth" value in the given set. Thus "7/6" in the - * month field only turns on month "7", it does NOT mean every 6th - * month, please note that subtlety. - *

+ * month field only turns on month "7", it does NOT mean every 6th + * month, please note that subtlety. + *

+ *

* The 'L' character is allowed for the day-of-month and day-of-week fields. - * This character is short-hand for "last", but it has different - * meaning in each of the two fields. For example, the value "L" in - * the day-of-month field means "the last day of the month" - day 31 - * for January, day 28 for February on non-leap years. If used in the - * day-of-week field by itself, it simply means "7" or + * This character is short-hand for "last", but it has different + * meaning in each of the two fields. For example, the value "L" in + * the day-of-month field means "the last day of the month" - day 31 + * for January, day 28 for February on non-leap years. If used in the + * day-of-week field by itself, it simply means "7" or * "SAT". But if used in the day-of-week field after another value, it * means "the last xxx day of the month" - for example "6L" - * means "the last friday of the month". You can also specify an offset - * from the last day of the month, such as "L-3" which would mean the third-to-last - * day of the calendar month. When using the 'L' option, it is important not to + * means "the last friday of the month". You can also specify an offset + * from the last day of the month, such as "L-3" which would mean the third-to-last + * day of the calendar month. When using the 'L' option, it is important not to * specify lists, or ranges of values, as you'll get confusing/unexpected results. - *

- * The 'W' character is allowed for the day-of-month field. This character - * is used to specify the weekday (Monday-Friday) nearest the given day. As an - * example, if you were to specify "15W" as the value for the + *

+ *

+ * The 'W' character is allowed for the day-of-month field. This character + * is used to specify the weekday (Monday-Friday) nearest the given day. As an + * example, if you were to specify "15W" as the value for the * day-of-month field, the meaning is: "the nearest weekday to the 15th of - * the month". So if the 15th is a Saturday, the trigger will fire on + * the month". So if the 15th is a Saturday, the trigger will fire on * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the - * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. + * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. * However if you specify "1W" as the value for day-of-month, and the - * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not - * 'jump' over the boundary of a month's days. The 'W' character can only be + * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not + * 'jump' over the boundary of a month's days. The 'W' character can only be * specified when the day-of-month is a single day, not a range or list of days. - *

- * The 'L' and 'W' characters can also be combined for the day-of-month - * expression to yield 'LW', which translates to "last weekday of the + *

+ *

+ * The 'L' and 'W' characters can also be combined for the day-of-month + * expression to yield 'LW', which translates to "last weekday of the * month". - *

+ *

+ *

* The '#' character is allowed for the day-of-week field. This character is - * used to specify "the nth" XXX day of the month. For example, the - * value of "6#3" in the day-of-week field means the third Friday of - * the month (day 6 = Friday and "#3" = the 3rd one in the month). - * Other examples: "2#1" = the first Monday of the month and + * used to specify "the nth" XXX day of the month. For example, the + * value of "6#3" in the day-of-week field means the third Friday of + * the month (day 6 = Friday and "#3" = the 3rd one in the month). + * Other examples: "2#1" = the first Monday of the month and * "4#5" = the fifth Wednesday of the month. Note that if you specify * "#5" and there is not 5 of the given day-of-week in the month, then * no firing will occur that month. If the '#' character is used, there can - * only be one expression in the day-of-week field ("3#1,6#3" is + * only be one expression in the day-of-week field ("3#1,6#3" is * not valid, since there are two expressions). - *

+ *

* - *

+ *

* The legal characters and the names of months and days of the week are not * case sensitive. - * + * *

* NOTES: + *

*
    *
  • Support for specifying both a day-of-week and a day-of-month value is * not complete (you'll need to use the '?' character in one of these fields). *
  • - *
  • Overflowing ranges is supported - that is, having a larger number on - * the left hand side than the right. You might do 22-2 to catch 10 o'clock - * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is - * very important to note that overuse of overflowing ranges creates ranges - * that don't make sense and no effort has been made to determine which - * interpretation CronExpression chooses. An example would be + *
  • Overflowing ranges is supported - that is, having a larger number on + * the left hand side than the right. You might do 22-2 to catch 10 o'clock + * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is + * very important to note that overuse of overflowing ranges creates ranges + * that don't make sense and no effort has been made to determine which + * interpretation CronExpression chooses. An example would be * "0 0 14-6 ? * FRI-MON".
  • *
- *

- * - * + * + * * @author Sharada Jambula, James House * @author Contributions from Mads Henderson * @author Refactoring from CronTrigger to CronExpression by Aaron Craven - * - * Borrowed from quartz v2.3.1 - * */ public final class CronExpression implements Serializable, Cloneable { private static final long serialVersionUID = 12423409423L; - + protected static final int SECOND = 0; protected static final int MINUTE = 1; protected static final int HOUR = 2; @@ -212,11 +218,14 @@ public final class CronExpression implements Serializable, Cloneable { protected static final int YEAR = 6; protected static final int ALL_SPEC_INT = 99; // '*' protected static final int NO_SPEC_INT = 98; // '?' + protected static final int MAX_LAST_DAY_OFFSET = 30; + protected static final int LAST_DAY_OFFSET_START = 32; // "L-30" + protected static final int LAST_DAY_OFFSET_END = LAST_DAY_OFFSET_START + MAX_LAST_DAY_OFFSET; // 'L' protected static final Integer ALL_SPEC = ALL_SPEC_INT; protected static final Integer NO_SPEC = NO_SPEC_INT; - - protected static final Map monthMap = new HashMap(20); - protected static final Map dayMap = new HashMap(60); + + protected static final Map monthMap = new HashMap<>(20); + protected static final Map dayMap = new HashMap<>(60); static { monthMap.put("JAN", 0); monthMap.put("FEB", 1); @@ -246,43 +255,41 @@ public final class CronExpression implements Serializable, Cloneable { protected transient TreeSet minutes; protected transient TreeSet hours; protected transient TreeSet daysOfMonth; + protected transient TreeSet nearestWeekdays; protected transient TreeSet months; protected transient TreeSet daysOfWeek; protected transient TreeSet years; - protected transient boolean lastdayOfWeek = false; - protected transient int nthdayOfWeek = 0; - protected transient boolean lastdayOfMonth = false; - protected transient boolean nearestWeekday = false; - protected transient int lastdayOffset = 0; + protected transient boolean lastDayOfWeek = false; + protected transient int nthDayOfWeek = 0; protected transient boolean expressionParsed = false; - + public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100; /** - * Constructs a new CronExpression based on the specified + * Constructs a new CronExpression based on the specified * parameter. - * + * * @param cronExpression String representation of the cron expression the * new object should represent * @throws java.text.ParseException - * if the string expression cannot be parsed into a valid + * if the string expression cannot be parsed into a valid * CronExpression */ public CronExpression(String cronExpression) throws ParseException { if (cronExpression == null) { throw new IllegalArgumentException("cronExpression cannot be null"); } - + this.cronExpression = cronExpression.toUpperCase(Locale.US); - + buildExpression(this.cronExpression); } - + /** * Constructs a new {@code CronExpression} as a copy of an existing * instance. - * + * * @param expression * The existing cron expression to be copied */ @@ -296,7 +303,7 @@ public final class CronExpression implements Serializable, Cloneable { try { buildExpression(cronExpression); } catch (ParseException ex) { - throw new AssertionError(); + throw new AssertionError("Could not parse expression!", ex); } if (expression.getTimeZone() != null) { setTimeZone((TimeZone) expression.getTimeZone().clone()); @@ -307,7 +314,7 @@ public final class CronExpression implements Serializable, Cloneable { * Indicates whether the given date satisfies the cron expression. Note that * milliseconds are ignored, so two Dates falling on different milliseconds * of the same second will always have the same result here. - * + * * @param date the date to evaluate * @return a boolean indicating whether the given date satisfies the cron * expression @@ -317,18 +324,18 @@ public final class CronExpression implements Serializable, Cloneable { testDateCal.setTime(date); testDateCal.set(Calendar.MILLISECOND, 0); Date originalDate = testDateCal.getTime(); - + testDateCal.add(Calendar.SECOND, -1); - + Date timeAfter = getTimeAfter(testDateCal.getTime()); return ((timeAfter != null) && (timeAfter.equals(originalDate))); } - + /** * Returns the next date/time after the given date/time which * satisfies the cron expression. - * + * * @param date the date/time at which to begin the search for the next valid * date/time * @return the next valid date/time @@ -336,48 +343,48 @@ public final class CronExpression implements Serializable, Cloneable { public Date getNextValidTimeAfter(Date date) { return getTimeAfter(date); } - + /** * Returns the next date/time after the given date/time which does * not satisfy the expression - * - * @param date the date/time at which to begin the search for the next + * + * @param date the date/time at which to begin the search for the next * invalid date/time * @return the next valid date/time */ public Date getNextInvalidTimeAfter(Date date) { long difference = 1000; - + //move back to the nearest second so differences will be accurate Calendar adjustCal = Calendar.getInstance(getTimeZone()); adjustCal.setTime(date); adjustCal.set(Calendar.MILLISECOND, 0); Date lastDate = adjustCal.getTime(); - + Date newDate; - + //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution. - + //keep getting the next included time until it's farther than one second // apart. At that point, lastDate is the last valid fire time. We return // the second immediately following it. while (difference == 1000) { newDate = getTimeAfter(lastDate); - if(newDate == null) { + if(newDate == null) break; - } + difference = newDate.getTime() - lastDate.getTime(); - + if (difference == 1000) { lastDate = newDate; } } - + return new Date(lastDate.getTime() + 1000); } - + /** - * Returns the time zone for which this CronExpression + * Returns the time zone for which this CronExpression * will be resolved. */ public TimeZone getTimeZone() { @@ -389,16 +396,16 @@ public final class CronExpression implements Serializable, Cloneable { } /** - * Sets the time zone for which this CronExpression + * Sets the time zone for which this CronExpression * will be resolved. */ public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; } - + /** * Returns the string representation of the CronExpression - * + * * @return a string representation of the CronExpression */ @Override @@ -407,30 +414,30 @@ public final class CronExpression implements Serializable, Cloneable { } /** - * Indicates whether the specified cron expression can be parsed into a + * Indicates whether the specified cron expression can be parsed into a * valid cron expression - * + * * @param cronExpression the expression to evaluate * @return a boolean indicating whether the given expression is a valid cron * expression */ public static boolean isValidExpression(String cronExpression) { - + try { new CronExpression(cronExpression); } catch (ParseException pe) { return false; } - + return true; } public static void validateExpression(String cronExpression) throws ParseException { - + new CronExpression(cronExpression); } - - + + //////////////////////////////////////////////////////////////////////////// // // Expression Parsing Functions @@ -443,25 +450,28 @@ public final class CronExpression implements Serializable, Cloneable { try { if (seconds == null) { - seconds = new TreeSet(); + seconds = new TreeSet<>(); } if (minutes == null) { - minutes = new TreeSet(); + minutes = new TreeSet<>(); } if (hours == null) { - hours = new TreeSet(); + hours = new TreeSet<>(); } if (daysOfMonth == null) { - daysOfMonth = new TreeSet(); + daysOfMonth = new TreeSet<>(); + } + if (nearestWeekdays == null) { + nearestWeekdays = new TreeSet<>(); } if (months == null) { - months = new TreeSet(); + months = new TreeSet<>(); } if (daysOfWeek == null) { - daysOfWeek = new TreeSet(); + daysOfWeek = new TreeSet<>(); } if (years == null) { - years = new TreeSet(); + years = new TreeSet<>(); } int exprOn = SECOND; @@ -469,13 +479,13 @@ public final class CronExpression implements Serializable, Cloneable { StringTokenizer exprsTok = new StringTokenizer(expression, " \t", false); + if(exprsTok.countTokens() > 7) { + throw new ParseException("Invalid expression has too many terms: " + expression, -1); + } + while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { String expr = exprsTok.nextToken().trim(); - // throw an exception if L is used with other days of the month - if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { - throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1); - } // throw an exception if L is used with other days of the week if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1); @@ -483,7 +493,7 @@ public final class CronExpression implements Serializable, Cloneable { if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) { throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1); } - + StringTokenizer vTok = new StringTokenizer(expr, ","); while (vTok.hasMoreTokens()) { String v = vTok.nextToken(); @@ -495,7 +505,7 @@ public final class CronExpression implements Serializable, Cloneable { if (exprOn <= DAY_OF_WEEK) { throw new ParseException("Unexpected end of expression.", - expression.length()); + expression.length()); } if (exprOn <= YEAR) { @@ -519,12 +529,12 @@ public final class CronExpression implements Serializable, Cloneable { throw pe; } catch (Exception e) { throw new ParseException("Illegal cron expression format (" - + e.toString() + ")", 0); + + e + ")", 0); } } protected int storeExpressionVals(int pos, String s, int type) - throws ParseException { + throws ParseException { int incr = 0; int i = skipWhiteSpace(pos, s); @@ -556,7 +566,7 @@ public final class CronExpression implements Serializable, Cloneable { sval = getDayOfWeekNumber(sub); if (sval < 0) { throw new ParseException("Invalid Day-of-Week value: '" - + sub + "'", i); + + sub + "'", i); } if (s.length() > i + 3) { c = s.charAt(i + 3); @@ -567,13 +577,13 @@ public final class CronExpression implements Serializable, Cloneable { if (eval < 0) { throw new ParseException( "Invalid Day-of-Week value: '" + sub - + "'", i); + + "'", i); } } else if (c == '#') { try { i += 4; - nthdayOfWeek = Integer.parseInt(s.substring(i)); - if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { + nthDayOfWeek = Integer.parseInt(s.substring(i)); + if (nthDayOfWeek < 1 || nthDayOfWeek > 5) { throw new Exception(); } } catch (Exception e) { @@ -582,7 +592,7 @@ public final class CronExpression implements Serializable, Cloneable { i); } } else if (c == 'L') { - lastdayOfWeek = true; + lastDayOfWeek = true; i++; } } @@ -601,22 +611,21 @@ public final class CronExpression implements Serializable, Cloneable { if (c == '?') { i++; - if ((i + 1) < s.length() + if ((i + 1) < s.length() && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { throw new ParseException("Illegal character after '?': " - + s.charAt(i), i); + + s.charAt(i), i); } if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) { throw new ParseException( - "'?' can only be specified for Day-of-Month or Day-of-Week.", - i); + "'?' can only be specified for Day-of-Month or Day-of-Week.", + i); } - if (type == DAY_OF_WEEK && !lastdayOfMonth) { - int val = daysOfMonth.last(); - if (val == NO_SPEC_INT) { + if (type == DAY_OF_WEEK) { + if (!daysOfMonth.isEmpty() && daysOfMonth.last() == NO_SPEC_INT) { throw new ParseException( - "'?' can only be specified for Day-of-Month -OR- Day-of-Week.", - i); + "'?' can only be specified for Day-of-Month -OR- Day-of-Week.", + i); } } @@ -630,7 +639,7 @@ public final class CronExpression implements Serializable, Cloneable { return i + 1; } else if (c == '/' && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s - .charAt(i + 1) == '\t')) { + .charAt(i + 1) == '\t')) { throw new ParseException("'/' must be followed by an integer.", i); } else if (c == '*') { i++; @@ -656,29 +665,39 @@ public final class CronExpression implements Serializable, Cloneable { addToSet(ALL_SPEC_INT, -1, incr, type); return i; } else if (c == 'L') { + + if(type < DAY_OF_MONTH) + throw new ParseException("'L' not expected in seconds, minutes or hours fields.", i); + i++; - if (type == DAY_OF_MONTH) { - lastdayOfMonth = true; - } if (type == DAY_OF_WEEK) { addToSet(7, 7, 0, type); } - if(type == DAY_OF_MONTH && s.length() > i) { - c = s.charAt(i); - if(c == '-') { - ValueSet vs = getValue(0, s, i+1); - lastdayOffset = vs.value; - if(lastdayOffset > 30) { - throw new ParseException("Offset from last day must be <= 30", i + 1); - } - i = vs.pos; - } - if(s.length() > i) { + if (type == DAY_OF_MONTH) { + int dom = LAST_DAY_OFFSET_END; + boolean nearestWeekday = false; + if (s.length() > i) { c = s.charAt(i); - if(c == 'W') { - nearestWeekday = true; - i++; + if (c == '-') { + ValueSet vs = getValue(0, s, i + 1); + int offset = vs.value; + if (offset > MAX_LAST_DAY_OFFSET) + throw new ParseException("Offset from last day must be <= " + MAX_LAST_DAY_OFFSET, i + 1); + dom -= offset; + i = vs.pos; } + if (s.length() > i) { + c = s.charAt(i); + if (c == 'W') { + nearestWeekday = true; + i++; + } + } + } + if (nearestWeekday) { + nearestWeekdays.add(dom); + } else { + daysOfMonth.add(dom); } } return i; @@ -706,21 +725,21 @@ public final class CronExpression implements Serializable, Cloneable { private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException { if (incr > 59 && (type == SECOND || type == MINUTE)) { - throw new ParseException("Increment > 60 : " + incr, idxPos); + throw new ParseException("Increment >= 60 : " + incr, idxPos); } else if (incr > 23 && (type == HOUR)) { - throw new ParseException("Increment > 24 : " + incr, idxPos); + throw new ParseException("Increment >= 24 : " + incr, idxPos); } else if (incr > 31 && (type == DAY_OF_MONTH)) { - throw new ParseException("Increment > 31 : " + incr, idxPos); + throw new ParseException("Increment >= 31 : " + incr, idxPos); } else if (incr > 7 && (type == DAY_OF_WEEK)) { - throw new ParseException("Increment > 7 : " + incr, idxPos); + throw new ParseException("Increment >= 7 : " + incr, idxPos); } else if (incr > 12 && (type == MONTH)) { - throw new ParseException("Increment > 12 : " + incr, idxPos); + throw new ParseException("Increment >= 12 : " + incr, idxPos); } } protected int checkNext(int pos, String s, int val, int type) - throws ParseException { - + throws ParseException { + int end = -1; int i = pos; @@ -733,10 +752,9 @@ public final class CronExpression implements Serializable, Cloneable { if (c == 'L') { if (type == DAY_OF_WEEK) { - if(val < 1 || val > 7) { + if(val < 1 || val > 7) throw new ParseException("Day-of-Week values must be between 1 and 7", -1); - } - lastdayOfWeek = true; + lastDayOfWeek = true; } else { throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i); } @@ -745,18 +763,14 @@ public final class CronExpression implements Serializable, Cloneable { i++; return i; } - + if (c == 'W') { - if (type == DAY_OF_MONTH) { - nearestWeekday = true; - } else { + if (type != DAY_OF_MONTH) { throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); } - if(val > 31) { + if(val > 31) throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i); - } - TreeSet set = getSet(type); - set.add(val); + nearestWeekdays.add(val); i++; return i; } @@ -767,8 +781,8 @@ public final class CronExpression implements Serializable, Cloneable { } i++; try { - nthdayOfWeek = Integer.parseInt(s.substring(i)); - if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { + nthDayOfWeek = Integer.parseInt(s.substring(i)); + if (nthDayOfWeek < 1 || nthDayOfWeek > 5) { throw new Exception(); } } catch (Exception e) { @@ -860,7 +874,7 @@ public final class CronExpression implements Serializable, Cloneable { public String getCronExpression() { return cronExpression; } - + public String getExpressionSummary() { StringBuilder buf = new StringBuilder(); @@ -876,23 +890,20 @@ public final class CronExpression implements Serializable, Cloneable { buf.append("daysOfMonth: "); buf.append(getExpressionSetSummary(daysOfMonth)); buf.append("\n"); + buf.append("nearestWeekdays: "); + buf.append(getExpressionSetSummary(nearestWeekdays)); + buf.append("\n"); buf.append("months: "); buf.append(getExpressionSetSummary(months)); buf.append("\n"); buf.append("daysOfWeek: "); buf.append(getExpressionSetSummary(daysOfWeek)); buf.append("\n"); - buf.append("lastdayOfWeek: "); - buf.append(lastdayOfWeek); - buf.append("\n"); - buf.append("nearestWeekday: "); - buf.append(nearestWeekday); + buf.append("lastDayOfWeek: "); + buf.append(lastDayOfWeek); buf.append("\n"); buf.append("NthDayOfWeek: "); - buf.append(nthdayOfWeek); - buf.append("\n"); - buf.append("lastdayOfMonth: "); - buf.append(lastdayOfMonth); + buf.append(nthDayOfWeek); buf.append("\n"); buf.append("years: "); buf.append(getExpressionSetSummary(years)); @@ -968,8 +979,8 @@ public final class CronExpression implements Serializable, Cloneable { } protected void addToSet(int val, int end, int incr, int type) - throws ParseException { - + throws ParseException { + TreeSet set = getSet(type); if (type == SECOND || type == MINUTE) { @@ -984,7 +995,7 @@ public final class CronExpression implements Serializable, Cloneable { "Hour values must be between 0 and 23", -1); } } else if (type == DAY_OF_MONTH) { - if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) + if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) { throw new ParseException( "Day of month values must be between 1 and 31", -1); @@ -1008,7 +1019,7 @@ public final class CronExpression implements Serializable, Cloneable { } else { set.add(NO_SPEC); } - + return; } @@ -1064,20 +1075,20 @@ public final class CronExpression implements Serializable, Cloneable { } } - // if the end of the range is before the start, then we need to overflow into - // the next day, month etc. This is done by adding the maximum amount for that + // if the end of the range is before the start, then we need to overflow into + // the next day, month etc. This is done by adding the maximum amount for that // type, and using modulus max to determine the value being added. int max = -1; if (stopAt < startAt) { switch (type) { - case SECOND : max = 60; break; - case MINUTE : max = 60; break; - case HOUR : max = 24; break; - case MONTH : max = 12; break; - case DAY_OF_WEEK : max = 7; break; - case DAY_OF_MONTH : max = 31; break; - case YEAR : throw new IllegalArgumentException("Start year must be less than stop year"); - default : throw new IllegalArgumentException("Unexpected type encountered"); + case SECOND : max = 60; break; + case MINUTE : max = 60; break; + case HOUR : max = 24; break; + case MONTH : max = 12; break; + case DAY_OF_WEEK : max = 7; break; + case DAY_OF_MONTH : max = 31; break; + case YEAR : throw new IllegalArgumentException("Start year must be less than stop year"); + default : throw new IllegalArgumentException("Unexpected type encountered"); } stopAt += max; } @@ -1133,7 +1144,7 @@ public final class CronExpression implements Serializable, Cloneable { c = s.charAt(i); } ValueSet val = new ValueSet(); - + val.pos = (i < s.length()) ? i : i + 1; val.value = Integer.parseInt(s1.toString()); return val; @@ -1174,7 +1185,7 @@ public final class CronExpression implements Serializable, Cloneable { public Date getTimeAfter(Date afterTime) { // Computation is based on Gregorian year only. - Calendar cl = new java.util.GregorianCalendar(getTimeZone()); + Calendar cl = new java.util.GregorianCalendar(getTimeZone()); // move ahead one second, since we're computing the time *after* the // given time @@ -1200,7 +1211,7 @@ public final class CronExpression implements Serializable, Cloneable { // get second................................................. st = seconds.tailSet(sec); - if (st != null && st.size() != 0) { + if (st != null && !st.isEmpty()) { sec = st.first(); } else { sec = seconds.first(); @@ -1215,7 +1226,7 @@ public final class CronExpression implements Serializable, Cloneable { // get minute................................................. st = minutes.tailSet(min); - if (st != null && st.size() != 0) { + if (st != null && !st.isEmpty()) { t = min; min = st.first(); } else { @@ -1236,7 +1247,7 @@ public final class CronExpression implements Serializable, Cloneable { // get hour................................................... st = hours.tailSet(hr); - if (st != null && st.size() != 0) { + if (st != null && !st.isEmpty()) { t = hr; hr = st.first(); } else { @@ -1258,66 +1269,17 @@ public final class CronExpression implements Serializable, Cloneable { // 1-based t = -1; int tmon = mon; - + // get day................................................... boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule - st = daysOfMonth.tailSet(day); - if (lastdayOfMonth) { - if(!nearestWeekday) { - t = day; - day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - day -= lastdayOffset; - if(t > day) { - mon++; - if(mon > 12) { - mon = 1; - tmon = 3333; // ensure test of mon != tmon further below fails - cl.add(Calendar.YEAR, 1); - } - day = 1; - } - } else { - t = day; - day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - day -= lastdayOffset; - - java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone()); - tcal.set(Calendar.SECOND, 0); - tcal.set(Calendar.MINUTE, 0); - tcal.set(Calendar.HOUR_OF_DAY, 0); - tcal.set(Calendar.DAY_OF_MONTH, day); - tcal.set(Calendar.MONTH, mon - 1); - tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); - - int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - int dow = tcal.get(Calendar.DAY_OF_WEEK); - - if(dow == Calendar.SATURDAY && day == 1) { - day += 2; - } else if(dow == Calendar.SATURDAY) { - day -= 1; - } else if(dow == Calendar.SUNDAY && day == ldom) { - day -= 2; - } else if(dow == Calendar.SUNDAY) { - day += 1; - } - - tcal.set(Calendar.SECOND, sec); - tcal.set(Calendar.MINUTE, min); - tcal.set(Calendar.HOUR_OF_DAY, hr); - tcal.set(Calendar.DAY_OF_MONTH, day); - tcal.set(Calendar.MONTH, mon - 1); - Date nTime = tcal.getTime(); - if(nTime.before(afterTime)) { - day = 1; - mon++; - } - } - } else if(nearestWeekday) { - t = day; - day = daysOfMonth.first(); + Optional smallestDay = findSmallestDay(day, mon, cl.get(Calendar.YEAR), daysOfMonth); + Optional smallestDayForWeekday = findSmallestDay(day, mon, cl.get(Calendar.YEAR), nearestWeekdays); + t = day; + day = -1; + if (smallestDayForWeekday.isPresent()) { + day = smallestDayForWeekday.get(); java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone()); tcal.set(Calendar.SECOND, 0); @@ -1326,7 +1288,7 @@ public final class CronExpression implements Serializable, Cloneable { tcal.set(Calendar.DAY_OF_MONTH, day); tcal.set(Calendar.MONTH, mon - 1); tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); - + int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); int dow = tcal.get(Calendar.DAY_OF_WEEK); @@ -1334,13 +1296,13 @@ public final class CronExpression implements Serializable, Cloneable { day += 2; } else if(dow == Calendar.SATURDAY) { day -= 1; - } else if(dow == Calendar.SUNDAY && day == ldom) { + } else if(dow == Calendar.SUNDAY && day == ldom) { day -= 2; - } else if(dow == Calendar.SUNDAY) { + } else if(dow == Calendar.SUNDAY) { day += 1; } - - + + tcal.set(Calendar.SECOND, sec); tcal.set(Calendar.MINUTE, min); tcal.set(Calendar.HOUR_OF_DAY, hr); @@ -1348,24 +1310,23 @@ public final class CronExpression implements Serializable, Cloneable { tcal.set(Calendar.MONTH, mon - 1); Date nTime = tcal.getTime(); if(nTime.before(afterTime)) { - day = daysOfMonth.first(); - mon++; + day = -1; } - } else if (st != null && st.size() != 0) { - t = day; - day = st.first(); - // make sure we don't over-run a short month, such as february - int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); - if (day > lastDay) { - day = daysOfMonth.first(); - mon++; + } + + boolean needAdvance = false; + if (smallestDay.isPresent()) { + if (day == -1 || smallestDay.get() < day) { + day = smallestDay.get(); + needAdvance = true; } - } else { - day = daysOfMonth.first(); + } else if (day == -1) { + day = 1; mon++; + needAdvance = true; } - - if (day != t || mon != tmon) { + + if (needAdvance && (day != t || mon != tmon)) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); @@ -1375,8 +1336,9 @@ public final class CronExpression implements Serializable, Cloneable { // are 1-based continue; } + } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule - if (lastdayOfWeek) { // are we looking for the last XXX day of + if (lastDayOfWeek) { // are we looking for the last XXX day of // the month? int dow = daysOfWeek.first(); // desired // d-o-w @@ -1419,7 +1381,7 @@ public final class CronExpression implements Serializable, Cloneable { continue; } - } else if (nthdayOfWeek != 0) { + } else if (nthDayOfWeek != 0) { // are we looking for the Nth XXX day in the month? int dow = daysOfWeek.first(); // desired // d-o-w @@ -1431,10 +1393,7 @@ public final class CronExpression implements Serializable, Cloneable { daysToAdd = dow + (7 - cDow); } - boolean dayShifted = false; - if (daysToAdd > 0) { - dayShifted = true; - } + boolean dayShifted = daysToAdd > 0; day += daysToAdd; int weekOfMonth = day / 7; @@ -1442,11 +1401,11 @@ public final class CronExpression implements Serializable, Cloneable { weekOfMonth++; } - daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; + daysToAdd = (nthDayOfWeek - weekOfMonth) * 7; day += daysToAdd; if (daysToAdd < 0 || day > getLastDayOfMonth(mon, cl - .get(Calendar.YEAR))) { + .get(Calendar.YEAR))) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); @@ -1468,7 +1427,7 @@ public final class CronExpression implements Serializable, Cloneable { int dow = daysOfWeek.first(); // desired // d-o-w st = daysOfWeek.tailSet(cDow); - if (st != null && st.size() > 0) { + if (st != null && !st.isEmpty()) { dow = st.first(); } @@ -1491,7 +1450,7 @@ public final class CronExpression implements Serializable, Cloneable { cl.set(Calendar.MONTH, mon); // no '- 1' here because we are promoting the month continue; - } else if (daysToAdd > 0) { // are we swithing days? + } else if (daysToAdd > 0) { // are we switching days? cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); @@ -1522,7 +1481,7 @@ public final class CronExpression implements Serializable, Cloneable { // get month................................................... st = months.tailSet(mon); - if (st != null && st.size() != 0) { + if (st != null && !st.isEmpty()) { t = mon; mon = st.first(); } else { @@ -1549,7 +1508,7 @@ public final class CronExpression implements Serializable, Cloneable { // get year................................................... st = years.tailSet(year); - if (st != null && st.size() != 0) { + if (st != null && !st.isEmpty()) { t = year; year = st.first(); } else { @@ -1578,7 +1537,7 @@ public final class CronExpression implements Serializable, Cloneable { /** * Advance the calendar to the particular hour paying particular attention * to daylight saving problems. - * + * * @param cal the calendar to operate on * @param hour the hour to set */ @@ -1590,23 +1549,64 @@ public final class CronExpression implements Serializable, Cloneable { } /** - * NOT YET IMPLEMENTED: Returns the time before the given time + * Returns the time before the given time * that the CronExpression matches. - */ - public Date getTimeBefore(Date endTime) { - // FUTURE_TODO: implement QUARTZ-423 - return null; + * + * @param endTime a time for which the previous + * matching time is returned + * @return the previous matching time before the given end time, + * or null if there are no previous matching times + */ + public Date getTimeBefore(Date endTime) { + // the current implementation is not a direct calculation, but rather + // uses getTimeAfter with a binary search to find the previous match time + long end = endTime.getTime(); + long min = 0; // the epoch date is the minimum supported by this class + long max = end; + // check if it's satisfiable at all + Date date = new Date(min); + Date after = getTimeAfter(date); + if (after == null || after.getTime() >= end) + return null; // there are no after-times before end + // from this point forward min's time-after is always less than end, + // and max's time-after is always equal to or greater than end + // so we just need to shrink the interval until they meet. + // optimization - perform inverse binary search to find a tighter lower bound + long interval = 60 * 60 * 1000; // start with a reasonable interval + while (interval < max) { + date.setTime(max - interval); + after = getTimeAfter(date); + if (after != null && after.getTime() < max) { + min = date.getTime(); // found a closer min + break; + } + interval *= 2; + } + // perform a regular binary search to find the earliest moment + // whose time-after is equal to or greater than the end time - + // this moment is the previous match time itself + while (max - min > 1000) { // we can stop at 1 second resolution + long mid = (min + max) >>> 1; + date.setTime(mid); + after = getTimeAfter(date); + if (after != null && after.getTime() < end) + min = mid; + else + max = mid; + } + date.setTime(max - max % 1000); // round to second + return date; } /** - * NOT YET IMPLEMENTED: Returns the final time that the + * NOT YET IMPLEMENTED: Returns the final time that the * CronExpression will match. */ public Date getFinalFireTime() { // FUTURE_TODO: implement QUARTZ-423 return null; } - + protected boolean isLeapYear(int year) { return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); } @@ -1643,18 +1643,43 @@ public final class CronExpression implements Serializable, Cloneable { + monthNum); } } - + + + private Optional findSmallestDay(int day, int mon, int year, TreeSet set) { + if (set.isEmpty()) { + return Optional.empty(); + } + + final int lastDay = getLastDayOfMonth(mon, year); + // For "L", "L-1", etc. + final int smallestDay = Optional.ofNullable(set.ceiling(LAST_DAY_OFFSET_END - (lastDay - day))) + .map(d -> d - LAST_DAY_OFFSET_START + 1) + .orElse(Integer.MAX_VALUE); + + // For "1", "2", etc. + SortedSet st = set.subSet(day, LAST_DAY_OFFSET_START); + // make sure we don't over-run a short month, such as february + if (!st.isEmpty() && st.first() < smallestDay && st.first() <= lastDay) { + return Optional.of(st.first()); + } + + if (smallestDay == Integer.MAX_VALUE) { + return Optional.empty(); + } else { + return Optional.of(smallestDay + lastDay - LAST_DAY_OFFSET_START + 1); + } + } private void readObject(java.io.ObjectInputStream stream) - throws java.io.IOException, ClassNotFoundException { - + throws java.io.IOException, ClassNotFoundException { + stream.defaultReadObject(); try { buildExpression(cronExpression); } catch (Exception ignore) { } // never happens - } - + } + @Override @Deprecated public Object clone() { @@ -1666,4 +1691,4 @@ class ValueSet { public int value; public int pos; -} +} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/exception/XxlJobException.java similarity index 82% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/exception/XxlJobException.java index faa6063c..16fc4474 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/exception/XxlJobException.java @@ -1,4 +1,4 @@ -package com.xxl.job.admin.core.exception; +package com.xxl.job.admin.scheduler.exception; /** * @author xuxueli 2019-05-04 23:19:29 diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/MisfireHandler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/MisfireHandler.java new file mode 100644 index 00000000..e3969364 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/MisfireHandler.java @@ -0,0 +1,17 @@ +package com.xxl.job.admin.scheduler.misfire; + +/** + * Misfire Handler + * + * @author xuxueli 2020-10-29 + */ +public abstract class MisfireHandler { + + /** + * misfire handle + * + * @param jobId jobId + */ + public abstract void handle(final int jobId); + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/MisfireStrategyEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/MisfireStrategyEnum.java new file mode 100644 index 00000000..bd31fb1c --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/MisfireStrategyEnum.java @@ -0,0 +1,54 @@ +package com.xxl.job.admin.scheduler.misfire; + +import com.xxl.job.admin.scheduler.misfire.strategy.MisfireDoNothing; +import com.xxl.job.admin.scheduler.misfire.strategy.MisfireFireOnceNow; +import com.xxl.job.admin.util.I18nUtil; + +/** + * @author xuxueli 2020-10-29 21:11:23 + */ +public enum MisfireStrategyEnum { + + /** + * do nothing + */ + DO_NOTHING(I18nUtil.getString("misfire_strategy_do_nothing"), new MisfireDoNothing()), + + /** + * fire once now + */ + FIRE_ONCE_NOW(I18nUtil.getString("misfire_strategy_fire_once_now"), new MisfireFireOnceNow()); + + private final String title; + private final MisfireHandler misfireHandler; + + MisfireStrategyEnum(String title, MisfireHandler misfireHandler) { + this.title = title; + this.misfireHandler = misfireHandler; + } + + public String getTitle() { + return title; + } + + public MisfireHandler getMisfireHandler() { + return misfireHandler; + } + + /** + * match misfire strategy + * + * @param name name of misfire strategy + * @param defaultItem default misfire strategy + * @return misfire strategy + */ + public static MisfireStrategyEnum match(String name, MisfireStrategyEnum defaultItem){ + for (MisfireStrategyEnum item: MisfireStrategyEnum.values()) { + if (item.name().equals(name)) { + return item; + } + } + return defaultItem; + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/strategy/MisfireDoNothing.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/strategy/MisfireDoNothing.java new file mode 100644 index 00000000..9f5844a6 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/strategy/MisfireDoNothing.java @@ -0,0 +1,15 @@ +package com.xxl.job.admin.scheduler.misfire.strategy; + +import com.xxl.job.admin.scheduler.misfire.MisfireHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MisfireDoNothing extends MisfireHandler { + private static final Logger logger = LoggerFactory.getLogger(MisfireDoNothing.class); + + @Override + public void handle(int jobId) { + logger.warn(">>>>>>>>>>> xxl-job, schedule MisfireDoNothing: jobId = " + jobId ); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/strategy/MisfireFireOnceNow.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/strategy/MisfireFireOnceNow.java new file mode 100644 index 00000000..479eef2a --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/misfire/strategy/MisfireFireOnceNow.java @@ -0,0 +1,19 @@ +package com.xxl.job.admin.scheduler.misfire.strategy; + +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.misfire.MisfireHandler; +import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MisfireFireOnceNow extends MisfireHandler { + protected static Logger logger = LoggerFactory.getLogger(MisfireFireOnceNow.class); + + @Override + public void handle(int jobId) { + // FIRE_ONCE_NOW 》 trigger + XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobId, TriggerTypeEnum.MISFIRE, -1, null, null, null); + logger.warn(">>>>>>>>>>> xxl-job, schedule MisfireFireOnceNow: jobId = " + jobId ); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/openapi/OpenApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/openapi/OpenApiController.java new file mode 100644 index 00000000..d6b3928f --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/openapi/OpenApiController.java @@ -0,0 +1,80 @@ +package com.xxl.job.admin.scheduler.openapi; + +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.core.constant.Const; +import com.xxl.job.core.openapi.AdminBiz; +import com.xxl.job.core.openapi.model.CallbackRequest; +import com.xxl.job.core.openapi.model.RegistryRequest; +import com.xxl.sso.core.annotation.XxlSso; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.json.GsonTool; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Created by xuxueli on 17/5/10. + */ +@Controller +public class OpenApiController { + + @Resource + private AdminBiz adminBiz; + + /** + * api + */ + @RequestMapping("/api/{uri}") + @ResponseBody + @XxlSso(login = false) + public Object api(HttpServletRequest request, + @PathVariable("uri") String uri, + @RequestHeader(value = Const.XXL_JOB_ACCESS_TOKEN, required = false) String accesstoken, + @RequestBody(required = false) String requestBody) { + + // valid + if (!"POST".equalsIgnoreCase(request.getMethod())) { + return Response.ofFail("invalid request, HttpMethod not support."); + } + if (StringTool.isBlank(uri)) { + return Response.ofFail("invalid request, uri-mapping empty."); + } + if (StringTool.isBlank(requestBody)) { + return Response.ofFail("invalid request, requestBody empty."); + } + + // valid token + if (StringTool.isNotBlank(XxlJobAdminBootstrap.getInstance().getAccessToken()) + && !XxlJobAdminBootstrap.getInstance().getAccessToken().equals(accesstoken)) { + return Response.ofFail("The access token is wrong."); + } + + // dispatch request + try { + switch (uri) { + case "callback": { + List callbackParamList = GsonTool.fromJson(requestBody, List.class, CallbackRequest.class); + return adminBiz.callback(callbackParamList); + } + case "registry": { + RegistryRequest registryParam = GsonTool.fromJson(requestBody, RegistryRequest.class); + return adminBiz.registry(registryParam); + } + case "registryRemove": { + RegistryRequest registryParam = GsonTool.fromJson(requestBody, RegistryRequest.class); + return adminBiz.registryRemove(registryParam); + } + default: + return Response.ofFail("invalid request, uri-mapping("+ uri +") not found."); + } + } catch (Exception e) { + return Response.ofFail("openapi invoke error: " + e.getMessage()); + } + + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/ExecutorRouteStrategyEnum.java similarity index 90% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/ExecutorRouteStrategyEnum.java index 7fff93a9..808d01a2 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/ExecutorRouteStrategyEnum.java @@ -1,7 +1,7 @@ -package com.xxl.job.admin.core.route; +package com.xxl.job.admin.scheduler.route; -import com.xxl.job.admin.core.route.strategy.*; -import com.xxl.job.admin.core.util.I18nUtil; +import com.xxl.job.admin.scheduler.route.strategy.*; +import com.xxl.job.admin.util.I18nUtil; /** * Created by xuxueli on 17/3/10. @@ -34,6 +34,9 @@ public enum ExecutorRouteStrategyEnum { return router; } + /** + * match router + */ public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem){ if (name != null) { for (ExecutorRouteStrategyEnum item: ExecutorRouteStrategyEnum.values()) { diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/ExecutorRouter.java similarity index 54% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/ExecutorRouter.java index 5de9a1d0..b777c782 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/ExecutorRouter.java @@ -1,7 +1,7 @@ -package com.xxl.job.admin.core.route; +package com.xxl.job.admin.scheduler.route; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,9 +16,9 @@ public abstract class ExecutorRouter { /** * route address * - * @param addressList + * @param addressList executor address list * @return ReturnT.content=address */ - public abstract ReturnT route(TriggerParam triggerParam, List addressList); + public abstract Response route(TriggerRequest triggerParam, List addressList); } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteBusyover.java similarity index 51% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteBusyover.java index 868560fc..1e5e85b5 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteBusyover.java @@ -1,12 +1,12 @@ -package com.xxl.job.admin.core.route.strategy; +package com.xxl.job.admin.scheduler.route.strategy; -import com.xxl.job.admin.core.scheduler.XxlJobScheduler; -import com.xxl.job.admin.core.route.ExecutorRouter; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.core.biz.ExecutorBiz; -import com.xxl.job.core.biz.model.IdleBeatParam; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.route.ExecutorRouter; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.core.openapi.ExecutorBiz; +import com.xxl.job.core.openapi.model.IdleBeatRequest; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; import java.util.List; @@ -16,17 +16,17 @@ import java.util.List; public class ExecutorRouteBusyover extends ExecutorRouter { @Override - public ReturnT route(TriggerParam triggerParam, List addressList) { + public Response route(TriggerRequest triggerParam, List addressList) { StringBuffer idleBeatResultSB = new StringBuffer(); for (String address : addressList) { // beat - ReturnT idleBeatResult = null; + Response idleBeatResult = null; try { - ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address); - idleBeatResult = executorBiz.idleBeat(new IdleBeatParam(triggerParam.getJobId())); + ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(address); + idleBeatResult = executorBiz.idleBeat(new IdleBeatRequest(triggerParam.getJobId())); } catch (Exception e) { logger.error(e.getMessage(), e); - idleBeatResult = new ReturnT(ReturnT.FAIL_CODE, ""+e ); + idleBeatResult = Response.ofFail( ""+e ); } idleBeatResultSB.append( (idleBeatResultSB.length()>0)?"

":"") .append(I18nUtil.getString("jobconf_idleBeat") + ":") @@ -35,14 +35,14 @@ public class ExecutorRouteBusyover extends ExecutorRouter { .append("
msg:").append(idleBeatResult.getMsg()); // beat success - if (idleBeatResult.getCode() == ReturnT.SUCCESS_CODE) { + if (idleBeatResult.isSuccess()) { idleBeatResult.setMsg(idleBeatResultSB.toString()); - idleBeatResult.setContent(address); + idleBeatResult.setData(address); return idleBeatResult; } } - return new ReturnT(ReturnT.FAIL_CODE, idleBeatResultSB.toString()); + return Response.ofFail( idleBeatResultSB.toString()); } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteConsistentHash.java similarity index 62% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteConsistentHash.java index 41ac671c..18dc9345 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteConsistentHash.java @@ -1,30 +1,32 @@ -package com.xxl.job.admin.core.route.strategy; +package com.xxl.job.admin.scheduler.route.strategy; -import com.xxl.job.admin.core.route.ExecutorRouter; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; +import com.xxl.job.admin.scheduler.route.ExecutorRouter; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; -import java.util.SortedMap; +import java.util.Map; import java.util.TreeMap; /** * 分组下机器地址相同,不同JOB均匀散列在不同机器上,保证分组下机器分配JOB平均;且每个JOB固定调度其中一台机器; * a、virtual node:解决不均衡问题 * b、hash method replace hashCode:String的hashCode可能重复,需要进一步扩大hashCode的取值范围 + * * Created by xuxueli on 17/3/10. */ public class ExecutorRouteConsistentHash extends ExecutorRouter { - private static int VIRTUAL_NODE_NUM = 100; + private static final int VIRTUAL_NODE_NUM = 100; /** * get hash code on 2^32 ring (md5散列的方式计算hash值) - * @param key - * @return + * + * @param key key + * @return hash code */ private static long hash(String key) { @@ -37,11 +39,7 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter { } md5.reset(); byte[] keyBytes = null; - try { - keyBytes = key.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Unknown string :" + key, e); - } + keyBytes = key.getBytes(StandardCharsets.UTF_8); md5.update(keyBytes); byte[] digest = md5.digest(); @@ -52,15 +50,22 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter { | ((long) (digest[1] & 0xFF) << 8) | (digest[0] & 0xFF); - long truncateHashCode = hashCode & 0xffffffffL; - return truncateHashCode; + return hashCode & 0xffffffffL; } + /** + * get address by jobId + * + * @param jobId job id + * @param addressList address list + * @return address + */ public String hashJob(int jobId, List addressList) { + // 1、hash ring // ------A1------A2-------A3------ // -----------J1------------------ - TreeMap addressRing = new TreeMap(); + TreeMap addressRing = new TreeMap<>(); for (String address: addressList) { for (int i = 0; i < VIRTUAL_NODE_NUM; i++) { long addressHash = hash("SHARD-" + address + "-NODE-" + i); @@ -68,18 +73,27 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter { } } + // 2、generate job-hash long jobHash = hash(String.valueOf(jobId)); - SortedMap lastRing = addressRing.tailMap(jobHash); + + // 3、route job-node + Map.Entry ceilingEntry = addressRing.ceilingEntry(jobHash); + if (ceilingEntry != null) { + return ceilingEntry.getValue(); + } + /*SortedMap lastRing = addressRing.tailMap(jobHash); if (!lastRing.isEmpty()) { return lastRing.get(lastRing.firstKey()); - } + }*/ + + // 4、default first node return addressRing.firstEntry().getValue(); } @Override - public ReturnT route(TriggerParam triggerParam, List addressList) { + public Response route(TriggerRequest triggerParam, List addressList) { String address = hashJob(triggerParam.getJobId(), addressList); - return new ReturnT(address); + return Response.ofSuccess(address); } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteFailover.java similarity index 53% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteFailover.java index a2e4c909..4d8974f6 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteFailover.java @@ -1,11 +1,11 @@ -package com.xxl.job.admin.core.route.strategy; +package com.xxl.job.admin.scheduler.route.strategy; -import com.xxl.job.admin.core.scheduler.XxlJobScheduler; -import com.xxl.job.admin.core.route.ExecutorRouter; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.core.biz.ExecutorBiz; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.route.ExecutorRouter; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.core.openapi.ExecutorBiz; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; import java.util.List; @@ -15,18 +15,18 @@ import java.util.List; public class ExecutorRouteFailover extends ExecutorRouter { @Override - public ReturnT route(TriggerParam triggerParam, List addressList) { + public Response route(TriggerRequest triggerParam, List addressList) { StringBuffer beatResultSB = new StringBuffer(); for (String address : addressList) { // beat - ReturnT beatResult = null; + Response beatResult = null; try { - ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address); + ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(address); beatResult = executorBiz.beat(); } catch (Exception e) { logger.error(e.getMessage(), e); - beatResult = new ReturnT(ReturnT.FAIL_CODE, ""+e ); + beatResult = Response.ofFail(e.getMessage() ); } beatResultSB.append( (beatResultSB.length()>0)?"

":"") .append(I18nUtil.getString("jobconf_beat") + ":") @@ -35,14 +35,14 @@ public class ExecutorRouteFailover extends ExecutorRouter { .append("
msg:").append(beatResult.getMsg()); // beat success - if (beatResult.getCode() == ReturnT.SUCCESS_CODE) { + if (beatResult.isSuccess()) { beatResult.setMsg(beatResultSB.toString()); - beatResult.setContent(address); + beatResult.setData(address); return beatResult; } } - return new ReturnT(ReturnT.FAIL_CODE, beatResultSB.toString()); + return Response.ofFail( beatResultSB.toString()); } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteFirst.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteFirst.java new file mode 100644 index 00000000..407276c5 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteFirst.java @@ -0,0 +1,19 @@ +package com.xxl.job.admin.scheduler.route.strategy; + +import com.xxl.job.admin.scheduler.route.ExecutorRouter; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; + +import java.util.List; + +/** + * Created by xuxueli on 17/3/10. + */ +public class ExecutorRouteFirst extends ExecutorRouter { + + @Override + public Response route(TriggerRequest triggerParam, List addressList){ + return Response.ofSuccess(addressList.get(0)); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteLFU.java similarity index 73% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteLFU.java index 9df19726..2f98d407 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteLFU.java @@ -1,8 +1,8 @@ -package com.xxl.job.admin.core.route.strategy; +package com.xxl.job.admin.scheduler.route.strategy; -import com.xxl.job.admin.core.route.ExecutorRouter; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; +import com.xxl.job.admin.scheduler.route.ExecutorRouter; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -17,6 +17,11 @@ import java.util.concurrent.ConcurrentMap; */ public class ExecutorRouteLFU extends ExecutorRouter { + /** + * job lfu map + * + * > + */ private static ConcurrentMap> jobLfuMap = new ConcurrentHashMap>(); private static long CACHE_VALID_TIME = 0; @@ -31,7 +36,7 @@ public class ExecutorRouteLFU extends ExecutorRouter { // lfu item init HashMap lfuItemMap = jobLfuMap.get(jobId); // Key排序可以用TreeMap+构造入参Compare;Value排序暂时只能通过ArrayList; if (lfuItemMap == null) { - lfuItemMap = new HashMap(); + lfuItemMap = new HashMap<>(); jobLfuMap.putIfAbsent(jobId, lfuItemMap); // 避免重复覆盖 } @@ -48,32 +53,26 @@ public class ExecutorRouteLFU extends ExecutorRouter { delKeys.add(existKey); } } - if (delKeys.size() > 0) { + if (!delKeys.isEmpty()) { for (String delKey: delKeys) { lfuItemMap.remove(delKey); } } // load least userd count address - List> lfuItemList = new ArrayList>(lfuItemMap.entrySet()); - Collections.sort(lfuItemList, new Comparator>() { - @Override - public int compare(Map.Entry o1, Map.Entry o2) { - return o1.getValue().compareTo(o2.getValue()); - } - }); + List> lfuItemList = new ArrayList<>(lfuItemMap.entrySet()); + lfuItemList.sort(Map.Entry.comparingByValue()); // 默认升序, 获取 Value 最小值 Map.Entry addressItem = lfuItemList.get(0); - String minAddress = addressItem.getKey(); addressItem.setValue(addressItem.getValue() + 1); return addressItem.getKey(); } @Override - public ReturnT route(TriggerParam triggerParam, List addressList) { + public Response route(TriggerRequest triggerParam, List addressList) { String address = route(triggerParam.getJobId(), addressList); - return new ReturnT(address); + return Response.ofSuccess(address); } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteLRU.java similarity index 79% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteLRU.java index 2d540067..089bb781 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteLRU.java @@ -1,8 +1,8 @@ -package com.xxl.job.admin.core.route.strategy; +package com.xxl.job.admin.scheduler.route.strategy; -import com.xxl.job.admin.core.route.ExecutorRouter; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; +import com.xxl.job.admin.scheduler.route.ExecutorRouter; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -19,6 +19,11 @@ import java.util.concurrent.ConcurrentMap; */ public class ExecutorRouteLRU extends ExecutorRouter { + /** + * job lru map + * + * > + */ private static ConcurrentMap> jobLRUMap = new ConcurrentHashMap>(); private static long CACHE_VALID_TIME = 0; @@ -38,7 +43,7 @@ public class ExecutorRouteLRU extends ExecutorRouter { * a、accessOrder:true=访问顺序排序(get/put时排序);false=插入顺序排期; * b、removeEldestEntry:新增元素时将会调用,返回true时会删除最老元素;可封装LinkedHashMap并重写该方法,比如定义最大容量,超出是返回true即可实现固定长度的LRU算法; */ - lruItem = new LinkedHashMap(16, 0.75f, true); + lruItem = new LinkedHashMap<>(16, 0.75f, true); jobLRUMap.putIfAbsent(jobId, lruItem); } @@ -55,22 +60,21 @@ public class ExecutorRouteLRU extends ExecutorRouter { delKeys.add(existKey); } } - if (delKeys.size() > 0) { + if (!delKeys.isEmpty()) { for (String delKey: delKeys) { lruItem.remove(delKey); } } - // load + // load first elment, eldest entry String eldestKey = lruItem.entrySet().iterator().next().getKey(); - String eldestValue = lruItem.get(eldestKey); - return eldestValue; + return lruItem.get(eldestKey); } @Override - public ReturnT route(TriggerParam triggerParam, List addressList) { + public Response route(TriggerRequest triggerParam, List addressList) { String address = route(triggerParam.getJobId(), addressList); - return new ReturnT(address); + return Response.ofSuccess(address); } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteLast.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteLast.java new file mode 100644 index 00000000..62747d85 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteLast.java @@ -0,0 +1,19 @@ +package com.xxl.job.admin.scheduler.route.strategy; + +import com.xxl.job.admin.scheduler.route.ExecutorRouter; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; + +import java.util.List; + +/** + * Created by xuxueli on 17/3/10. + */ +public class ExecutorRouteLast extends ExecutorRouter { + + @Override + public Response route(TriggerRequest triggerParam, List addressList) { + return Response.ofSuccess(addressList.get(addressList.size()-1)); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteRandom.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteRandom.java new file mode 100644 index 00000000..ae760d29 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteRandom.java @@ -0,0 +1,23 @@ +package com.xxl.job.admin.scheduler.route.strategy; + +import com.xxl.job.admin.scheduler.route.ExecutorRouter; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; + +import java.util.List; +import java.util.Random; + +/** + * Created by xuxueli on 17/3/10. + */ +public class ExecutorRouteRandom extends ExecutorRouter { + + private static Random localRandom = new Random(); + + @Override + public Response route(TriggerRequest triggerParam, List addressList) { + String address = addressList.get(localRandom.nextInt(addressList.size())); + return Response.ofSuccess(address); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteRound.java similarity index 78% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteRound.java index d0ea2baa..8242e68b 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/route/strategy/ExecutorRouteRound.java @@ -1,8 +1,8 @@ -package com.xxl.job.admin.core.route.strategy; +package com.xxl.job.admin.scheduler.route.strategy; -import com.xxl.job.admin.core.route.ExecutorRouter; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; +import com.xxl.job.admin.scheduler.route.ExecutorRouter; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.response.Response; import java.util.List; import java.util.Random; @@ -38,9 +38,9 @@ public class ExecutorRouteRound extends ExecutorRouter { } @Override - public ReturnT route(TriggerParam triggerParam, List addressList) { + public Response route(TriggerRequest triggerParam, List addressList) { String address = addressList.get(count(triggerParam.getJobId())%addressList.size()); - return new ReturnT(address); + return Response.ofSuccess(address); } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobCompleteHelper.java similarity index 65% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobCompleteHelper.java index 33e345e4..ae9a443e 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobCompleteHelper.java @@ -1,12 +1,12 @@ -package com.xxl.job.admin.core.thread; - -import com.xxl.job.admin.core.complete.XxlJobCompleter; -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.model.XxlJobLog; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.core.biz.model.HandleCallbackParam; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.util.DateUtil; +package com.xxl.job.admin.scheduler.thread; + +import com.xxl.job.admin.model.XxlJobLog; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.core.openapi.model.CallbackRequest; +import com.xxl.job.core.context.XxlJobContext; +import com.xxl.tool.core.DateTool; +import com.xxl.tool.response.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,23 +15,22 @@ import java.util.List; import java.util.concurrent.*; /** - * job lose-monitor instance + * job complate, for callback and result-lost * * @author xuxueli 2015-9-1 18:05:56 */ public class JobCompleteHelper { - private static Logger logger = LoggerFactory.getLogger(JobCompleteHelper.class); + private static final Logger logger = LoggerFactory.getLogger(JobCompleteHelper.class); - private static JobCompleteHelper instance = new JobCompleteHelper(); - public static JobCompleteHelper getInstance(){ - return instance; - } - // ---------------------- monitor ---------------------- private ThreadPoolExecutor callbackThreadPool = null; private Thread monitorThread; private volatile boolean toStop = false; + + /** + * start + */ public void start(){ // for callback @@ -75,8 +74,8 @@ public class JobCompleteHelper { while (!toStop) { try { // 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败; - Date losedTime = DateUtil.addMinutes(new Date(), -10); - List losedJobIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLostJobIds(losedTime); + Date losedTime = DateTool.addMinutes(new Date(), -10); + List losedJobIds = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findLostJobIds(losedTime); if (losedJobIds!=null && losedJobIds.size()>0) { for (Long logId: losedJobIds) { @@ -85,10 +84,10 @@ public class JobCompleteHelper { jobLog.setId(logId); jobLog.setHandleTime(new Date()); - jobLog.setHandleCode(ReturnT.FAIL_CODE); + jobLog.setHandleCode(XxlJobContext.HANDLE_CODE_FAIL); jobLog.setHandleMsg( I18nUtil.getString("joblog_lost_fail") ); - XxlJobCompleter.updateHandleInfoAndFinish(jobLog); + XxlJobAdminBootstrap.getInstance().getJobCompleter().complete(jobLog); } } @@ -117,7 +116,10 @@ public class JobCompleteHelper { monitorThread.start(); } - public void toStop(){ + /** + * stop + */ + public void stop(){ toStop = true; // stop registryOrRemoveThreadPool @@ -135,30 +137,30 @@ public class JobCompleteHelper { // ---------------------- helper ---------------------- - public ReturnT callback(List callbackParamList) { + public Response callback(List callbackParamList) { callbackThreadPool.execute(new Runnable() { @Override public void run() { - for (HandleCallbackParam handleCallbackParam: callbackParamList) { - ReturnT callbackResult = callback(handleCallbackParam); - logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}", - (callbackResult.getCode()== ReturnT.SUCCESS_CODE?"success":"fail"), handleCallbackParam, callbackResult); + for (CallbackRequest callbackRequest: callbackParamList) { + Response callbackResult = doCallback(callbackRequest); + logger.debug(">>>>>>>>> JobApiController.callback {}, callbackRequest={}, callbackResult={}", + (callbackResult.isSuccess()?"success":"fail"), callbackRequest, callbackResult); } } }); - return ReturnT.SUCCESS; + return Response.ofSuccess(); } - private ReturnT callback(HandleCallbackParam handleCallbackParam) { + private Response doCallback(CallbackRequest handleCallbackParam) { // valid log item - XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(handleCallbackParam.getLogId()); + XxlJobLog log = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().load(handleCallbackParam.getLogId()); if (log == null) { - return new ReturnT(ReturnT.FAIL_CODE, "log item not found."); + return Response.ofFail( "log item not found."); } if (log.getHandleCode() > 0) { - return new ReturnT(ReturnT.FAIL_CODE, "log repeate callback."); // avoid repeat callback, trigger child job etc + return Response.ofFail("log repeate callback."); // avoid repeat callback, trigger child job etc } // handle msg @@ -174,9 +176,9 @@ public class JobCompleteHelper { log.setHandleTime(new Date()); log.setHandleCode(handleCallbackParam.getHandleCode()); log.setHandleMsg(handleMsg.toString()); - XxlJobCompleter.updateHandleInfoAndFinish(log); + XxlJobAdminBootstrap.getInstance().getJobCompleter().complete(log); - return ReturnT.SUCCESS; + return Response.ofSuccess(); } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobFailAlarmMonitorHelper.java similarity index 56% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobFailAlarmMonitorHelper.java index c5d58c59..9708a576 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobFailAlarmMonitorHelper.java @@ -1,10 +1,10 @@ -package com.xxl.job.admin.core.thread; +package com.xxl.job.admin.scheduler.thread; -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobLog; -import com.xxl.job.admin.core.trigger.TriggerTypeEnum; -import com.xxl.job.admin.core.util.I18nUtil; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobLog; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum; +import com.xxl.job.admin.util.I18nUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,22 +12,22 @@ import java.util.List; import java.util.concurrent.TimeUnit; /** - * job monitor instance + * job fail-monitor helper * * @author xuxueli 2015-9-1 18:05:56 */ -public class JobFailMonitorHelper { - private static Logger logger = LoggerFactory.getLogger(JobFailMonitorHelper.class); +public class JobFailAlarmMonitorHelper { + private static Logger logger = LoggerFactory.getLogger(JobFailAlarmMonitorHelper.class); - private static JobFailMonitorHelper instance = new JobFailMonitorHelper(); - public static JobFailMonitorHelper getInstance(){ - return instance; - } // ---------------------- monitor ---------------------- private Thread monitorThread; private volatile boolean toStop = false; + + /** + * start + */ public void start(){ monitorThread = new Thread(new Runnable() { @@ -38,42 +38,42 @@ public class JobFailMonitorHelper { while (!toStop) { try { - List failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findFailJobLogIds(1000); + List failLogIds = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findFailJobLogIds(1000); if (failLogIds!=null && !failLogIds.isEmpty()) { for (long failLogId: failLogIds) { // lock log - int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, 0, -1); + int lockRet = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().updateAlarmStatus(failLogId, 0, -1); if (lockRet < 1) { continue; } - XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId); - XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId()); + XxlJobLog log = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().load(failLogId); + XxlJobInfo info = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().loadById(log.getJobId()); // 1、fail retry monitor if (log.getExecutorFailRetryCount() > 0) { - JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam(), null); - String retryMsg = "

>>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<<
"; + XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam(), null); + String retryMsg = "

>>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<<
"; log.setTriggerMsg(log.getTriggerMsg() + retryMsg); - XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log); + XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().updateTriggerInfo(log); } // 2、fail alarm monitor int newAlarmStatus = 0; // 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败 if (info != null) { - boolean alarmResult = XxlJobAdminConfig.getAdminConfig().getJobAlarmer().alarm(info, log); + boolean alarmResult = XxlJobAdminBootstrap.getInstance().getJobAlarmer().alarm(info, log); newAlarmStatus = alarmResult?2:3; } else { newAlarmStatus = 1; } - XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, -1, newAlarmStatus); + XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().updateAlarmStatus(failLogId, -1, newAlarmStatus); } } } catch (Throwable e) { if (!toStop) { - logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e); + logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e.getMessage(), e); } } @@ -96,7 +96,10 @@ public class JobFailMonitorHelper { monitorThread.start(); } - public void toStop(){ + /** + * stop + */ + public void stop(){ toStop = true; // interrupt and wait monitorThread.interrupt(); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobLogReportHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobLogReportHelper.java new file mode 100644 index 00000000..7ad7170e --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobLogReportHelper.java @@ -0,0 +1,162 @@ +package com.xxl.job.admin.scheduler.thread; + +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.model.XxlJobLogReport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * job log report helper + * + * @author xuxueli 2019-11-22 + */ +public class JobLogReportHelper { + private static final Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class); + + + private Thread logReportThread; + private volatile boolean toStop = false; + + /** + * start + */ + public void start(){ + logReportThread = new Thread(new Runnable() { + + @Override + public void run() { + + // last clean log time + long lastCleanLogTime = 0; + + + while (!toStop) { + + // 1、log-report refresh: refresh log report in 3 days + try { + + for (int i = 0; i < 3; i++) { + + // today + Calendar itemDay = Calendar.getInstance(); + itemDay.add(Calendar.DAY_OF_MONTH, -i); + itemDay.set(Calendar.HOUR_OF_DAY, 0); + itemDay.set(Calendar.MINUTE, 0); + itemDay.set(Calendar.SECOND, 0); + itemDay.set(Calendar.MILLISECOND, 0); + + Date todayFrom = itemDay.getTime(); + + itemDay.set(Calendar.HOUR_OF_DAY, 23); + itemDay.set(Calendar.MINUTE, 59); + itemDay.set(Calendar.SECOND, 59); + itemDay.set(Calendar.MILLISECOND, 999); + + Date todayTo = itemDay.getTime(); + + // refresh log-report every minute + XxlJobLogReport xxlJobLogReport = new XxlJobLogReport(); + xxlJobLogReport.setTriggerDay(todayFrom); + xxlJobLogReport.setRunningCount(0); + xxlJobLogReport.setSucCount(0); + xxlJobLogReport.setFailCount(0); + xxlJobLogReport.setUpdateTime(new Date()); + + // fill count-data + Map triggerCountMap = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findLogReport(todayFrom, todayTo); + if (triggerCountMap!=null && !triggerCountMap.isEmpty()) { + int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")?Integer.parseInt(String.valueOf(triggerCountMap.get("triggerDayCount"))):0; + int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")?Integer.parseInt(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))):0; + int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")?Integer.parseInt(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))):0; + int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc; + + xxlJobLogReport.setRunningCount(triggerDayCountRunning); + xxlJobLogReport.setSucCount(triggerDayCountSuc); + xxlJobLogReport.setFailCount(triggerDayCountFail); + } + + // do refresh: + XxlJobAdminBootstrap.getInstance().getXxlJobLogReportMapper().saveOrUpdate(xxlJobLogReport); // 0-fail; 1-save suc; 2-update suc; + /*if (ret < 1) { + XxlJobAdminBootstrap.getInstance().getXxlJobLogReportMapper().save(xxlJobLogReport); + }*/ + } + + } catch (Throwable e) { + if (!toStop) { + logger.error(">>>>>>>>>>> xxl-job, JobLogReportHelper(log-report refresh) error:{}", e.getMessage(), e); + } + } + + // 2、log-clean: switch open & once each day + try { + if (XxlJobAdminBootstrap.getInstance().getLogretentiondays()>0 + && System.currentTimeMillis() - lastCleanLogTime > 24*60*60*1000) { + + // expire-time + Calendar expiredDay = Calendar.getInstance(); + expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminBootstrap.getInstance().getLogretentiondays()); + expiredDay.set(Calendar.HOUR_OF_DAY, 0); + expiredDay.set(Calendar.MINUTE, 0); + expiredDay.set(Calendar.SECOND, 0); + expiredDay.set(Calendar.MILLISECOND, 0); + Date clearBeforeTime = expiredDay.getTime(); + + // clean expired log + List logIds = null; + do { + logIds = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findClearLogIds(0, 0, clearBeforeTime, 0, 1000); + if (logIds!=null && !logIds.isEmpty()) { + XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().clearLog(logIds); + } + } while (logIds!=null && !logIds.isEmpty()); + + // update clean time + lastCleanLogTime = System.currentTimeMillis(); + } + } catch (Throwable e) { + if (!toStop) { + logger.error(">>>>>>>>>>> xxl-job, JobLogReportHelper(log-clean) error:{}", e.getMessage(), e); + } + } + + try { + TimeUnit.MINUTES.sleep(1); + } catch (Throwable e) { + if (!toStop) { + logger.error(e.getMessage(), e); + } + } + + } + + logger.info(">>>>>>>>>>> xxl-job, job log report thread stop"); + + } + }); + logReportThread.setDaemon(true); + logReportThread.setName("xxl-job, admin JobLogReportHelper"); + logReportThread.start(); + } + + /** + * stop + */ + public void stop(){ + toStop = true; + // interrupt and wait + logReportThread.interrupt(); + try { + logReportThread.join(); + } catch (Throwable e) { + logger.error(e.getMessage(), e); + } + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobRegistryHelper.java similarity index 64% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobRegistryHelper.java index 9e961c57..e72468a9 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobRegistryHelper.java @@ -1,34 +1,36 @@ -package com.xxl.job.admin.core.thread; - -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.model.XxlJobGroup; -import com.xxl.job.admin.core.model.XxlJobRegistry; -import com.xxl.job.core.biz.model.RegistryParam; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.enums.RegistryConfig; +package com.xxl.job.admin.scheduler.thread; + +import com.xxl.job.admin.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobRegistry; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.core.constant.RegistType; +import com.xxl.job.core.openapi.model.RegistryRequest; +import com.xxl.job.core.constant.Const; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.response.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.util.StringUtils; import java.util.*; import java.util.concurrent.*; /** - * job registry instance + * job registry instance helper + * * @author xuxueli 2016-10-02 19:10:24 */ public class JobRegistryHelper { private static Logger logger = LoggerFactory.getLogger(JobRegistryHelper.class); - private static JobRegistryHelper instance = new JobRegistryHelper(); - public static JobRegistryHelper getInstance(){ - return instance; - } private ThreadPoolExecutor registryOrRemoveThreadPool = null; private Thread registryMonitorThread; private volatile boolean toStop = false; + + /** + * start + */ public void start(){ // for registry or remove @@ -59,21 +61,21 @@ public class JobRegistryHelper { while (!toStop) { try { // auto registry group - List groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0); + List groupList = XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().findByAddressType(0); if (groupList!=null && !groupList.isEmpty()) { // remove dead address (admin/executor) - List ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findDead(RegistryConfig.DEAD_TIMEOUT, new Date()); - if (ids!=null && ids.size()>0) { - XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(ids); + List ids = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findDead(Const.DEAD_TIMEOUT, new Date()); + if (ids!=null && !ids.isEmpty()) { + XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().removeDead(ids); } // fresh online address (admin/executor) HashMap> appAddressMap = new HashMap>(); - List list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT, new Date()); + List list = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findAll(Const.DEAD_TIMEOUT, new Date()); if (list != null) { for (XxlJobRegistry item: list) { - if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) { + if (RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) { String appname = item.getRegistryKey(); List registryList = appAddressMap.get(appname); if (registryList == null) { @@ -104,7 +106,7 @@ public class JobRegistryHelper { group.setAddressList(addressListStr); group.setUpdateTime(new Date()); - XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group); + XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().update(group); } } } catch (Throwable e) { @@ -113,7 +115,7 @@ public class JobRegistryHelper { } } try { - TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT); + TimeUnit.SECONDS.sleep(Const.BEAT_TIMEOUT); } catch (Throwable e) { if (!toStop) { logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e); @@ -128,13 +130,17 @@ public class JobRegistryHelper { registryMonitorThread.start(); } - public void toStop(){ + + /** + * stop + */ + public void stop(){ toStop = true; // stop registryOrRemoveThreadPool registryOrRemoveThreadPool.shutdownNow(); - // stop monitir (interrupt and wait) + // stop monitor (interrupt and wait) registryMonitorThread.interrupt(); try { registryMonitorThread.join(); @@ -144,15 +150,18 @@ public class JobRegistryHelper { } - // ---------------------- helper ---------------------- + // ---------------------- tool ---------------------- - public ReturnT registry(RegistryParam registryParam) { + /** + * registry + */ + public Response registry(RegistryRequest registryParam) { // valid - if (!StringUtils.hasText(registryParam.getRegistryGroup()) - || !StringUtils.hasText(registryParam.getRegistryKey()) - || !StringUtils.hasText(registryParam.getRegistryValue())) { - return new ReturnT(ReturnT.FAIL_CODE, "Illegal Argument."); + if (StringTool.isBlank(registryParam.getRegistryGroup()) + || StringTool.isBlank(registryParam.getRegistryKey()) + || StringTool.isBlank(registryParam.getRegistryValue())) { + return Response.ofFail("Illegal Argument."); } // async execute @@ -160,7 +169,7 @@ public class JobRegistryHelper { @Override public void run() { // 0-fail; 1-save suc; 2-update suc; - int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registrySaveOrUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date()); + int ret = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().registrySaveOrUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date()); if (ret == 1) { // fresh (add) freshGroupRegistryInfo(registryParam); @@ -175,23 +184,26 @@ public class JobRegistryHelper { } }); - return ReturnT.SUCCESS; + return Response.ofSuccess(); } - public ReturnT registryRemove(RegistryParam registryParam) { + /** + * registry remove + */ + public Response registryRemove(RegistryRequest registryParam) { // valid - if (!StringUtils.hasText(registryParam.getRegistryGroup()) - || !StringUtils.hasText(registryParam.getRegistryKey()) - || !StringUtils.hasText(registryParam.getRegistryValue())) { - return new ReturnT(ReturnT.FAIL_CODE, "Illegal Argument."); + if (StringTool.isBlank(registryParam.getRegistryGroup()) + || StringTool.isBlank(registryParam.getRegistryKey()) + || StringTool.isBlank(registryParam.getRegistryValue())) { + return Response.ofFail("Illegal Argument."); } // async execute registryOrRemoveThreadPool.execute(new Runnable() { @Override public void run() { - int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue()); + int ret = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue()); if (ret > 0) { // fresh (delete) freshGroupRegistryInfo(registryParam); @@ -199,10 +211,10 @@ public class JobRegistryHelper { } }); - return ReturnT.SUCCESS; + return Response.ofSuccess(); } - private void freshGroupRegistryInfo(RegistryParam registryParam){ + private void freshGroupRegistryInfo(RegistryRequest registryParam){ // Under consideration, prevent affecting core tables } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobScheduleHelper.java similarity index 53% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobScheduleHelper.java index 3f5e1106..5dd13e82 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobScheduleHelper.java @@ -1,16 +1,18 @@ -package com.xxl.job.admin.core.thread; - -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.cron.CronExpression; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum; -import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum; -import com.xxl.job.admin.core.trigger.TriggerTypeEnum; +package com.xxl.job.admin.scheduler.thread; + +import com.xxl.job.admin.constant.TriggerStatus; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.misfire.MisfireStrategyEnum; +import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum; +import com.xxl.job.admin.scheduler.type.ScheduleTypeEnum; +import com.xxl.tool.core.CollectionTool; +import com.xxl.tool.core.MapTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; -import java.sql.Connection; -import java.sql.PreparedStatement; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -19,21 +21,27 @@ import java.util.concurrent.TimeUnit; * @author xuxueli 2019-05-21 */ public class JobScheduleHelper { - private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class); + private static final Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class); - private static JobScheduleHelper instance = new JobScheduleHelper(); - public static JobScheduleHelper getInstance(){ - return instance; - } - public static final long PRE_READ_MS = 5000; // pre read + /** + * pre-read time for scheduler, increase efficiency + */ + public static final long PRE_READ_MS = 5000; + /* + * elegant shutdown wait seconds + */ + private static final long ELEGANT_SHUTDOWN_WAITING_SECONDS = 10; private Thread scheduleThread; private Thread ringThread; private volatile boolean scheduleThreadToStop = false; private volatile boolean ringThreadToStop = false; - private volatile static Map> ringData = new ConcurrentHashMap<>(); + private final Map> ringData = new ConcurrentHashMap<>(); + /** + * start + */ public void start(){ // schedule thread @@ -41,6 +49,7 @@ public class JobScheduleHelper { @Override public void run() { + // align time try { TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 ); } catch (Throwable e) { @@ -50,74 +59,64 @@ public class JobScheduleHelper { } logger.info(">>>>>>>>> init xxl-job admin scheduler success."); - // pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20) - int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20; + // pre-read count: treadpool-size * 10 (trigger-qps: 1000ms / 100ms each trigger cost) + int preReadCount = (XxlJobAdminBootstrap.getInstance().getTriggerPoolFastMax() + XxlJobAdminBootstrap.getInstance().getTriggerPoolSlowMax()) * 10; + // do schedule while (!scheduleThreadToStop) { - // Scan Job + // param long start = System.currentTimeMillis(); - - Connection conn = null; - Boolean connAutoCommit = null; - PreparedStatement preparedStatement = null; - boolean preReadSuc = true; - try { - - conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection(); - connAutoCommit = conn.getAutoCommit(); - conn.setAutoCommit(false); - preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" ); - preparedStatement.execute(); + // transaction start + TransactionStatus transactionStatus = null; + try { + transactionStatus = XxlJobAdminBootstrap.getInstance().getTransactionManager().getTransaction(new DefaultTransactionDefinition()); + // 1、job lock + String lockedRecord = XxlJobAdminBootstrap.getInstance().getXxlJobLockMapper().scheduleLock(); + long nowTime = System.currentTimeMillis(); - // tx start + // scan and process job + List scheduleList = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount); + if (CollectionTool.isNotEmpty(scheduleList)) { - // 1、pre read - long nowTime = System.currentTimeMillis(); - List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount); - if (scheduleList!=null && scheduleList.size()>0) { // 2、push time-ring for (XxlJobInfo jobInfo: scheduleList) { // time-ring jump if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) { // 2.1、trigger-expire > 5s:pass && make next-trigger-time - logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId()); - // 1、misfire match + // 1、misfire handle MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING); - if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) { - // FIRE_ONCE_NOW 》 trigger - JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null); - logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() ); - } + misfireStrategyEnum.getMisfireHandler().handle(jobInfo.getId()); // 2、fresh next - refreshNextValidTime(jobInfo, new Date()); + refreshNextTriggerTime(jobInfo, new Date()); - } else if (nowTime > jobInfo.getTriggerNextTime()) { + } else if (nowTime >= jobInfo.getTriggerNextTime()) { // 2.2、trigger-expire < 5s:direct-trigger && make next-trigger-time - // 1、trigger - JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null); - logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() ); + // 1、trigger direct + XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null); + logger.debug(">>>>>>>>>>> xxl-job, schedule expire, direct trigger : jobId = " + jobInfo.getId() ); // 2、fresh next - refreshNextValidTime(jobInfo, new Date()); + refreshNextTriggerTime(jobInfo, new Date()); // next-trigger-time in 5s, pre-read again - if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) { + if (jobInfo.getTriggerStatus()== TriggerStatus.RUNNING.getValue() && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) { // 1、make ring second int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60); - // 2、push time ring + // 2、push time ring (pre read) pushTimeRing(ringSecond, jobInfo.getId()); + logger.debug(">>>>>>>>>>> xxl-job, schedule pre-read, push trigger : jobId = " + jobInfo.getId() ); // 3、fresh next - refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime())); + refreshNextTriggerTime(jobInfo, new Date(jobInfo.getTriggerNextTime())); } @@ -129,68 +128,45 @@ public class JobScheduleHelper { // 2、push time ring pushTimeRing(ringSecond, jobInfo.getId()); + logger.debug(">>>>>>>>>>> xxl-job, schedule normal, push trigger : jobId = " + jobInfo.getId() ); // 3、fresh next - refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime())); + refreshNextTriggerTime(jobInfo, new Date(jobInfo.getTriggerNextTime())); } } // 3、update trigger info - for (XxlJobInfo jobInfo: scheduleList) { - XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo); + /*for (XxlJobInfo jobInfo: scheduleList) { + XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleUpdate(jobInfo); + }*/ + int batchSize = XxlJobAdminBootstrap.getInstance().getScheduleBatchSize(); + List> scheduleListBatches = CollectionTool.split(scheduleList, batchSize); + for (List scheduleListBatch : scheduleListBatches) { + int totalAffected = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleBatchUpdate(scheduleListBatch); + logger.debug(">>>>>>>>>>> xxl-job, JobScheduleHelper scheduleBatchUpdate records:" + totalAffected); } } else { preReadSuc = false; } - // tx stop - - } catch (Throwable e) { if (!scheduleThreadToStop) { - logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e); + logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e.getMessage(), e); } } finally { - - // commit - if (conn != null) { - try { - conn.commit(); - } catch (Throwable e) { - if (!scheduleThreadToStop) { - logger.error(e.getMessage(), e); - } - } - try { - conn.setAutoCommit(connAutoCommit); - } catch (Throwable e) { - if (!scheduleThreadToStop) { - logger.error(e.getMessage(), e); - } - } - try { - conn.close(); - } catch (Throwable e) { - if (!scheduleThreadToStop) { - logger.error(e.getMessage(), e); - } - } - } - - // close PreparedStatement - if (null != preparedStatement) { - try { - preparedStatement.close(); - } catch (Throwable e) { - if (!scheduleThreadToStop) { - logger.error(e.getMessage(), e); - } + // transaction commit + try { + if (transactionStatus != null) { + XxlJobAdminBootstrap.getInstance().getTransactionManager().commit(transactionStatus); // avlid schedule repeat } + } catch (Throwable e) { + logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread transaction commit error:{}", e.getMessage(), e); } } + // transaction end long cost = System.currentTimeMillis()-start; @@ -235,28 +211,37 @@ public class JobScheduleHelper { try { // second data List ringItemData = new ArrayList<>(); - int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度; - for (int i = 0; i < 2; i++) { - List tmpData = ringData.remove( (nowSecond+60-i)%60 ); - if (tmpData != null) { - ringItemData.addAll(tmpData); + + // collect rind data, by second + int nowSecond = Calendar.getInstance().get(Calendar.SECOND); + for (int i = 0; i <= 2; i++) { // 避免调度遗漏:处理耗时太长、跨过刻度,除当前刻度外 + 向前校验2个刻度; + List ringItemList = ringData.remove( (nowSecond+60-i)%60 ); + if (CollectionTool.isNotEmpty(ringItemList)) { + // distinct for each second + List ringItemListDistinct = ringItemList.stream().distinct().toList(); // 避免调度重复:重复推送时间轮刻度,去重只保留一个;; + if (ringItemListDistinct.size() < ringItemList.size()) { + logger.warn(">>>>>>>>>>> xxl-job, time-ring found job repeat beat : " + nowSecond + " = " + ringItemData); + } + + // collect ring item + ringItemData.addAll(ringItemListDistinct); } } // ring trigger - logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) ); - if (ringItemData.size() > 0) { + logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + ringItemData); + if (CollectionTool.isNotEmpty(ringItemData)) { // do trigger for (int jobId: ringItemData) { // do trigger - JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null); + XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null); } // clear ringItemData.clear(); } } catch (Throwable e) { if (!ringThreadToStop) { - logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e); + logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e.getMessage(), e); } } } @@ -268,24 +253,35 @@ public class JobScheduleHelper { ringThread.start(); } - private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) { + /** + * refresh next trigger time of job + * + * @param jobInfo job info + * @param fromTime from time + */ + private void refreshNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) { try { - Date nextValidTime = generateNextValidTime(jobInfo, fromTime); - if (nextValidTime != null) { + // generate next trigger time + ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), ScheduleTypeEnum.NONE); + Date nextTriggerTime = scheduleTypeEnum.getScheduleType().generateNextTriggerTime(jobInfo, fromTime); + + // refresh next trigger-time + status + if (nextTriggerTime != null) { + // generate success jobInfo.setTriggerStatus(-1); // pass, may be Inaccurate jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime()); - jobInfo.setTriggerNextTime(nextValidTime.getTime()); + jobInfo.setTriggerNextTime(nextTriggerTime.getTime()); } else { - // generateNextValidTime fail, stop job - jobInfo.setTriggerStatus(0); + // generate fail, stop job + jobInfo.setTriggerStatus(TriggerStatus.STOPPED.getValue()); jobInfo.setTriggerLastTime(0); jobInfo.setTriggerNextTime(0); logger.error(">>>>>>>>>>> xxl-job, refreshNextValidTime fail for job: jobId={}, scheduleType={}, scheduleConf={}", jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf()); } } catch (Throwable e) { - // generateNextValidTime error, stop job - jobInfo.setTriggerStatus(0); + // generate error, stop job + jobInfo.setTriggerStatus(TriggerStatus.STOPPED.getValue()); jobInfo.setTriggerLastTime(0); jobInfo.setTriggerNextTime(0); @@ -294,19 +290,27 @@ public class JobScheduleHelper { } } + /** + * push time ring + * + * @param ringSecond ring second + * @param jobId job id + */ private void pushTimeRing(int ringSecond, int jobId){ - // push async ring - List ringItemData = ringData.get(ringSecond); - if (ringItemData == null) { - ringItemData = new ArrayList(); - ringData.put(ringSecond, ringItemData); - } - ringItemData.add(jobId); - - logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData) ); + // get ringItemData, init when not exists + List ringItemList = ringData.computeIfAbsent( + ringSecond, + k -> new ArrayList<>()); + + // push async rind + ringItemList.add(jobId); + logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + List.of(ringItemList)); } - public void toStop(){ + /** + * stop + */ + public void stop(){ // 1、stop schedule scheduleThreadToStop = true; @@ -325,12 +329,12 @@ public class JobScheduleHelper { } } - // if has ring data + // if has ring data, wait for elegent shutdown boolean hasRingData = false; - if (!ringData.isEmpty()) { + if (MapTool.isNotEmpty(ringData)) { for (int second : ringData.keySet()) { - List tmpData = ringData.get(second); - if (tmpData!=null && tmpData.size()>0) { + List ringItemList = ringData.get(second); + if (CollectionTool.isNotEmpty(ringItemList)) { hasRingData = true; break; } @@ -338,7 +342,7 @@ public class JobScheduleHelper { } if (hasRingData) { try { - TimeUnit.SECONDS.sleep(8); + TimeUnit.SECONDS.sleep(ELEGANT_SHUTDOWN_WAITING_SECONDS); } catch (Throwable e) { logger.error(e.getMessage(), e); } @@ -364,17 +368,4 @@ public class JobScheduleHelper { logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper stop"); } - - // ---------------------- tools ---------------------- - public static Date generateNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception { - ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null); - if (ScheduleTypeEnum.CRON == scheduleTypeEnum) { - Date nextValidTime = new CronExpression(jobInfo.getScheduleConf()).getNextValidTimeAfter(fromTime); - return nextValidTime; - } else if (ScheduleTypeEnum.FIX_RATE == scheduleTypeEnum /*|| ScheduleTypeEnum.FIX_DELAY == scheduleTypeEnum*/) { - return new Date(fromTime.getTime() + Integer.valueOf(jobInfo.getScheduleConf())*1000 ); - } - return null; - } - } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobTriggerPoolHelper.java similarity index 75% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobTriggerPoolHelper.java index e2dca548..9a12ee29 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobTriggerPoolHelper.java @@ -1,8 +1,7 @@ -package com.xxl.job.admin.core.thread; +package com.xxl.job.admin.scheduler.thread; -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.trigger.TriggerTypeEnum; -import com.xxl.job.admin.core.trigger.XxlJobTrigger; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @author xuxueli 2018-07-03 21:08:07 */ public class JobTriggerPoolHelper { - private static Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class); + private static final Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class); // ---------------------- trigger pool ---------------------- @@ -24,10 +23,13 @@ public class JobTriggerPoolHelper { private ThreadPoolExecutor fastTriggerPool = null; private ThreadPoolExecutor slowTriggerPool = null; + /** + * start + */ public void start(){ fastTriggerPool = new ThreadPoolExecutor( 10, - XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(), + XxlJobAdminBootstrap.getInstance().getTriggerPoolFastMax(), 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(2000), @@ -46,7 +48,7 @@ public class JobTriggerPoolHelper { slowTriggerPool = new ThreadPoolExecutor( 10, - XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(), + XxlJobAdminBootstrap.getInstance().getTriggerPoolSlowMax(), 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(5000), @@ -64,7 +66,9 @@ public class JobTriggerPoolHelper { }); } - + /** + * stop + */ public void stop() { //triggerPool.shutdown(); fastTriggerPool.shutdownNow(); @@ -78,15 +82,27 @@ public class JobTriggerPoolHelper { private volatile ConcurrentMap jobTimeoutCountMap = new ConcurrentHashMap<>(); + // ---------------------- tool ---------------------- + /** - * add trigger + * trigger job + * + * @param jobId + * @param triggerType + * @param failRetryCount + * >=0: use this param + * <0: use param from job info config + * @param executorShardingParam + * @param executorParam + * null: use job param + * not null: cover job param */ - public void addTrigger(final int jobId, - final TriggerTypeEnum triggerType, - final int failRetryCount, - final String executorShardingParam, - final String executorParam, - final String addressList) { + public void trigger(final int jobId, + final TriggerTypeEnum triggerType, + final int failRetryCount, + final String executorShardingParam, + final String executorParam, + final String addressList) { // choose thread pool ThreadPoolExecutor triggerPool_ = fastTriggerPool; @@ -104,7 +120,7 @@ public class JobTriggerPoolHelper { try { // do trigger - XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList); + XxlJobAdminBootstrap.getInstance().getJobTrigger().trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList); } catch (Throwable e) { logger.error(e.getMessage(), e); } finally { @@ -135,32 +151,4 @@ public class JobTriggerPoolHelper { }); } - - - // ---------------------- helper ---------------------- - - private static JobTriggerPoolHelper helper = new JobTriggerPoolHelper(); - - public static void toStart() { - helper.start(); - } - public static void toStop() { - helper.stop(); - } - - /** - * @param jobId - * @param triggerType - * @param failRetryCount - * >=0: use this param - * <0: use param from job info config - * @param executorShardingParam - * @param executorParam - * null: use job param - * not null: cover job param - */ - public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam, String addressList) { - helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList); - } - } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/trigger/JobTrigger.java similarity index 53% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/trigger/JobTrigger.java index 748befc6..9ce2f81a 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/trigger/JobTrigger.java @@ -1,29 +1,46 @@ -package com.xxl.job.admin.core.trigger; - -import com.xxl.job.admin.core.conf.XxlJobAdminConfig; -import com.xxl.job.admin.core.model.XxlJobGroup; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobLog; -import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum; -import com.xxl.job.admin.core.scheduler.XxlJobScheduler; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.core.biz.ExecutorBiz; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; -import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; -import com.xxl.job.core.util.IpUtil; -import com.xxl.job.core.util.ThrowableUtil; +package com.xxl.job.admin.scheduler.trigger; + +import com.xxl.job.admin.mapper.XxlJobGroupMapper; +import com.xxl.job.admin.mapper.XxlJobInfoMapper; +import com.xxl.job.admin.mapper.XxlJobLogMapper; +import com.xxl.job.admin.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobLog; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.route.ExecutorRouteStrategyEnum; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.core.constant.ExecutorBlockStrategyEnum; +import com.xxl.job.core.context.XxlJobContext; +import com.xxl.job.core.openapi.ExecutorBiz; +import com.xxl.job.core.openapi.model.TriggerRequest; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.error.ThrowableTool; +import com.xxl.tool.http.IPTool; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; import java.util.Date; /** * xxl-job trigger - * Created by xuxueli on 17/7/13. + * + * @author xuxueli 17/7/13. */ -public class XxlJobTrigger { - private static Logger logger = LoggerFactory.getLogger(XxlJobTrigger.class); +@Component +public class JobTrigger { + private static final Logger logger = LoggerFactory.getLogger(JobTrigger.class); + + + @Resource + private XxlJobInfoMapper xxlJobInfoMapper; + @Resource + private XxlJobGroupMapper xxlJobGroupMapper; + @Resource + private XxlJobLogMapper xxlJobLogMapper; + /** * trigger job @@ -34,6 +51,8 @@ public class XxlJobTrigger { * >=0: use this param * <0: use param from job info config * @param executorShardingParam + * null: new sharding, all nodes + * not null: for retry, only one node * @param executorParam * null: use job param * not null: cover job param @@ -41,7 +60,7 @@ public class XxlJobTrigger { * null: use executor addressList * not null: cover */ - public static void trigger(int jobId, + public void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, @@ -49,7 +68,7 @@ public class XxlJobTrigger { String addressList) { // load data - XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId); + XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(jobId); if (jobInfo == null) { logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId); return; @@ -58,57 +77,67 @@ public class XxlJobTrigger { jobInfo.setExecutorParam(executorParam); } int finalFailRetryCount = failRetryCount>=0?failRetryCount:jobInfo.getExecutorFailRetryCount(); - XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup()); + XxlJobGroup group = xxlJobGroupMapper.load(jobInfo.getJobGroup()); // cover addressList - if (addressList!=null && addressList.trim().length()>0) { + if (StringTool.isNotBlank(addressList)) { group.setAddressType(1); group.setAddressList(addressList.trim()); } // sharding param int[] shardingParam = null; + Date triggerTime = new Date(); if (executorShardingParam!=null){ String[] shardingArr = executorShardingParam.split("/"); - if (shardingArr.length==2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) { + if (shardingArr.length==2 && StringTool.isNumeric(shardingArr[0]) && StringTool.isNumeric(shardingArr[1])) { shardingParam = new int[2]; - shardingParam[0] = Integer.valueOf(shardingArr[0]); - shardingParam[1] = Integer.valueOf(shardingArr[1]); + shardingParam[0] = Integer.parseInt(shardingArr[0]); + shardingParam[1] = Integer.parseInt(shardingArr[1]); } } if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) && group.getRegistryList()!=null && !group.getRegistryList().isEmpty() && shardingParam==null) { for (int i = 0; i < group.getRegistryList().size(); i++) { - processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size()); + processTrigger(group, jobInfo, finalFailRetryCount, triggerType, triggerTime, i, group.getRegistryList().size()); } } else { if (shardingParam == null) { shardingParam = new int[]{0, 1}; } - processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]); + processTrigger(group, jobInfo, finalFailRetryCount, triggerType, triggerTime, shardingParam[0], shardingParam[1]); } } - private static boolean isNumeric(String str){ + /*private static boolean isNumeric(String str){ try { int result = Integer.valueOf(str); return true; } catch (NumberFormatException e) { return false; } - } + }*/ /** + * process trigger with log + * * @param group job group, registry list may be empty - * @param jobInfo - * @param finalFailRetryCount - * @param triggerType + * @param jobInfo job info + * @param finalFailRetryCount the fail-retry count + * @param triggerType trigger type + * @param triggerTime trigger time * @param index sharding index * @param total sharding index */ - private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){ + private void processTrigger(XxlJobGroup group, + XxlJobInfo jobInfo, + int finalFailRetryCount, + TriggerTypeEnum triggerType, + Date triggerTime, + int index, + int total){ // param ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION); // block strategy @@ -119,12 +148,12 @@ public class XxlJobTrigger { XxlJobLog jobLog = new XxlJobLog(); jobLog.setJobGroup(jobInfo.getJobGroup()); jobLog.setJobId(jobInfo.getId()); - jobLog.setTriggerTime(new Date()); - XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog); - logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId()); + jobLog.setTriggerTime(triggerTime); + xxlJobLogMapper.save(jobLog); + logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getJobId()); // 2、init trigger-param - TriggerParam triggerParam = new TriggerParam(); + TriggerRequest triggerParam = new TriggerRequest(); triggerParam.setJobId(jobInfo.getId()); triggerParam.setExecutorHandler(jobInfo.getExecutorHandler()); triggerParam.setExecutorParams(jobInfo.getExecutorParam()); @@ -140,7 +169,7 @@ public class XxlJobTrigger { // 3、init address String address = null; - ReturnT routeAddressResult = null; + Response routeAddressResult = null; if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) { if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) { if (index < group.getRegistryList().size()) { @@ -150,39 +179,60 @@ public class XxlJobTrigger { } } else { routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList()); - if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) { - address = routeAddressResult.getContent(); + if (routeAddressResult.isSuccess()) { + address = routeAddressResult.getData(); } } } else { - routeAddressResult = new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty")); + routeAddressResult = Response.of(XxlJobContext.HANDLE_CODE_FAIL, I18nUtil.getString("jobconf_trigger_address_empty")); } // 4、trigger remote executor - ReturnT triggerResult = null; + Response triggerResult = null; if (address != null) { - triggerResult = runExecutor(triggerParam, address); + triggerResult = doTrigger(triggerParam, address); } else { - triggerResult = new ReturnT(ReturnT.FAIL_CODE, null); + triggerResult = Response.of(XxlJobContext.HANDLE_CODE_FAIL, "Address Router Fail."); } // 5、collection trigger info - StringBuffer triggerMsgSb = new StringBuffer(); + // trigger config + StringBuilder triggerMsgSb = new StringBuilder(); triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append(":").append(triggerType.getTitle()); - triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append(":").append(IpUtil.getIp()); + triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append(":").append(IPTool.getIp()); triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append(":") .append( (group.getAddressType() == 0)?I18nUtil.getString("jobgroup_field_addressType_0"):I18nUtil.getString("jobgroup_field_addressType_1") ); triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append(":").append(group.getRegistryList()); triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append(":").append(executorRouteStrategyEnum.getTitle()); if (shardingParam != null) { - triggerMsgSb.append("("+shardingParam+")"); + triggerMsgSb.append("(").append(shardingParam).append(")"); } triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append(":").append(blockStrategy.getTitle()); triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_timeout")).append(":").append(jobInfo.getExecutorTimeout()); triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(":").append(finalFailRetryCount); - triggerMsgSb.append("

>>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_run") +"<<<<<<<<<<<
") - .append((routeAddressResult!=null&&routeAddressResult.getMsg()!=null)?routeAddressResult.getMsg()+"

":"").append(triggerResult.getMsg()!=null?triggerResult.getMsg():""); + // trigger data + triggerMsgSb.append("

>>>>>>>>>>>").append(I18nUtil.getString("jobconf_trigger_run")).append("<<<<<<<<<<<
"); + triggerMsgSb.append("
").append(I18nUtil.getString("joblog_field_executorAddress")).append(":"); + if (StringTool.isNotBlank(address)) { + triggerMsgSb.append(address); + } else if (routeAddressResult!=null && !routeAddressResult.isSuccess() && routeAddressResult.getMsg()!=null) { + triggerMsgSb.append("address route fail, ").append(routeAddressResult.getMsg()); + } else { + triggerMsgSb.append("address route fail."); + } + if (StringTool.isNotBlank(jobInfo.getExecutorHandler())) { + triggerMsgSb.append("
").append("JobHandler").append(":").append(jobInfo.getExecutorHandler()); + } + triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorparam")).append(":").append(jobInfo.getExecutorParam()); + triggerMsgSb.append("
").append(I18nUtil.getString("joblog_field_triggerMsg")).append(":"); + if (triggerResult.isSuccess()) { + triggerMsgSb.append("success"); + } else if (triggerResult.getMsg()!=null) { + triggerMsgSb.append("error, ").append(triggerResult.getMsg()); + } else { + triggerMsgSb.append("fail"); + } // 6、save log trigger-info jobLog.setExecutorAddress(address); @@ -193,34 +243,39 @@ public class XxlJobTrigger { //jobLog.setTriggerTime(); jobLog.setTriggerCode(triggerResult.getCode()); jobLog.setTriggerMsg(triggerMsgSb.toString()); - XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog); + xxlJobLogMapper.updateTriggerInfo(jobLog); - logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId()); + logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getJobId()); } /** - * run executor - * @param triggerParam - * @param address - * @return + * do trigger with address + * + * @param triggerParam trigger param + * @param address the address + * @return return */ - public static ReturnT runExecutor(TriggerParam triggerParam, String address){ - ReturnT runResult = null; + private Response doTrigger(TriggerRequest triggerParam, String address){ try { - ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address); - runResult = executorBiz.run(triggerParam); + // build client + ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(address); + + // invoke + Response runResult = executorBiz.run(triggerParam); + + // build result + StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":"); + runResultSB.append("
address:").append(address); + runResultSB.append("
code:").append(runResult.getCode()); + runResultSB.append("
msg:").append(runResult.getMsg()); + + // return + runResult.setMsg(runResultSB.toString()); + return runResult; } catch (Exception e) { logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e); - runResult = new ReturnT(ReturnT.FAIL_CODE, ThrowableUtil.toString(e)); + return Response.of(XxlJobContext.HANDLE_CODE_FAIL, ThrowableTool.toString(e)); } - - StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":"); - runResultSB.append("
address:").append(address); - runResultSB.append("
code:").append(runResult.getCode()); - runResultSB.append("
msg:").append(runResult.getMsg()); - - runResult.setMsg(runResultSB.toString()); - return runResult; } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/trigger/TriggerTypeEnum.java similarity index 88% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/trigger/TriggerTypeEnum.java index 446c90e9..6a3e69cd 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/trigger/TriggerTypeEnum.java @@ -1,6 +1,6 @@ -package com.xxl.job.admin.core.trigger; +package com.xxl.job.admin.scheduler.trigger; -import com.xxl.job.admin.core.util.I18nUtil; +import com.xxl.job.admin.util.I18nUtil; /** * trigger type enum diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/ScheduleType.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/ScheduleType.java new file mode 100644 index 00000000..1c4f8df2 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/ScheduleType.java @@ -0,0 +1,22 @@ +package com.xxl.job.admin.scheduler.type; + +import com.xxl.job.admin.model.XxlJobInfo; + +import java.util.Date; + +/** + * Schedule Type + * + * @author xuxueli 2020-10-29 + */ +public abstract class ScheduleType { + + /** + * generate next trigger time + * + * @param jobInfo job info + * @param fromTime from time + */ + public abstract Date generateNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) throws Exception; + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/ScheduleTypeEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/ScheduleTypeEnum.java new file mode 100644 index 00000000..f76e5313 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/ScheduleTypeEnum.java @@ -0,0 +1,62 @@ +package com.xxl.job.admin.scheduler.type; + +import com.xxl.job.admin.scheduler.type.strategy.CronScheduleType; +import com.xxl.job.admin.scheduler.type.strategy.FixRateScheduleType; +import com.xxl.job.admin.scheduler.type.strategy.NoneScheduleType; +import com.xxl.job.admin.util.I18nUtil; + +/** + * @author xuxueli 2020-10-29 21:11:23 + */ +public enum ScheduleTypeEnum { + + NONE(I18nUtil.getString("schedule_type_none"), new NoneScheduleType()), + + /** + * schedule by cron + */ + CRON(I18nUtil.getString("schedule_type_cron"), new CronScheduleType()), + + /** + * schedule by fixed rate (in seconds) + */ + FIX_RATE(I18nUtil.getString("schedule_type_fix_rate"), new FixRateScheduleType()), + + /** + * schedule by fix delay (in seconds), after the last time + */ + /*FIX_DELAY(I18nUtil.getString("schedule_type_fix_delay"))*/; + + private final String title; + private final ScheduleType scheduleType;; + + ScheduleTypeEnum(String title, ScheduleType scheduleType) { + this.title = title; + this.scheduleType = scheduleType; + } + + public String getTitle() { + return title; + } + + public ScheduleType getScheduleType() { + return scheduleType; + } + + /** + * match by name + * + * @param name name of ScheduleTypeEnum + * @param defaultItem default item + * @return ScheduleTypeEnum + */ + public static ScheduleTypeEnum match(String name, ScheduleTypeEnum defaultItem){ + for (ScheduleTypeEnum item: ScheduleTypeEnum.values()) { + if (item.name().equals(name)) { + return item; + } + } + return defaultItem; + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/strategy/CronScheduleType.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/strategy/CronScheduleType.java new file mode 100644 index 00000000..e7b86611 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/strategy/CronScheduleType.java @@ -0,0 +1,17 @@ +package com.xxl.job.admin.scheduler.type.strategy; + +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.scheduler.cron.CronExpression; +import com.xxl.job.admin.scheduler.type.ScheduleType; + +import java.util.Date; + +public class CronScheduleType extends ScheduleType { + + @Override + public Date generateNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) throws Exception { + // generate next trigger time, with cron + return new CronExpression(jobInfo.getScheduleConf()).getNextValidTimeAfter(fromTime); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/strategy/FixRateScheduleType.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/strategy/FixRateScheduleType.java new file mode 100644 index 00000000..298ab7d1 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/strategy/FixRateScheduleType.java @@ -0,0 +1,26 @@ +package com.xxl.job.admin.scheduler.type.strategy; + +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.scheduler.type.ScheduleType; +import com.xxl.tool.core.DateTool; + +import java.util.Date; + +public class FixRateScheduleType extends ScheduleType { + + @Override + public Date generateNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) throws Exception { + + // generate next trigger time, fix rate delay + Date nextTriggerTime = new Date(fromTime.getTime() + Long.parseLong(jobInfo.getScheduleConf()) * 1000L); + + // assign second: + if (nextTriggerTime.getTime() % 1000 != 0) { + nextTriggerTime = DateTool.addSeconds(DateTool.setMilliseconds(nextTriggerTime, 0), 1); + } + + return nextTriggerTime; + + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/strategy/NoneScheduleType.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/strategy/NoneScheduleType.java new file mode 100644 index 00000000..d17b1e83 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/type/strategy/NoneScheduleType.java @@ -0,0 +1,16 @@ +package com.xxl.job.admin.scheduler.type.strategy; + +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.scheduler.type.ScheduleType; + +import java.util.Date; + +public class NoneScheduleType extends ScheduleType { + + @Override + public Date generateNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) throws Exception { + // generate none trigger-time + return null; + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java index 12297f44..9e57302d 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java @@ -1,9 +1,9 @@ package com.xxl.job.admin.service; - -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobUser; -import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.sso.core.model.LoginInfo; +import com.xxl.tool.response.PageModel; +import com.xxl.tool.response.Response; import java.util.Date; import java.util.Map; @@ -17,82 +17,47 @@ public interface XxlJobService { /** * page list - * - * @param start - * @param length - * @param jobGroup - * @param jobDesc - * @param executorHandler - * @param author - * @return */ - public Map pageList(int start, int length, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author); + public Response> pageList(int offset, int pagesize, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author); /** * add job - * - * @param jobInfo - * @return */ - public ReturnT add(XxlJobInfo jobInfo, XxlJobUser loginUser); + public Response add(XxlJobInfo jobInfo, LoginInfo loginInfo); /** * update job - * - * @param jobInfo - * @return */ - public ReturnT update(XxlJobInfo jobInfo, XxlJobUser loginUser); + public Response update(XxlJobInfo jobInfo, LoginInfo loginInfo); /** * remove job - * * - * @param id - * @return */ - public ReturnT remove(int id); + public Response remove(int id, LoginInfo loginInfo); /** * start job - * - * @param id - * @return */ - public ReturnT start(int id); + public Response start(int id, LoginInfo loginInfo); /** * stop job - * - * @param id - * @return */ - public ReturnT stop(int id); + public Response stop(int id, LoginInfo loginInfo); /** * trigger - * - * @param loginUser - * @param jobId - * @param executorParam - * @param addressList - * @return */ - public ReturnT trigger(XxlJobUser loginUser, int jobId, String executorParam, String addressList); + public Response trigger(LoginInfo loginInfo, int jobId, String executorParam, String addressList); /** * dashboard info - * - * @return */ public Map dashboardInfo(); /** * chart info - * - * @param startDate - * @param endDate - * @return */ - public ReturnT> chartInfo(Date startDate, Date endDate); + public Response> chartInfo(Date startDate, Date endDate); } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java index 3c01e94d..5575edb5 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java @@ -1,11 +1,10 @@ package com.xxl.job.admin.service.impl; -import com.xxl.job.admin.core.thread.JobCompleteHelper; -import com.xxl.job.admin.core.thread.JobRegistryHelper; -import com.xxl.job.core.biz.AdminBiz; -import com.xxl.job.core.biz.model.HandleCallbackParam; -import com.xxl.job.core.biz.model.RegistryParam; -import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.core.openapi.AdminBiz; +import com.xxl.job.core.openapi.model.CallbackRequest; +import com.xxl.job.core.openapi.model.RegistryRequest; +import com.xxl.tool.response.Response; import org.springframework.stereotype.Service; import java.util.List; @@ -16,20 +15,19 @@ import java.util.List; @Service public class AdminBizImpl implements AdminBiz { - @Override - public ReturnT callback(List callbackParamList) { - return JobCompleteHelper.getInstance().callback(callbackParamList); + public Response callback(List callbackRequestList) { + return XxlJobAdminBootstrap.getInstance().getJobCompleteHelper().callback(callbackRequestList); } @Override - public ReturnT registry(RegistryParam registryParam) { - return JobRegistryHelper.getInstance().registry(registryParam); + public Response registry(RegistryRequest registryRequest) { + return XxlJobAdminBootstrap.getInstance().getJobRegistryHelper().registry(registryRequest); } @Override - public ReturnT registryRemove(RegistryParam registryParam) { - return JobRegistryHelper.getInstance().registryRemove(registryParam); + public Response registryRemove(RegistryRequest registryRequest) { + return XxlJobAdminBootstrap.getInstance().getJobRegistryHelper().registryRemove(registryRequest); } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/LoginService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/LoginService.java deleted file mode 100644 index 49d799b3..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/LoginService.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.xxl.job.admin.service.impl; - -import com.xxl.job.admin.core.model.XxlJobUser; -import com.xxl.job.admin.core.util.CookieUtil; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.admin.core.util.JacksonUtil; -import com.xxl.job.admin.dao.XxlJobUserDao; -import com.xxl.job.core.biz.model.ReturnT; -import org.springframework.stereotype.Service; -import org.springframework.util.DigestUtils; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.math.BigInteger; - -/** - * @author xuxueli 2019-05-04 22:13:264 - */ -@Service -public class LoginService { - - public static final String LOGIN_IDENTITY_KEY = "XXL_JOB_LOGIN_IDENTITY"; - - @Resource - private XxlJobUserDao xxlJobUserDao; - - - // ---------------------- token tool ---------------------- - - private String makeToken(XxlJobUser xxlJobUser){ - String tokenJson = JacksonUtil.writeValueAsString(xxlJobUser); - String tokenHex = new BigInteger(tokenJson.getBytes()).toString(16); - return tokenHex; - } - private XxlJobUser parseToken(String tokenHex){ - XxlJobUser xxlJobUser = null; - if (tokenHex != null) { - String tokenJson = new String(new BigInteger(tokenHex, 16).toByteArray()); // username_password(md5) - xxlJobUser = JacksonUtil.readValue(tokenJson, XxlJobUser.class); - } - return xxlJobUser; - } - - - // ---------------------- login tool, with cookie and db ---------------------- - - public ReturnT login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean ifRemember){ - - // param - if (username==null || username.trim().length()==0 || password==null || password.trim().length()==0){ - return new ReturnT(500, I18nUtil.getString("login_param_empty")); - } - - // valid passowrd - XxlJobUser xxlJobUser = xxlJobUserDao.loadByUserName(username); - if (xxlJobUser == null) { - return new ReturnT(500, I18nUtil.getString("login_param_unvalid")); - } - String passwordMd5 = DigestUtils.md5DigestAsHex(password.getBytes()); - if (!passwordMd5.equals(xxlJobUser.getPassword())) { - return new ReturnT(500, I18nUtil.getString("login_param_unvalid")); - } - - String loginToken = makeToken(xxlJobUser); - - // do login - CookieUtil.set(response, LOGIN_IDENTITY_KEY, loginToken, ifRemember); - return ReturnT.SUCCESS; - } - - /** - * logout - * - * @param request - * @param response - */ - public ReturnT logout(HttpServletRequest request, HttpServletResponse response){ - CookieUtil.remove(request, response, LOGIN_IDENTITY_KEY); - return ReturnT.SUCCESS; - } - - /** - * logout - * - * @param request - * @return - */ - public XxlJobUser ifLogin(HttpServletRequest request, HttpServletResponse response){ - String cookieToken = CookieUtil.getValue(request, LOGIN_IDENTITY_KEY); - if (cookieToken != null) { - XxlJobUser cookieUser = null; - try { - cookieUser = parseToken(cookieToken); - } catch (Exception e) { - logout(request, response); - } - if (cookieUser != null) { - XxlJobUser dbUser = xxlJobUserDao.loadByUserName(cookieUser.getUsername()); - if (dbUser != null) { - if (cookieUser.getPassword().equals(dbUser.getPassword())) { - return dbUser; - } - } - } - } - return null; - } - - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java index c3e8a14e..40f45f19 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java @@ -1,28 +1,33 @@ package com.xxl.job.admin.service.impl; -import com.xxl.job.admin.core.cron.CronExpression; -import com.xxl.job.admin.core.model.XxlJobGroup; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.model.XxlJobLogReport; -import com.xxl.job.admin.core.model.XxlJobUser; -import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum; -import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum; -import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum; -import com.xxl.job.admin.core.thread.JobScheduleHelper; -import com.xxl.job.admin.core.thread.JobTriggerPoolHelper; -import com.xxl.job.admin.core.trigger.TriggerTypeEnum; -import com.xxl.job.admin.core.util.I18nUtil; -import com.xxl.job.admin.dao.*; +import com.xxl.job.admin.constant.TriggerStatus; +import com.xxl.job.admin.mapper.*; +import com.xxl.job.admin.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.model.XxlJobLogReport; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.cron.CronExpression; +import com.xxl.job.admin.scheduler.misfire.MisfireStrategyEnum; +import com.xxl.job.admin.scheduler.route.ExecutorRouteStrategyEnum; +import com.xxl.job.admin.scheduler.thread.JobScheduleHelper; +import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum; +import com.xxl.job.admin.scheduler.type.ScheduleTypeEnum; import com.xxl.job.admin.service.XxlJobService; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; +import com.xxl.job.admin.util.I18nUtil; +import com.xxl.job.admin.util.JobGroupPermissionUtil; +import com.xxl.job.core.constant.ExecutorBlockStrategyEnum; import com.xxl.job.core.glue.GlueTypeEnum; -import com.xxl.job.core.util.DateUtil; +import com.xxl.sso.core.model.LoginInfo; +import com.xxl.tool.core.DateTool; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.json.GsonTool; +import com.xxl.tool.response.PageModel; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import javax.annotation.Resource; import java.text.MessageFormat; import java.util.*; @@ -35,75 +40,75 @@ public class XxlJobServiceImpl implements XxlJobService { private static Logger logger = LoggerFactory.getLogger(XxlJobServiceImpl.class); @Resource - private XxlJobGroupDao xxlJobGroupDao; + private XxlJobGroupMapper xxlJobGroupMapper; @Resource - private XxlJobInfoDao xxlJobInfoDao; + private XxlJobInfoMapper xxlJobInfoMapper; @Resource - public XxlJobLogDao xxlJobLogDao; + public XxlJobLogMapper xxlJobLogMapper; @Resource - private XxlJobLogGlueDao xxlJobLogGlueDao; + private XxlJobLogGlueMapper xxlJobLogGlueMapper; @Resource - private XxlJobLogReportDao xxlJobLogReportDao; + private XxlJobLogReportMapper xxlJobLogReportMapper; @Override - public Map pageList(int start, int length, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) { + public Response> pageList(int offset, int pagesize, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) { // page list - List list = xxlJobInfoDao.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author); - int list_count = xxlJobInfoDao.pageListCount(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author); - + List list = xxlJobInfoMapper.pageList(offset, pagesize, jobGroup, triggerStatus, jobDesc, executorHandler, author); + int list_count = xxlJobInfoMapper.pageListCount(offset, pagesize, jobGroup, triggerStatus, jobDesc, executorHandler, author); + // package result - Map maps = new HashMap(); - maps.put("recordsTotal", list_count); // 总记录数 - maps.put("recordsFiltered", list_count); // 过滤后的总记录数 - maps.put("data", list); // 分页列表 - return maps; + PageModel pageModel = new PageModel<>(); + pageModel.setData(list); + pageModel.setTotal(list_count); + + return Response.ofSuccess(pageModel); } @Override - public ReturnT add(XxlJobInfo jobInfo, XxlJobUser loginUser) { + public Response add(XxlJobInfo jobInfo, LoginInfo loginInfo) { // valid base - XxlJobGroup group = xxlJobGroupDao.load(jobInfo.getJobGroup()); + XxlJobGroup group = xxlJobGroupMapper.load(jobInfo.getJobGroup()); if (group == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_choose")+I18nUtil.getString("jobinfo_field_jobgroup")) ); + return Response.ofFail (I18nUtil.getString("system_please_choose")+I18nUtil.getString("jobinfo_field_jobgroup")); } - if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) ); + if (StringTool.isBlank(jobInfo.getJobDesc())) { + return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) ); } - if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) ); + if (StringTool.isBlank(jobInfo.getAuthor())) { + return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) ); } // valid trigger ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null); if (scheduleTypeEnum == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } if (scheduleTypeEnum == ScheduleTypeEnum.CRON) { if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) { - return new ReturnT(ReturnT.FAIL_CODE, "Cron"+I18nUtil.getString("system_unvalid")); + return Response.ofFail ( "Cron"+I18nUtil.getString("system_invalid")); } } else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE/* || scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) { if (jobInfo.getScheduleConf() == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")) ); } try { - int fixSecond = Integer.valueOf(jobInfo.getScheduleConf()); + int fixSecond = Integer.parseInt(jobInfo.getScheduleConf()); if (fixSecond < 1) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } } catch (Exception e) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } } // valid job if (GlueTypeEnum.match(jobInfo.getGlueType()) == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_gluetype")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("jobinfo_field_gluetype")+I18nUtil.getString("system_invalid")) ); } - if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && (jobInfo.getExecutorHandler()==null || jobInfo.getExecutorHandler().trim().length()==0) ) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+"JobHandler") ); + if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && StringTool.isBlank(jobInfo.getExecutorHandler()) ) { + return Response.ofFail ( (I18nUtil.getString("system_please_input")+"JobHandler") ); } // 》fix "\r" in shell if (GlueTypeEnum.GLUE_SHELL==GlueTypeEnum.match(jobInfo.getGlueType()) && jobInfo.getGlueSource()!=null) { @@ -112,32 +117,33 @@ public class XxlJobServiceImpl implements XxlJobService { // valid advanced if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_invalid")) ); } if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_invalid")) ); } if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_invalid")) ); } // 》ChildJobId valid - if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) { + if (StringTool.isNotBlank(jobInfo.getChildJobId())) { String[] childJobIds = jobInfo.getChildJobId().split(","); for (String childJobIdItem: childJobIds) { - if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) { - XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.parseInt(childJobIdItem)); + if (StringTool.isNotBlank(childJobIdItem) && StringTool.isNumeric(childJobIdItem)) { + XxlJobInfo childJobInfo = xxlJobInfoMapper.loadById(Integer.parseInt(childJobIdItem)); if (childJobInfo==null) { - return new ReturnT(ReturnT.FAIL_CODE, + return Response.ofFail ( MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem)); } - if (!loginUser.validPermission(childJobInfo.getJobGroup())) { - return new ReturnT(ReturnT.FAIL_CODE, + // valid jobGroup permission + if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, childJobInfo.getJobGroup())) { + return Response.ofFail ( MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_permission_limit")), childJobIdItem)); } } else { - return new ReturnT(ReturnT.FAIL_CODE, - MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem)); + return Response.ofFail ( + MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_invalid")), childJobIdItem)); } } @@ -155,92 +161,90 @@ public class XxlJobServiceImpl implements XxlJobService { jobInfo.setAddTime(new Date()); jobInfo.setUpdateTime(new Date()); jobInfo.setGlueUpdatetime(new Date()); - xxlJobInfoDao.save(jobInfo); + // remove the whitespace + jobInfo.setExecutorHandler(jobInfo.getExecutorHandler().trim()); + xxlJobInfoMapper.save(jobInfo); if (jobInfo.getId() < 1) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_add")+I18nUtil.getString("system_fail")) ); + return Response.ofFail ( (I18nUtil.getString("jobinfo_field_add")+I18nUtil.getString("system_fail")) ); } - return new ReturnT(String.valueOf(jobInfo.getId())); - } + // write operation log + logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}", + loginInfo.getUserName(), "jobinfo-save", GsonTool.toJson(jobInfo)); - private boolean isNumeric(String str){ - try { - int result = Integer.valueOf(str); - return true; - } catch (NumberFormatException e) { - return false; - } + return Response.ofSuccess(String.valueOf(jobInfo.getId())); } @Override - public ReturnT update(XxlJobInfo jobInfo, XxlJobUser loginUser) { + public Response update(XxlJobInfo jobInfo, LoginInfo loginInfo) { // valid base - if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) ); + if (StringTool.isBlank(jobInfo.getJobDesc())) { + return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) ); } - if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) ); + if (StringTool.isBlank(jobInfo.getAuthor())) { + return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) ); } // valid trigger ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null); if (scheduleTypeEnum == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } if (scheduleTypeEnum == ScheduleTypeEnum.CRON) { if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) { - return new ReturnT(ReturnT.FAIL_CODE, "Cron"+I18nUtil.getString("system_unvalid") ); + return Response.ofFail ( "Cron"+I18nUtil.getString("system_invalid") ); } } else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE /*|| scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) { if (jobInfo.getScheduleConf() == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } try { - int fixSecond = Integer.valueOf(jobInfo.getScheduleConf()); + int fixSecond = Integer.parseInt(jobInfo.getScheduleConf()); if (fixSecond < 1) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } } catch (Exception e) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } } // valid advanced if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_invalid")) ); } if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_invalid")) ); } if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_invalid")) ); } // 》ChildJobId valid - if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) { + if (StringTool.isNotBlank(jobInfo.getChildJobId())) { String[] childJobIds = jobInfo.getChildJobId().split(","); for (String childJobIdItem: childJobIds) { - if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) { + if (StringTool.isNotBlank(childJobIdItem) && StringTool.isNumeric(childJobIdItem)) { // parse child int childJobId = Integer.parseInt(childJobIdItem); if (childJobId == jobInfo.getId()) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_childJobId")+"("+childJobId+")"+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("jobinfo_field_childJobId")+"("+childJobId+")"+I18nUtil.getString("system_invalid")) ); } // valid child - XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(childJobId); + XxlJobInfo childJobInfo = xxlJobInfoMapper.loadById(childJobId); if (childJobInfo==null) { - return new ReturnT(ReturnT.FAIL_CODE, + return Response.ofFail ( MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem)); } - if (!loginUser.validPermission(childJobInfo.getJobGroup())) { - return new ReturnT(ReturnT.FAIL_CODE, + // valid jobGroup permission + if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, childJobInfo.getJobGroup())) { + return Response.ofFail ( MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_permission_limit")), childJobIdItem)); } } else { - return new ReturnT(ReturnT.FAIL_CODE, - MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem)); + return Response.ofFail ( + MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_invalid")), childJobIdItem)); } } @@ -255,30 +259,32 @@ public class XxlJobServiceImpl implements XxlJobService { } // group valid - XxlJobGroup jobGroup = xxlJobGroupDao.load(jobInfo.getJobGroup()); + XxlJobGroup jobGroup = xxlJobGroupMapper.load(jobInfo.getJobGroup()); if (jobGroup == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_jobgroup")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("jobinfo_field_jobgroup")+I18nUtil.getString("system_invalid")) ); } // stage job info - XxlJobInfo exists_jobInfo = xxlJobInfoDao.loadById(jobInfo.getId()); + XxlJobInfo exists_jobInfo = xxlJobInfoMapper.loadById(jobInfo.getId()); if (exists_jobInfo == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_not_found")) ); + return Response.ofFail ( (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_not_found")) ); } // next trigger time (5s后生效,避开预读周期) long nextTriggerTime = exists_jobInfo.getTriggerNextTime(); - boolean scheduleDataNotChanged = jobInfo.getScheduleType().equals(exists_jobInfo.getScheduleType()) && jobInfo.getScheduleConf().equals(exists_jobInfo.getScheduleConf()); - if (exists_jobInfo.getTriggerStatus() == 1 && !scheduleDataNotChanged) { + boolean scheduleDataNotChanged = jobInfo.getScheduleType().equals(exists_jobInfo.getScheduleType()) + && jobInfo.getScheduleConf().equals(exists_jobInfo.getScheduleConf()); // 触发配置如果不变,避免重复计算; + if (exists_jobInfo.getTriggerStatus() == TriggerStatus.RUNNING.getValue() && !scheduleDataNotChanged) { try { - Date nextValidTime = JobScheduleHelper.generateNextValidTime(jobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS)); + // generate next trigger time + Date nextValidTime = scheduleTypeEnum.getScheduleType().generateNextTriggerTime(jobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS)); if (nextValidTime == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } nextTriggerTime = nextValidTime.getTime(); } catch (Exception e) { logger.error(e.getMessage(), e); - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } } @@ -290,7 +296,8 @@ public class XxlJobServiceImpl implements XxlJobService { exists_jobInfo.setScheduleConf(jobInfo.getScheduleConf()); exists_jobInfo.setMisfireStrategy(jobInfo.getMisfireStrategy()); exists_jobInfo.setExecutorRouteStrategy(jobInfo.getExecutorRouteStrategy()); - exists_jobInfo.setExecutorHandler(jobInfo.getExecutorHandler()); + // remove the whitespace + exists_jobInfo.setExecutorHandler(jobInfo.getExecutorHandler().trim()); exists_jobInfo.setExecutorParam(jobInfo.getExecutorParam()); exists_jobInfo.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy()); exists_jobInfo.setExecutorTimeout(jobInfo.getExecutorTimeout()); @@ -299,84 +306,126 @@ public class XxlJobServiceImpl implements XxlJobService { exists_jobInfo.setTriggerNextTime(nextTriggerTime); exists_jobInfo.setUpdateTime(new Date()); - xxlJobInfoDao.update(exists_jobInfo); + xxlJobInfoMapper.update(exists_jobInfo); + // write operation log + logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}", + loginInfo.getUserName(), "jobinfo-update", GsonTool.toJson(exists_jobInfo)); - return ReturnT.SUCCESS; + return Response.ofSuccess(); } @Override - public ReturnT remove(int id) { - XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id); + public Response remove(int id, LoginInfo loginInfo) { + // valid job + XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(id); if (xxlJobInfo == null) { - return ReturnT.SUCCESS; + return Response.ofSuccess(); + } + + // valid jobGroup permission + if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, xxlJobInfo.getJobGroup())) { + return Response.ofFail(I18nUtil.getString("system_permission_limit")); } - xxlJobInfoDao.delete(id); - xxlJobLogDao.delete(id); - xxlJobLogGlueDao.deleteByJobId(id); - return ReturnT.SUCCESS; + xxlJobInfoMapper.delete(id); + xxlJobLogMapper.delete(id); + xxlJobLogGlueMapper.deleteByJobId(id); + + // write operation log + logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}", + loginInfo.getUserName(), "jobinfo-remove", id); + + return Response.ofSuccess(); } @Override - public ReturnT start(int id) { - XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id); + public Response start(int id, LoginInfo loginInfo) { + // load and valid + XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(id); + if (xxlJobInfo == null) { + return Response.ofFail(I18nUtil.getString("jobinfo_glue_jobid_invalid")); + } - // valid + // valid jobGroup permission + if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, xxlJobInfo.getJobGroup())) { + return Response.ofFail(I18nUtil.getString("system_permission_limit")); + } + + // valid ScheduleType: can not be none ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(xxlJobInfo.getScheduleType(), ScheduleTypeEnum.NONE); if (ScheduleTypeEnum.NONE == scheduleTypeEnum) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type_none_limit_start")) ); + return Response.ofFail(I18nUtil.getString("schedule_type_none_limit_start")); } // next trigger time (5s后生效,避开预读周期) long nextTriggerTime = 0; try { - Date nextValidTime = JobScheduleHelper.generateNextValidTime(xxlJobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS)); + // generate next trigger time + Date nextValidTime = scheduleTypeEnum.getScheduleType().generateNextTriggerTime(xxlJobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS)); + if (nextValidTime == null) { - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } nextTriggerTime = nextValidTime.getTime(); } catch (Exception e) { logger.error(e.getMessage(), e); - return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); + return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) ); } - xxlJobInfo.setTriggerStatus(1); + xxlJobInfo.setTriggerStatus(TriggerStatus.RUNNING.getValue()); xxlJobInfo.setTriggerLastTime(0); xxlJobInfo.setTriggerNextTime(nextTriggerTime); xxlJobInfo.setUpdateTime(new Date()); - xxlJobInfoDao.update(xxlJobInfo); - return ReturnT.SUCCESS; + xxlJobInfoMapper.update(xxlJobInfo); + + // write operation log + logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}", + loginInfo.getUserName(), "jobinfo-start", id); + + return Response.ofSuccess(); } @Override - public ReturnT stop(int id) { - XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id); + public Response stop(int id, LoginInfo loginInfo) { + // load and valid + XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(id); + if (xxlJobInfo == null) { + return Response.ofFail(I18nUtil.getString("jobinfo_glue_jobid_invalid")); + } - xxlJobInfo.setTriggerStatus(0); + // valid jobGroup permission + if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, xxlJobInfo.getJobGroup())) { + return Response.ofFail(I18nUtil.getString("system_permission_limit")); + } + + // stop + xxlJobInfo.setTriggerStatus(TriggerStatus.STOPPED.getValue()); xxlJobInfo.setTriggerLastTime(0); xxlJobInfo.setTriggerNextTime(0); xxlJobInfo.setUpdateTime(new Date()); - xxlJobInfoDao.update(xxlJobInfo); - return ReturnT.SUCCESS; - } + xxlJobInfoMapper.update(xxlJobInfo); + // write operation log + logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}", + loginInfo.getUserName(), "jobinfo-stop", id); + return Response.ofSuccess(); + } @Override - public ReturnT trigger(XxlJobUser loginUser, int jobId, String executorParam, String addressList) { - // permission - if (loginUser == null) { - return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("system_permission_limit")); - } - XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(jobId); + public Response trigger(LoginInfo loginInfo, int jobId, String executorParam, String addressList) { + // valid job + XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(jobId); if (xxlJobInfo == null) { - return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("jobinfo_glue_jobid_unvalid")); + return Response.ofFail(I18nUtil.getString("jobinfo_glue_jobid_invalid")); } - if (!hasPermission(loginUser, xxlJobInfo.getJobGroup())) { - return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("system_permission_limit")); + + // valid jobGroup permission + if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, xxlJobInfo.getJobGroup())) { + return Response.ofFail(I18nUtil.getString("system_permission_limit")); } // force cover job param @@ -384,28 +433,22 @@ public class XxlJobServiceImpl implements XxlJobService { executorParam = ""; } - JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList); - return ReturnT.SUCCESS; - } + XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobId, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList); - private boolean hasPermission(XxlJobUser loginUser, int jobGroup){ - if (loginUser.getRole() == 1) { - return true; - } - List groupIdStrs = new ArrayList<>(); - if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) { - groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(",")); - } - return groupIdStrs.contains(String.valueOf(jobGroup)); + // write operation log + logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}", + loginInfo.getUserName(), "jobinfo-trigger", jobId); + + return Response.ofSuccess(); } @Override public Map dashboardInfo() { - int jobInfoCount = xxlJobInfoDao.findAllCount(); + int jobInfoCount = xxlJobInfoMapper.findAllCount(); int jobLogCount = 0; int jobLogSuccessCount = 0; - XxlJobLogReport xxlJobLogReport = xxlJobLogReportDao.queryLogReportTotal(); + XxlJobLogReport xxlJobLogReport = xxlJobLogReportMapper.queryLogReportTotal(); if (xxlJobLogReport != null) { jobLogCount = xxlJobLogReport.getRunningCount() + xxlJobLogReport.getSucCount() + xxlJobLogReport.getFailCount(); jobLogSuccessCount = xxlJobLogReport.getSucCount(); @@ -413,7 +456,7 @@ public class XxlJobServiceImpl implements XxlJobService { // executor count Set executorAddressSet = new HashSet(); - List groupList = xxlJobGroupDao.findAll(); + List groupList = xxlJobGroupMapper.findAll(); if (groupList!=null && !groupList.isEmpty()) { for (XxlJobGroup group: groupList) { @@ -434,7 +477,7 @@ public class XxlJobServiceImpl implements XxlJobService { } @Override - public ReturnT> chartInfo(Date startDate, Date endDate) { + public Response> chartInfo(Date startDate, Date endDate) { // process List triggerDayList = new ArrayList(); @@ -445,11 +488,11 @@ public class XxlJobServiceImpl implements XxlJobService { int triggerCountSucTotal = 0; int triggerCountFailTotal = 0; - List logReportList = xxlJobLogReportDao.queryLogReport(startDate, endDate); + List logReportList = xxlJobLogReportMapper.queryLogReport(startDate, endDate); - if (logReportList!=null && logReportList.size()>0) { + if (logReportList!=null && !logReportList.isEmpty()) { for (XxlJobLogReport item: logReportList) { - String day = DateUtil.formatDate(item.getTriggerDay()); + String day = DateTool.formatDate(item.getTriggerDay()); int triggerDayCountRunning = item.getRunningCount(); int triggerDayCountSuc = item.getSucCount(); int triggerDayCountFail = item.getFailCount(); @@ -465,7 +508,7 @@ public class XxlJobServiceImpl implements XxlJobService { } } else { for (int i = -6; i <= 0; i++) { - triggerDayList.add(DateUtil.formatDate(DateUtil.addDays(new Date(), i))); + triggerDayList.add(DateTool.formatDate(DateTool.addDays(new Date(), i))); triggerDayCountRunningList.add(0); triggerDayCountSucList.add(0); triggerDayCountFailList.add(0); @@ -482,7 +525,7 @@ public class XxlJobServiceImpl implements XxlJobService { result.put("triggerCountSucTotal", triggerCountSucTotal); result.put("triggerCountFailTotal", triggerCountFailTotal); - return new ReturnT>(result); + return Response.ofSuccess(result); } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/util/I18nUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/I18nUtil.java new file mode 100644 index 00000000..dc840b59 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/I18nUtil.java @@ -0,0 +1,131 @@ +package com.xxl.job.admin.util; + +import com.xxl.job.core.constant.ExecutorBlockStrategyEnum; +import com.xxl.tool.core.PropTool; +import com.xxl.tool.freemarker.FtlTool; +import com.xxl.tool.json.GsonTool; +import freemarker.template.Configuration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * i18n util + * + * @author xuxueli 2018-01-17 20:39:06 + */ +@Component +public class I18nUtil implements InitializingBean { + private static Logger logger = LoggerFactory.getLogger(I18nUtil.class); + + // ---------------------- for i18n config ---------------------- + + /** + * i18n config + */ + @Value("${xxl.job.i18n}") + private String i18n; + + /** + * freemarker config + */ + @Autowired + private Configuration configuration; + + @Override + public void afterPropertiesSet() throws Exception { + // init freemarker shared variable + configuration.setSharedVariable("I18nUtil", FtlTool.generateStaticModel(I18nUtil.class.getName())); + // init single + single = this; + + // init i18n-enum + initI18nEnum(); + } + + /** + * get i18n + */ + public String getI18n() { + if (!Arrays.asList("zh_CN", "zh_TC", "en").contains(i18n)) { + return "zh_CN"; + } + return i18n; + } + + private static I18nUtil single = null; + private static I18nUtil getSingle() { + return single; + } + + // ---------------------- tool ---------------------- + + private static Properties prop = null; + public static Properties loadI18nProp(){ + if (prop != null) { + return prop; + } + // build i18n filepath + String i18n = getSingle().getI18n(); + String i18nFile = MessageFormat.format("i18n/message_{0}.properties", i18n); + + // load prop + prop = PropTool.loadProp(i18nFile); + return prop; + } + + /** + * get val of i18n key + * + * @param key + * @return + */ + public static String getString(String key) { + return loadI18nProp().getProperty(key); + } + + /** + * get mult val of i18n mult key, as json + * + * @param keys + * @return + */ + public static String getMultString(String... keys) { + Map map = new HashMap<>(); + + Properties prop = loadI18nProp(); + if (keys!=null && keys.length>0) { + for (String key: keys) { + map.put(key, prop.getProperty(key)); + } + } else { + for (String key: prop.stringPropertyNames()) { + map.put(key, prop.getProperty(key)); + } + } + + return GsonTool.toJson(map); + } + + + // ---------------------- init I18n-enum ---------------------- + + /** + * init i18n-enum + */ + private void initI18nEnum(){ + for (ExecutorBlockStrategyEnum item : ExecutorBlockStrategyEnum.values()) { + item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name()))); + } + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/util/JobGroupPermissionUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/JobGroupPermissionUtil.java new file mode 100644 index 00000000..08603fbb --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/JobGroupPermissionUtil.java @@ -0,0 +1,67 @@ +package com.xxl.job.admin.util; + +import com.xxl.job.admin.constant.Consts; +import com.xxl.job.admin.model.XxlJobGroup; +import com.xxl.sso.core.helper.XxlSsoHelper; +import com.xxl.sso.core.model.LoginInfo; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.response.Response; +import jakarta.servlet.http.HttpServletRequest; + +import java.util.ArrayList; +import java.util.List; + +/** + * jobGroup permission util + * + * @author xuxueli 2025-08-24 + */ +public class JobGroupPermissionUtil { + + /** + * check if has jobgroup permission + */ + public static boolean hasJobGroupPermission(LoginInfo loginInfo, int jobGroup){ + if (XxlSsoHelper.hasRole(loginInfo, Consts.ADMIN_ROLE).isSuccess()) { + return true; + } else { + List jobGroups = (loginInfo.getExtraInfo()!=null && loginInfo.getExtraInfo().containsKey("jobGroups")) + ? StringTool.split(loginInfo.getExtraInfo().get("jobGroups"), ",") :new ArrayList<>(); + return jobGroups.contains(String.valueOf(jobGroup)); + } + } + + /** + * valid jobGroup permission + */ + public static LoginInfo validJobGroupPermission(HttpServletRequest request, int jobGroup) { + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + if (!(loginInfoResponse.isSuccess() && hasJobGroupPermission(loginInfoResponse.getData(), jobGroup))) { + throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginInfoResponse.getData().getUserName() +"]"); + } + return loginInfoResponse.getData(); + } + + /** + * filter jobGroupList by permission + */ + public static List filterJobGroupByPermission(HttpServletRequest request, List jobGroupListTotal){ + Response loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request); + + if (XxlSsoHelper.hasRole(loginInfoResponse.getData(), Consts.ADMIN_ROLE).isSuccess()) { + return jobGroupListTotal; + } else { + List jobGroups = (loginInfoResponse.getData().getExtraInfo()!=null + && loginInfoResponse.getData().getExtraInfo().get("jobGroups")!=null + ) + ? StringTool.split(loginInfoResponse.getData().getExtraInfo().get("jobGroups"), ",") + :new ArrayList<>(); + + return jobGroupListTotal + .stream() + .filter(jobGroup -> jobGroups.contains(String.valueOf(jobGroup.getId()))) + .toList(); + } + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/CommonDataInterceptor.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/CommonDataInterceptor.java new file mode 100644 index 00000000..7bd49e5c --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/CommonDataInterceptor.java @@ -0,0 +1,39 @@ +//package com.xxl.job.admin.web.interceptor; +// +//import com.xxl.job.admin.util.I18nUtil; +//import com.xxl.tool.freemarker.FtlTool; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.web.servlet.AsyncHandlerInterceptor; +//import org.springframework.web.servlet.ModelAndView; +//import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +// +///** +// * push cookies to model as cookieMap +// * +// * @author xuxueli 2015-12-12 18:09:04 +// */ +//@Configuration +//public class CommonDataInterceptor implements WebMvcConfigurer { +// +// @Override +// public void addInterceptors(InterceptorRegistry registry) { +// registry.addInterceptor(new AsyncHandlerInterceptor() { +// @Override +// public void postHandle(HttpServletRequest request, +// HttpServletResponse response, +// Object handler, +// ModelAndView modelAndView) throws Exception { +// +// // static method +// if (modelAndView != null) { +// modelAndView.addObject("I18nUtil", FtlTool.generateStaticModel(I18nUtil.class.getName())); +// } +// +// } +// }).addPathPatterns("/**"); +// } +// +//} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/CookieUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/CookieUtil.java new file mode 100644 index 00000000..f545204d --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/CookieUtil.java @@ -0,0 +1,98 @@ +//package com.xxl.job.admin.util; +// +//import jakarta.servlet.http.Cookie; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +// +///** +// * Cookie.Util +// * +// * @author xuxueli 2015-12-12 18:01:06 +// */ +//public class CookieUtil { +// +// // 默认缓存时间,单位/秒, 2H +// private static final int COOKIE_MAX_AGE = Integer.MAX_VALUE; +// // 保存路径,根路径 +// private static final String COOKIE_PATH = "/"; +// +// /** +// * 保存 +// * +// * @param response +// * @param key +// * @param value +// * @param ifRemember +// */ +// public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) { +// int age = ifRemember?COOKIE_MAX_AGE:-1; +// set(response, key, value, null, COOKIE_PATH, age, true); +// } +// +// /** +// * 保存 +// * +// * @param response +// * @param key +// * @param value +// * @param maxAge +// */ +// private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) { +// Cookie cookie = new Cookie(key, value); +// if (domain != null) { +// cookie.setDomain(domain); +// } +// cookie.setPath(path); +// cookie.setMaxAge(maxAge); +// cookie.setHttpOnly(isHttpOnly); +// response.addCookie(cookie); +// } +// +// /** +// * 查询value +// * +// * @param request +// * @param key +// * @return +// */ +// public static String getValue(HttpServletRequest request, String key) { +// Cookie cookie = get(request, key); +// if (cookie != null) { +// return cookie.getValue(); +// } +// return null; +// } +// +// /** +// * 查询Cookie +// * +// * @param request +// * @param key +// */ +// private static Cookie get(HttpServletRequest request, String key) { +// Cookie[] arr_cookie = request.getCookies(); +// if (arr_cookie != null && arr_cookie.length > 0) { +// for (Cookie cookie : arr_cookie) { +// if (cookie.getName().equals(key)) { +// return cookie; +// } +// } +// } +// return null; +// } +// +// /** +// * 删除Cookie +// * +// * @param request +// * @param response +// * @param key +// */ +// public static void remove(HttpServletRequest request, HttpServletResponse response, String key) { +// Cookie cookie = get(request, key); +// if (cookie != null) { +// set(response, key, "", null, COOKIE_PATH, 0, true); +// } +// } +// +//} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/FtlUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/FtlUtil.java new file mode 100644 index 00000000..3dcf25f5 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/FtlUtil.java @@ -0,0 +1,31 @@ +//package com.xxl.job.admin.util; +// +//import freemarker.ext.beans.BeansWrapper; +//import freemarker.ext.beans.BeansWrapperBuilder; +//import freemarker.template.Configuration; +//import freemarker.template.TemplateHashModel; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +///** +// * ftl util +// * +// * @author xuxueli 2018-01-17 20:37:48 +// */ +//public class FtlUtil { +// private static Logger logger = LoggerFactory.getLogger(FtlUtil.class); +// +// private static BeansWrapper wrapper = new BeansWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build(); //BeansWrapper.getDefaultInstance(); +// +// public static TemplateHashModel generateStaticModel(String packageName) { +// try { +// TemplateHashModel staticModels = wrapper.getStaticModels(); +// TemplateHashModel fileStatics = (TemplateHashModel) staticModels.get(packageName); +// return fileStatics; +// } catch (Exception e) { +// logger.error(e.getMessage(), e); +// } +// return null; +// } +// +//} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/JacksonUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/JacksonUtil.java new file mode 100644 index 00000000..61ef49ea --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/JacksonUtil.java @@ -0,0 +1,92 @@ +//package com.xxl.job.admin.util.old; +// +//import com.fasterxml.jackson.core.JsonGenerationException; +//import com.fasterxml.jackson.core.JsonParseException; +//import com.fasterxml.jackson.databind.JavaType; +//import com.fasterxml.jackson.databind.JsonMappingException; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.IOException; +// +///** +// * Jackson util +// * +// * 1、obj need private and set/get; +// * 2、do not support inner class; +// * +// * @author xuxueli 2015-9-25 18:02:56 +// */ +//public class JacksonUtil { +// private static Logger logger = LoggerFactory.getLogger(JacksonUtil.class); +// +// private final static ObjectMapper objectMapper = new ObjectMapper(); +// public static ObjectMapper getInstance() { +// return objectMapper; +// } +// +// /** +// * bean、array、List、Map --> json +// * +// * @param obj +// * @return json string +// * @throws Exception +// */ +// public static String writeValueAsString(Object obj) { +// try { +// return getInstance().writeValueAsString(obj); +// } catch (JsonGenerationException e) { +// logger.error(e.getMessage(), e); +// } catch (JsonMappingException e) { +// logger.error(e.getMessage(), e); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } +// return null; +// } +// +// /** +// * string --> bean、Map、List(array) +// * +// * @param jsonStr +// * @param clazz +// * @return obj +// * @throws Exception +// */ +// public static T readValue(String jsonStr, Class clazz) { +// try { +// return getInstance().readValue(jsonStr, clazz); +// } catch (JsonParseException e) { +// logger.error(e.getMessage(), e); +// } catch (JsonMappingException e) { +// logger.error(e.getMessage(), e); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } +// return null; +// } +// +// /** +// * string --> List... +// * +// * @param jsonStr +// * @param parametrized +// * @param parameterClasses +// * @param +// * @return +// */ +// public static T readValue(String jsonStr, Class parametrized, Class... parameterClasses) { +// try { +// JavaType javaType = getInstance().getTypeFactory().constructParametricType(parametrized, parameterClasses); +// return getInstance().readValue(jsonStr, javaType); +// } catch (JsonParseException e) { +// logger.error(e.getMessage(), e); +// } catch (JsonMappingException e) { +// logger.error(e.getMessage(), e); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } +// return null; +// } +//} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/LocalCacheUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/LocalCacheUtil.java new file mode 100644 index 00000000..8026ad39 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/LocalCacheUtil.java @@ -0,0 +1,133 @@ +//package com.xxl.job.admin.util; +// +//import java.util.concurrent.ConcurrentHashMap; +//import java.util.concurrent.ConcurrentMap; +// +///** +// * local cache tool +// * +// * @author xuxueli 2018-01-22 21:37:34 +// */ +//public class LocalCacheUtil { +// +// private static ConcurrentMap cacheRepository = new ConcurrentHashMap(); // 类型建议用抽象父类,兼容性更好; +// private static class LocalCacheData{ +// private String key; +// private Object val; +// private long timeoutTime; +// +// public LocalCacheData() { +// } +// +// public LocalCacheData(String key, Object val, long timeoutTime) { +// this.key = key; +// this.val = val; +// this.timeoutTime = timeoutTime; +// } +// +// public String getKey() { +// return key; +// } +// +// public void setKey(String key) { +// this.key = key; +// } +// +// public Object getVal() { +// return val; +// } +// +// public void setVal(Object val) { +// this.val = val; +// } +// +// public long getTimeoutTime() { +// return timeoutTime; +// } +// +// public void setTimeoutTime(long timeoutTime) { +// this.timeoutTime = timeoutTime; +// } +// } +// +// +// /** +// * set cache +// * +// * @param key +// * @param val +// * @param cacheTime +// * @return +// */ +// public static boolean set(String key, Object val, long cacheTime){ +// +// // clean timeout cache, before set new cache (avoid cache too much) +// cleanTimeoutCache(); +// +// // set new cache +// if (key==null || key.trim().length()==0) { +// return false; +// } +// if (val == null) { +// remove(key); +// } +// if (cacheTime <= 0) { +// remove(key); +// } +// long timeoutTime = System.currentTimeMillis() + cacheTime; +// LocalCacheData localCacheData = new LocalCacheData(key, val, timeoutTime); +// cacheRepository.put(localCacheData.getKey(), localCacheData); +// return true; +// } +// +// /** +// * remove cache +// * +// * @param key +// * @return +// */ +// public static boolean remove(String key){ +// if (key==null || key.trim().length()==0) { +// return false; +// } +// cacheRepository.remove(key); +// return true; +// } +// +// /** +// * get cache +// * +// * @param key +// * @return +// */ +// public static Object get(String key){ +// if (key==null || key.trim().length()==0) { +// return null; +// } +// LocalCacheData localCacheData = cacheRepository.get(key); +// if (localCacheData!=null && System.currentTimeMillis()=localCacheData.getTimeoutTime()) { +// cacheRepository.remove(key); +// } +// } +// } +// return true; +// } +// +//} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/RemoteHttpJobBean.java similarity index 100% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/RemoteHttpJobBean.java diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/XxlJobDynamicScheduler.java similarity index 100% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/XxlJobDynamicScheduler.java diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/XxlJobThreadPool.java similarity index 100% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/util/old/XxlJobThreadPool.java diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/web/error/WebErrorPageRegistrar.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/web/error/WebErrorPageRegistrar.java new file mode 100644 index 00000000..89a42dc6 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/web/error/WebErrorPageRegistrar.java @@ -0,0 +1,19 @@ +package com.xxl.job.admin.web.error; + +import org.springframework.boot.web.error.ErrorPage; +import org.springframework.boot.web.error.ErrorPageRegistrar; +import org.springframework.boot.web.error.ErrorPageRegistry; +import org.springframework.stereotype.Component; + +/** + * error page + */ +@Component +public class WebErrorPageRegistrar implements ErrorPageRegistrar { + + @Override + public void registerErrorPages(ErrorPageRegistry registry) { + ErrorPage errorPage = new ErrorPage("/errorpage"); + registry.addErrorPages(errorPage); + } +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/resolver/WebExceptionResolver.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/web/error/WebHandlerExceptionResolver.java similarity index 50% rename from xxl-job-admin/src/main/java/com/xxl/job/admin/controller/resolver/WebExceptionResolver.java rename to xxl-job-admin/src/main/java/com/xxl/job/admin/web/error/WebHandlerExceptionResolver.java index 114407b6..d9b77bb1 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/resolver/WebExceptionResolver.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/web/error/WebHandlerExceptionResolver.java @@ -1,8 +1,10 @@ -package com.xxl.job.admin.controller.resolver; +package com.xxl.job.admin.web.error; -import com.xxl.job.admin.core.exception.XxlJobException; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.admin.core.util.JacksonUtil; +import com.xxl.job.admin.scheduler.exception.XxlJobException; +import com.xxl.tool.json.GsonTool; +import com.xxl.tool.response.Response; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -11,8 +13,6 @@ import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** @@ -21,46 +21,45 @@ import java.io.IOException; * @author xuxueli 2016-1-6 19:22:18 */ @Component -public class WebExceptionResolver implements HandlerExceptionResolver { - private static transient Logger logger = LoggerFactory.getLogger(WebExceptionResolver.class); +public class WebHandlerExceptionResolver implements HandlerExceptionResolver { + private static transient Logger logger = LoggerFactory.getLogger(WebHandlerExceptionResolver.class); @Override - public ModelAndView resolveException(HttpServletRequest request, - HttpServletResponse response, Object handler, Exception ex) { + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { if (!(ex instanceof XxlJobException)) { logger.error("WebExceptionResolver:{}", ex); } - // if json + // parse isJson boolean isJson = false; if (handler instanceof HandlerMethod) { HandlerMethod method = (HandlerMethod)handler; - ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class); - if (responseBody != null) { - isJson = true; - } + isJson = method.getMethodAnnotation(ResponseBody.class)!=null; } - // error result - ReturnT errorResult = new ReturnT(ReturnT.FAIL_CODE, ex.toString().replaceAll("\n", "
")); - - // response + // process error ModelAndView mv = new ModelAndView(); if (isJson) { try { - response.setContentType("application/json;charset=utf-8"); - response.getWriter().print(JacksonUtil.writeValueAsString(errorResult)); + // errorMsg + String errorMsg = GsonTool.toJson(Response.ofFail(ex.toString())); + + // write response + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().println(errorMsg); } catch (IOException e) { logger.error(e.getMessage(), e); } return mv; } else { - mv.addObject("exceptionMsg", errorResult.getMsg()); - mv.setViewName("/common/common.exception"); + mv.addObject("exceptionMsg", ex.toString()); + mv.setViewName("common/common.errorpage"); return mv; } + } } \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/web/xxlsso/SimpleLoginStore.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/web/xxlsso/SimpleLoginStore.java new file mode 100644 index 00000000..0f4815e0 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/web/xxlsso/SimpleLoginStore.java @@ -0,0 +1,84 @@ +package com.xxl.job.admin.web.xxlsso; + +import com.xxl.job.admin.constant.Consts; +import com.xxl.job.admin.mapper.XxlJobUserMapper; +import com.xxl.job.admin.model.XxlJobUser; +import com.xxl.sso.core.model.LoginInfo; +import com.xxl.sso.core.store.LoginStore; +import com.xxl.tool.core.MapTool; +import com.xxl.tool.response.Response; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * Simple LoginStore + * + * 1、store by database; + * 2、If you have higher performance requirements, it is recommended to use RedisLoginStore; + * + * @author xuxueli 2025-08-03 + */ +@Component +public class SimpleLoginStore implements LoginStore { + + + @Resource + private XxlJobUserMapper xxlJobUserMapper; + + + @Override + public Response set(LoginInfo loginInfo) { + + // parse token-signature + String token_sign = loginInfo.getSignature(); + + // write token by UserId + int ret = xxlJobUserMapper.updateToken(Integer.parseInt(loginInfo.getUserId()), token_sign); + return ret > 0 ? Response.ofSuccess() : Response.ofFail("token set fail"); + } + + @Override + public Response update(LoginInfo loginInfo) { + return Response.ofFail("not support"); + } + + @Override + public Response remove(String userId) { + // delete token-signature + int ret = xxlJobUserMapper.updateToken(Integer.parseInt(userId), ""); + return ret > 0 ? Response.ofSuccess() : Response.ofFail("token remove fail"); + } + + /** + * check through DB query + */ + @Override + public Response get(String userId) { + + // load login-user + XxlJobUser user = xxlJobUserMapper.loadById(Integer.parseInt(userId)); + if (user == null) { + return Response.ofFail("userId invalid."); + } + + // parse role + List roleList = user.getRole()==1? List.of(Consts.ADMIN_ROLE):null; + + // parse jobGroup permission + Map extraInfo = MapTool.newMap( + "jobGroups", user.getPermission() + ); + + // build LoginInfo + LoginInfo loginInfo = new LoginInfo(userId, user.getToken()); + loginInfo.setUserName(user.getUsername()); + loginInfo.setRoleList(roleList); + loginInfo.setExtraInfo(extraInfo); + + return Response.ofSuccess(loginInfo); + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/web/xxlsso/XxlSsoConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/web/xxlsso/XxlSsoConfig.java new file mode 100644 index 00000000..6b10f873 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/web/xxlsso/XxlSsoConfig.java @@ -0,0 +1,62 @@ +package com.xxl.job.admin.web.xxlsso; + +import com.xxl.sso.core.auth.interceptor.XxlSsoWebInterceptor; +import com.xxl.sso.core.bootstrap.XxlSsoBootstrap; +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author xuxueli 2018-11-15 + */ +@Configuration +public class XxlSsoConfig implements WebMvcConfigurer { + + + @Value("${xxl-sso.token.key}") + private String tokenKey; + + @Value("${xxl-sso.token.timeout}") + private long tokenTimeout; + + @Value("${xxl-sso.client.excluded.paths}") + private String excludedPaths; + + @Value("${xxl-sso.client.login.path}") + private String loginPath; + + + @Resource + private SimpleLoginStore loginStore; + + + /** + * 1、配置 XxlSsoBootstrap + */ + @Bean(initMethod = "start", destroyMethod = "stop") + public XxlSsoBootstrap xxlSsoBootstrap() { + + XxlSsoBootstrap bootstrap = new XxlSsoBootstrap(); + bootstrap.setLoginStore(loginStore); + bootstrap.setTokenKey(tokenKey); + bootstrap.setTokenTimeout(tokenTimeout); + return bootstrap; + } + + /** + * 2、配置 XxlSso 拦截器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + + // 2.1、build xxl-sso interceptor + XxlSsoWebInterceptor webInterceptor = new XxlSsoWebInterceptor(excludedPaths, loginPath); + + // 2.2、add interceptor + registry.addInterceptor(webInterceptor).addPathPatterns("/**"); + } + +} diff --git a/xxl-job-admin/src/main/resources/application.properties b/xxl-job-admin/src/main/resources/application.properties index 5eb0f736..4ea78699 100644 --- a/xxl-job-admin/src/main/resources/application.properties +++ b/xxl-job-admin/src/main/resources/application.properties @@ -3,7 +3,7 @@ server.port=8080 server.servlet.context-path=/xxl-job-admin ### actuator -management.server.base-path=/actuator +management.endpoints.web.base-path=/actuator management.health.mail.enabled=false ### resources @@ -20,7 +20,7 @@ spring.freemarker.settings.number_format=0.########## spring.freemarker.settings.new_builtin_class_resolver=safer ### mybatis -mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml +mybatis.mapper-locations=classpath:/mapper/*Mapper.xml ### datasource-pool spring.datasource.type=com.zaxxer.hikari.HikariDataSource @@ -54,15 +54,24 @@ spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFact ### xxl-job, access token xxl.job.accessToken=default_token -### xxl-job, access token +### xxl-job, timeout by second, default 3s xxl.job.timeout=3 ### xxl-job, i18n (default is zh_CN, and you can choose "zh_CN", "zh_TC" and "en") xxl.job.i18n=zh_CN ## xxl-job, triggerpool max size -xxl.job.triggerpool.fast.max=200 -xxl.job.triggerpool.slow.max=100 +xxl.job.triggerpool.fast.max=300 +xxl.job.triggerpool.slow.max=200 + +## xxl-job, schedule batch size +xxl.job.schedule.batchsize=100 ### xxl-job, log retention days xxl.job.logretentiondays=30 + +### xxl-sso +xxl-sso.token.key=xxl_job_login_token +xxl-sso.token.timeout=604800000 +xxl-sso.client.excluded.paths= +xxl-sso.client.login.path=/auth/login diff --git a/xxl-job-admin/src/main/resources/i18n/message_en.properties b/xxl-job-admin/src/main/resources/i18n/message_en.properties index 904a43e8..6c5ab9b5 100644 --- a/xxl-job-admin/src/main/resources/i18n/message_en.properties +++ b/xxl-job-admin/src/main/resources/i18n/message_en.properties @@ -1,6 +1,6 @@ admin_name=Scheduling Center -admin_name_full=Distributed Task Scheduling Platform XXL-JOB -admin_version=2.5.0 +admin_name_full=Distributed Task Scheduling Platform|XXL-JOB +admin_version=3.4.0 admin_i18n=en ## system @@ -10,18 +10,16 @@ system_close=Close system_save=Save system_cancel=Cancel system_search=Search +system_reset=Reset system_status=Status system_opt=Operate +system_opt_add=Add system_please_input=please input system_please_choose=please choose system_success=success system_fail=fail -system_add_suc=add success -system_add_fail=add fail -system_update_suc=update success -system_update_fail=update fail +system_error=error system_all=All -system_api_error=net error system_show=Show system_empty=Empty system_opt_suc=operate success @@ -29,16 +27,26 @@ system_opt_fail=operate fail system_opt_edit=Edit system_opt_del=Delete system_opt_copy=Copy -system_unvalid=illegal +system_invalid=illegal system_not_found=not exist system_nav=Navigation system_digits=digits -system_lengh_limit=Length limit +system_length_limit=Length limit system_permission_limit=Permission limit system_welcome=Welcome +system_num_range=Numerical range limit +system_one=One +system_data=Data +system_selected_nothing=No data selected + +## tab +tab_opt=Tab Operation +tab_close_current=Close Current Tabs +tab_close_other=Close Other Tabs +tab_close_all=Close All Tabs +tab_refresh=Refresh Tab ## daterangepicker -daterangepicker_ranges_recent_hour=recent one hour daterangepicker_ranges_today=today daterangepicker_ranges_yesterday=yesterday daterangepicker_ranges_this_month=this month @@ -51,23 +59,6 @@ daterangepicker_custom_endtime=end time daterangepicker_custom_daysofweek=Sun,Mon,Tue,Wed,Thu,Fri,Sat daterangepicker_custom_monthnames=Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec -## dataTable -dataTable_sProcessing=processing... -dataTable_sLengthMenu= _MENU_ records per page -dataTable_sZeroRecords=No matching results -dataTable_sInfo=page _PAGE_ ( Total _PAGES_ pages,_TOTAL_ records ) -dataTable_sInfoEmpty=No Record -dataTable_sInfoFiltered=(Filtered by _MAX_ results) -dataTable_sSearch=Search -dataTable_sEmptyTable=Table data is empty -dataTable_sLoadingRecords=Loading... -dataTable_sFirst=FIRST PAGE -dataTable_sPrevious=Previous Page -dataTable_sNext=Next Page -dataTable_sLast=LAST PAGE -dataTable_sSortAscending=: Rank this column in ascending order -dataTable_sSortDescending=: Rank this column in descending order - ## login login_btn=Login login_remember_me=Remember Me @@ -80,7 +71,7 @@ login_password_lt_4=Password length should not be less than 4 login_success=Login success login_fail=Login fail login_param_empty=Username or password is empty -login_param_unvalid=Username or password error +login_param_invalid=Username or password error ## logout logout_btn=Logout @@ -94,6 +85,13 @@ change_pwd_suc_to_logout=Change password successful, about to log out login change_pwd_field_oldpwd=old password change_pwd_field_newpwd=new password +## change skin +change_skin=Change Theme + +## help +admin_help=User Guide +admin_help_document=Official Documentation + ## dashboard job_dashboard_name=Run report job_dashboard_job_num=Job number @@ -137,11 +135,12 @@ jobinfo_opt_run=Run Once jobinfo_opt_run_tips=Please input the address for this trigger. Null will be obtained from the executor jobinfo_opt_registryinfo=Registry Info jobinfo_opt_next_time=Next trigger time +jobinfo_glue_source=GLUE Source jobinfo_glue_remark=Resource Remark jobinfo_glue_remark_limit=Resource Remark length is limited to 4~100 jobinfo_glue_rollback=Version Backtrack -jobinfo_glue_jobid_unvalid=Job ID is illegal -jobinfo_glue_gluetype_unvalid=The job is not GLUE Type +jobinfo_glue_jobid_invalid=Job ID is illegal +jobinfo_glue_gluetype_invalid=The job is not GLUE Type jobinfo_field_executorTimeout_placeholder=Job Timeout period,in seconds. effect if greater than zero schedule_type=Schedule Type schedule_type_none=None @@ -169,7 +168,7 @@ joblog_field_triggerCode=Trigger Result joblog_field_triggerMsg=Trigger Msg joblog_field_handleTime=Handle Time joblog_field_handleCode=Handle Result -joblog_field_handleMsg=Trigger Msg +joblog_field_handleMsg=Handle Msg joblog_field_executorAddress=Executor Address joblog_clean=Clean joblog_clean_log=Clean Log @@ -183,7 +182,7 @@ joblog_clean_type_6=Clean up log data ten thousand record ago joblog_clean_type_7=Clean up log data thirty thousand record ago joblog_clean_type_8=Clean up log data hundred thousand record ago joblog_clean_type_9=Clean up all log data -joblog_clean_type_unvalid=Clean type is illegal +joblog_clean_type_invalid=Clean type is illegal joblog_handleCode_200=Success joblog_handleCode_500=Fail joblog_handleCode_502=Timeout @@ -195,7 +194,7 @@ joblog_rolling_log=Rolling log joblog_rolling_log_refresh=Refresh joblog_rolling_log_triggerfail=The job trigger fail, can not view the rolling log joblog_rolling_log_failoften=The request for the Rolling log is terminated, the number of failed requests exceeds the limit, Reload the log on the refresh page -joblog_logid_unvalid=Log ID is illegal +joblog_logid_invalid=Log ID is illegal ## job group jobgroup_name=Executor Manage @@ -209,7 +208,7 @@ jobgroup_field_addressType_0=Automatic registration jobgroup_field_addressType_1=Manual registration jobgroup_field_addressType_limit=Manually registration type, the machine address must not be empty jobgroup_field_registryList=machine address -jobgroup_field_registryList_unvalid=registry machine address is illegal +jobgroup_field_registryList_invalid=registry machine address is illegal jobgroup_field_registryList_placeholder=Please enter the machine address, if there are more than one comma separated jobgroup_field_appname_limit=Limit the beginning of a lowercase letter, consists of lowercase letters、number and hyphen. jobgroup_field_appname_length=AppName length is limited to 4~64 @@ -271,7 +270,3 @@ user_username_repeat=Username Repeat user_username_valid=Restrictions start with a lowercase letter and consist of lowercase letters and Numbers user_password_update_placeholder=Please input password, empty means not update user_update_loginuser_limit=Operation of current login account is not allowed - -## help -job_help=Tutorial -job_help_document=Official Document diff --git a/xxl-job-admin/src/main/resources/i18n/message_zh_CN.properties b/xxl-job-admin/src/main/resources/i18n/message_zh_CN.properties index d683d2bc..80415baf 100644 --- a/xxl-job-admin/src/main/resources/i18n/message_zh_CN.properties +++ b/xxl-job-admin/src/main/resources/i18n/message_zh_CN.properties @@ -1,6 +1,6 @@ admin_name=任务调度中心 -admin_name_full=分布式任务调度平台XXL-JOB -admin_version=2.5.0 +admin_name_full=分布式任务调度平台|XXL-JOB +admin_version=3.4.0 admin_i18n= ## system @@ -10,18 +10,16 @@ system_close=关闭 system_save=保存 system_cancel=取消 system_search=搜索 +system_reset=重置 system_status=状态 system_opt=操作 +system_opt_add=新增 system_please_input=请输入 system_please_choose=请选择 system_success=成功 system_fail=失败 -system_add_suc=新增成功 -system_add_fail=新增失败 -system_update_suc=更新成功 -system_update_fail=更新失败 +system_error=错误 system_all=全部 -system_api_error=接口异常 system_show=查看 system_empty=无 system_opt_suc=操作成功 @@ -29,16 +27,26 @@ system_opt_fail=操作失败 system_opt_edit=编辑 system_opt_del=删除 system_opt_copy=复制 -system_unvalid=非法 +system_invalid=非法 system_not_found=不存在 system_nav=导航 system_digits=整数 -system_lengh_limit=长度限制 +system_length_limit=长度限制 system_permission_limit=权限拦截 system_welcome=欢迎 +system_num_range=数值范围限制 +system_one=一条 +system_data=数据 +system_selected_nothing=未选择 + +## tab +tab_opt=页签操作 +tab_close_current=关闭当前 +tab_close_other=关闭其他 +tab_close_all=全部关闭 +tab_refresh=刷新 ## daterangepicker -daterangepicker_ranges_recent_hour=最近一小时 daterangepicker_ranges_today=今日 daterangepicker_ranges_yesterday=昨日 daterangepicker_ranges_this_month=本月 @@ -51,23 +59,6 @@ daterangepicker_custom_endtime=结束时间 daterangepicker_custom_daysofweek=日,一,二,三,四,五,六 daterangepicker_custom_monthnames=一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月 -## dataTable -dataTable_sProcessing=处理中... -dataTable_sLengthMenu=每页 _MENU_ 条记录 -dataTable_sZeroRecords=没有匹配结果 -dataTable_sInfo=第 _PAGE_ 页 ( 总共 _PAGES_ 页,_TOTAL_ 条记录 ) -dataTable_sInfoEmpty=无记录 -dataTable_sInfoFiltered=(由 _MAX_ 项结果过滤) -dataTable_sSearch=搜索 -dataTable_sEmptyTable=表中数据为空 -dataTable_sLoadingRecords=载入中... -dataTable_sFirst=首页 -dataTable_sPrevious=上页 -dataTable_sNext=下页 -dataTable_sLast=末页 -dataTable_sSortAscending=: 以升序排列此列 -dataTable_sSortDescending=: 以降序排列此列 - ## login login_btn=登录 login_remember_me=记住密码 @@ -80,7 +71,7 @@ login_password_lt_4=登录密码不应低于4位 login_success=登录成功 login_fail=登录失败 login_param_empty=账号或密码为空 -login_param_unvalid=账号或密码错误 +login_param_invalid=账号或密码错误 ## logout logout_btn=注销 @@ -94,6 +85,13 @@ change_pwd_suc_to_logout=修改密码成功,即将注销登陆 change_pwd_field_oldpwd=旧密码 change_pwd_field_newpwd=新密码 +## change skin +change_skin=切换主题 + +## help +admin_help=使用教程 +admin_help_document=官方文档 + ## dashboard job_dashboard_name=运行报表 job_dashboard_job_num=任务数量 @@ -110,7 +108,7 @@ job_dashboard_rate_report=成功比例图 ## job info jobinfo_name=任务管理 jobinfo_job=任务 -jobinfo_field_add=新增 +jobinfo_field_add=新增任务 jobinfo_field_update=更新任务 jobinfo_field_id=任务ID jobinfo_field_jobgroup=执行器 @@ -137,11 +135,12 @@ jobinfo_opt_run=执行一次 jobinfo_opt_run_tips=请输入本次执行的机器地址,为空则从执行器获取 jobinfo_opt_registryinfo=注册节点 jobinfo_opt_next_time=下次执行时间 +jobinfo_glue_source=GLUE源码 jobinfo_glue_remark=源码备注 jobinfo_glue_remark_limit=源码备注长度限制为4~100 jobinfo_glue_rollback=版本回溯 -jobinfo_glue_jobid_unvalid=任务ID非法 -jobinfo_glue_gluetype_unvalid=该任务非GLUE模式 +jobinfo_glue_jobid_invalid=任务ID非法 +jobinfo_glue_gluetype_invalid=该任务非GLUE模式 jobinfo_field_executorTimeout_placeholder=任务超时时间,单位秒,大于零时生效 schedule_type=调度类型 schedule_type_none=无 @@ -183,7 +182,7 @@ joblog_clean_type_6=清理一万条以前日志数据 joblog_clean_type_7=清理三万条以前日志数据 joblog_clean_type_8=清理十万条以前日志数据 joblog_clean_type_9=清理所有日志数据 -joblog_clean_type_unvalid=清理类型参数异常 +joblog_clean_type_invalid=清理类型参数异常 joblog_handleCode_200=成功 joblog_handleCode_500=失败 joblog_handleCode_502=失败(超时) @@ -195,7 +194,7 @@ joblog_rolling_log=执行日志 joblog_rolling_log_refresh=刷新 joblog_rolling_log_triggerfail=任务发起调度失败,无法查看执行日志 joblog_rolling_log_failoften=终止请求Rolling日志,请求失败次数超上限,可刷新页面重新加载日志 -joblog_logid_unvalid=日志ID非法 +joblog_logid_invalid=日志ID非法 ## job group jobgroup_name=执行器管理 @@ -209,7 +208,7 @@ jobgroup_field_addressType_0=自动注册 jobgroup_field_addressType_1=手动录入 jobgroup_field_addressType_limit=手动录入注册方式,机器地址不可为空 jobgroup_field_registryList=机器地址 -jobgroup_field_registryList_unvalid=机器地址格式非法 +jobgroup_field_registryList_invalid=机器地址格式非法 jobgroup_field_registryList_placeholder=请输入执行器地址列表,多地址逗号分隔 jobgroup_field_appname_limit=限制以小写字母开头,由小写字母、数字和中划线组成 jobgroup_field_appname_length=AppName长度限制为4~64 @@ -271,7 +270,3 @@ user_username_repeat=账号重复 user_username_valid=限制以小写字母开头,由小写字母、数字组成 user_password_update_placeholder=请输入新密码,为空则不更新密码 user_update_loginuser_limit=禁止操作当前登录账号 - -## help -job_help=使用教程 -job_help_document=官方文档 \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/i18n/message_zh_TC.properties b/xxl-job-admin/src/main/resources/i18n/message_zh_TC.properties index f428143b..f40bf676 100755 --- a/xxl-job-admin/src/main/resources/i18n/message_zh_TC.properties +++ b/xxl-job-admin/src/main/resources/i18n/message_zh_TC.properties @@ -1,6 +1,6 @@ admin_name=任務調度中心 -admin_name_full=分布式任務調度平臺XXL-JOB -admin_version=2.5.0 +admin_name_full=分布式任務調度平臺|XXL-JOB +admin_version=3.4.0 admin_i18n= ## system @@ -10,18 +10,16 @@ system_close=關閉 system_save=儲存 system_cancel=取消 system_search=搜尋 +system_reset=重置 system_status=狀態 system_opt=操作 +system_opt_add=新增 system_please_input=請輸入 system_please_choose=请選擇 system_success=成功 system_fail=失敗 -system_add_suc=新增成功 -system_add_fail=新增失敗 -system_update_suc=更新成功 -system_update_fail=更新失敗 +system_error=錯誤 system_all=全部 -system_api_error=API錯誤 system_show=查看 system_empty=無 system_opt_suc=操作成功 @@ -29,16 +27,26 @@ system_opt_fail=操作失敗 system_opt_edit=編輯 system_opt_del=刪除 system_opt_copy=復制 -system_unvalid=非法 +system_invalid=非法 system_not_found=不存在 system_nav=導航 system_digits=整數 -system_lengh_limit=長度限制 +system_length_limit=長度限制 system_permission_limit=權限控管 system_welcome=歡迎 +system_num_range=數值範圍限制 +system_one=一條 +system_data=數據 +system_selected_nothing=请選擇 + +## tab +tab_opt=頁籤操作 +tab_close_current=關閉當前 +tab_close_other=關閉其他 +tab_close_all=全部關閉 +tab_refresh=刷新 ## daterangepicker -daterangepicker_ranges_recent_hour=最近一小時 daterangepicker_ranges_today=今日 daterangepicker_ranges_yesterday=昨日 daterangepicker_ranges_this_month=本月 @@ -51,23 +59,6 @@ daterangepicker_custom_endtime=結束時間 daterangepicker_custom_daysofweek=日,一,二,三,四,五,六 daterangepicker_custom_monthnames=一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月 -## dataTable -dataTable_sProcessing=處理中... -dataTable_sLengthMenu=每頁 _MENU_ 條記錄 -dataTable_sZeroRecords=沒有相符合記錄 -dataTable_sInfo=第 _PAGE_ 頁 ( 總共 _PAGES_ 頁,_TOTAL_ 條記錄 ) -dataTable_sInfoEmpty=無記錄 -dataTable_sInfoFiltered=(由 _MAX_ 項結果過濾) -dataTable_sSearch=搜尋 -dataTable_sEmptyTable=表中資料為空 -dataTable_sLoadingRecords=載入中... -dataTable_sFirst=首頁 -dataTable_sPrevious=上頁 -dataTable_sNext=下頁 -dataTable_sLast=末頁 -dataTable_sSortAscending=: 以升幂排序此列 -dataTable_sSortDescending=: 以降幂排序此列 - ## login login_btn=登入 login_remember_me=記住密碼 @@ -80,7 +71,7 @@ login_password_lt_4=登入密碼不應低於4位數 login_success=登入成功 login_fail=登入失敗 login_param_empty=帳號或密碼為空值 -login_param_unvalid=帳號或密碼錯誤 +login_param_invalid=帳號或密碼錯誤 ## logout logout_btn=登出 @@ -94,6 +85,13 @@ change_pwd_suc_to_logout=修改密碼成功,即將登出 change_pwd_field_oldpwd=舊密碼 change_pwd_field_newpwd=新密碼 +## change skin +change_skin=切換主題 + +## help +admin_help=使用教程 +admin_help_document=官方文檔 + ## dashboard job_dashboard_name=運行報表 job_dashboard_job_num=任務數量 @@ -110,7 +108,7 @@ job_dashboard_rate_report=成功比例圖 ## job info jobinfo_name=任務管理 jobinfo_job=任務 -jobinfo_field_add=新增 +jobinfo_field_add=新增任務 jobinfo_field_update=更新任務 jobinfo_field_id=任務ID jobinfo_field_jobgroup=執行器 @@ -137,11 +135,12 @@ jobinfo_opt_run=執行一次 jobinfo_opt_run_tips=請輸入本次執行的機器地址,為空則從執行器獲取 jobinfo_opt_registryinfo=注冊節點 jobinfo_opt_next_time=下次執行時間 +jobinfo_glue_source=GLUE源碼 jobinfo_glue_remark=源碼備註 jobinfo_glue_remark_limit=源碼備註長度限制為4~100 jobinfo_glue_rollback=版本回復 -jobinfo_glue_jobid_unvalid=任務ID非法 -jobinfo_glue_gluetype_unvalid=該任務非GLUE模式 +jobinfo_glue_jobid_invalid=任務ID非法 +jobinfo_glue_gluetype_invalid=該任務非GLUE模式 jobinfo_field_executorTimeout_placeholder=任務超時時間,單位秒,大於零時生效 schedule_type=調度類型 schedule_type_none=無 @@ -183,7 +182,7 @@ joblog_clean_type_6=清理一萬條以前日誌資料 joblog_clean_type_7=清理三萬條以前日誌資料 joblog_clean_type_8=清理十萬條以前日誌資料 joblog_clean_type_9=清理所有日誌資料 -joblog_clean_type_unvalid=清理類型參数異常 +joblog_clean_type_invalid=清理類型參数異常 joblog_handleCode_200=成功 joblog_handleCode_500=失敗 joblog_handleCode_502=失敗(超時) @@ -195,7 +194,7 @@ joblog_rolling_log=執行日誌 joblog_rolling_log_refresh=更新 joblog_rolling_log_triggerfail=任務發起調度失敗,無法查看執行日誌 joblog_rolling_log_failoften=終止請求Rolling日誌,請求失敗次數超上限,可刷新頁面重新加載日誌 -joblog_logid_unvalid=日誌ID非法 +joblog_logid_invalid=日誌ID非法 ## job group jobgroup_name=執行器管理 @@ -209,7 +208,7 @@ jobgroup_field_addressType_0=自動注冊 jobgroup_field_addressType_1=手動登錄 jobgroup_field_addressType_limit=手動登錄注冊方式,機器地址不可為空 jobgroup_field_registryList=機器地址 -jobgroup_field_registryList_unvalid=機器地址格式非法 +jobgroup_field_registryList_invalid=機器地址格式非法 jobgroup_field_registryList_placeholder=請輸入執行器地址列表,多個地址請以逗號分隔 jobgroup_field_appname_limit=限制以小寫字母開頭,由小寫字母、數字和中划線組成 jobgroup_field_appname_length=AppName長度限制為4~64 @@ -271,7 +270,3 @@ user_username_repeat=帳號重複 user_username_valid=限制以小寫字母開頭,由小寫字母、數字組成 user_password_update_placeholder=請輸入新密碼,為空則不更新密碼 user_update_loginuser_limit=禁止操作當前登入帳號 - -## help -job_help=使用教程 -job_help_document=官方文件 \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/logback.xml b/xxl-job-admin/src/main/resources/logback.xml index d4b08c24..3c6983dc 100644 --- a/xxl-job-admin/src/main/resources/logback.xml +++ b/xxl-job-admin/src/main/resources/logback.xml @@ -2,22 +2,20 @@ logback - - + - %d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ${log.path} - ${log.path}.%d{yyyy-MM-dd}.zip + ${log.path}.%d{yyyy-MM-dd}.log - %date %level [%thread] %logger{36} [%file : %line] %msg%n - + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml b/xxl-job-admin/src/main/resources/mapper/XxlJobGroupMapper.xml similarity index 88% rename from xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml rename to xxl-job-admin/src/main/resources/mapper/XxlJobGroupMapper.xml index 87299f88..a38d711d 100644 --- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml +++ b/xxl-job-admin/src/main/resources/mapper/XxlJobGroupMapper.xml @@ -1,9 +1,9 @@ - + - + @@ -34,12 +34,12 @@ ORDER BY t.app_name, t.title, t.id ASC - + INSERT INTO xxl_job_group ( `app_name`, `title`, `address_type`, `address_list`, `update_time`) values ( #{appname}, #{title}, #{addressType}, #{addressList}, #{updateTime} ); - + UPDATE xxl_job_group SET `app_name` = #{appname}, `title` = #{title}, diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml b/xxl-job-admin/src/main/resources/mapper/XxlJobInfoMapper.xml similarity index 82% rename from xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml rename to xxl-job-admin/src/main/resources/mapper/XxlJobInfoMapper.xml index 0dc53ac1..041bd4e6 100644 --- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml +++ b/xxl-job-admin/src/main/resources/mapper/XxlJobInfoMapper.xml @@ -1,9 +1,9 @@ - + - + @@ -111,7 +111,7 @@ - + INSERT INTO xxl_job_info ( job_group, job_desc, @@ -173,7 +173,7 @@ WHERE t.id = #{id} - + UPDATE xxl_job_info SET job_group = #{jobGroup}, @@ -188,8 +188,8 @@ executor_handler = #{executorHandler}, executor_param = #{executorParam}, executor_block_strategy = #{executorBlockStrategy}, - executor_timeout = ${executorTimeout}, - executor_fail_retry_count = ${executorFailRetryCount}, + executor_timeout = #{executorTimeout}, + executor_fail_retry_count = #{executorFailRetryCount}, glue_type = #{glueType}, glue_source = #{glueSource}, glue_remark = #{glueRemark}, @@ -228,7 +228,7 @@ LIMIT #{pagesize} - + UPDATE xxl_job_info SET trigger_last_time = #{triggerLastTime}, @@ -240,4 +240,39 @@ AND trigger_status = 1 + + UPDATE xxl_job_info + SET + trigger_last_time = CASE id + + WHEN #{item.id} THEN #{item.triggerLastTime} + + ELSE trigger_last_time + END, + trigger_next_time = CASE id + + WHEN #{item.id} THEN #{item.triggerNextTime} + + ELSE trigger_next_time + END, + trigger_status = CASE id + + WHEN #{item.id} THEN + + + #{item.triggerStatus} + + trigger_status + + + ELSE trigger_status + END + WHERE id IN + + #{item.id} + + AND trigger_status = 1 + + + diff --git a/xxl-job-admin/src/main/resources/mapper/XxlJobLockMapper.xml b/xxl-job-admin/src/main/resources/mapper/XxlJobLockMapper.xml new file mode 100644 index 00000000..ee365998 --- /dev/null +++ b/xxl-job-admin/src/main/resources/mapper/XxlJobLockMapper.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml b/xxl-job-admin/src/main/resources/mapper/XxlJobLogGlueMapper.xml similarity index 82% rename from xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml rename to xxl-job-admin/src/main/resources/mapper/XxlJobLogGlueMapper.xml index 2dd1a96a..e75fc55c 100644 --- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml +++ b/xxl-job-admin/src/main/resources/mapper/XxlJobLogGlueMapper.xml @@ -1,71 +1,71 @@ - - - - - - - - - - - - - - - - t.id, - t.job_id, - t.glue_type, - t.glue_source, - t.glue_remark, - t.add_time, - t.update_time - - - - INSERT INTO xxl_job_logglue ( - `job_id`, - `glue_type`, - `glue_source`, - `glue_remark`, - `add_time`, - `update_time` - ) VALUES ( - #{jobId}, - #{glueType}, - #{glueSource}, - #{glueRemark}, - #{addTime}, - #{updateTime} - ); - - - - - - - DELETE FROM xxl_job_logglue - WHERE id NOT in( - SELECT id FROM( - SELECT id FROM xxl_job_logglue - WHERE `job_id` = #{jobId} - ORDER BY update_time desc - LIMIT 0, #{limit} - ) t1 - ) AND `job_id` = #{jobId} - - - - DELETE FROM xxl_job_logglue - WHERE `job_id` = #{jobId} - - + + + + + + + + + + + + + + + + t.id, + t.job_id, + t.glue_type, + t.glue_source, + t.glue_remark, + t.add_time, + t.update_time + + + + INSERT INTO xxl_job_logglue ( + `job_id`, + `glue_type`, + `glue_source`, + `glue_remark`, + `add_time`, + `update_time` + ) VALUES ( + #{jobId}, + #{glueType}, + #{glueSource}, + #{glueRemark}, + #{addTime}, + #{updateTime} + ); + + + + + + + DELETE FROM xxl_job_logglue + WHERE id NOT in( + SELECT id FROM( + SELECT id FROM xxl_job_logglue + WHERE `job_id` = #{jobId} + ORDER BY update_time desc + LIMIT 0, #{limit} + ) t1 + ) AND `job_id` = #{jobId} + + + + DELETE FROM xxl_job_logglue + WHERE `job_id` = #{jobId} + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml b/xxl-job-admin/src/main/resources/mapper/XxlJobLogMapper.xml similarity index 91% rename from xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml rename to xxl-job-admin/src/main/resources/mapper/XxlJobLogMapper.xml index 3c0b4044..ca19534e 100644 --- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml +++ b/xxl-job-admin/src/main/resources/mapper/XxlJobLogMapper.xml @@ -1,9 +1,9 @@ - + - + @@ -48,7 +48,7 @@ SELECT FROM xxl_job_log AS t - + AND t.job_group = #{jobGroup} @@ -74,7 +74,7 @@ AND t.handle_code = 0 - ORDER BY t.trigger_time DESC + ORDER BY t.id DESC LIMIT #{offset}, #{pagesize} @@ -82,7 +82,7 @@ SELECT count(1) FROM xxl_job_log AS t - + AND t.job_group = #{jobGroup} @@ -117,7 +117,7 @@ - + INSERT INTO xxl_job_log ( `job_group`, `job_id`, @@ -178,9 +178,9 @@ diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml b/xxl-job-admin/src/main/resources/mapper/XxlJobLogReportMapper.xml similarity index 58% rename from xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml rename to xxl-job-admin/src/main/resources/mapper/XxlJobLogReportMapper.xml index 579d5f39..92a99363 100644 --- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml +++ b/xxl-job-admin/src/main/resources/mapper/XxlJobLogReportMapper.xml @@ -1,14 +1,15 @@ - + - + + @@ -16,10 +17,11 @@ t.trigger_day, t.running_count, t.suc_count, - t.fail_count + t.fail_count, + t.update_time - + + –> @@ -42,7 +44,28 @@ `suc_count` = #{sucCount}, `fail_count` = #{failCount} WHERE `trigger_day` = #{triggerDay} - + --> + + + INSERT INTO xxl_job_log_report ( + `trigger_day`, + `running_count`, + `suc_count`, + `fail_count`, + `update_time` + ) VALUES ( + #{triggerDay}, + #{runningCount}, + #{sucCount}, + #{failCount}, + #{updateTime} + ) + ON DUPLICATE KEY UPDATE + `running_count` = #{runningCount}, + `suc_count` = #{sucCount}, + `fail_count` = #{failCount}, + `update_time` = #{updateTime} + - + + + INSERT INTO xxl_job_user ( username, password, @@ -67,7 +75,7 @@ ); - + UPDATE xxl_job_user SET @@ -84,4 +92,10 @@ WHERE id = #{id} + + UPDATE xxl_job_user + SET token = #{token} + WHERE id = #{id} + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/pace.min.js b/xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/pace.min.js deleted file mode 100644 index 234f9b3e..00000000 --- a/xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/pace.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! pace 1.0.2 */ -(function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X=[].slice,Y={}.hasOwnProperty,Z=function(a,b){function c(){this.constructor=a}for(var d in b)Y.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},$=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};for(u={catchupTime:100,initialRate:.03,minTime:250,ghostTime:100,maxProgressPerFrame:20,easeFactor:1.25,startOnPageLoad:!0,restartOnPushState:!0,restartOnRequestAfter:500,target:"body",elements:{checkInterval:100,selectors:["body"]},eventLag:{minSamples:10,sampleCount:3,lagThreshold:3},ajax:{trackMethods:["GET"],trackWebSockets:!0,ignoreURLs:[]}},C=function(){var a;return null!=(a="undefined"!=typeof performance&&null!==performance&&"function"==typeof performance.now?performance.now():void 0)?a:+new Date},E=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,t=window.cancelAnimationFrame||window.mozCancelAnimationFrame,null==E&&(E=function(a){return setTimeout(a,50)},t=function(a){return clearTimeout(a)}),G=function(a){var b,c;return b=C(),(c=function(){var d;return d=C()-b,d>=33?(b=C(),a(d,function(){return E(c)})):setTimeout(c,33-d)})()},F=function(){var a,b,c;return c=arguments[0],b=arguments[1],a=3<=arguments.length?X.call(arguments,2):[],"function"==typeof c[b]?c[b].apply(c,a):c[b]},v=function(){var a,b,c,d,e,f,g;for(b=arguments[0],d=2<=arguments.length?X.call(arguments,1):[],f=0,g=d.length;g>f;f++)if(c=d[f])for(a in c)Y.call(c,a)&&(e=c[a],null!=b[a]&&"object"==typeof b[a]&&null!=e&&"object"==typeof e?v(b[a],e):b[a]=e);return b},q=function(a){var b,c,d,e,f;for(c=b=0,e=0,f=a.length;f>e;e++)d=a[e],c+=Math.abs(d),b++;return c/b},x=function(a,b){var c,d,e;if(null==a&&(a="options"),null==b&&(b=!0),e=document.querySelector("[data-pace-"+a+"]")){if(c=e.getAttribute("data-pace-"+a),!b)return c;try{return JSON.parse(c)}catch(f){return d=f,"undefined"!=typeof console&&null!==console?console.error("Error parsing inline pace options",d):void 0}}},g=function(){function a(){}return a.prototype.on=function(a,b,c,d){var e;return null==d&&(d=!1),null==this.bindings&&(this.bindings={}),null==(e=this.bindings)[a]&&(e[a]=[]),this.bindings[a].push({handler:b,ctx:c,once:d})},a.prototype.once=function(a,b,c){return this.on(a,b,c,!0)},a.prototype.off=function(a,b){var c,d,e;if(null!=(null!=(d=this.bindings)?d[a]:void 0)){if(null==b)return delete this.bindings[a];for(c=0,e=[];cQ;Q++)K=U[Q],D[K]===!0&&(D[K]=u[K]);i=function(a){function b(){return V=b.__super__.constructor.apply(this,arguments)}return Z(b,a),b}(Error),b=function(){function a(){this.progress=0}return a.prototype.getElement=function(){var a;if(null==this.el){if(a=document.querySelector(D.target),!a)throw new i;this.el=document.createElement("div"),this.el.className="pace pace-active",document.body.className=document.body.className.replace(/pace-done/g,""),document.body.className+=" pace-running",this.el.innerHTML='
\n
\n
\n
',null!=a.firstChild?a.insertBefore(this.el,a.firstChild):a.appendChild(this.el)}return this.el},a.prototype.finish=function(){var a;return a=this.getElement(),a.className=a.className.replace("pace-active",""),a.className+=" pace-inactive",document.body.className=document.body.className.replace("pace-running",""),document.body.className+=" pace-done"},a.prototype.update=function(a){return this.progress=a,this.render()},a.prototype.destroy=function(){try{this.getElement().parentNode.removeChild(this.getElement())}catch(a){i=a}return this.el=void 0},a.prototype.render=function(){var a,b,c,d,e,f,g;if(null==document.querySelector(D.target))return!1;for(a=this.getElement(),d="translate3d("+this.progress+"%, 0, 0)",g=["webkitTransform","msTransform","transform"],e=0,f=g.length;f>e;e++)b=g[e],a.children[0].style[b]=d;return(!this.lastRenderedProgress||this.lastRenderedProgress|0!==this.progress|0)&&(a.children[0].setAttribute("data-progress-text",""+(0|this.progress)+"%"),this.progress>=100?c="99":(c=this.progress<10?"0":"",c+=0|this.progress),a.children[0].setAttribute("data-progress",""+c)),this.lastRenderedProgress=this.progress},a.prototype.done=function(){return this.progress>=100},a}(),h=function(){function a(){this.bindings={}}return a.prototype.trigger=function(a,b){var c,d,e,f,g;if(null!=this.bindings[a]){for(f=this.bindings[a],g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.call(this,b));return g}},a.prototype.on=function(a,b){var c;return null==(c=this.bindings)[a]&&(c[a]=[]),this.bindings[a].push(b)},a}(),P=window.XMLHttpRequest,O=window.XDomainRequest,N=window.WebSocket,w=function(a,b){var c,d,e;e=[];for(d in b.prototype)try{e.push(null==a[d]&&"function"!=typeof b[d]?"function"==typeof Object.defineProperty?Object.defineProperty(a,d,{get:function(){return b.prototype[d]},configurable:!0,enumerable:!0}):a[d]=b.prototype[d]:void 0)}catch(f){c=f}return e},A=[],j.ignore=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?X.call(arguments,1):[],A.unshift("ignore"),c=b.apply(null,a),A.shift(),c},j.track=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?X.call(arguments,1):[],A.unshift("track"),c=b.apply(null,a),A.shift(),c},J=function(a){var b;if(null==a&&(a="GET"),"track"===A[0])return"force";if(!A.length&&D.ajax){if("socket"===a&&D.ajax.trackWebSockets)return!0;if(b=a.toUpperCase(),$.call(D.ajax.trackMethods,b)>=0)return!0}return!1},k=function(a){function b(){var a,c=this;b.__super__.constructor.apply(this,arguments),a=function(a){var b;return b=a.open,a.open=function(d,e){return J(d)&&c.trigger("request",{type:d,url:e,request:a}),b.apply(a,arguments)}},window.XMLHttpRequest=function(b){var c;return c=new P(b),a(c),c};try{w(window.XMLHttpRequest,P)}catch(d){}if(null!=O){window.XDomainRequest=function(){var b;return b=new O,a(b),b};try{w(window.XDomainRequest,O)}catch(d){}}if(null!=N&&D.ajax.trackWebSockets){window.WebSocket=function(a,b){var d;return d=null!=b?new N(a,b):new N(a),J("socket")&&c.trigger("request",{type:"socket",url:a,protocols:b,request:d}),d};try{w(window.WebSocket,N)}catch(d){}}}return Z(b,a),b}(h),R=null,y=function(){return null==R&&(R=new k),R},I=function(a){var b,c,d,e;for(e=D.ajax.ignoreURLs,c=0,d=e.length;d>c;c++)if(b=e[c],"string"==typeof b){if(-1!==a.indexOf(b))return!0}else if(b.test(a))return!0;return!1},y().on("request",function(b){var c,d,e,f,g;return f=b.type,e=b.request,g=b.url,I(g)?void 0:j.running||D.restartOnRequestAfter===!1&&"force"!==J(f)?void 0:(d=arguments,c=D.restartOnRequestAfter||0,"boolean"==typeof c&&(c=0),setTimeout(function(){var b,c,g,h,i,k;if(b="socket"===f?e.readyState<2:0<(h=e.readyState)&&4>h){for(j.restart(),i=j.sources,k=[],c=0,g=i.length;g>c;c++){if(K=i[c],K instanceof a){K.watch.apply(K,d);break}k.push(void 0)}return k}},c))}),a=function(){function a(){var a=this;this.elements=[],y().on("request",function(){return a.watch.apply(a,arguments)})}return a.prototype.watch=function(a){var b,c,d,e;return d=a.type,b=a.request,e=a.url,I(e)?void 0:(c="socket"===d?new n(b):new o(b),this.elements.push(c))},a}(),o=function(){function a(a){var b,c,d,e,f,g,h=this;if(this.progress=0,null!=window.ProgressEvent)for(c=null,a.addEventListener("progress",function(a){return h.progress=a.lengthComputable?100*a.loaded/a.total:h.progress+(100-h.progress)/2},!1),g=["load","abort","timeout","error"],d=0,e=g.length;e>d;d++)b=g[d],a.addEventListener(b,function(){return h.progress=100},!1);else f=a.onreadystatechange,a.onreadystatechange=function(){var b;return 0===(b=a.readyState)||4===b?h.progress=100:3===a.readyState&&(h.progress=50),"function"==typeof f?f.apply(null,arguments):void 0}}return a}(),n=function(){function a(a){var b,c,d,e,f=this;for(this.progress=0,e=["error","open"],c=0,d=e.length;d>c;c++)b=e[c],a.addEventListener(b,function(){return f.progress=100},!1)}return a}(),d=function(){function a(a){var b,c,d,f;for(null==a&&(a={}),this.elements=[],null==a.selectors&&(a.selectors=[]),f=a.selectors,c=0,d=f.length;d>c;c++)b=f[c],this.elements.push(new e(b))}return a}(),e=function(){function a(a){this.selector=a,this.progress=0,this.check()}return a.prototype.check=function(){var a=this;return document.querySelector(this.selector)?this.done():setTimeout(function(){return a.check()},D.elements.checkInterval)},a.prototype.done=function(){return this.progress=100},a}(),c=function(){function a(){var a,b,c=this;this.progress=null!=(b=this.states[document.readyState])?b:100,a=document.onreadystatechange,document.onreadystatechange=function(){return null!=c.states[document.readyState]&&(c.progress=c.states[document.readyState]),"function"==typeof a?a.apply(null,arguments):void 0}}return a.prototype.states={loading:0,interactive:50,complete:100},a}(),f=function(){function a(){var a,b,c,d,e,f=this;this.progress=0,a=0,e=[],d=0,c=C(),b=setInterval(function(){var g;return g=C()-c-50,c=C(),e.push(g),e.length>D.eventLag.sampleCount&&e.shift(),a=q(e),++d>=D.eventLag.minSamples&&a=100&&(this.done=!0),b===this.last?this.sinceLastUpdate+=a:(this.sinceLastUpdate&&(this.rate=(b-this.last)/this.sinceLastUpdate),this.catchup=(b-this.progress)/D.catchupTime,this.sinceLastUpdate=0,this.last=b),b>this.progress&&(this.progress+=this.catchup*a),c=1-Math.pow(this.progress/100,D.easeFactor),this.progress+=c*this.rate*a,this.progress=Math.min(this.lastProgress+D.maxProgressPerFrame,this.progress),this.progress=Math.max(0,this.progress),this.progress=Math.min(100,this.progress),this.lastProgress=this.progress,this.progress},a}(),L=null,H=null,r=null,M=null,p=null,s=null,j.running=!1,z=function(){return D.restartOnPushState?j.restart():void 0},null!=window.history.pushState&&(T=window.history.pushState,window.history.pushState=function(){return z(),T.apply(window.history,arguments)}),null!=window.history.replaceState&&(W=window.history.replaceState,window.history.replaceState=function(){return z(),W.apply(window.history,arguments)}),l={ajax:a,elements:d,document:c,eventLag:f},(B=function(){var a,c,d,e,f,g,h,i;for(j.sources=L=[],g=["ajax","elements","document","eventLag"],c=0,e=g.length;e>c;c++)a=g[c],D[a]!==!1&&L.push(new l[a](D[a]));for(i=null!=(h=D.extraSources)?h:[],d=0,f=i.length;f>d;d++)K=i[d],L.push(new K(D));return j.bar=r=new b,H=[],M=new m})(),j.stop=function(){return j.trigger("stop"),j.running=!1,r.destroy(),s=!0,null!=p&&("function"==typeof t&&t(p),p=null),B()},j.restart=function(){return j.trigger("restart"),j.stop(),j.start()},j.go=function(){var a;return j.running=!0,r.render(),a=C(),s=!1,p=G(function(b,c){var d,e,f,g,h,i,k,l,n,o,p,q,t,u,v,w;for(l=100-r.progress,e=p=0,f=!0,i=q=0,u=L.length;u>q;i=++q)for(K=L[i],o=null!=H[i]?H[i]:H[i]=[],h=null!=(w=K.elements)?w:[K],k=t=0,v=h.length;v>t;k=++t)g=h[k],n=null!=o[k]?o[k]:o[k]=new m(g),f&=n.done,n.done||(e++,p+=n.tick(b));return d=p/e,r.update(M.tick(b,d)),r.done()||f||s?(r.update(100),j.trigger("done"),setTimeout(function(){return r.finish(),j.running=!1,j.trigger("hide")},Math.max(D.ghostTime,Math.max(D.minTime-(C()-a),0)))):c()})},j.start=function(a){v(D,a),j.running=!0;try{r.render()}catch(b){i=b}return document.querySelector(".pace")?(j.trigger("start"),j.go()):setTimeout(j.start,50)},"function"==typeof define&&define.amd?define(["pace"],function(){return j}):"object"==typeof exports?module.exports=j:D.startOnPageLoad&&j.start()}).call(this); \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/themes/blue/pace-theme-flash.css b/xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/themes/blue/pace-theme-flash.css deleted file mode 100644 index d9bca466..00000000 --- a/xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/themes/blue/pace-theme-flash.css +++ /dev/null @@ -1,77 +0,0 @@ -/* This is a compiled file, you should be editing the file in the templates directory */ -.pace { - -webkit-pointer-events: none; - pointer-events: none; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} - -.pace-inactive { - display: none; -} - -.pace .pace-progress { - background: #2299dd; - position: fixed; - z-index: 2000; - top: 0; - right: 100%; - width: 100%; - height: 2px; -} - -.pace .pace-progress-inner { - display: block; - position: absolute; - right: 0px; - width: 100px; - height: 100%; - box-shadow: 0 0 10px #2299dd, 0 0 5px #2299dd; - opacity: 1.0; - -webkit-transform: rotate(3deg) translate(0px, -4px); - -moz-transform: rotate(3deg) translate(0px, -4px); - -ms-transform: rotate(3deg) translate(0px, -4px); - -o-transform: rotate(3deg) translate(0px, -4px); - transform: rotate(3deg) translate(0px, -4px); -} - -.pace .pace-activity { - display: block; - position: fixed; - z-index: 2000; - top: 15px; - right: 15px; - width: 14px; - height: 14px; - border: solid 2px transparent; - border-top-color: #2299dd; - border-left-color: #2299dd; - border-radius: 10px; - -webkit-animation: pace-spinner 400ms linear infinite; - -moz-animation: pace-spinner 400ms linear infinite; - -ms-animation: pace-spinner 400ms linear infinite; - -o-animation: pace-spinner 400ms linear infinite; - animation: pace-spinner 400ms linear infinite; -} - -@-webkit-keyframes pace-spinner { - 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } - 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } -} -@-moz-keyframes pace-spinner { - 0% { -moz-transform: rotate(0deg); transform: rotate(0deg); } - 100% { -moz-transform: rotate(360deg); transform: rotate(360deg); } -} -@-o-keyframes pace-spinner { - 0% { -o-transform: rotate(0deg); transform: rotate(0deg); } - 100% { -o-transform: rotate(360deg); transform: rotate(360deg); } -} -@-ms-keyframes pace-spinner { - 0% { -ms-transform: rotate(0deg); transform: rotate(0deg); } - 100% { -ms-transform: rotate(360deg); transform: rotate(360deg); } -} -@keyframes pace-spinner { - 0% { transform: rotate(0deg); transform: rotate(0deg); } - 100% { transform: rotate(360deg); transform: rotate(360deg); } -} diff --git a/xxl-job-admin/src/main/resources/static/adminlte/bower_components/ckeditor/ckeditor.js b/xxl-job-admin/src/main/resources/static/adminlte/bower_components/ckeditor/ckeditor.js new file mode 100644 index 00000000..4c5f558b --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/adminlte/bower_components/ckeditor/ckeditor.js @@ -0,0 +1,1236 @@ +/* +Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license +*/ +(function(){window.CKEDITOR&&window.CKEDITOR.dom||(window.CKEDITOR||(window.CKEDITOR=function(){var a=/(^|.*[\\\/])ckeditor\.js(?:\?.*|;.*)?$/i,f={timestamp:"J5S9",version:"4.12.1 (Standard)",revision:"64749bb245",rnd:Math.floor(900*Math.random())+100,_:{pending:[],basePathSrcPattern:a},status:"unloaded",basePath:function(){var b=window.CKEDITOR_BASEPATH||"";if(!b)for(var c=document.getElementsByTagName("script"),f=0;fe.getListenerIndex(d)){e=e.listeners;f||(f=this);isNaN(g)&&(g=10);var n=this;h.fn=d;h.priority=g;for(var q=e.length-1;0<=q;q--)if(e[q].priority<=g)return e.splice(q+1,0,h),{removeListener:m};e.unshift(h)}return{removeListener:m}},once:function(){var a=Array.prototype.slice.call(arguments),b=a[1];a[1]=function(a){a.removeListener();return b.apply(this, +arguments)};return this.on.apply(this,a)},capture:function(){CKEDITOR.event.useCapture=1;var a=this.on.apply(this,arguments);CKEDITOR.event.useCapture=0;return a},fire:function(){var a=0,b=function(){a=1},l=0,k=function(){l=1};return function(g,h,m){var e=f(this)[g];g=a;var n=l;a=l=0;if(e){var q=e.listeners;if(q.length)for(var q=q.slice(0),y,u=0;udocument.documentMode),mobile:-1c||b.quirks);b.gecko&&(f=a.match(/rv:([\d\.]+)/))&&(f=f[1].split("."),c=1E4*f[0]+100*(f[1]||0)+1*(f[2]||0));b.air&&(c=parseFloat(a.match(/ adobeair\/(\d+)/)[1]));b.webkit&&(c=parseFloat(a.match(/ applewebkit\/(\d+)/)[1]));b.version=c;b.isCompatible=!(b.ie&&7>c)&&!(b.gecko&&4E4>c)&&!(b.webkit&& +534>c);b.hidpi=2<=window.devicePixelRatio;b.needsBrFiller=b.gecko||b.webkit||b.ie&&10c;b.cssClass="cke_browser_"+(b.ie?"ie":b.gecko?"gecko":b.webkit?"webkit":"unknown");b.quirks&&(b.cssClass+=" cke_browser_quirks");b.ie&&(b.cssClass+=" cke_browser_ie"+(b.quirks?"6 cke_browser_iequirks":b.version));b.air&&(b.cssClass+=" cke_browser_air");b.iOS&&(b.cssClass+=" cke_browser_ios");b.hidpi&&(b.cssClass+=" cke_hidpi");return b}()),"unloaded"==CKEDITOR.status&&function(){CKEDITOR.event.implementOn(CKEDITOR); +CKEDITOR.loadFullCore=function(){if("basic_ready"!=CKEDITOR.status)CKEDITOR.loadFullCore._load=1;else{delete CKEDITOR.loadFullCore;var a=document.createElement("script");a.type="text/javascript";a.src=CKEDITOR.basePath+"ckeditor.js";document.getElementsByTagName("head")[0].appendChild(a)}};CKEDITOR.loadFullCoreTimeout=0;CKEDITOR.add=function(a){(this._.pending||(this._.pending=[])).push(a)};(function(){CKEDITOR.domReady(function(){var a=CKEDITOR.loadFullCore,f=CKEDITOR.loadFullCoreTimeout;a&&(CKEDITOR.status= +"basic_ready",a&&a._load?a():f&&setTimeout(function(){CKEDITOR.loadFullCore&&CKEDITOR.loadFullCore()},1E3*f))})})();CKEDITOR.status="basic_loaded"}(),"use strict",CKEDITOR.VERBOSITY_WARN=1,CKEDITOR.VERBOSITY_ERROR=2,CKEDITOR.verbosity=CKEDITOR.VERBOSITY_WARN|CKEDITOR.VERBOSITY_ERROR,CKEDITOR.warn=function(a,f){CKEDITOR.verbosity&CKEDITOR.VERBOSITY_WARN&&CKEDITOR.fire("log",{type:"warn",errorCode:a,additionalData:f})},CKEDITOR.error=function(a,f){CKEDITOR.verbosity&CKEDITOR.VERBOSITY_ERROR&&CKEDITOR.fire("log", +{type:"error",errorCode:a,additionalData:f})},CKEDITOR.on("log",function(a){if(window.console&&window.console.log){var f=console[a.data.type]?a.data.type:"log",b=a.data.errorCode;if(a=a.data.additionalData)console[f]("[CKEDITOR] Error code: "+b+".",a);else console[f]("[CKEDITOR] Error code: "+b+".");console[f]("[CKEDITOR] For more information about this error go to https://ckeditor.com/docs/ckeditor4/latest/guide/dev_errors.html#"+b)}},null,null,999),CKEDITOR.dom={},function(){function a(a,e,b){this._minInterval= +a;this._context=b;this._lastOutput=this._scheduledTimer=0;this._output=CKEDITOR.tools.bind(e,b||{});var g=this;this.input=function(){function a(){g._lastOutput=(new Date).getTime();g._scheduledTimer=0;g._call()}if(!g._scheduledTimer||!1!==g._reschedule()){var e=(new Date).getTime()-g._lastOutput;e/g,k=/|\s) /g,function(a,e){return e+"\x26nbsp;"}).replace(/ (?=<)/g,"\x26nbsp;")},getNextNumber:function(){var a=0;return function(){return++a}}(),getNextId:function(){return"cke_"+this.getNextNumber()},getUniqueId:function(){for(var a="e",e=0;8>e;e++)a+=Math.floor(65536*(1+Math.random())).toString(16).substring(1);return a},override:function(a,e){var g=e(a);g.prototype=a.prototype;return g},setTimeout:function(a,e,g,b,h){h||(h=window);g||(g= +h);return h.setTimeout(function(){b?a.apply(g,[].concat(b)):a.apply(g)},e||0)},throttle:function(a,e,g){return new this.buffers.throttle(a,e,g)},trim:function(){var a=/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g;return function(e){return e.replace(a,"")}}(),ltrim:function(){var a=/^[ \t\n\r]+/g;return function(e){return e.replace(a,"")}}(),rtrim:function(){var a=/[ \t\n\r]+$/g;return function(e){return e.replace(a,"")}}(),indexOf:function(a,e){if("function"==typeof e)for(var g=0,b=a.length;gparseFloat(e);g&&(e=e.replace("-",""));a.setStyle("width",e);e=a.$.clientWidth;return g?-e:e}return e}}(),repeat:function(a, +e){return Array(e+1).join(a)},tryThese:function(){for(var a,e=0,g=arguments.length;ee;e++)a[e]=("0"+parseInt(a[e],10).toString(16)).slice(-2);return"#"+a.join("")})},normalizeHex:function(a){return a.replace(/#(([0-9a-f]{3}){1,2})($|;|\s+)/gi,function(a,e,g,b){a=e.toLowerCase();3==a.length&&(a=a.split(""),a=[a[0],a[0],a[1],a[1],a[2],a[2]].join(""));return"#"+a+b})},parseCssText:function(a,e,g){var b={};g&&(a=(new CKEDITOR.dom.element("span")).setAttribute("style",a).getAttribute("style")||"");a&&(a=CKEDITOR.tools.normalizeHex(CKEDITOR.tools.convertRgbToHex(a))); +if(!a||";"==a)return b;a.replace(/"/g,'"').replace(/\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(a,g,h){e&&(g=g.toLowerCase(),"font-family"==g&&(h=h.replace(/\s*,\s*/g,",")),h=CKEDITOR.tools.trim(h));b[g]=h});return b},writeCssText:function(a,e){var g,b=[];for(g in a)b.push(g+":"+a[g]);e&&b.sort();return b.join("; ")},objectCompare:function(a,e,g){var b;if(!a&&!e)return!0;if(!a||!e)return!1;for(b in a)if(a[b]!=e[b])return!1;if(!g)for(b in e)if(a[b]!=e[b])return!1;return!0},objectKeys:function(a){return CKEDITOR.tools.object.keys(a)}, +convertArrayToObject:function(a,e){var g={};1==arguments.length&&(e=!0);for(var b=0,h=a.length;bg;g++)a.push(Math.floor(256*Math.random())); +for(g=0;gCKEDITOR.env.version||CKEDITOR.env.ie6Compat)?4===a.button?CKEDITOR.MOUSE_BUTTON_MIDDLE:1===a.button? +CKEDITOR.MOUSE_BUTTON_LEFT:CKEDITOR.MOUSE_BUTTON_RIGHT:a.button:!1},convertHexStringToBytes:function(a){var e=[],g=a.length/2,b;for(b=0;bc)for(m=c;3>m;m++)h[m]=0;d[0]=(h[0]&252)>>2;d[1]=(h[0]&3)<<4|h[1]>>4;d[2]=(h[1]&15)<<2|(h[2]&192)>>6;d[3]=h[2]&63;for(m=0;4>m;m++)e=m<=c?e+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d[m]): +e+"\x3d"}return e},style:{parse:{_colors:{aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aqua:"#00FFFF",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blue:"#0000FF",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9", +darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",fuchsia:"#FF00FF", +gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",gray:"#808080",green:"#008000",greenyellow:"#ADFF2F",grey:"#808080",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgray:"#D3D3D3",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1", +lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",lime:"#00FF00",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",maroon:"#800000",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA", +mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",navy:"#000080",oldlace:"#FDF5E6",olive:"#808000",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",purple:"#800080",rebeccapurple:"#663399",red:"#FF0000",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072", +sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",silver:"#C0C0C0",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",teal:"#008080",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",windowtext:"windowtext",wheat:"#F5DEB3",white:"#FFFFFF",whitesmoke:"#F5F5F5",yellow:"#FFFF00",yellowgreen:"#9ACD32"},_borderStyle:"none hidden dotted dashed solid double groove ridge inset outset".split(" "), +_widthRegExp:/^(thin|medium|thick|[\+-]?\d+(\.\d+)?[a-z%]+|[\+-]?0+(\.0+)?|\.\d+[a-z%]+)$/,_rgbaRegExp:/rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi,_hslaRegExp:/hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi,background:function(a){var e={},g=this._findColor(a);g.length&&(e.color=g[0],CKEDITOR.tools.array.forEach(g,function(e){a=a.replace(e,"")}));if(a=CKEDITOR.tools.trim(a))e.unprocessed=a;return e},margin:function(a){return CKEDITOR.tools.style.parse.sideShorthand(a, +function(a){return a.match(/(?:\-?[\.\d]+(?:%|\w*)|auto|inherit|initial|unset|revert)/g)||["0px"]})},sideShorthand:function(a,e){function g(a){b.top=h[a[0]];b.right=h[a[1]];b.bottom=h[a[2]];b.left=h[a[3]]}var b={},h=e?e(a):a.split(/\s+/);switch(h.length){case 1:g([0,0,0,0]);break;case 2:g([0,1,0,1]);break;case 3:g([0,1,2,1]);break;case 4:g([0,1,2,3])}return b},border:function(a){return CKEDITOR.tools.style.border.fromCssRule(a)},_findColor:function(a){var e=[],g=CKEDITOR.tools.array,e=e.concat(a.match(this._rgbaRegExp)|| +[]),e=e.concat(a.match(this._hslaRegExp)||[]);return e=e.concat(g.filter(a.split(/\s+/),function(a){return a.match(/^\#[a-f0-9]{3}(?:[a-f0-9]{3})?$/gi)?!0:a.toLowerCase()in CKEDITOR.tools.style.parse._colors}))}}},array:{filter:function(a,e,g){var b=[];this.forEach(a,function(h,c){e.call(g,h,c,a)&&b.push(h)});return b},find:function(a,e,g){for(var b=a.length,h=0;hCKEDITOR.env.version)for(h=0;hCKEDITOR.env.version&&(this.type==CKEDITOR.NODE_ELEMENT||this.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT)&&c(d);return d},hasPrevious:function(){return!!this.$.previousSibling},hasNext:function(){return!!this.$.nextSibling},insertAfter:function(a){a.$.parentNode.insertBefore(this.$,a.$.nextSibling);return a},insertBefore:function(a){a.$.parentNode.insertBefore(this.$, +a.$);return a},insertBeforeMe:function(a){this.$.parentNode.insertBefore(a.$,this.$);return a},getAddress:function(a){for(var f=[],b=this.getDocument().$.documentElement,c=this.$;c&&c!=b;){var d=c.parentNode;d&&f.unshift(this.getIndex.call({$:c},a));c=d}return f},getDocument:function(){return new CKEDITOR.dom.document(this.$.ownerDocument||this.$.parentNode.ownerDocument)},getIndex:function(a){function f(a,g){var h=g?a.nextSibling:a.previousSibling;return h&&h.nodeType==CKEDITOR.NODE_TEXT?b(h)?f(h, +g):h:null}function b(a){return!a.nodeValue||a.nodeValue==CKEDITOR.dom.selection.FILLING_CHAR_SEQUENCE}var c=this.$,d=-1,l;if(!this.$.parentNode||a&&c.nodeType==CKEDITOR.NODE_TEXT&&b(c)&&!f(c)&&!f(c,!0))return-1;do a&&c!=this.$&&c.nodeType==CKEDITOR.NODE_TEXT&&(l||b(c))||(d++,l=c.nodeType==CKEDITOR.NODE_TEXT);while(c=c.previousSibling);return d},getNextSourceNode:function(a,f,b){if(b&&!b.call){var c=b;b=function(a){return!a.equals(c)}}a=!a&&this.getFirst&&this.getFirst();var d;if(!a){if(this.type== +CKEDITOR.NODE_ELEMENT&&b&&!1===b(this,!0))return null;a=this.getNext()}for(;!a&&(d=(d||this).getParent());){if(b&&!1===b(d,!0))return null;a=d.getNext()}return!a||b&&!1===b(a)?null:f&&f!=a.type?a.getNextSourceNode(!1,f,b):a},getPreviousSourceNode:function(a,f,b){if(b&&!b.call){var c=b;b=function(a){return!a.equals(c)}}a=!a&&this.getLast&&this.getLast();var d;if(!a){if(this.type==CKEDITOR.NODE_ELEMENT&&b&&!1===b(this,!0))return null;a=this.getPrevious()}for(;!a&&(d=(d||this).getParent());){if(b&&!1=== +b(d,!0))return null;a=d.getPrevious()}return!a||b&&!1===b(a)?null:f&&a.type!=f?a.getPreviousSourceNode(!1,f,b):a},getPrevious:function(a){var f=this.$,b;do b=(f=f.previousSibling)&&10!=f.nodeType&&new CKEDITOR.dom.node(f);while(b&&a&&!a(b));return b},getNext:function(a){var f=this.$,b;do b=(f=f.nextSibling)&&new CKEDITOR.dom.node(f);while(b&&a&&!a(b));return b},getParent:function(a){var f=this.$.parentNode;return f&&(f.nodeType==CKEDITOR.NODE_ELEMENT||a&&f.nodeType==CKEDITOR.NODE_DOCUMENT_FRAGMENT)? +new CKEDITOR.dom.node(f):null},getParents:function(a){var f=this,b=[];do b[a?"push":"unshift"](f);while(f=f.getParent());return b},getCommonAncestor:function(a){if(a.equals(this))return this;if(a.contains&&a.contains(this))return a;var f=this.contains?this:this.getParent();do if(f.contains(a))return f;while(f=f.getParent());return null},getPosition:function(a){var f=this.$,b=a.$;if(f.compareDocumentPosition)return f.compareDocumentPosition(b);if(f==b)return CKEDITOR.POSITION_IDENTICAL;if(this.type== +CKEDITOR.NODE_ELEMENT&&a.type==CKEDITOR.NODE_ELEMENT){if(f.contains){if(f.contains(b))return CKEDITOR.POSITION_CONTAINS+CKEDITOR.POSITION_PRECEDING;if(b.contains(f))return CKEDITOR.POSITION_IS_CONTAINED+CKEDITOR.POSITION_FOLLOWING}if("sourceIndex"in f)return 0>f.sourceIndex||0>b.sourceIndex?CKEDITOR.POSITION_DISCONNECTED:f.sourceIndex=document.documentMode||!f||(a=f+":"+a);return new CKEDITOR.dom.nodeList(this.$.getElementsByTagName(a))},getHead:function(){var a=this.$.getElementsByTagName("head")[0];return a=a?new CKEDITOR.dom.element(a):this.getDocumentElement().append(new CKEDITOR.dom.element("head"),!0)},getBody:function(){return new CKEDITOR.dom.element(this.$.body)}, +getDocumentElement:function(){return new CKEDITOR.dom.element(this.$.documentElement)},getWindow:function(){return new CKEDITOR.dom.window(this.$.parentWindow||this.$.defaultView)},write:function(a){this.$.open("text/html","replace");CKEDITOR.env.ie&&(a=a.replace(/(?:^\s*]*?>)|^/i,'$\x26\n\x3cscript data-cke-temp\x3d"1"\x3e('+CKEDITOR.tools.fixDomain+")();\x3c/script\x3e"));this.$.write(a);this.$.close()},find:function(a){return new CKEDITOR.dom.nodeList(this.$.querySelectorAll(a))},findOne:function(a){return(a= +this.$.querySelector(a))?new CKEDITOR.dom.element(a):null},_getHtml5ShivFrag:function(){var a=this.getCustomData("html5ShivFrag");a||(a=this.$.createDocumentFragment(),CKEDITOR.tools.enableHtml5Elements(a,!0),this.setCustomData("html5ShivFrag",a));return a}}),CKEDITOR.dom.nodeList=function(a){this.$=a},CKEDITOR.dom.nodeList.prototype={count:function(){return this.$.length},getItem:function(a){return 0>a||a>=this.$.length?null:(a=this.$[a])?new CKEDITOR.dom.node(a):null},toArray:function(){return CKEDITOR.tools.array.map(this.$, +function(a){return new CKEDITOR.dom.node(a)})}},CKEDITOR.dom.element=function(a,f){"string"==typeof a&&(a=(f?f.$:document).createElement(a));CKEDITOR.dom.domObject.call(this,a)},CKEDITOR.dom.element.get=function(a){return(a="string"==typeof a?document.getElementById(a)||document.getElementsByName(a)[0]:a)&&(a.$?a:new CKEDITOR.dom.element(a))},CKEDITOR.dom.element.prototype=new CKEDITOR.dom.node,CKEDITOR.dom.element.createFromHtml=function(a,f){var b=new CKEDITOR.dom.element("div",f);b.setHtml(a); +return b.getFirst().remove()},CKEDITOR.dom.element.setMarker=function(a,f,b,c){var d=f.getCustomData("list_marker_id")||f.setCustomData("list_marker_id",CKEDITOR.tools.getNextNumber()).getCustomData("list_marker_id"),l=f.getCustomData("list_marker_names")||f.setCustomData("list_marker_names",{}).getCustomData("list_marker_names");a[d]=f;l[b]=1;return f.setCustomData(b,c)},CKEDITOR.dom.element.clearAllMarkers=function(a){for(var f in a)CKEDITOR.dom.element.clearMarkers(a,a[f],1)},CKEDITOR.dom.element.clearMarkers= +function(a,f,b){var c=f.getCustomData("list_marker_names"),d=f.getCustomData("list_marker_id"),l;for(l in c)f.removeCustomData(l);f.removeCustomData("list_marker_names");b&&(f.removeCustomData("list_marker_id"),delete a[d])},function(){function a(a,b){return-1<(" "+a+" ").replace(l," ").indexOf(" "+b+" ")}function f(a){var b=!0;a.$.id||(a.$.id="cke_tmp_"+CKEDITOR.tools.getNextNumber(),b=!1);return function(){b||a.removeAttribute("id")}}function b(a,b){var c=CKEDITOR.tools.escapeCss(a.$.id);return"#"+ +c+" "+b.split(/,\s*/).join(", #"+c+" ")}function c(a){for(var b=0,c=0,e=k[a].length;cCKEDITOR.env.version?this.$.text+=a:this.append(new CKEDITOR.dom.text(a))},appendBogus:function(a){if(a||CKEDITOR.env.needsBrFiller){for(a=this.getLast();a&&a.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.rtrim(a.getText());)a=a.getPrevious();a&& +a.is&&a.is("br")||(a=this.getDocument().createElement("br"),CKEDITOR.env.gecko&&a.setAttribute("type","_moz"),this.append(a))}},breakParent:function(a,b){var c=new CKEDITOR.dom.range(this.getDocument());c.setStartAfter(this);c.setEndAfter(a);var e=c.extractContents(!1,b||!1),d;c.insertNode(this.remove());if(CKEDITOR.env.ie&&!CKEDITOR.env.edge){for(c=new CKEDITOR.dom.element("div");d=e.getFirst();)d.$.style.backgroundColor&&(d.$.style.backgroundColor=d.$.style.backgroundColor),c.append(d);c.insertAfter(this); +c.remove(!0)}else e.insertAfterNode(this)},contains:document.compareDocumentPosition?function(a){return!!(this.$.compareDocumentPosition(a.$)&16)}:function(a){var b=this.$;return a.type!=CKEDITOR.NODE_ELEMENT?b.contains(a.getParent().$):b!=a.$&&b.contains(a.$)},focus:function(){function a(){try{this.$.focus()}catch(b){}}return function(b){b?CKEDITOR.tools.setTimeout(a,100,this):a.call(this)}}(),getHtml:function(){var a=this.$.innerHTML;return CKEDITOR.env.ie?a.replace(/<\?[^>]*>/g,""):a},getOuterHtml:function(){if(this.$.outerHTML)return this.$.outerHTML.replace(/<\?[^>]*>/, +"");var a=this.$.ownerDocument.createElement("div");a.appendChild(this.$.cloneNode(!0));return a.innerHTML},getClientRect:function(a){var b=CKEDITOR.tools.extend({},this.$.getBoundingClientRect());!b.width&&(b.width=b.right-b.left);!b.height&&(b.height=b.bottom-b.top);return a?CKEDITOR.tools.getAbsoluteRectPosition(this.getWindow(),b):b},setHtml:CKEDITOR.env.ie&&9>CKEDITOR.env.version?function(a){try{var b=this.$;if(this.getParent())return b.innerHTML=a;var c=this.getDocument()._getHtml5ShivFrag(); +c.appendChild(b);b.innerHTML=a;c.removeChild(b);return a}catch(e){this.$.innerHTML="";b=new CKEDITOR.dom.element("body",this.getDocument());b.$.innerHTML=a;for(b=b.getChildren();b.count();)this.append(b.getItem(0));return a}}:function(a){return this.$.innerHTML=a},setText:function(){var a=document.createElement("p");a.innerHTML="x";a=a.textContent;return function(b){this.$[a?"textContent":"innerText"]=b}}(),getAttribute:function(){var a=function(a){return this.$.getAttribute(a,2)};return CKEDITOR.env.ie&& +(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(a){switch(a){case "class":a="className";break;case "http-equiv":a="httpEquiv";break;case "name":return this.$.name;case "tabindex":return a=this.$.getAttribute(a,2),0!==a&&0===this.$.tabIndex&&(a=null),a;case "checked":return a=this.$.attributes.getNamedItem(a),(a.specified?a.nodeValue:this.$.checked)?"checked":null;case "hspace":case "value":return this.$[a];case "style":return this.$.style.cssText;case "contenteditable":case "contentEditable":return this.$.attributes.getNamedItem("contentEditable").specified? +this.$.getAttribute("contentEditable"):null}return this.$.getAttribute(a,2)}:a}(),getAttributes:function(a){var b={},c=this.$.attributes,e;a=CKEDITOR.tools.isArray(a)?a:[];for(e=0;e=document.documentMode){var b=this.$.scopeName;"HTML"!=b&&(a=b.toLowerCase()+":"+a)}this.getName=function(){return a};return this.getName()},getValue:function(){return this.$.value},getFirst:function(a){var b=this.$.firstChild;(b=b&&new CKEDITOR.dom.node(b))&&a&&!a(b)&&(b=b.getNext(a));return b},getLast:function(a){var b=this.$.lastChild; +(b=b&&new CKEDITOR.dom.node(b))&&a&&!a(b)&&(b=b.getPrevious(a));return b},getStyle:function(a){return this.$.style[CKEDITOR.tools.cssStyleToDomStyle(a)]},is:function(){var a=this.getName();if("object"==typeof arguments[0])return!!arguments[0][a];for(var b=0;bCKEDITOR.env.version&&this.is("a")){var c=this.getParent();c.type==CKEDITOR.NODE_ELEMENT&&(c=c.clone(),c.setHtml(b),b=c.getHtml(),c.setHtml(a),a=c.getHtml())}return b==a},isVisible:function(){var a=(this.$.offsetHeight||this.$.offsetWidth)&&"hidden"!=this.getComputedStyle("visibility"),b,c;a&&CKEDITOR.env.webkit&&(b=this.getWindow(),!b.equals(CKEDITOR.document.getWindow())&& +(c=b.$.frameElement)&&(a=(new CKEDITOR.dom.element(c)).isVisible()));return!!a},isEmptyInlineRemoveable:function(){if(!CKEDITOR.dtd.$removeEmpty[this.getName()])return!1;for(var a=this.getChildren(),b=0,c=a.count();bCKEDITOR.env.version?function(b){return"name"==b?!!this.$.name:a.call(this,b)}:a:function(a){return!!this.$.attributes.getNamedItem(a)}}(),hide:function(){this.setStyle("display","none")},moveChildren:function(a,b){var c=this.$;a=a.$;if(c!=a){var e;if(b)for(;e=c.lastChild;)a.insertBefore(c.removeChild(e), +a.firstChild);else for(;e=c.firstChild;)a.appendChild(c.removeChild(e))}},mergeSiblings:function(){function a(b,g,e){if(g&&g.type==CKEDITOR.NODE_ELEMENT){for(var c=[];g.data("cke-bookmark")||g.isEmptyInlineRemoveable();)if(c.push(g),g=e?g.getNext():g.getPrevious(),!g||g.type!=CKEDITOR.NODE_ELEMENT)return;if(b.isIdentical(g)){for(var d=e?b.getLast():b.getFirst();c.length;)c.shift().move(b,!e);g.moveChildren(b,!e);g.remove();d&&d.type==CKEDITOR.NODE_ELEMENT&&d.mergeSiblings()}}}return function(b){if(!1=== +b||CKEDITOR.dtd.$removeEmpty[this.getName()]||this.is("a"))a(this,this.getNext(),!0),a(this,this.getPrevious())}}(),show:function(){this.setStyles({display:"",visibility:""})},setAttribute:function(){var a=function(a,b){this.$.setAttribute(a,b);return this};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(b,c){"class"==b?this.$.className=c:"style"==b?this.$.style.cssText=c:"tabindex"==b?this.$.tabIndex=c:"checked"==b?this.$.checked=c:"contenteditable"==b?a.call(this, +"contentEditable",c):a.apply(this,arguments);return this}:CKEDITOR.env.ie8Compat&&CKEDITOR.env.secure?function(b,c){if("src"==b&&c.match(/^http:\/\//))try{a.apply(this,arguments)}catch(e){}else a.apply(this,arguments);return this}:a}(),setAttributes:function(a){for(var b in a)this.setAttribute(b,a[b]);return this},setValue:function(a){this.$.value=a;return this},removeAttribute:function(){var a=function(a){this.$.removeAttribute(a)};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)? +function(a){"class"==a?a="className":"tabindex"==a?a="tabIndex":"contenteditable"==a&&(a="contentEditable");this.$.removeAttribute(a)}:a}(),removeAttributes:function(a){if(CKEDITOR.tools.isArray(a))for(var b=0;bCKEDITOR.env.version?(a=Math.round(100*a),this.setStyle("filter",100<=a?"":"progid:DXImageTransform.Microsoft.Alpha(opacity\x3d"+a+")")):this.setStyle("opacity",a)},unselectable:function(){this.setStyles(CKEDITOR.tools.cssVendorPrefix("user-select","none"));if(CKEDITOR.env.ie){this.setAttribute("unselectable","on");for(var a,b=this.getElementsByTag("*"),c=0,e=b.count();cl||0l?l:d);c&&(0>f||0f?f:e,0)},setState:function(a,b,c){b=b||"cke";switch(a){case CKEDITOR.TRISTATE_ON:this.addClass(b+"_on");this.removeClass(b+"_off");this.removeClass(b+"_disabled");c&&this.setAttribute("aria-pressed",!0);c&&this.removeAttribute("aria-disabled");break;case CKEDITOR.TRISTATE_DISABLED:this.addClass(b+"_disabled");this.removeClass(b+"_off");this.removeClass(b+"_on");c&&this.setAttribute("aria-disabled", +!0);c&&this.removeAttribute("aria-pressed");break;default:this.addClass(b+"_off"),this.removeClass(b+"_on"),this.removeClass(b+"_disabled"),c&&this.removeAttribute("aria-pressed"),c&&this.removeAttribute("aria-disabled")}},getFrameDocument:function(){var a=this.$;try{a.contentWindow.document}catch(b){a.src=a.src}return a&&new CKEDITOR.dom.document(a.contentWindow.document)},copyAttributes:function(a,b){var c=this.$.attributes;b=b||{};for(var e=0;e=A.getChildCount()?(A=A.getChild(C-1),F=!0):A=A.getChild(C):J=F=!0;x.type==CKEDITOR.NODE_TEXT?t?E=!0:x.split(B):0ha)for(;Z;)Z=h(Z,K,!0);K=W}t||m()}}function b(){var a=!1,b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.dom.walker.bookmark(!0),d=CKEDITOR.dom.walker.bogus();return function(g){return c(g)||b(g)?!0:d(g)&&!a?a=!0:g.type==CKEDITOR.NODE_TEXT&&(g.hasAscendant("pre")||CKEDITOR.tools.trim(g.getText()).length)||g.type==CKEDITOR.NODE_ELEMENT&&!g.is(l)?!1:!0}}function c(a){var b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.dom.walker.bookmark(1); +return function(d){return c(d)||b(d)?!0:!a&&k(d)||d.type==CKEDITOR.NODE_ELEMENT&&d.is(CKEDITOR.dtd.$removeEmpty)}}function d(a){return function(){var b;return this[a?"getPreviousNode":"getNextNode"](function(a){!b&&m(a)&&(b=a);return h(a)&&!(k(a)&&a.equals(b))})}}var l={abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,"var":1},k=CKEDITOR.dom.walker.bogus(),g=/^[\t\r\n ]*(?: |\xa0)$/, +h=CKEDITOR.dom.walker.editable(),m=CKEDITOR.dom.walker.ignored(!0);CKEDITOR.dom.range.prototype={clone:function(){var a=new CKEDITOR.dom.range(this.root);a._setStartContainer(this.startContainer);a.startOffset=this.startOffset;a._setEndContainer(this.endContainer);a.endOffset=this.endOffset;a.collapsed=this.collapsed;return a},collapse:function(a){a?(this._setEndContainer(this.startContainer),this.endOffset=this.startOffset):(this._setStartContainer(this.endContainer),this.startOffset=this.endOffset); +this.collapsed=!0},cloneContents:function(a){var b=new CKEDITOR.dom.documentFragment(this.document);this.collapsed||f(this,2,b,!1,"undefined"==typeof a?!0:a);return b},deleteContents:function(a){this.collapsed||f(this,0,null,a)},extractContents:function(a,b){var c=new CKEDITOR.dom.documentFragment(this.document);this.collapsed||f(this,1,c,a,"undefined"==typeof b?!0:b);return c},createBookmark:function(a){var b,c,d,g,h=this.collapsed;b=this.document.createElement("span");b.data("cke-bookmark",1);b.setStyle("display", +"none");b.setHtml("\x26nbsp;");a&&(d="cke_bm_"+CKEDITOR.tools.getNextNumber(),b.setAttribute("id",d+(h?"C":"S")));h||(c=b.clone(),c.setHtml("\x26nbsp;"),a&&c.setAttribute("id",d+"E"),g=this.clone(),g.collapse(),g.insertNode(c));g=this.clone();g.collapse(!0);g.insertNode(b);c?(this.setStartAfter(b),this.setEndBefore(c)):this.moveToPosition(b,CKEDITOR.POSITION_AFTER_END);return{startNode:a?d+(h?"C":"S"):b,endNode:a?d+"E":c,serializable:a,collapsed:h}},createBookmark2:function(){function a(b){var e= +b.container,d=b.offset,g;g=e;var h=d;g=g.type!=CKEDITOR.NODE_ELEMENT||0===h||h==g.getChildCount()?0:g.getChild(h-1).type==CKEDITOR.NODE_TEXT&&g.getChild(h).type==CKEDITOR.NODE_TEXT;g&&(e=e.getChild(d-1),d=e.getLength());if(e.type==CKEDITOR.NODE_ELEMENT&&0=a.offset&&(a.offset=d.getIndex(),a.container=d.getParent()))}}var c=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_TEXT,!0);return function(c){var d=this.collapsed,g={container:this.startContainer, +offset:this.startOffset},h={container:this.endContainer,offset:this.endOffset};c&&(a(g),b(g,this.root),d||(a(h),b(h,this.root)));return{start:g.container.getAddress(c),end:d?null:h.container.getAddress(c),startOffset:g.offset,endOffset:h.offset,normalized:c,collapsed:d,is2:!0}}}(),moveToBookmark:function(a){if(a.is2){var b=this.document.getByAddress(a.start,a.normalized),c=a.startOffset,d=a.end&&this.document.getByAddress(a.end,a.normalized);a=a.endOffset;this.setStart(b,c);d?this.setEnd(d,a):this.collapse(!0)}else b= +(c=a.serializable)?this.document.getById(a.startNode):a.startNode,a=c?this.document.getById(a.endNode):a.endNode,this.setStartBefore(b),b.remove(),a?(this.setEndBefore(a),a.remove()):this.collapse(!0)},getBoundaryNodes:function(){var a=this.startContainer,b=this.endContainer,c=this.startOffset,d=this.endOffset,g;if(a.type==CKEDITOR.NODE_ELEMENT)if(g=a.getChildCount(),g>c)a=a.getChild(c);else if(1>g)a=a.getPreviousSourceNode();else{for(a=a.$;a.lastChild;)a=a.lastChild;a=new CKEDITOR.dom.node(a);a= +a.getNextSourceNode()||a}if(b.type==CKEDITOR.NODE_ELEMENT)if(g=b.getChildCount(),g>d)b=b.getChild(d).getPreviousSourceNode(!0);else if(1>g)b=b.getPreviousSourceNode();else{for(b=b.$;b.lastChild;)b=b.lastChild;b=new CKEDITOR.dom.node(b)}a.getPosition(b)&CKEDITOR.POSITION_FOLLOWING&&(a=b);return{startNode:a,endNode:b}},getCommonAncestor:function(a,b){var c=this.startContainer,d=this.endContainer,c=c.equals(d)?a&&c.type==CKEDITOR.NODE_ELEMENT&&this.startOffset==this.endOffset-1?c.getChild(this.startOffset): +c:c.getCommonAncestor(d);return b&&!c.is?c.getParent():c},optimize:function(){var a=this.startContainer,b=this.startOffset;a.type!=CKEDITOR.NODE_ELEMENT&&(b?b>=a.getLength()&&this.setStartAfter(a):this.setStartBefore(a));a=this.endContainer;b=this.endOffset;a.type!=CKEDITOR.NODE_ELEMENT&&(b?b>=a.getLength()&&this.setEndAfter(a):this.setEndBefore(a))},optimizeBookmark:function(){var a=this.startContainer,b=this.endContainer;a.is&&a.is("span")&&a.data("cke-bookmark")&&this.setStartAt(a,CKEDITOR.POSITION_BEFORE_START); +b&&b.is&&b.is("span")&&b.data("cke-bookmark")&&this.setEndAt(b,CKEDITOR.POSITION_AFTER_END)},trim:function(a,b){var c=this.startContainer,d=this.startOffset,g=this.collapsed;if((!a||g)&&c&&c.type==CKEDITOR.NODE_TEXT){if(d)if(d>=c.getLength())d=c.getIndex()+1,c=c.getParent();else{var h=c.split(d),d=c.getIndex()+1,c=c.getParent();this.startContainer.equals(this.endContainer)?this.setEnd(h,this.endOffset-this.startOffset):c.equals(this.endContainer)&&(this.endOffset+=1)}else d=c.getIndex(),c=c.getParent(); +this.setStart(c,d);if(g){this.collapse(!0);return}}c=this.endContainer;d=this.endOffset;b||g||!c||c.type!=CKEDITOR.NODE_TEXT||(d?(d>=c.getLength()||c.split(d),d=c.getIndex()+1):d=c.getIndex(),c=c.getParent(),this.setEnd(c,d))},enlarge:function(a,b){function c(a){return a&&a.type==CKEDITOR.NODE_ELEMENT&&a.hasAttribute("contenteditable")?null:a}var d=new RegExp(/[^\s\ufeff]/);switch(a){case CKEDITOR.ENLARGE_INLINE:var g=1;case CKEDITOR.ENLARGE_ELEMENT:var h=function(a,b){var e=new CKEDITOR.dom.range(m); +e.setStart(a,b);e.setEndAt(m,CKEDITOR.POSITION_BEFORE_END);var e=new CKEDITOR.dom.walker(e),c;for(e.guard=function(a){return!(a.type==CKEDITOR.NODE_ELEMENT&&a.isBlockBoundary())};c=e.next();){if(c.type!=CKEDITOR.NODE_TEXT)return!1;H=c!=a?c.getText():c.substring(b);if(d.test(H))return!1}return!0};if(this.collapsed)break;var f=this.getCommonAncestor(),m=this.root,l,k,t,x,A,B=!1,C,H;C=this.startContainer;var F=this.startOffset;C.type==CKEDITOR.NODE_TEXT?(F&&(C=!CKEDITOR.tools.trim(C.substring(0,F)).length&& +C,B=!!C),C&&((x=C.getPrevious())||(t=C.getParent()))):(F&&(x=C.getChild(F-1)||C.getLast()),x||(t=C));for(t=c(t);t||x;){if(t&&!x){!A&&t.equals(f)&&(A=!0);if(g?t.isBlockBoundary():!m.contains(t))break;B&&"inline"==t.getComputedStyle("display")||(B=!1,A?l=t:this.setStartBefore(t));x=t.getPrevious()}for(;x;)if(C=!1,x.type==CKEDITOR.NODE_COMMENT)x=x.getPrevious();else{if(x.type==CKEDITOR.NODE_TEXT)H=x.getText(),d.test(H)&&(x=null),C=/[\s\ufeff]$/.test(H);else if((x.$.offsetWidth>(CKEDITOR.env.webkit?1: +0)||b&&x.is("br"))&&!x.data("cke-bookmark"))if(B&&CKEDITOR.dtd.$removeEmpty[x.getName()]){H=x.getText();if(d.test(H))x=null;else for(var F=x.$.getElementsByTagName("*"),I=0,J;J=F[I++];)if(!CKEDITOR.dtd.$removeEmpty[J.nodeName.toLowerCase()]){x=null;break}x&&(C=!!H.length)}else x=null;C&&(B?A?l=t:t&&this.setStartBefore(t):B=!0);if(x){C=x.getPrevious();if(!t&&!C){t=x;x=null;break}x=C}else t=null}t&&(t=c(t.getParent()))}C=this.endContainer;F=this.endOffset;t=x=null;A=B=!1;C.type==CKEDITOR.NODE_TEXT? +CKEDITOR.tools.trim(C.substring(F)).length?B=!0:(B=!C.getLength(),F==C.getLength()?(x=C.getNext())||(t=C.getParent()):h(C,F)&&(t=C.getParent())):(x=C.getChild(F))||(t=C);for(;t||x;){if(t&&!x){!A&&t.equals(f)&&(A=!0);if(g?t.isBlockBoundary():!m.contains(t))break;B&&"inline"==t.getComputedStyle("display")||(B=!1,A?k=t:t&&this.setEndAfter(t));x=t.getNext()}for(;x;){C=!1;if(x.type==CKEDITOR.NODE_TEXT)H=x.getText(),h(x,0)||(x=null),C=/^[\s\ufeff]/.test(H);else if(x.type==CKEDITOR.NODE_ELEMENT){if((0=f.getLength()?h.setStartAfter(f):(h.setStartBefore(f),c=0):h.setStartBefore(f));m&&m.type==CKEDITOR.NODE_TEXT&&(k?k>=m.getLength()?h.setEndAfter(m):(h.setEndAfter(m),t=0):h.setEndBefore(m));var h=new CKEDITOR.dom.walker(h),x=CKEDITOR.dom.walker.bookmark(),A=CKEDITOR.dom.walker.bogus();h.evaluator=function(b){return b.type==(a==CKEDITOR.SHRINK_ELEMENT?CKEDITOR.NODE_ELEMENT:CKEDITOR.NODE_TEXT)}; +var B;h.guard=function(b,c){if(g&&A(b)||x(b))return!0;if(a==CKEDITOR.SHRINK_ELEMENT&&b.type==CKEDITOR.NODE_TEXT||c&&b.equals(B)||!1===d&&b.type==CKEDITOR.NODE_ELEMENT&&b.isBlockBoundary()||b.type==CKEDITOR.NODE_ELEMENT&&b.hasAttribute("contenteditable"))return!1;c||b.type!=CKEDITOR.NODE_ELEMENT||(B=b);return!0};c&&(f=h[a==CKEDITOR.SHRINK_ELEMENT?"lastForward":"next"]())&&this.setStartAt(f,b?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_START);t&&(h.reset(),(h=h[a==CKEDITOR.SHRINK_ELEMENT? +"lastBackward":"previous"]())&&this.setEndAt(h,b?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_END));return!(!c&&!t)}},insertNode:function(a){this.optimizeBookmark();this.trim(!1,!0);var b=this.startContainer,c=b.getChild(this.startOffset);c?a.insertBefore(c):b.append(a);a.getParent()&&a.getParent().equals(this.endContainer)&&this.endOffset++;this.setStartBefore(a)},moveToPosition:function(a,b){this.setStartAt(a,b);this.collapse(!0)},moveToRange:function(a){this.setStart(a.startContainer,a.startOffset); +this.setEnd(a.endContainer,a.endOffset)},selectNodeContents:function(a){this.setStart(a,0);this.setEnd(a,a.type==CKEDITOR.NODE_TEXT?a.getLength():a.getChildCount())},setStart:function(b,c){b.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[b.getName()]&&(c=b.getIndex(),b=b.getParent());this._setStartContainer(b);this.startOffset=c;this.endContainer||(this._setEndContainer(b),this.endOffset=c);a(this)},setEnd:function(b,c){b.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[b.getName()]&&(c=b.getIndex()+ +1,b=b.getParent());this._setEndContainer(b);this.endOffset=c;this.startContainer||(this._setStartContainer(b),this.startOffset=c);a(this)},setStartAfter:function(a){this.setStart(a.getParent(),a.getIndex()+1)},setStartBefore:function(a){this.setStart(a.getParent(),a.getIndex())},setEndAfter:function(a){this.setEnd(a.getParent(),a.getIndex()+1)},setEndBefore:function(a){this.setEnd(a.getParent(),a.getIndex())},setStartAt:function(b,c){switch(c){case CKEDITOR.POSITION_AFTER_START:this.setStart(b,0); +break;case CKEDITOR.POSITION_BEFORE_END:b.type==CKEDITOR.NODE_TEXT?this.setStart(b,b.getLength()):this.setStart(b,b.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setStartBefore(b);break;case CKEDITOR.POSITION_AFTER_END:this.setStartAfter(b)}a(this)},setEndAt:function(b,c){switch(c){case CKEDITOR.POSITION_AFTER_START:this.setEnd(b,0);break;case CKEDITOR.POSITION_BEFORE_END:b.type==CKEDITOR.NODE_TEXT?this.setEnd(b,b.getLength()):this.setEnd(b,b.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setEndBefore(b); +break;case CKEDITOR.POSITION_AFTER_END:this.setEndAfter(b)}a(this)},fixBlock:function(a,b){var c=this.createBookmark(),d=this.document.createElement(b);this.collapse(a);this.enlarge(CKEDITOR.ENLARGE_BLOCK_CONTENTS);this.extractContents().appendTo(d);d.trim();this.insertNode(d);var g=d.getBogus();g&&g.remove();d.appendBogus();this.moveToBookmark(c);return d},splitBlock:function(a,b){var c=new CKEDITOR.dom.elementPath(this.startContainer,this.root),d=new CKEDITOR.dom.elementPath(this.endContainer,this.root), +g=c.block,h=d.block,f=null;if(!c.blockLimit.equals(d.blockLimit))return null;"br"!=a&&(g||(g=this.fixBlock(!0,a),h=(new CKEDITOR.dom.elementPath(this.endContainer,this.root)).block),h||(h=this.fixBlock(!1,a)));c=g&&this.checkStartOfBlock();d=h&&this.checkEndOfBlock();this.deleteContents();g&&g.equals(h)&&(d?(f=new CKEDITOR.dom.elementPath(this.startContainer,this.root),this.moveToPosition(h,CKEDITOR.POSITION_AFTER_END),h=null):c?(f=new CKEDITOR.dom.elementPath(this.startContainer,this.root),this.moveToPosition(g, +CKEDITOR.POSITION_BEFORE_START),g=null):(h=this.splitElement(g,b||!1),g.is("ul","ol")||g.appendBogus()));return{previousBlock:g,nextBlock:h,wasStartOfBlock:c,wasEndOfBlock:d,elementPath:f}},splitElement:function(a,b){if(!this.collapsed)return null;this.setEndAt(a,CKEDITOR.POSITION_BEFORE_END);var c=this.extractContents(!1,b||!1),d=a.clone(!1,b||!1);c.appendTo(d);d.insertAfter(a);this.moveToPosition(a,CKEDITOR.POSITION_AFTER_END);return d},removeEmptyBlocksAtEnd:function(){function a(e){return function(a){return b(a)|| +c(a)||a.type==CKEDITOR.NODE_ELEMENT&&a.isEmptyInlineRemoveable()||e.is("table")&&a.is("caption")?!1:!0}}var b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.dom.walker.bookmark(!1);return function(b){for(var c=this.createBookmark(),d=this[b?"endPath":"startPath"](),g=d.block||d.blockLimit,h;g&&!g.equals(d.root)&&!g.getFirst(a(g));)h=g.getParent(),this[b?"setEndAt":"setStartAt"](g,CKEDITOR.POSITION_AFTER_END),g.remove(1),g=h;this.moveToBookmark(c)}}(),startPath:function(){return new CKEDITOR.dom.elementPath(this.startContainer, +this.root)},endPath:function(){return new CKEDITOR.dom.elementPath(this.endContainer,this.root)},checkBoundaryOfElement:function(a,b){var d=b==CKEDITOR.START,g=this.clone();g.collapse(d);g[d?"setStartAt":"setEndAt"](a,d?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END);g=new CKEDITOR.dom.walker(g);g.evaluator=c(d);return g[d?"checkBackward":"checkForward"]()},checkStartOfBlock:function(){var a=this.startContainer,c=this.startOffset;CKEDITOR.env.ie&&c&&a.type==CKEDITOR.NODE_TEXT&&(a=CKEDITOR.tools.ltrim(a.substring(0, +c)),g.test(a)&&this.trim(0,1));this.trim();a=new CKEDITOR.dom.elementPath(this.startContainer,this.root);c=this.clone();c.collapse(!0);c.setStartAt(a.block||a.blockLimit,CKEDITOR.POSITION_AFTER_START);a=new CKEDITOR.dom.walker(c);a.evaluator=b();return a.checkBackward()},checkEndOfBlock:function(){var a=this.endContainer,c=this.endOffset;CKEDITOR.env.ie&&a.type==CKEDITOR.NODE_TEXT&&(a=CKEDITOR.tools.rtrim(a.substring(c)),g.test(a)&&this.trim(1,0));this.trim();a=new CKEDITOR.dom.elementPath(this.endContainer, +this.root);c=this.clone();c.collapse(!1);c.setEndAt(a.block||a.blockLimit,CKEDITOR.POSITION_BEFORE_END);a=new CKEDITOR.dom.walker(c);a.evaluator=b();return a.checkForward()},getPreviousNode:function(a,b,c){var d=this.clone();d.collapse(1);d.setStartAt(c||this.root,CKEDITOR.POSITION_AFTER_START);c=new CKEDITOR.dom.walker(d);c.evaluator=a;c.guard=b;return c.previous()},getNextNode:function(a,b,c){var d=this.clone();d.collapse();d.setEndAt(c||this.root,CKEDITOR.POSITION_BEFORE_END);c=new CKEDITOR.dom.walker(d); +c.evaluator=a;c.guard=b;return c.next()},checkReadOnly:function(){function a(b,c){for(;b;){if(b.type==CKEDITOR.NODE_ELEMENT){if("false"==b.getAttribute("contentEditable")&&!b.data("cke-editable"))return 0;if(b.is("html")||"true"==b.getAttribute("contentEditable")&&(b.contains(c)||b.equals(c)))break}b=b.getParent()}return 1}return function(){var b=this.startContainer,c=this.endContainer;return!(a(b,c)&&a(c,b))}}(),moveToElementEditablePosition:function(a,b){if(a.type==CKEDITOR.NODE_ELEMENT&&!a.isEditable(!1))return this.moveToPosition(a, +b?CKEDITOR.POSITION_AFTER_END:CKEDITOR.POSITION_BEFORE_START),!0;for(var c=0;a;){if(a.type==CKEDITOR.NODE_TEXT){b&&this.endContainer&&this.checkEndOfBlock()&&g.test(a.getText())?this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START):this.moveToPosition(a,b?CKEDITOR.POSITION_AFTER_END:CKEDITOR.POSITION_BEFORE_START);c=1;break}if(a.type==CKEDITOR.NODE_ELEMENT)if(a.isEditable())this.moveToPosition(a,b?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_START),c=1;else if(b&&a.is("br")&&this.endContainer&& +this.checkEndOfBlock())this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START);else if("false"==a.getAttribute("contenteditable")&&a.is(CKEDITOR.dtd.$block))return this.setStartBefore(a),this.setEndAfter(a),!0;var d=a,h=c,f=void 0;d.type==CKEDITOR.NODE_ELEMENT&&d.isEditable(!1)&&(f=d[b?"getLast":"getFirst"](m));h||f||(f=d[b?"getPrevious":"getNext"](m));a=f}return!!c},moveToClosestEditablePosition:function(a,b){var c,d=0,g,h,f=[CKEDITOR.POSITION_AFTER_END,CKEDITOR.POSITION_BEFORE_START];a?(c=new CKEDITOR.dom.range(this.root), +c.moveToPosition(a,f[b?0:1])):c=this.clone();if(a&&!a.is(CKEDITOR.dtd.$block))d=1;else if(g=c[b?"getNextEditableNode":"getPreviousEditableNode"]())d=1,(h=g.type==CKEDITOR.NODE_ELEMENT)&&g.is(CKEDITOR.dtd.$block)&&"false"==g.getAttribute("contenteditable")?(c.setStartAt(g,CKEDITOR.POSITION_BEFORE_START),c.setEndAt(g,CKEDITOR.POSITION_AFTER_END)):!CKEDITOR.env.needsBrFiller&&h&&g.is(CKEDITOR.dom.walker.validEmptyBlockContainers)?(c.setEnd(g,0),c.collapse()):c.moveToPosition(g,f[b?1:0]);d&&this.moveToRange(c); +return!!d},moveToElementEditStart:function(a){return this.moveToElementEditablePosition(a)},moveToElementEditEnd:function(a){return this.moveToElementEditablePosition(a,!0)},getEnclosedNode:function(){var a=this.clone();a.optimize();if(a.startContainer.type!=CKEDITOR.NODE_ELEMENT||a.endContainer.type!=CKEDITOR.NODE_ELEMENT)return null;var a=new CKEDITOR.dom.walker(a),b=CKEDITOR.dom.walker.bookmark(!1,!0),c=CKEDITOR.dom.walker.whitespaces(!0);a.evaluator=function(a){return c(a)&&b(a)};var d=a.next(); +a.reset();return d&&d.equals(a.previous())?d:null},getTouchedStartNode:function(){var a=this.startContainer;return this.collapsed||a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.startOffset)||a},getTouchedEndNode:function(){var a=this.endContainer;return this.collapsed||a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.endOffset-1)||a},getNextEditableNode:d(),getPreviousEditableNode:d(1),_getTableElement:function(a){a=a||{td:1,th:1,tr:1,tbody:1,thead:1,tfoot:1,table:1};var b=this.startContainer,c= +this.endContainer,d=b.getAscendant("table",!0),g=c.getAscendant("table",!0);return d&&!this.root.contains(d)?null:CKEDITOR.env.safari&&d&&c.equals(this.root)?b.getAscendant(a,!0):this.getEnclosedNode()?this.getEnclosedNode().getAscendant(a,!0):d&&g&&(d.equals(g)||d.contains(g)||g.contains(d))?b.getAscendant(a,!0):null},scrollIntoView:function(){var a=new CKEDITOR.dom.element.createFromHtml("\x3cspan\x3e\x26nbsp;\x3c/span\x3e",this.document),b,c,d,g=this.clone();g.optimize();(d=g.startContainer.type== +CKEDITOR.NODE_TEXT)?(c=g.startContainer.getText(),b=g.startContainer.split(g.startOffset),a.insertAfter(g.startContainer)):g.insertNode(a);a.scrollIntoView();d&&(g.startContainer.setText(c),b.remove());a.remove()},getClientRects:function(){function a(b,c){var e=CKEDITOR.tools.array.map(b,function(a){return a}),d=new CKEDITOR.dom.range(c.root),g,h,f;c.startContainer instanceof CKEDITOR.dom.element&&(h=0===c.startOffset&&c.startContainer.hasAttribute("data-widget"));c.endContainer instanceof CKEDITOR.dom.element&& +(f=(f=c.endOffset===(c.endContainer.getChildCount?c.endContainer.getChildCount():c.endContainer.length))&&c.endContainer.hasAttribute("data-widget"));h&&d.setStart(c.startContainer.getParent(),c.startContainer.getIndex());f&&d.setEnd(c.endContainer.getParent(),c.endContainer.getIndex()+1);if(h||f)c=d;d=c.cloneContents().find("[data-cke-widget-id]").toArray();if(d=CKEDITOR.tools.array.map(d,function(a){var b=c.root.editor;a=a.getAttribute("data-cke-widget-id");return b.widgets.instances[a].element}))return d= +CKEDITOR.tools.array.map(d,function(a){var b;b=a.getParent().hasClass("cke_widget_wrapper")?a.getParent():a;g=this.root.getDocument().$.createRange();g.setStart(b.getParent().$,b.getIndex());g.setEnd(b.getParent().$,b.getIndex()+1);b=g.getClientRects();b.widgetRect=a.getClientRect();return b},c),CKEDITOR.tools.array.forEach(d,function(a){function b(d){CKEDITOR.tools.array.forEach(e,function(b,g){var h=CKEDITOR.tools.objectCompare(a[d],b);h||(h=CKEDITOR.tools.objectCompare(a.widgetRect,b));h&&(Array.prototype.splice.call(e, +g,a.length-d,a.widgetRect),c=!0)});c||(darguments.length||(this.range=a,this.forceBrBreak=0,this.enlargeBr=1,this.enforceRealBlocks=0,this._||(this._={}))}function f(a){var b=[];a.forEach(function(a){if("true"==a.getAttribute("contenteditable"))return b.push(a),!1},CKEDITOR.NODE_ELEMENT,!0);return b}function b(a,c,d,g){a:{null==g&&(g=f(d));for(var h;h=g.shift();)if(h.getDtd().p){g={element:h,remaining:g};break a}g=null}if(!g)return 0;if((h=CKEDITOR.filter.instances[g.element.data("cke-filter")])&& +!h.check(c))return b(a,c,d,g.remaining);c=new CKEDITOR.dom.range(g.element);c.selectNodeContents(g.element);c=c.createIterator();c.enlargeBr=a.enlargeBr;c.enforceRealBlocks=a.enforceRealBlocks;c.activeFilter=c.filter=h;a._.nestedEditable={element:g.element,container:d,remaining:g.remaining,iterator:c};return 1}function c(a,b,c){if(!b)return!1;a=a.clone();a.collapse(!c);return a.checkBoundaryOfElement(b,c?CKEDITOR.START:CKEDITOR.END)}var d=/^[\r\n\t ]+$/,l=CKEDITOR.dom.walker.bookmark(!1,!0),k=CKEDITOR.dom.walker.whitespaces(!0), +g=function(a){return l(a)&&k(a)},h={dd:1,dt:1,li:1};a.prototype={getNextParagraph:function(a){var e,f,k,y,u;a=a||"p";if(this._.nestedEditable){if(e=this._.nestedEditable.iterator.getNextParagraph(a))return this.activeFilter=this._.nestedEditable.iterator.activeFilter,e;this.activeFilter=this.filter;if(b(this,a,this._.nestedEditable.container,this._.nestedEditable.remaining))return this.activeFilter=this._.nestedEditable.iterator.activeFilter,this._.nestedEditable.iterator.getNextParagraph(a);this._.nestedEditable= +null}if(!this.range.root.getDtd()[a])return null;if(!this._.started){var p=this.range.clone();f=p.startPath();var v=p.endPath(),w=!p.collapsed&&c(p,f.block),r=!p.collapsed&&c(p,v.block,1);p.shrink(CKEDITOR.SHRINK_ELEMENT,!0);w&&p.setStartAt(f.block,CKEDITOR.POSITION_BEFORE_END);r&&p.setEndAt(v.block,CKEDITOR.POSITION_AFTER_START);f=p.endContainer.hasAscendant("pre",!0)||p.startContainer.hasAscendant("pre",!0);p.enlarge(this.forceBrBreak&&!f||!this.enlargeBr?CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:CKEDITOR.ENLARGE_BLOCK_CONTENTS); +p.collapsed||(f=new CKEDITOR.dom.walker(p.clone()),v=CKEDITOR.dom.walker.bookmark(!0,!0),f.evaluator=v,this._.nextNode=f.next(),f=new CKEDITOR.dom.walker(p.clone()),f.evaluator=v,f=f.previous(),this._.lastNode=f.getNextSourceNode(!0,null,p.root),this._.lastNode&&this._.lastNode.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.trim(this._.lastNode.getText())&&this._.lastNode.getParent().isBlockBoundary()&&(v=this.range.clone(),v.moveToPosition(this._.lastNode,CKEDITOR.POSITION_AFTER_END),v.checkEndOfBlock()&& +(v=new CKEDITOR.dom.elementPath(v.endContainer,v.root),this._.lastNode=(v.block||v.blockLimit).getNextSourceNode(!0))),this._.lastNode&&p.root.contains(this._.lastNode)||(this._.lastNode=this._.docEndMarker=p.document.createText(""),this._.lastNode.insertAfter(f)),p=null);this._.started=1;f=p}v=this._.nextNode;p=this._.lastNode;for(this._.nextNode=null;v;){var w=0,r=v.hasAscendant("pre"),z=v.type!=CKEDITOR.NODE_ELEMENT,t=0;if(z)v.type==CKEDITOR.NODE_TEXT&&d.test(v.getText())&&(z=0);else{var x=v.getName(); +if(CKEDITOR.dtd.$block[x]&&"false"==v.getAttribute("contenteditable")){e=v;b(this,a,e);break}else if(v.isBlockBoundary(this.forceBrBreak&&!r&&{br:1})){if("br"==x)z=1;else if(!f&&!v.getChildCount()&&"hr"!=x){e=v;k=v.equals(p);break}f&&(f.setEndAt(v,CKEDITOR.POSITION_BEFORE_START),"br"!=x&&(this._.nextNode=v));w=1}else{if(v.getFirst()){f||(f=this.range.clone(),f.setStartAt(v,CKEDITOR.POSITION_BEFORE_START));v=v.getFirst();continue}z=1}}z&&!f&&(f=this.range.clone(),f.setStartAt(v,CKEDITOR.POSITION_BEFORE_START)); +k=(!w||z)&&v.equals(p);if(f&&!w)for(;!v.getNext(g)&&!k;){x=v.getParent();if(x.isBlockBoundary(this.forceBrBreak&&!r&&{br:1})){w=1;z=0;k||x.equals(p);f.setEndAt(x,CKEDITOR.POSITION_BEFORE_END);break}v=x;z=1;k=v.equals(p);t=1}z&&f.setEndAt(v,CKEDITOR.POSITION_AFTER_END);v=this._getNextSourceNode(v,t,p);if((k=!v)||w&&f)break}if(!e){if(!f)return this._.docEndMarker&&this._.docEndMarker.remove(),this._.nextNode=null;e=new CKEDITOR.dom.elementPath(f.startContainer,f.root);v=e.blockLimit;w={div:1,th:1,td:1}; +e=e.block;!e&&v&&!this.enforceRealBlocks&&w[v.getName()]&&f.checkStartOfBlock()&&f.checkEndOfBlock()&&!v.equals(f.root)?e=v:!e||this.enforceRealBlocks&&e.is(h)?(e=this.range.document.createElement(a),f.extractContents().appendTo(e),e.trim(),f.insertNode(e),y=u=!0):"li"!=e.getName()?f.checkStartOfBlock()&&f.checkEndOfBlock()||(e=e.clone(!1),f.extractContents().appendTo(e),e.trim(),u=f.splitBlock(),y=!u.wasStartOfBlock,u=!u.wasEndOfBlock,f.insertNode(e)):k||(this._.nextNode=e.equals(p)?null:this._getNextSourceNode(f.getBoundaryNodes().endNode, +1,p))}y&&(y=e.getPrevious())&&y.type==CKEDITOR.NODE_ELEMENT&&("br"==y.getName()?y.remove():y.getLast()&&"br"==y.getLast().$.nodeName.toLowerCase()&&y.getLast().remove());u&&(y=e.getLast())&&y.type==CKEDITOR.NODE_ELEMENT&&"br"==y.getName()&&(!CKEDITOR.env.needsBrFiller||y.getPrevious(l)||y.getNext(l))&&y.remove();this._.nextNode||(this._.nextNode=k||e.equals(p)||!p?null:this._getNextSourceNode(e,1,p));return e},_getNextSourceNode:function(a,b,c){function d(a){return!(a.equals(c)||a.equals(g))}var g= +this.range.root;for(a=a.getNextSourceNode(b,null,d);!l(a);)a=a.getNextSourceNode(b,null,d);return a}};CKEDITOR.dom.range.prototype.createIterator=function(){return new a(this)}}(),CKEDITOR.command=function(a,f){this.uiItems=[];this.exec=function(b){if(this.state==CKEDITOR.TRISTATE_DISABLED||!this.checkAllowed())return!1;this.editorFocus&&a.focus();return!1===this.fire("exec")?!0:!1!==f.exec.call(this,a,b)};this.refresh=function(a,b){if(!this.readOnly&&a.readOnly)return!0;if(this.context&&!b.isContextFor(this.context)|| +!this.checkAllowed(!0))return this.disable(),!0;this.startDisabled||this.enable();this.modes&&!this.modes[a.mode]&&this.disable();return!1===this.fire("refresh",{editor:a,path:b})?!0:f.refresh&&!1!==f.refresh.apply(this,arguments)};var b;this.checkAllowed=function(c){return c||"boolean"!=typeof b?b=a.activeFilter.checkFeature(this):b};CKEDITOR.tools.extend(this,f,{modes:{wysiwyg:1},editorFocus:1,contextSensitive:!!f.context,state:CKEDITOR.TRISTATE_DISABLED});CKEDITOR.event.call(this)},CKEDITOR.command.prototype= +{enable:function(){this.state==CKEDITOR.TRISTATE_DISABLED&&this.checkAllowed()&&this.setState(this.preserveState&&"undefined"!=typeof this.previousState?this.previousState:CKEDITOR.TRISTATE_OFF)},disable:function(){this.setState(CKEDITOR.TRISTATE_DISABLED)},setState:function(a){if(this.state==a||a!=CKEDITOR.TRISTATE_DISABLED&&!this.checkAllowed())return!1;this.previousState=this.state;this.state=a;this.fire("state");return!0},toggleState:function(){this.state==CKEDITOR.TRISTATE_OFF?this.setState(CKEDITOR.TRISTATE_ON): +this.state==CKEDITOR.TRISTATE_ON&&this.setState(CKEDITOR.TRISTATE_OFF)}},CKEDITOR.event.implementOn(CKEDITOR.command.prototype),CKEDITOR.ENTER_P=1,CKEDITOR.ENTER_BR=2,CKEDITOR.ENTER_DIV=3,CKEDITOR.config={customConfig:"config.js",autoUpdateElement:!0,language:"",defaultLanguage:"en",contentsLangDirection:"",enterMode:CKEDITOR.ENTER_P,forceEnterMode:!1,shiftEnterMode:CKEDITOR.ENTER_BR,docType:"\x3c!DOCTYPE html\x3e",bodyId:"",bodyClass:"",fullPage:!1,height:200,contentsCss:CKEDITOR.getUrl("contents.css"), +extraPlugins:"",removePlugins:"",protectedSource:[],tabIndex:0,width:"",baseFloatZIndex:1E4,blockedKeystrokes:[CKEDITOR.CTRL+66,CKEDITOR.CTRL+73,CKEDITOR.CTRL+85]},function(){function a(a,b,c,e,d){var g,h;a=[];for(g in b){h=b[g];h="boolean"==typeof h?{}:"function"==typeof h?{match:h}:I(h);"$"!=g.charAt(0)&&(h.elements=g);c&&(h.featureName=c.toLowerCase());var f=h;f.elements=k(f.elements,/\s+/)||null;f.propertiesOnly=f.propertiesOnly||!0===f.elements;var m=/\s*,\s*/,l=void 0;for(l in L){f[l]=k(f[l], +m)||null;var n=f,v=G[l],D=k(f[G[l]],m),x=f[l],A=[],B=!0,r=void 0;D?B=!1:D={};for(r in x)"!"==r.charAt(0)&&(r=r.slice(1),A.push(r),D[r]=!0,B=!1);for(;r=A.pop();)x[r]=x["!"+r],delete x["!"+r];n[v]=(B?!1:D)||null}f.match=f.match||null;e.push(h);a.push(h)}b=d.elements;d=d.generic;var F;c=0;for(e=a.length;c=--g&&(l&&CKEDITOR.document.getDocumentElement().removeStyle("cursor"),e(b))},q=function(b,c){a[b]=1;var e=f[b];delete f[b];for(var d=0;d=CKEDITOR.env.version||CKEDITOR.env.ie9Compat)?d.$.onreadystatechange=function(){if("loaded"==d.$.readyState||"complete"==d.$.readyState)d.$.onreadystatechange=null,q(b,!0)}:(d.$.onload=function(){setTimeout(function(){d.$.onload=null;d.$.onerror=null;q(b,!0)},0)},d.$.onerror=function(){d.$.onload=null;d.$.onerror=null;q(b,!1)}));d.appendTo(CKEDITOR.document.getHead())}}};l&&CKEDITOR.document.getDocumentElement().setStyle("cursor","wait");for(var u=0;u]+)>)|(?:!--([\S|\s]*?)--\x3e)|(?:([^\/\s>]+)((?:\s+[\w\-:.]+(?:\s*=\s*?(?:(?:"[^"]*")|(?:'[^']*')|[^\s"'\/>]+))?)*)[\S\s]*?(\/?)>))/g}},function(){var a=/([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g, +f={checked:1,compact:1,declare:1,defer:1,disabled:1,ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1};CKEDITOR.htmlParser.prototype={onTagOpen:function(){},onTagClose:function(){},onText:function(){},onCDATA:function(){},onComment:function(){},parse:function(b){for(var c,d,l=0,k;c=this._.htmlPartsRegex.exec(b);){d=c.index;if(d>l)if(l=b.substring(l,d),k)k.push(l);else this.onText(l);l=this._.htmlPartsRegex.lastIndex;if(d=c[1])if(d=d.toLowerCase(),k&&CKEDITOR.dtd.$cdata[d]&& +(this.onCDATA(k.join("")),k=null),!k){this.onTagClose(d);continue}if(k)k.push(c[0]);else if(d=c[3]){if(d=d.toLowerCase(),!/="/.test(d)){var g={},h,m=c[4];c=!!c[5];if(m)for(;h=a.exec(m);){var e=h[1].toLowerCase();h=h[2]||h[3]||h[4]||"";g[e]=!h&&f[e]?e:CKEDITOR.tools.htmlDecodeAttr(h)}this.onTagOpen(d,g,c);!k&&CKEDITOR.dtd.$cdata[d]&&(k=[])}}else if(d=c[2])this.onComment(d)}if(b.length>l)this.onText(b.substring(l,b.length))}}}(),CKEDITOR.htmlParser.basicWriter=CKEDITOR.tools.createClass({$:function(){this._= +{output:[]}},proto:{openTag:function(a){this._.output.push("\x3c",a)},openTagClose:function(a,f){f?this._.output.push(" /\x3e"):this._.output.push("\x3e")},attribute:function(a,f){"string"==typeof f&&(f=CKEDITOR.tools.htmlEncodeAttr(f));this._.output.push(" ",a,'\x3d"',f,'"')},closeTag:function(a){this._.output.push("\x3c/",a,"\x3e")},text:function(a){this._.output.push(a)},comment:function(a){this._.output.push("\x3c!--",a,"--\x3e")},write:function(a){this._.output.push(a)},reset:function(){this._.output= +[];this._.indent=!1},getHtml:function(a){var f=this._.output.join("");a&&this.reset();return f}}}),"use strict",function(){CKEDITOR.htmlParser.node=function(){};CKEDITOR.htmlParser.node.prototype={remove:function(){var a=this.parent.children,f=CKEDITOR.tools.indexOf(a,this),b=this.previous,c=this.next;b&&(b.next=c);c&&(c.previous=b);a.splice(f,1);this.parent=null},replaceWith:function(a){var f=this.parent.children,b=CKEDITOR.tools.indexOf(f,this),c=a.previous=this.previous,d=a.next=this.next;c&&(c.next= +a);d&&(d.previous=a);f[b]=a;a.parent=this.parent;this.parent=null},insertAfter:function(a){var f=a.parent.children,b=CKEDITOR.tools.indexOf(f,a),c=a.next;f.splice(b+1,0,this);this.next=a.next;this.previous=a;a.next=this;c&&(c.previous=this);this.parent=a.parent},insertBefore:function(a){var f=a.parent.children,b=CKEDITOR.tools.indexOf(f,a);f.splice(b,0,this);this.next=a;(this.previous=a.previous)&&(a.previous.next=this);a.previous=this;this.parent=a.parent},getAscendant:function(a){var f="function"== +typeof a?a:"string"==typeof a?function(b){return b.name==a}:function(b){return b.name in a},b=this.parent;for(;b&&b.type==CKEDITOR.NODE_ELEMENT;){if(f(b))return b;b=b.parent}return null},wrapWith:function(a){this.replaceWith(a);a.add(this);return a},getIndex:function(){return CKEDITOR.tools.indexOf(this.parent.children,this)},getFilterContext:function(a){return a||{}}}}(),"use strict",CKEDITOR.htmlParser.comment=function(a){this.value=a;this._={isBlockLike:!1}},CKEDITOR.htmlParser.comment.prototype= +CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_COMMENT,filter:function(a,f){var b=this.value;if(!(b=a.onComment(f,b,this)))return this.remove(),!1;if("string"!=typeof b)return this.replaceWith(b),!1;this.value=b;return!0},writeHtml:function(a,f){f&&this.filter(f);a.comment(this.value)}}),"use strict",function(){CKEDITOR.htmlParser.text=function(a){this.value=a;this._={isBlockLike:!1}};CKEDITOR.htmlParser.text.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT, +filter:function(a,f){if(!(this.value=a.onText(f,this.value,this)))return this.remove(),!1},writeHtml:function(a,f){f&&this.filter(f);a.text(this.value)}})}(),"use strict",function(){CKEDITOR.htmlParser.cdata=function(a){this.value=a};CKEDITOR.htmlParser.cdata.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(){},writeHtml:function(a){a.write(this.value)}})}(),"use strict",CKEDITOR.htmlParser.fragment=function(){this.children=[];this.parent=null; +this._={isBlockLike:!0,hasInlineStarted:!1}},function(){function a(a){return a.attributes["data-cke-survive"]?!1:"a"==a.name&&a.attributes.href||CKEDITOR.dtd.$removeEmpty[a.name]}var f=CKEDITOR.tools.extend({table:1,ul:1,ol:1,dl:1},CKEDITOR.dtd.table,CKEDITOR.dtd.ul,CKEDITOR.dtd.ol,CKEDITOR.dtd.dl),b={ol:1,ul:1},c=CKEDITOR.tools.extend({},{html:1},CKEDITOR.dtd.html,CKEDITOR.dtd.body,CKEDITOR.dtd.head,{style:1,script:1}),d={ul:"li",ol:"li",dl:"dd",table:"tbody",tbody:"tr",thead:"tr",tfoot:"tr",tr:"td"}; +CKEDITOR.htmlParser.fragment.fromHtml=function(l,k,g){function h(a){var b;if(0k;k++)if(f=d[k]){f= +f.exec(a,c,this);if(!1===f)return null;if(f&&f!=c)return this.onNode(a,f);if(c.parent&&!c.name)break}return c},onNode:function(a,c){var d=c.type;return d==CKEDITOR.NODE_ELEMENT?this.onElement(a,c):d==CKEDITOR.NODE_TEXT?new CKEDITOR.htmlParser.text(this.onText(a,c.value)):d==CKEDITOR.NODE_COMMENT?new CKEDITOR.htmlParser.comment(this.onComment(a,c.value)):null},onAttribute:function(a,c,d,f){return(d=this.attributesRules[d])?d.exec(a,f,c,this):f}}});CKEDITOR.htmlParser.filterRulesGroup=a;a.prototype= +{add:function(a,c,d){this.rules.splice(this.findIndex(c),0,{value:a,priority:c,options:d})},addMany:function(a,c,d){for(var f=[this.findIndex(c),0],k=0,g=a.length;k/g,"\x26gt;")+"\x3c/textarea\x3e");return"\x3ccke:encoded\x3e"+encodeURIComponent(a)+"\x3c/cke:encoded\x3e"})}function n(a){return a.replace(D,function(a,b){return decodeURIComponent(b)})}function q(a){return a.replace(/\x3c!--(?!{cke_protected})[\s\S]+?--\x3e/g, +function(a){return"\x3c!--"+z+"{C}"+encodeURIComponent(a).replace(/--/g,"%2D%2D")+"--\x3e"})}function y(a){return CKEDITOR.tools.array.reduce(a.split(""),function(a,b){var c=b.toLowerCase(),e=b.toUpperCase(),d=u(c);c!==e&&(d+="|"+u(e));return a+("("+d+")")},"")}function u(a){var b;b=a.charCodeAt(0);var c=b.toString(16);b={htmlCode:"\x26#"+b+";?",hex:"\x26#x0*"+c+";?",entity:{"\x3c":"\x26lt;","\x3e":"\x26gt;",":":"\x26colon;"}[a]};for(var e in b)b[e]&&(a+="|"+b[e]);return a}function p(a){return a.replace(/\x3c!--\{cke_protected\}\{C\}([\s\S]+?)--\x3e/g, +function(a,b){return decodeURIComponent(b)})}function v(a,b){var c=b._.dataStore;return a.replace(/\x3c!--\{cke_protected\}([\s\S]+?)--\x3e/g,function(a,b){return decodeURIComponent(b)}).replace(/\{cke_protected_(\d+)\}/g,function(a,b){return c&&c[b]||""})}function w(a,b){var c=[],e=b.config.protectedSource,d=b._.dataStore||(b._.dataStore={id:1}),g=/<\!--\{cke_temp(comment)?\}(\d*?)--\x3e/g,e=[/|$)/gi,//gi,//gi].concat(e);a=a.replace(/\x3c!--[\s\S]*?--\x3e/g, +function(a){return"\x3c!--{cke_tempcomment}"+(c.push(a)-1)+"--\x3e"});for(var h=0;h]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=\/>]+))+\s*\/?>/g,function(a){return a.replace(/\x3c!--\{cke_protected\}([^>]*)--\x3e/g, +function(a,b){d[d.id]=decodeURIComponent(b);return"{cke_protected_"+d.id++ +"}"})});return a=a.replace(/<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g,function(a,c,e,d){return"\x3c"+c+e+"\x3e"+v(p(d),b)+"\x3c/"+c+"\x3e"})}CKEDITOR.htmlDataProcessor=function(b){var c,d,g=this;this.editor=b;this.dataFilter=c=new CKEDITOR.htmlParser.filter;this.htmlFilter=d=new CKEDITOR.htmlParser.filter;this.writer=new CKEDITOR.htmlParser.basicWriter;c.addRules(B);c.addRules(C,{applyToAll:!0});c.addRules(a(b,"data"), +{applyToAll:!0});d.addRules(H);d.addRules(F,{applyToAll:!0});d.addRules(a(b,"html"),{applyToAll:!0});b.on("toHtml",function(a){a=a.data;var c=a.dataValue,d,c=c.replace(N,""),c=w(c,b),c=e(c,G),c=m(c),c=e(c,L),c=c.replace(Q,"$1cke:$2"),c=c.replace(K,"\x3ccke:$1$2\x3e\x3c/cke:$1\x3e"),c=c.replace(/(]*>)(\r\n|\n)/g,"$1$2$2"),c=c.replace(/([^a-z0-9<\-])(on\w{3,})(?!>)/gi,"$1data-cke-"+CKEDITOR.rnd+"-$2");d=a.context||b.editable().getName();var g;CKEDITOR.env.ie&&9>CKEDITOR.env.version&&"pre"== +d&&(d="div",c="\x3cpre\x3e"+c+"\x3c/pre\x3e",g=1);d=b.document.createElement(d);d.setHtml("a"+c);c=d.getHtml().substr(1);c=c.replace(new RegExp("data-cke-"+CKEDITOR.rnd+"-","ig"),"");g&&(c=c.replace(/^
|<\/pre>$/gi,""));c=c.replace(O,"$1$2");c=n(c);c=p(c);d=!1===a.fixForBody?!1:f(a.enterMode,b.config.autoParagraph);c=CKEDITOR.htmlParser.fragment.fromHtml(c,a.context,d);d&&(g=c,!g.children.length&&CKEDITOR.dtd[g.name][d]&&(d=new CKEDITOR.htmlParser.element(d),g.add(d)));a.dataValue=c},null,null,
+5);b.on("toHtml",function(a){a.data.filter.applyTo(a.data.dataValue,!0,a.data.dontFilter,a.data.enterMode)&&b.fire("dataFiltered")},null,null,6);b.on("toHtml",function(a){a.data.dataValue.filterChildren(g.dataFilter,!0)},null,null,10);b.on("toHtml",function(a){a=a.data;var b=a.dataValue,c=new CKEDITOR.htmlParser.basicWriter;b.writeChildrenHtml(c);b=c.getHtml(!0);a.dataValue=q(b)},null,null,15);b.on("toDataFormat",function(a){var c=a.data.dataValue;a.data.enterMode!=CKEDITOR.ENTER_BR&&(c=c.replace(/^
/i, +""));a.data.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(c,a.data.context,f(a.data.enterMode,b.config.autoParagraph))},null,null,5);b.on("toDataFormat",function(a){a.data.dataValue.filterChildren(g.htmlFilter,!0)},null,null,10);b.on("toDataFormat",function(a){a.data.filter.applyTo(a.data.dataValue,!1,!0)},null,null,11);b.on("toDataFormat",function(a){var c=a.data.dataValue,e=g.writer;e.reset();c.writeChildrenHtml(e);c=e.getHtml(!0);c=p(c);c=v(c,b);a.data.dataValue=c},null,null,15)};CKEDITOR.htmlDataProcessor.prototype= +{toHtml:function(a,b,c,e){var d=this.editor,g,h,f,m;b&&"object"==typeof b?(g=b.context,c=b.fixForBody,e=b.dontFilter,h=b.filter,f=b.enterMode,m=b.protectedWhitespaces):g=b;g||null===g||(g=d.editable().getName());return d.fire("toHtml",{dataValue:a,context:g,fixForBody:c,dontFilter:e,filter:h||d.filter,enterMode:f||d.enterMode,protectedWhitespaces:m}).dataValue},toDataFormat:function(a,b){var c,e,d;b&&(c=b.context,e=b.filter,d=b.enterMode);c||null===c||(c=this.editor.editable().getName());return this.editor.fire("toDataFormat", +{dataValue:a,filter:e||this.editor.filter,context:c,enterMode:d||this.editor.enterMode}).dataValue}};var r=/(?: |\xa0)$/,z="{cke_protected}",t=CKEDITOR.dtd,x="caption colgroup col thead tfoot tbody".split(" "),A=CKEDITOR.tools.extend({},t.$blockLimit,t.$block),B={elements:{input:g,textarea:g}},C={attributeNames:[[/^on/,"data-cke-pa-on"],[/^srcdoc/,"data-cke-pa-srcdoc"],[/^data-cke-expando$/,""]],elements:{iframe:function(a){if(a.attributes&&a.attributes.src){var b=a.attributes.src.toLowerCase().replace(/[^a-z]/gi, +"");if(0===b.indexOf("javascript")||0===b.indexOf("data"))a.attributes["data-cke-pa-src"]=a.attributes.src,delete a.attributes.src}}}},H={elements:{embed:function(a){var b=a.parent;if(b&&"object"==b.name){var c=b.attributes.width,b=b.attributes.height;c&&(a.attributes.width=c);b&&(a.attributes.height=b)}},a:function(a){var b=a.attributes;if(!(a.children.length||b.name||b.id||a.attributes["data-cke-saved-name"]))return!1}}},F={elementNames:[[/^cke:/,""],[/^\?xml:namespace$/,""]],attributeNames:[[/^data-cke-(saved|pa)-/, +""],[/^data-cke-.*/,""],["hidefocus",""]],elements:{$:function(a){var b=a.attributes;if(b){if(b["data-cke-temp"])return!1;for(var c=["name","href","src"],e,d=0;de? +1:-1})},param:function(a){a.children=[];a.isEmpty=!0;return a},span:function(a){"Apple-style-span"==a.attributes["class"]&&delete a.name},html:function(a){delete a.attributes.contenteditable;delete a.attributes["class"]},body:function(a){delete a.attributes.spellcheck;delete a.attributes.contenteditable},style:function(a){var b=a.children[0];b&&b.value&&(b.value=CKEDITOR.tools.trim(b.value));a.attributes.type||(a.attributes.type="text/css")},title:function(a){var b=a.children[0];!b&&k(a,b=new CKEDITOR.htmlParser.text); +b.value=a.attributes["data-cke-title"]||""},input:h,textarea:h},attributes:{"class":function(a){return CKEDITOR.tools.ltrim(a.replace(/(?:^|\s+)cke_[^\s]*/g,""))||!1}}};CKEDITOR.env.ie&&(F.attributes.style=function(a){return a.replace(/(^|;)([^\:]+)/g,function(a){return a.toLowerCase()})});var I=/<(a|area|img|input|source)\b([^>]*)>/gi,J=/([\w-:]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,E=/^(href|src|name)$/i,L=/(?:])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi, +G=/(])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,D=/([^<]*)<\/cke:encoded>/gi,N=new RegExp("("+y("\x3ccke:encoded\x3e")+"(.*?)"+y("\x3c/cke:encoded\x3e")+")|("+y("\x3c")+y("/")+"?"+y("cke:encoded\x3e")+")","gi"),Q=/(<\/?)((?:object|embed|param|html|body|head|title)([\s][^>]*)?>)/gi,O=/(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi,K=/]*?)\/?>(?!\s*<\/cke:\1)/gi}(),"use strict",CKEDITOR.htmlParser.element=function(a,f){this.name=a;this.attributes=f||{};this.children= +[];var b=a||"",c=b.match(/^cke:(.*)/);c&&(b=c[1]);b=!!(CKEDITOR.dtd.$nonBodyContent[b]||CKEDITOR.dtd.$block[b]||CKEDITOR.dtd.$listItem[b]||CKEDITOR.dtd.$tableContent[b]||CKEDITOR.dtd.$nonEditable[b]||"br"==b);this.isEmpty=!!CKEDITOR.dtd.$empty[a];this.isUnknown=!CKEDITOR.dtd[a];this._={isBlockLike:b,hasInlineStarted:this.isEmpty||!b}},CKEDITOR.htmlParser.cssStyle=function(a){var f={};((a instanceof CKEDITOR.htmlParser.element?a.attributes.style:a)||"").replace(/"/g,'"').replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, +function(a,c,d){"font-family"==c&&(d=d.replace(/["']/g,""));f[c.toLowerCase()]=d});return{rules:f,populate:function(a){var c=this.toString();c&&(a instanceof CKEDITOR.dom.element?a.setAttribute("style",c):a instanceof CKEDITOR.htmlParser.element?a.attributes.style=c:a.style=c)},toString:function(){var a=[],c;for(c in f)f[c]&&a.push(c,":",f[c],";");return a.join("")}}},function(){function a(a){return function(b){return b.type==CKEDITOR.NODE_ELEMENT&&("string"==typeof a?b.name==a:b.name in a)}}var f= +function(a,b){a=a[0];b=b[0];return ab?1:0},b=CKEDITOR.htmlParser.fragment.prototype;CKEDITOR.htmlParser.element.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_ELEMENT,add:b.add,clone:function(){return new CKEDITOR.htmlParser.element(this.name,this.attributes)},filter:function(a,b){var f=this,k,g;b=f.getFilterContext(b);if(!f.parent)a.onRoot(b,f);for(;;){k=f.name;if(!(g=a.onElementName(b,k)))return this.remove(),!1;f.name=g;if(!(f=a.onElement(b,f)))return this.remove(), +!1;if(f!==this)return this.replaceWith(f),!1;if(f.name==k)break;if(f.type!=CKEDITOR.NODE_ELEMENT)return this.replaceWith(f),!1;if(!f.name)return this.replaceWithChildren(),!1}k=f.attributes;var h,m;for(h in k){for(g=k[h];;)if(m=a.onAttributeName(b,h))if(m!=h)delete k[h],h=m;else break;else{delete k[h];break}m&&(!1===(g=a.onAttribute(b,f,m,g))?delete k[m]:k[m]=g)}f.isEmpty||this.filterChildren(a,!1,b);return!0},filterChildren:b.filterChildren,writeHtml:function(a,b){b&&this.filter(b);var l=this.name, +k=[],g=this.attributes,h,m;a.openTag(l,g);for(h in g)k.push([h,g[h]]);a.sortAttributes&&k.sort(f);h=0;for(m=k.length;hCKEDITOR.env.version||CKEDITOR.env.quirks))this.hasFocus&&(this.focus(),b());else if(this.hasFocus)this.focus(), +a();else this.once("focus",function(){a()},null,null,-999)},getHtmlFromRange:function(a){if(a.collapsed)return new CKEDITOR.dom.documentFragment(a.document);a={doc:this.getDocument(),range:a.clone()};z.eol.detect(a,this);z.bogus.exclude(a);z.cell.shrink(a);a.fragment=a.range.cloneContents();z.tree.rebuild(a,this);z.eol.fix(a,this);return new CKEDITOR.dom.documentFragment(a.fragment.$)},extractHtmlFromRange:function(a,b){var c=t,e={range:a,doc:a.document},d=this.getHtmlFromRange(a);if(a.collapsed)return a.optimize(), +d;a.enlarge(CKEDITOR.ENLARGE_INLINE,1);c.table.detectPurge(e);e.bookmark=a.createBookmark();delete e.range;var g=this.editor.createRange();g.moveToPosition(e.bookmark.startNode,CKEDITOR.POSITION_BEFORE_START);e.targetBookmark=g.createBookmark();c.list.detectMerge(e,this);c.table.detectRanges(e,this);c.block.detectMerge(e,this);e.tableContentsRanges?(c.table.deleteRanges(e),a.moveToBookmark(e.bookmark),e.range=a):(a.moveToBookmark(e.bookmark),e.range=a,a.extractContents(c.detectExtractMerge(e)));a.moveToBookmark(e.targetBookmark); +a.optimize();c.fixUneditableRangePosition(a);c.list.merge(e,this);c.table.purge(e,this);c.block.merge(e,this);if(b){c=a.startPath();if(e=a.checkStartOfBlock()&&a.checkEndOfBlock()&&c.block&&!a.root.equals(c.block)){a:{var e=c.block.getElementsByTag("span"),g=0,f;if(e)for(;f=e.getItem(g++);)if(!q(f)){e=!0;break a}e=!1}e=!e}e&&(a.moveToPosition(c.block,CKEDITOR.POSITION_BEFORE_START),c.block.remove())}else c.autoParagraph(this.editor,a),y(a.startContainer)&&a.startContainer.appendBogus();a.startContainer.mergeSiblings(); +return d},setup:function(){var a=this.editor;this.attachListener(a,"beforeGetData",function(){var b=this.getData();this.is("textarea")||!1!==a.config.ignoreEmptyParagraph&&(b=b.replace(p,function(a,b){return b}));a.setData(b,null,1)},this);this.attachListener(a,"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(a,"afterSetData",function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a, +"beforeFocus",function(){var b=a.getSelection();(b=b&&b.getNative())&&"Control"==b.type||this.focus()},this);this.attachListener(a,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode,a.data.range)},this);this.attachListener(a,"insertElement",function(a){this.insertElement(a.data)},this);this.attachListener(a,"insertText",function(a){this.insertText(a.data)},this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?this.attachClass("cke_editable_inline"): +a.elementMode!=CKEDITOR.ELEMENT_MODE_REPLACE&&a.elementMode!=CKEDITOR.ELEMENT_MODE_APPENDTO||this.attachClass("cke_editable_themed");this.attachClass("cke_contents_"+a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]=+a.readOnly;a.keystrokeHandler.attach(this);this.on("blur",function(){this.hasFocus=!1},null,null,-1);this.on("focus",function(){this.hasFocus=!0},null,null,-1);if(CKEDITOR.env.webkit)this.on("scroll",function(){a._.previousScrollTop=a.editable().$.scrollTop},null, +null,-1);if(CKEDITOR.env.edge&&14CKEDITOR.env.version?m.$.styleSheet.cssText=h:m.setText(h)):(h=g.appendStyleText(h),h=new CKEDITOR.dom.element(h.ownerNode||h.owningElement),f.setCustomData("stylesheet", +h),h.data("cke-temp",1))}f=g.getCustomData("stylesheet_ref")||0;g.setCustomData("stylesheet_ref",f+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){a=a.data;var b=(new CKEDITOR.dom.elementPath(a.getTarget(),this)).contains("a");b&&2!=a.$.button&&b.isReadOnly()&&a.preventDefault()});var k={8:1,46:1};this.attachListener(a,"key",function(b){if(a.readOnly)return!0;var c=b.data.domEvent.getKey(),e;b=a.getSelection();if(0!==b.getRanges().length){if(c in +k){var d,g=b.getRanges()[0],f=g.startPath(),h,m,q,c=8==c;CKEDITOR.env.ie&&11>CKEDITOR.env.version&&(d=b.getSelectedElement())||(d=l(b))?(a.fire("saveSnapshot"),g.moveToPosition(d,CKEDITOR.POSITION_BEFORE_START),d.remove(),g.select(),a.fire("saveSnapshot"),e=1):g.collapsed&&((h=f.block)&&(q=h[c?"getPrevious":"getNext"](n))&&q.type==CKEDITOR.NODE_ELEMENT&&q.is("table")&&g[c?"checkStartOfBlock":"checkEndOfBlock"]()?(a.fire("saveSnapshot"),g[c?"checkEndOfBlock":"checkStartOfBlock"]()&&h.remove(),g["moveToElementEdit"+ +(c?"End":"Start")](q),g.select(),a.fire("saveSnapshot"),e=1):f.blockLimit&&f.blockLimit.is("td")&&(m=f.blockLimit.getAscendant("table"))&&g.checkBoundaryOfElement(m,c?CKEDITOR.START:CKEDITOR.END)&&(q=m[c?"getPrevious":"getNext"](n))?(a.fire("saveSnapshot"),g["moveToElementEdit"+(c?"End":"Start")](q),g.checkStartOfBlock()&&g.checkEndOfBlock()?q.remove():g.select(),a.fire("saveSnapshot"),e=1):(m=f.contains(["td","th","caption"]))&&g.checkBoundaryOfElement(m,c?CKEDITOR.START:CKEDITOR.END)&&(e=1))}return!e}}); +a.blockless&&CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller&&this.attachListener(this,"keyup",function(b){b.data.getKeystroke()in k&&!this.getFirst(c)&&(this.appendBogus(),b=a.createRange(),b.moveToPosition(this,CKEDITOR.POSITION_AFTER_START),b.select())});this.attachListener(this,"dblclick",function(b){if(a.readOnly)return!1;b={element:b.data.getTarget()};a.fire("doubleclick",b)});CKEDITOR.env.ie&&this.attachListener(this,"click",b);CKEDITOR.env.ie&&!CKEDITOR.env.edge||this.attachListener(this,"mousedown", +function(b){var c=b.data.getTarget();c.is("img","hr","input","textarea","select")&&!c.isReadOnly()&&(a.getSelection().selectElement(c),c.is("input","textarea","select")&&b.data.preventDefault())});CKEDITOR.env.edge&&this.attachListener(this,"mouseup",function(b){(b=b.data.getTarget())&&b.is("img")&&!b.isReadOnly()&&a.getSelection().selectElement(b)});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(b){if(2==b.data.$.button&&(b=b.data.getTarget(),!b.getAscendant("table")&&!b.getOuterHtml().replace(p, +""))){var c=a.createRange();c.moveToElementEditStart(b);c.select(!0)}});CKEDITOR.env.webkit&&(this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&&a.data.preventDefault()}),this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()}));CKEDITOR.env.webkit&&this.attachListener(a,"key",function(b){if(a.readOnly)return!0;var c=b.data.domEvent.getKey();if(c in k&&(b=a.getSelection(),0!==b.getRanges().length)){var c= +8==c,d=b.getRanges()[0];b=d.startPath();if(d.collapsed)a:{var g=b.block;if(g&&d[c?"checkStartOfBlock":"checkEndOfBlock"]()&&d.moveToClosestEditablePosition(g,!c)&&d.collapsed){if(d.startContainer.type==CKEDITOR.NODE_ELEMENT){var f=d.startContainer.getChild(d.startOffset-(c?1:0));if(f&&f.type==CKEDITOR.NODE_ELEMENT&&f.is("hr")){a.fire("saveSnapshot");f.remove();b=!0;break a}}d=d.startPath().block;if(!d||d&&d.contains(g))b=void 0;else{a.fire("saveSnapshot");var h;(h=(c?d:g).getBogus())&&h.remove(); +h=a.getSelection();f=h.createBookmarks();(c?g:d).moveChildren(c?d:g,!1);b.lastElement.mergeSiblings();e(g,d,!c);h.selectBookmarks(f);b=!0}}else b=!1}else c=d,h=b.block,d=c.endPath().block,h&&d&&!h.equals(d)?(a.fire("saveSnapshot"),(g=h.getBogus())&&g.remove(),c.enlarge(CKEDITOR.ENLARGE_INLINE),c.deleteContents(),d.getParent()&&(d.moveChildren(h,!1),b.lastElement.mergeSiblings(),e(h,d,!0)),c=a.getSelection().getRanges()[0],c.collapse(1),c.optimize(),""===c.startContainer.getHtml()&&c.startContainer.appendBogus(), +c.select(),b=!0):b=!1;if(!b)return;a.getSelection().scrollIntoView();a.fire("saveSnapshot");return!1}},this,null,100)}}},_:{detach:function(){this.editor.setData(this.editor.getData(),0,1);this.clearListeners();this.restoreAttrs();var a;if(a=this.removeCustomData("classes"))for(;a.length;)this.removeClass(a.pop());if(!this.is("textarea")){a=this.getDocument();var b=a.getHead();if(b.getCustomData("stylesheet")){var c=a.getCustomData("stylesheet_ref");--c?a.setCustomData("stylesheet_ref",c):(a.removeCustomData("stylesheet_ref"), +b.removeCustomData("stylesheet").remove())}}this.editor.fire("contentDomUnload");delete this.editor}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;arguments.length&&(b=this._.editable=a?a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),null));return b};CKEDITOR.on("instanceLoaded",function(b){var c=b.editor;c.on("insertElement",function(a){a=a.data;a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))&&("false"!=a.getAttribute("contentEditable")&& +a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1"),a.setAttribute("contentEditable",!1))});c.on("selectionChange",function(b){if(!c.readOnly){var e=c.getSelection();e&&!e.isLocked&&(e=c.checkDirty(),c.fire("lockSnapshot"),a(b),c.fire("unlockSnapshot"),!e&&c.resetDirty())}})});CKEDITOR.on("instanceCreated",function(a){var b=a.editor;b.on("mode",function(){var a=b.editable();if(a&&a.isInline()){var c=b.title;a.changeAttr("role","textbox");a.changeAttr("aria-multiline","true");a.changeAttr("aria-label", +c);c&&a.changeAttr("title",c);var e=b.fire("ariaEditorHelpLabel",{}).label;if(e&&(c=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents"))){var d=CKEDITOR.tools.getNextId(),e=CKEDITOR.dom.element.createFromHtml('\x3cspan id\x3d"'+d+'" class\x3d"cke_voice_label"\x3e'+e+"\x3c/span\x3e");c.append(e);a.changeAttr("aria-describedby",d)}}})});CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}");n=CKEDITOR.dom.walker.whitespaces(!0); +q=CKEDITOR.dom.walker.bookmark(!1,!0);y=CKEDITOR.dom.walker.empty();u=CKEDITOR.dom.walker.bogus();p=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi;v=function(){function a(b){return b.type==CKEDITOR.NODE_ELEMENT}function b(c,e){var d,g,f,h,m=[],k=e.range.startContainer;d=e.range.startPath();for(var k=n[k.getName()],l=0,q=c.getChildren(),G=q.count(),v=-1,r=-1,F=0,t=d.contains(n.$list);lCKEDITOR.env.version&&e.getChildCount()&&e.getFirst().remove())}return function(e){var d=e.startContainer,g=d.getAscendant("table",1),f=!1;c(g.getElementsByTag("td"));c(g.getElementsByTag("th"));g=e.clone();g.setStart(d,0);g= +a(g).lastBackward();g||(g=e.clone(),g.setEndAt(d,CKEDITOR.POSITION_BEFORE_END),g=a(g).lastForward(),f=!0);g||(g=d);g.is("table")?(e.setStartAt(g,CKEDITOR.POSITION_BEFORE_START),e.collapse(!0),g.remove()):(g.is({tbody:1,thead:1,tfoot:1})&&(g=b(g,"tr",f)),g.is("tr")&&(g=b(g,g.getParent().is("thead")?"th":"td",f)),(d=g.getBogus())&&d.remove(),e.moveToPosition(g,f?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END))}}();r=function(){function a(b){b=new CKEDITOR.dom.walker(b);b.guard=function(a, +b){if(b)return!1;if(a.type==CKEDITOR.NODE_ELEMENT)return a.is(CKEDITOR.dtd.$list)||a.is(CKEDITOR.dtd.$listItem)};b.evaluator=function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.is(CKEDITOR.dtd.$listItem)};return b}return function(b){var c=b.startContainer,e=!1,d;d=b.clone();d.setStart(c,0);d=a(d).lastBackward();d||(d=b.clone(),d.setEndAt(c,CKEDITOR.POSITION_BEFORE_END),d=a(d).lastForward(),e=!0);d||(d=c);d.is(CKEDITOR.dtd.$list)?(b.setStartAt(d,CKEDITOR.POSITION_BEFORE_START),b.collapse(!0),d.remove()): +((c=d.getBogus())&&c.remove(),b.moveToPosition(d,e?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END),b.select())}}();z={eol:{detect:function(a,b){var c=a.range,e=c.clone(),d=c.clone(),g=new CKEDITOR.dom.elementPath(c.startContainer,b),f=new CKEDITOR.dom.elementPath(c.endContainer,b);e.collapse(1);d.collapse();g.block&&e.checkBoundaryOfElement(g.block,CKEDITOR.END)&&(c.setStartAfter(g.block),a.prependEolBr=1);f.block&&d.checkBoundaryOfElement(f.block,CKEDITOR.START)&&(c.setEndBefore(f.block), +a.appendEolBr=1)},fix:function(a,b){var c=b.getDocument(),e;a.appendEolBr&&(e=this.createEolBr(c),a.fragment.append(e));!a.prependEolBr||e&&!e.getPrevious()||a.fragment.append(this.createEolBr(c),1)},createEolBr:function(a){return a.createElement("br",{attributes:{"data-cke-eol":1}})}},bogus:{exclude:function(a){var b=a.range.getBoundaryNodes(),c=b.startNode,b=b.endNode;!b||!u(b)||c&&c.equals(b)||a.range.setEndBefore(b)}},tree:{rebuild:function(a,b){var c=a.range,e=c.getCommonAncestor(),d=new CKEDITOR.dom.elementPath(e, +b),g=new CKEDITOR.dom.elementPath(c.startContainer,b),c=new CKEDITOR.dom.elementPath(c.endContainer,b),f;e.type==CKEDITOR.NODE_TEXT&&(e=e.getParent());if(d.blockLimit.is({tr:1,table:1})){var h=d.contains("table").getParent();f=function(a){return!a.equals(h)}}else if(d.block&&d.block.is(CKEDITOR.dtd.$listItem)&&(g=g.contains(CKEDITOR.dtd.$list),c=c.contains(CKEDITOR.dtd.$list),!g.equals(c))){var m=d.contains(CKEDITOR.dtd.$list).getParent();f=function(a){return!a.equals(m)}}f||(f=function(a){return!a.equals(d.block)&& +!a.equals(d.blockLimit)});this.rebuildFragment(a,b,e,f)},rebuildFragment:function(a,b,c,e){for(var d;c&&!c.equals(b)&&e(c);)d=c.clone(0,1),a.fragment.appendTo(d),a.fragment=d,c=c.getParent()}},cell:{shrink:function(a){a=a.range;var b=a.startContainer,c=a.endContainer,e=a.startOffset,d=a.endOffset;b.type==CKEDITOR.NODE_ELEMENT&&b.equals(c)&&b.is("tr")&&++e==d&&a.shrink(CKEDITOR.SHRINK_TEXT)}}};t=function(){function a(b,c){var e=b.getParent();if(e.is(CKEDITOR.dtd.$inline))b[c?"insertBefore":"insertAfter"](e)} +function b(c,e,d){a(e);a(d,1);for(var g;g=d.getNext();)g.insertAfter(e),e=g;y(c)&&c.remove()}function c(a,b){var e=new CKEDITOR.dom.range(a);e.setStartAfter(b.startNode);e.setEndBefore(b.endNode);return e}return{list:{detectMerge:function(a,b){var e=c(b,a.bookmark),d=e.startPath(),g=e.endPath(),f=d.contains(CKEDITOR.dtd.$list),h=g.contains(CKEDITOR.dtd.$list);a.mergeList=f&&h&&f.getParent().equals(h.getParent())&&!f.equals(h);a.mergeListItems=d.block&&g.block&&d.block.is(CKEDITOR.dtd.$listItem)&& +g.block.is(CKEDITOR.dtd.$listItem);if(a.mergeList||a.mergeListItems)e=e.clone(),e.setStartBefore(a.bookmark.startNode),e.setEndAfter(a.bookmark.endNode),a.mergeListBookmark=e.createBookmark()},merge:function(a,c){if(a.mergeListBookmark){var e=a.mergeListBookmark.startNode,d=a.mergeListBookmark.endNode,g=new CKEDITOR.dom.elementPath(e,c),f=new CKEDITOR.dom.elementPath(d,c);if(a.mergeList){var h=g.contains(CKEDITOR.dtd.$list),m=f.contains(CKEDITOR.dtd.$list);h.equals(m)||(m.moveChildren(h),m.remove())}a.mergeListItems&& +(g=g.contains(CKEDITOR.dtd.$listItem),f=f.contains(CKEDITOR.dtd.$listItem),g.equals(f)||b(f,e,d));e.remove();d.remove()}}},block:{detectMerge:function(a,b){if(!a.tableContentsRanges&&!a.mergeListBookmark){var c=new CKEDITOR.dom.range(b);c.setStartBefore(a.bookmark.startNode);c.setEndAfter(a.bookmark.endNode);a.mergeBlockBookmark=c.createBookmark()}},merge:function(a,c){if(a.mergeBlockBookmark&&!a.purgeTableBookmark){var e=a.mergeBlockBookmark.startNode,d=a.mergeBlockBookmark.endNode,g=new CKEDITOR.dom.elementPath(e, +c),f=new CKEDITOR.dom.elementPath(d,c),g=g.block,f=f.block;g&&f&&!g.equals(f)&&b(f,e,d);e.remove();d.remove()}}},table:function(){function a(c){var d=[],g,f=new CKEDITOR.dom.walker(c),h=c.startPath().contains(e),m=c.endPath().contains(e),k={};f.guard=function(a,f){if(a.type==CKEDITOR.NODE_ELEMENT){var l="visited_"+(f?"out":"in");if(a.getCustomData(l))return;CKEDITOR.dom.element.setMarker(k,a,l,1)}if(f&&h&&a.equals(h))g=c.clone(),g.setEndAt(h,CKEDITOR.POSITION_BEFORE_END),d.push(g);else if(!f&&m&& +a.equals(m))g=c.clone(),g.setStartAt(m,CKEDITOR.POSITION_AFTER_START),d.push(g);else{if(l=!f)l=a.type==CKEDITOR.NODE_ELEMENT&&a.is(e)&&(!h||b(a,h))&&(!m||b(a,m));if(!l&&(l=f))if(a.is(e))var l=h&&h.getAscendant("table",!0),n=m&&m.getAscendant("table",!0),q=a.getAscendant("table",!0),l=l&&l.contains(q)||n&&n.contains(q);else l=void 0;l&&(g=c.clone(),g.selectNodeContents(a),d.push(g))}};f.lastForward();CKEDITOR.dom.element.clearAllMarkers(k);return d}function b(a,c){var e=CKEDITOR.POSITION_CONTAINS+ +CKEDITOR.POSITION_IS_CONTAINED,d=a.getPosition(c);return d===CKEDITOR.POSITION_IDENTICAL?!1:0===(d&e)}var e={td:1,th:1,caption:1};return{detectPurge:function(a){var b=a.range,c=b.clone();c.enlarge(CKEDITOR.ENLARGE_ELEMENT);var c=new CKEDITOR.dom.walker(c),d=0;c.evaluator=function(a){a.type==CKEDITOR.NODE_ELEMENT&&a.is(e)&&++d};c.checkForward();if(1g&&d&&d.intersectsNode(c.$)){var f=[{node:e.anchorNode,offset:e.anchorOffset},{node:e.focusNode,offset:e.focusOffset}];e.anchorNode==c.$&&e.anchorOffset>g&&(f[0].offset-=g);e.focusNode==c.$&&e.focusOffset>g&&(f[1].offset-=g)}}c.setText(q(c.getText(),1));f&&(c=a.getDocument().$, +e=c.getSelection(),c=c.createRange(),c.setStart(f[0].node,f[0].offset),c.collapse(!0),e.removeAllRanges(),e.addRange(c),e.extend(f[1].node,f[1].offset))}}function q(a,b){return b?a.replace(z,function(a,b){return b?" ":""}):a.replace(r,"")}function y(a,b){var c=b&&CKEDITOR.tools.htmlEncode(b)||"\x26nbsp;",c=CKEDITOR.dom.element.createFromHtml('\x3cdiv data-cke-hidden-sel\x3d"1" data-cke-temp\x3d"1" style\x3d"'+(CKEDITOR.env.ie&&14>CKEDITOR.env.version?"display:none":"position:fixed;top:0;left:-1000px;width:0;height:0;overflow:hidden;")+ +'"\x3e'+c+"\x3c/div\x3e",a.document);a.fire("lockSnapshot");a.editable().append(c);var e=a.getSelection(1),d=a.createRange(),g=e.root.on("selectionchange",function(a){a.cancel()},null,null,0);d.setStartAt(c,CKEDITOR.POSITION_AFTER_START);d.setEndAt(c,CKEDITOR.POSITION_BEFORE_END);e.selectRanges([d]);g.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=c}function u(a){var b={37:1,39:1,8:1,46:1};return function(c){var e=c.data.getKeystroke();if(b[e]){var d=a.getSelection().getRanges(), +g=d[0];1==d.length&&g.collapsed&&(e=g[38>e?"getPreviousEditableNode":"getNextEditableNode"]())&&e.type==CKEDITOR.NODE_ELEMENT&&"false"==e.getAttribute("contenteditable")&&(a.getSelection().fake(e),c.data.preventDefault(),c.cancel())}}}function p(a){for(var b=0;b=e.getLength()?h.setStartAfter(e):h.setStartBefore(e));d&&d.type==CKEDITOR.NODE_TEXT&&(f?h.setEndAfter(d):h.setEndBefore(d));e=new CKEDITOR.dom.walker(h);e.evaluator=function(e){if(e.type==CKEDITOR.NODE_ELEMENT&&e.isReadOnly()){var d=c.clone();c.setEndBefore(e);c.collapsed&&a.splice(b--,1);e.getPosition(h.endContainer)& +CKEDITOR.POSITION_CONTAINS||(d.setStartAfter(e),d.collapsed||a.splice(b+1,0,d));return!0}return!1};e.next()}}return a}var v="function"!=typeof window.getSelection,w=1,r=CKEDITOR.tools.repeat("​",7),z=new RegExp(r+"( )?","g"),t,x,A,B=CKEDITOR.dom.walker.invisible(1),C=function(){function a(b){return function(a){var c=a.editor.createRange();c.moveToClosestEditablePosition(a.selected,b)&&a.editor.getSelection().selectRanges([c]);return!1}}function b(a){return function(b){var c=b.editor,e=c.createRange(), +d;if(!c.readOnly)return(d=e.moveToClosestEditablePosition(b.selected,a))||(d=e.moveToClosestEditablePosition(b.selected,!a)),d&&c.getSelection().selectRanges([e]),c.fire("saveSnapshot"),b.selected.remove(),d||(e.moveToElementEditablePosition(c.editable()),c.getSelection().selectRanges([e])),c.fire("saveSnapshot"),!1}}var c=a(),e=a(1);return{37:c,38:c,39:e,40:e,8:b(),46:b(1)}}();CKEDITOR.on("instanceCreated",function(a){function b(){var a=c.getSelection();a&&a.removeAllRanges()}var c=a.editor;c.on("contentDom", +function(){function a(){t=new CKEDITOR.dom.selection(c.getSelection());t.lock()}function b(){g.removeListener("mouseup",b);m.removeListener("mouseup",b);var a=CKEDITOR.document.$.selection,c=a.createRange();"None"!=a.type&&c.parentElement()&&c.parentElement().ownerDocument==d.$&&c.select()}function e(a){a=a.getRanges()[0];return a?(a=a.startContainer.getAscendant(function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.hasAttribute("contenteditable")},!0))&&"false"===a.getAttribute("contenteditable")? +a:null:null}var d=c.document,g=CKEDITOR.document,f=c.editable(),h=d.getBody(),m=d.getDocumentElement(),q=f.isInline(),r,t;CKEDITOR.env.gecko&&f.attachListener(f,"focus",function(a){a.removeListener();0!==r&&(a=c.getSelection().getNative())&&a.isCollapsed&&a.anchorNode==f.$&&(a=c.createRange(),a.moveToElementEditStart(f),a.select())},null,null,-2);f.attachListener(f,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusin":"focus",function(){if(r&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){r=c._.previousActive&& +c._.previousActive.equals(d.getActive());var a=null!=c._.previousScrollTop&&c._.previousScrollTop!=f.$.scrollTop;CKEDITOR.env.webkit&&r&&a&&(f.$.scrollTop=c._.previousScrollTop)}c.unlockSelection(r);r=0},null,null,-1);f.attachListener(f,"mousedown",function(){r=0});if(CKEDITOR.env.ie||q)v?f.attachListener(f,"beforedeactivate",a,null,null,-1):f.attachListener(c,"selectionCheck",a,null,null,-1),f.attachListener(f,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusout":"blur",function(){c.lockSelection(t); +r=1},null,null,-1),f.attachListener(f,"mousedown",function(){r=0});if(CKEDITOR.env.ie&&!q){var w;f.attachListener(f,"mousedown",function(a){2==a.data.$.button&&((a=c.document.getSelection())&&a.getType()!=CKEDITOR.SELECTION_NONE||(w=c.window.getScrollPosition()))});f.attachListener(f,"mouseup",function(a){2==a.data.$.button&&w&&(c.document.$.documentElement.scrollLeft=w.x,c.document.$.documentElement.scrollTop=w.y);w=null});if("BackCompat"!=d.$.compatMode){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat){var z, +y;m.on("mousedown",function(a){function b(a){a=a.data.$;if(z){var c=h.$.createTextRange();try{c.moveToPoint(a.clientX,a.clientY)}catch(e){}z.setEndPoint(0>y.compareEndPoints("StartToStart",c)?"EndToEnd":"StartToStart",c);z.select()}}function c(){m.removeListener("mousemove",b);g.removeListener("mouseup",c);m.removeListener("mouseup",c);z.select()}a=a.data;if(a.getTarget().is("html")&&a.$.yCKEDITOR.env.version)m.on("mousedown",function(a){a.data.getTarget().is("html")&&(g.on("mouseup",b),m.on("mouseup",b))})}}f.attachListener(f,"selectionchange",l,c);f.attachListener(f,"keyup",k,c);f.attachListener(f,"touchstart",k,c);f.attachListener(f,"touchend",k,c);CKEDITOR.env.ie&&f.attachListener(f,"keydown",function(a){var b=this.getSelection(1),c=e(b);c&&!c.equals(f)&&(b.selectElement(c),a.data.preventDefault())}, +c);f.attachListener(f,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusin":"focus",function(){c.forceNextSelectionCheck();c.selectionChange(1)});if(q&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){var p;f.attachListener(f,"mousedown",function(){p=1});f.attachListener(d.getDocumentElement(),"mouseup",function(){p&&k.call(c);p=0})}else f.attachListener(CKEDITOR.env.ie?f:d.getDocumentElement(),"mouseup",k,c);CKEDITOR.env.webkit&&f.attachListener(d,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:f.hasFocus&& +n(f)}},null,null,-1);f.attachListener(f,"keydown",u(c),null,null,-1)});c.on("setData",function(){c.unlockSelection();CKEDITOR.env.webkit&&b()});c.on("contentDomUnload",function(){c.unlockSelection()});if(CKEDITOR.env.ie9Compat)c.on("beforeDestroy",b,null,null,9);c.on("dataReady",function(){delete c._.fakeSelection;delete c._.hiddenSelectionContainer;c.selectionChange(1)});c.on("loadSnapshot",function(){var a=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_ELEMENT),b=c.editable().getLast(a);b&&b.hasAttribute("data-cke-hidden-sel")&& +(b.remove(),CKEDITOR.env.gecko&&(a=c.editable().getFirst(a))&&a.is("br")&&a.getAttribute("_moz_editor_bogus_node")&&a.remove())},null,null,100);c.on("key",function(a){if("wysiwyg"==c.mode){var b=c.getSelection();if(b.isFake){var e=C[a.data.keyCode];if(e)return e({editor:c,selected:b.getSelectedElement(),selection:b,keyEvent:a})}}})});if(CKEDITOR.env.webkit)CKEDITOR.on("instanceReady",function(a){var b=a.editor;b.on("selectionChange",function(){var a=b.editable(),c=a.getCustomData("cke-fillingChar"); +c&&(c.getCustomData("ready")?(n(a),a.editor.fire("selectionCheck")):c.setCustomData("ready",1))},null,null,-1);b.on("beforeSetMode",function(){n(b.editable())},null,null,-1);b.on("getSnapshot",function(a){a.data&&(a.data=q(a.data))},b,null,20);b.on("toDataFormat",function(a){a.data.dataValue=q(a.data.dataValue)},null,null,0)});CKEDITOR.editor.prototype.selectionChange=function(a){(a?l:k).call(this)};CKEDITOR.editor.prototype.getSelection=function(a){return!this._.savedSelection&&!this._.fakeSelection|| +a?(a=this.editable())&&"wysiwyg"==this.mode?new CKEDITOR.dom.selection(a):null:this._.savedSelection||this._.fakeSelection};CKEDITOR.editor.prototype.lockSelection=function(a){a=a||this.getSelection(1);return a.getType()!=CKEDITOR.SELECTION_NONE?(!a.isLocked&&a.lock(),this._.savedSelection=a,!0):!1};CKEDITOR.editor.prototype.unlockSelection=function(a){var b=this._.savedSelection;return b?(b.unlock(a),delete this._.savedSelection,!0):!1};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath}; +CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection){var b=a;a=a.root}var c=a instanceof CKEDITOR.dom.element; +this.rev=b?b.rev:w++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=c?a:this.document.getBody();this.isLocked=0;this._={cache:{}};if(b)return CKEDITOR.tools.extend(this._.cache,b._.cache),this.isFake=b.isFake,this.isLocked=b.isLocked,this;a=this.getNative();var e,d;if(a)if(a.getRangeAt)e=(d=a.rangeCount&&a.getRangeAt(0))&&new CKEDITOR.dom.node(d.commonAncestorContainer);else{try{d=a.createRange()}catch(g){}e=d&&CKEDITOR.dom.element.get(d.item&&d.item(0)||d.parentElement())}if(!e|| +e.type!=CKEDITOR.NODE_ELEMENT&&e.type!=CKEDITOR.NODE_TEXT||!this.root.equals(e)&&!this.root.contains(e))this._.cache.type=CKEDITOR.SELECTION_NONE,this._.cache.startElement=null,this._.cache.selectedElement=null,this._.cache.selectedText="",this._.cache.ranges=new CKEDITOR.dom.rangeList;return this};var H={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.tools.extend(CKEDITOR.dom.selection,{_removeFillingCharSequenceString:q, +_createFillingCharSequenceNode:e,FILLING_CHAR_SEQUENCE:r});CKEDITOR.dom.selection.prototype={getNative:function(){return void 0!==this._.cache.nativeSel?this._.cache.nativeSel:this._.cache.nativeSel=v?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:v?function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_NONE;try{var c=this.getNative(),e=c.type;"Text"==e&&(b=CKEDITOR.SELECTION_TEXT);"Control"==e&&(b=CKEDITOR.SELECTION_ELEMENT);c.createRange().parentElement()&& +(b=CKEDITOR.SELECTION_TEXT)}catch(d){}return a.type=b}:function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_TEXT,c=this.getNative();if(!c||!c.rangeCount)b=CKEDITOR.SELECTION_NONE;else if(1==c.rangeCount){var c=c.getRangeAt(0),e=c.startContainer;e==c.endContainer&&1==e.nodeType&&1==c.endOffset-c.startOffset&&H[e.childNodes[c.startOffset].nodeName.toLowerCase()]&&(b=CKEDITOR.SELECTION_ELEMENT)}return a.type=b},getRanges:function(){var a=v?function(){function a(b){return(new CKEDITOR.dom.node(b)).getIndex()} +var b=function(b,c){b=b.duplicate();b.collapse(c);var e=b.parentElement();if(!e.hasChildNodes())return{container:e,offset:0};for(var d=e.children,g,f,h=b.duplicate(),m=0,k=d.length-1,l=-1,n,q;m<=k;)if(l=Math.floor((m+k)/2),g=d[l],h.moveToElementText(g),n=h.compareEndPoints("StartToStart",b),0n)m=l+1;else return{container:e,offset:a(g)};if(-1==l||l==d.length-1&&0>n){h.moveToElementText(e);h.setEndPoint("StartToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;d=e.childNodes;if(!h)return g= +d[d.length-1],g.nodeType!=CKEDITOR.NODE_TEXT?{container:e,offset:d.length}:{container:g,offset:g.nodeValue.length};for(e=d.length;0]*>)[ \t\r\n]*/gi,"$1");f=f.replace(/([ \t\n\r]+| )/g, +" ");f=f.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var h=a.getDocument().createElement("div");h.append(g);g.$.outerHTML="\x3cpre\x3e"+f+"\x3c/pre\x3e";g.copyAttributes(h.getFirst());g=h.getFirst().remove()}else g.setHtml(f);b=g}else f?b=q(c?[a.getHtml()]:e(a),b):a.moveChildren(b);b.replace(a);if(d){var c=b,m;(m=c.getPrevious(E))&&m.type==CKEDITOR.NODE_ELEMENT&&m.is("pre")&&(d=n(m.getHtml(),/\n$/,"")+"\n\n"+n(c.getHtml(),/^\n/,""),CKEDITOR.env.ie?c.$.outerHTML="\x3cpre\x3e"+d+"\x3c/pre\x3e": +c.setHtml(d),m.remove())}else c&&v(b)}function e(a){var b=[];n(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,function(a,b,c){return b+"\x3c/pre\x3e"+c+"\x3cpre\x3e"}).replace(/([\s\S]*?)<\/pre>/gi,function(a,c){b.push(c)});return b}function n(a,b,c){var e="",d="";a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,b,c){b&&(e=b);c&&(d=c);return""});return e+a.replace(b,c)+d}function q(a,b){var c; +1=c?(l=d.createText(""),l.insertAfter(this)):(a=d.createText(""),a.insertAfter(l),a.remove()));return l},substring:function(a,f){return"number"!=typeof f?this.$.nodeValue.substr(a):this.$.nodeValue.substring(a,f)}}),function(){function a(a,c,d){var f=a.serializable,k=c[d?"endContainer":"startContainer"],g=d?"endOffset":"startOffset",h=f?c.document.getById(a.startNode):a.startNode;a=f?c.document.getById(a.endNode):a.endNode;k.equals(h.getPrevious())? +(c.startOffset=c.startOffset-k.getLength()-a.getPrevious().getLength(),k=a.getNext()):k.equals(a.getPrevious())&&(c.startOffset-=k.getLength(),k=a.getNext());k.equals(h.getParent())&&c[g]++;k.equals(a.getParent())&&c[g]++;c[d?"endContainer":"startContainer"]=k;return c}CKEDITOR.dom.rangeList=function(a){if(a instanceof CKEDITOR.dom.rangeList)return a;a?a instanceof CKEDITOR.dom.range&&(a=[a]):a=[];return CKEDITOR.tools.extend(a,f)};var f={createIterator:function(){var a=this,c=CKEDITOR.dom.walker.bookmark(), +d=[],f;return{getNextRange:function(k){f=void 0===f?0:f+1;var g=a[f];if(g&&1b?-1:1}),g=0,f;gCKEDITOR.env.version?a[h].$.styleSheet.cssText+=f:a[h].$.innerHTML+=f}}var l={};CKEDITOR.skin={path:a,loadPart:function(c, +e){CKEDITOR.skin.name!=CKEDITOR.skinName.split(",")[0]?CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(a()+"skin.js"),function(){b(c,e)}):b(c,e)},getPath:function(a){return CKEDITOR.getUrl(f(a))},icons:{},addIcon:function(a,b,c,d){a=a.toLowerCase();this.icons[a]||(this.icons[a]={path:b,offset:c||0,bgsize:d||"16px"})},getIconStyle:function(a,b,c,d,g){var f;a&&(a=a.toLowerCase(),b&&(f=this.icons[a+"-rtl"]),f||(f=this.icons[a]));a=c||f&&f.path||"";d=d||f&&f.offset;g=g||f&&f.bgsize||"16px";a&&(a=a.replace(/'/g, +"\\'"));return a&&"background-image:url('"+CKEDITOR.getUrl(a)+"');background-position:0 "+d+"px;background-size:"+g+";"}};CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{getUiColor:function(){return this.uiColor},setUiColor:function(a){var b=c(CKEDITOR.document);return(this.setUiColor=function(a){this.uiColor=a;var c=CKEDITOR.skin.chameleon,f="",m="";"function"==typeof c&&(f=c(this,"editor"),m=c(this,"panel"));a=[[h,a]];d([b],f,a);d(g,m,a)}).call(this,a)}});var k="cke_ui_color",g=[],h=/\$color/g; +CKEDITOR.on("instanceLoaded",function(a){if(!CKEDITOR.env.ie||!CKEDITOR.env.quirks){var b=a.editor;a=function(a){a=(a.data[0]||a.data).element.getElementsByTag("iframe").getItem(0).getFrameDocument();if(!a.getById("cke_ui_color")){var f=c(a);g.push(f);b.on("destroy",function(){g=CKEDITOR.tools.array.filter(g,function(a){return f!==a})});(a=b.getUiColor())&&d([f],CKEDITOR.skin.chameleon(b,"panel"),[[h,a]])}};b.on("panelShow",a);b.on("menuShow",a);b.config.uiColor&&b.setUiColor(b.config.uiColor)}})}(), +function(){if(CKEDITOR.env.webkit)CKEDITOR.env.hc=!1;else{var a=CKEDITOR.dom.element.createFromHtml('\x3cdiv style\x3d"width:0;height:0;position:absolute;left:-10000px;border:1px solid;border-color:red blue"\x3e\x3c/div\x3e',CKEDITOR.document);a.appendTo(CKEDITOR.document.getHead());try{var f=a.getComputedStyle("border-top-color"),b=a.getComputedStyle("border-right-color");CKEDITOR.env.hc=!(!f||f!=b)}catch(c){CKEDITOR.env.hc=!1}a.remove()}CKEDITOR.env.hc&&(CKEDITOR.env.cssClass+=" cke_hc");CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}"); +CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(a=CKEDITOR._.pending)for(delete CKEDITOR._.pending,f=0;ff;f++){var k=f,g;g=parseInt(d[f],16);g=("0"+(0>c?0|g*(1+c):0| +g+(255-g)*c).toString(16)).slice(-2);d[k]=g}return"#"+d.join("")}}(),f={editor:new CKEDITOR.template("{id}.cke_chrome [border-color:{defaultBorder};] {id} .cke_top [ background-color:{defaultBackground};border-bottom-color:{defaultBorder};] {id} .cke_bottom [background-color:{defaultBackground};border-top-color:{defaultBorder};] {id} .cke_resizer [border-right-color:{ckeResizer}] {id} .cke_dialog_title [background-color:{defaultBackground};border-bottom-color:{defaultBorder};] {id} .cke_dialog_footer [background-color:{defaultBackground};outline-color:{defaultBorder};] {id} .cke_dialog_tab [background-color:{dialogTab};border-color:{defaultBorder};] {id} .cke_dialog_tab:hover [background-color:{lightBackground};] {id} .cke_dialog_contents [border-top-color:{defaultBorder};] {id} .cke_dialog_tab_selected, {id} .cke_dialog_tab_selected:hover [background:{dialogTabSelected};border-bottom-color:{dialogTabSelectedBorder};] {id} .cke_dialog_body [background:{dialogBody};border-color:{defaultBorder};] {id} a.cke_button_off:hover,{id} a.cke_button_off:focus,{id} a.cke_button_off:active [background-color:{darkBackground};border-color:{toolbarElementsBorder};] {id} .cke_button_on [background-color:{ckeButtonOn};border-color:{toolbarElementsBorder};] {id} .cke_toolbar_separator,{id} .cke_toolgroup a.cke_button:last-child:after,{id} .cke_toolgroup a.cke_button.cke_button_disabled:hover:last-child:after [background-color: {toolbarElementsBorder};border-color: {toolbarElementsBorder};] {id} a.cke_combo_button:hover,{id} a.cke_combo_button:focus,{id} .cke_combo_on a.cke_combo_button [border-color:{toolbarElementsBorder};background-color:{darkBackground};] {id} .cke_combo:after [border-color:{toolbarElementsBorder};] {id} .cke_path_item [color:{elementsPathColor};] {id} a.cke_path_item:hover,{id} a.cke_path_item:focus,{id} a.cke_path_item:active [background-color:{darkBackground};] {id}.cke_panel [border-color:{defaultBorder};] "), +panel:new CKEDITOR.template(".cke_panel_grouptitle [background-color:{lightBackground};border-color:{defaultBorder};] .cke_menubutton_icon [background-color:{menubuttonIcon};] .cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active [background-color:{menubuttonHover};] .cke_menubutton:hover .cke_menubutton_icon, .cke_menubutton:focus .cke_menubutton_icon, .cke_menubutton:active .cke_menubutton_icon [background-color:{menubuttonIconHover};] .cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon [background-color:{menubuttonIcon};] .cke_menuseparator [background-color:{menubuttonIcon};] a:hover.cke_colorbox, a:active.cke_colorbox [border-color:{defaultBorder};] a:hover.cke_colorauto, a:hover.cke_colormore, a:active.cke_colorauto, a:active.cke_colormore [background-color:{ckeColorauto};border-color:{defaultBorder};] ")}; +return function(b,c){var d=a(b.uiColor,.4),d={id:"."+b.id,defaultBorder:a(d,-.2),toolbarElementsBorder:a(d,-.25),defaultBackground:d,lightBackground:a(d,.8),darkBackground:a(d,-.15),ckeButtonOn:a(d,.4),ckeResizer:a(d,-.4),ckeColorauto:a(d,.8),dialogBody:a(d,.7),dialogTab:a(d,.65),dialogTabSelected:"#FFF",dialogTabSelectedBorder:"#FFF",elementsPathColor:a(d,-.6),menubuttonHover:a(d,.1),menubuttonIcon:a(d,.5),menubuttonIconHover:a(d,.3)};return f[c].output(d).replace(/\[/g,"{").replace(/\]/g,"}")}}(), +CKEDITOR.plugins.add("dialogui",{onLoad:function(){var a=function(a){this._||(this._={});this._["default"]=this._.initValue=a["default"]||"";this._.required=a.required||!1;for(var b=[this._],c=1;carguments.length)){var g=a.call(this,c);g.labelId=CKEDITOR.tools.getNextId()+ +"_label";this._.children=[];var f={role:c.role||"presentation"};c.includeLabel&&(f["aria-labelledby"]=g.labelId);CKEDITOR.ui.dialog.uiElement.call(this,b,c,e,"div",null,f,function(){var a=[],e=c.required?" cke_required":"";"horizontal"!=c.labelLayout?a.push('\x3clabel class\x3d"cke_dialog_ui_labeled_label'+e+'" ',' id\x3d"'+g.labelId+'"',g.inputId?' for\x3d"'+g.inputId+'"':"",(c.labelStyle?' style\x3d"'+c.labelStyle+'"':"")+"\x3e",c.label,"\x3c/label\x3e",'\x3cdiv class\x3d"cke_dialog_ui_labeled_content"', +c.controlStyle?' style\x3d"'+c.controlStyle+'"':"",' role\x3d"presentation"\x3e',d.call(this,b,c),"\x3c/div\x3e"):(e={type:"hbox",widths:c.widths,padding:0,children:[{type:"html",html:'\x3clabel class\x3d"cke_dialog_ui_labeled_label'+e+'" id\x3d"'+g.labelId+'" for\x3d"'+g.inputId+'"'+(c.labelStyle?' style\x3d"'+c.labelStyle+'"':"")+"\x3e"+CKEDITOR.tools.htmlEncode(c.label)+"\x3c/label\x3e"},{type:"html",html:'\x3cspan class\x3d"cke_dialog_ui_labeled_content"'+(c.controlStyle?' style\x3d"'+c.controlStyle+ +'"':"")+"\x3e"+d.call(this,b,c)+"\x3c/span\x3e"}]},CKEDITOR.dialog._.uiElementBuilders.hbox.build(b,e,a));return a.join("")})}},textInput:function(b,c,e){if(!(3>arguments.length)){a.call(this,c);var d=this._.inputId=CKEDITOR.tools.getNextId()+"_textInput",f={"class":"cke_dialog_ui_input_"+c.type,id:d,type:c.type};c.validate&&(this.validate=c.validate);c.maxLength&&(f.maxlength=c.maxLength);c.size&&(f.size=c.size);c.inputStyle&&(f.style=c.inputStyle);var k=this,l=!1;b.on("load",function(){k.getInputElement().on("keydown", +function(a){13==a.data.getKeystroke()&&(l=!0)});k.getInputElement().on("keyup",function(a){13==a.data.getKeystroke()&&l&&(b.getButton("ok")&&setTimeout(function(){b.getButton("ok").click()},0),l=!1);k.bidi&&g.call(k,a)},null,null,1E3)});CKEDITOR.ui.dialog.labeledElement.call(this,b,c,e,function(){var a=['\x3cdiv class\x3d"cke_dialog_ui_input_',c.type,'" role\x3d"presentation"'];c.width&&a.push('style\x3d"width:'+c.width+'" ');a.push("\x3e\x3cinput ");f["aria-labelledby"]=this._.labelId;this._.required&& +(f["aria-required"]=this._.required);for(var b in f)a.push(b+'\x3d"'+f[b]+'" ');a.push(" /\x3e\x3c/div\x3e");return a.join("")})}},textarea:function(b,c,e){if(!(3>arguments.length)){a.call(this,c);var d=this,f=this._.inputId=CKEDITOR.tools.getNextId()+"_textarea",k={};c.validate&&(this.validate=c.validate);k.rows=c.rows||5;k.cols=c.cols||20;k["class"]="cke_dialog_ui_input_textarea "+(c["class"]||"");"undefined"!=typeof c.inputStyle&&(k.style=c.inputStyle);c.dir&&(k.dir=c.dir);if(d.bidi)b.on("load", +function(){d.getInputElement().on("keyup",g)},d);CKEDITOR.ui.dialog.labeledElement.call(this,b,c,e,function(){k["aria-labelledby"]=this._.labelId;this._.required&&(k["aria-required"]=this._.required);var a=['\x3cdiv class\x3d"cke_dialog_ui_input_textarea" role\x3d"presentation"\x3e\x3ctextarea id\x3d"',f,'" '],b;for(b in k)a.push(b+'\x3d"'+CKEDITOR.tools.htmlEncode(k[b])+'" ');a.push("\x3e",CKEDITOR.tools.htmlEncode(d._["default"]),"\x3c/textarea\x3e\x3c/div\x3e");return a.join("")})}},checkbox:function(b, +c,e){if(!(3>arguments.length)){var d=a.call(this,c,{"default":!!c["default"]});c.validate&&(this.validate=c.validate);CKEDITOR.ui.dialog.uiElement.call(this,b,c,e,"span",null,null,function(){var a=CKEDITOR.tools.extend({},c,{id:c.id?c.id+"_checkbox":CKEDITOR.tools.getNextId()+"_checkbox"},!0),e=[],g=CKEDITOR.tools.getNextId()+"_label",f={"class":"cke_dialog_ui_checkbox_input",type:"checkbox","aria-labelledby":g};k(a);c["default"]&&(f.checked="checked");"undefined"!=typeof a.inputStyle&&(a.style=a.inputStyle); +d.checkbox=new CKEDITOR.ui.dialog.uiElement(b,a,e,"input",null,f);e.push(' \x3clabel id\x3d"',g,'" for\x3d"',f.id,'"'+(c.labelStyle?' style\x3d"'+c.labelStyle+'"':"")+"\x3e",CKEDITOR.tools.htmlEncode(c.label),"\x3c/label\x3e");return e.join("")})}},radio:function(b,c,e){if(!(3>arguments.length)){a.call(this,c);this._["default"]||(this._["default"]=this._.initValue=c.items[0][1]);c.validate&&(this.validate=c.validate);var d=[],g=this;c.role="radiogroup";c.includeLabel=!0;CKEDITOR.ui.dialog.labeledElement.call(this, +b,c,e,function(){for(var a=[],e=[],f=(c.id?c.id:CKEDITOR.tools.getNextId())+"_radio",l=0;larguments.length)){var d=a.call(this,c);c.validate&&(this.validate=c.validate);d.inputId=CKEDITOR.tools.getNextId()+"_select";CKEDITOR.ui.dialog.labeledElement.call(this,b,c,e,function(){var a=CKEDITOR.tools.extend({},c,{id:c.id?c.id+"_select":CKEDITOR.tools.getNextId()+"_select"},!0),e=[],g=[],f={id:d.inputId,"class":"cke_dialog_ui_input_select","aria-labelledby":this._.labelId};e.push('\x3cdiv class\x3d"cke_dialog_ui_input_', +c.type,'" role\x3d"presentation"');c.width&&e.push('style\x3d"width:'+c.width+'" ');e.push("\x3e");void 0!==c.size&&(f.size=c.size);void 0!==c.multiple&&(f.multiple=c.multiple);k(a);for(var l=0,w;larguments.length)){void 0===c["default"]&&(c["default"]="");var d=CKEDITOR.tools.extend(a.call(this,c),{definition:c,buttons:[]});c.validate&&(this.validate=c.validate);b.on("load",function(){CKEDITOR.document.getById(d.frameId).getParent().addClass("cke_dialog_ui_input_file")});CKEDITOR.ui.dialog.labeledElement.call(this,b,c,e,function(){d.frameId=CKEDITOR.tools.getNextId()+"_fileInput";var a=['\x3ciframe frameborder\x3d"0" allowtransparency\x3d"0" class\x3d"cke_dialog_ui_input_file" role\x3d"presentation" id\x3d"', +d.frameId,'" title\x3d"',c.label,'" src\x3d"javascript:void('];a.push(CKEDITOR.env.ie?"(function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.close();")+"})()":"0");a.push(')"\x3e\x3c/iframe\x3e');return a.join("")})}},fileButton:function(b,c,e){var d=this;if(!(3>arguments.length)){a.call(this,c);c.validate&&(this.validate=c.validate);var g=CKEDITOR.tools.extend({},c),f=g.onClick;g.className=(g.className?g.className+" ":"")+"cke_dialog_ui_button";g.onClick=function(a){var e= +c["for"];a=f?f.call(this,a):!1;!1!==a&&("xhr"!==a&&b.getContentElement(e[0],e[1]).submit(),this.disable())};b.on("load",function(){b.getContentElement(c["for"][0],c["for"][1])._.buttons.push(d)});CKEDITOR.ui.dialog.button.call(this,b,g,e)}},html:function(){var a=/^\s*<[\w:]+\s+([^>]*)?>/,b=/^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/,c=/\/$/;return function(d,g,f){if(!(3>arguments.length)){var k=[],l=g.html;"\x3c"!=l.charAt(0)&&(l="\x3cspan\x3e"+l+"\x3c/span\x3e");var v=g.focus;if(v){var w=this.focus; +this.focus=function(){("function"==typeof v?v:w).call(this);this.fire("focus")};g.isFocusable&&(this.isFocusable=this.isFocusable);this.keyboardFocusable=!0}CKEDITOR.ui.dialog.uiElement.call(this,d,g,k,"span",null,null,"");k=k.join("").match(a);l=l.match(b)||["","",""];c.test(l[1])&&(l[1]=l[1].slice(0,-1),l[2]="/"+l[2]);f.push([l[1]," ",k[1]||"",l[2]].join(""))}}}(),fieldset:function(a,b,c,d,g){var f=g.label;this._={children:b};CKEDITOR.ui.dialog.uiElement.call(this,a,g,d,"fieldset",null,null,function(){var a= +[];f&&a.push("\x3clegend"+(g.labelStyle?' style\x3d"'+g.labelStyle+'"':"")+"\x3e"+f+"\x3c/legend\x3e");for(var b=0;bb.getChildCount()?(new CKEDITOR.dom.text(a,CKEDITOR.document)).appendTo(b):b.getChild(0).$.nodeValue= +a;return this},getLabel:function(){var a=CKEDITOR.document.getById(this._.labelId);return!a||1>a.getChildCount()?"":a.getChild(0).getText()},eventProcessors:d},!0);CKEDITOR.ui.dialog.button.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{click:function(){return this._.disabled?!1:this.fire("click",{dialog:this._.dialog})},enable:function(){this._.disabled=!1;var a=this.getElement();a&&a.removeClass("cke_disabled")},disable:function(){this._.disabled=!0;this.getElement().addClass("cke_disabled")}, +isVisible:function(){return this.getElement().getFirst().isVisible()},isEnabled:function(){return!this._.disabled},eventProcessors:CKEDITOR.tools.extend({},CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,{onClick:function(a,b){this.on("click",function(){b.apply(this,arguments)})}},!0),accessKeyUp:function(){this.click()},accessKeyDown:function(){this.focus()},keyboardFocusable:!0},!0);CKEDITOR.ui.dialog.textInput.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return CKEDITOR.document.getById(this._.inputId)}, +focus:function(){var a=this.selectParentTab();setTimeout(function(){var b=a.getInputElement();b&&b.$.focus()},0)},select:function(){var a=this.selectParentTab();setTimeout(function(){var b=a.getInputElement();b&&(b.$.focus(),b.$.select())},0)},accessKeyUp:function(){this.select()},setValue:function(a){if(this.bidi){var b=a&&a.charAt(0);(b="‪"==b?"ltr":"‫"==b?"rtl":null)&&(a=a.slice(1));this.setDirectionMarker(b)}a||(a="");return CKEDITOR.ui.dialog.uiElement.prototype.setValue.apply(this,arguments)}, +getValue:function(){var a=CKEDITOR.ui.dialog.uiElement.prototype.getValue.call(this);if(this.bidi&&a){var b=this.getDirectionMarker();b&&(a=("ltr"==b?"‪":"‫")+a)}return a},setDirectionMarker:function(a){var b=this.getInputElement();a?b.setAttributes({dir:a,"data-cke-dir-marker":a}):this.getDirectionMarker()&&b.removeAttributes(["dir","data-cke-dir-marker"])},getDirectionMarker:function(){return this.getInputElement().data("cke-dir-marker")},keyboardFocusable:!0},c,!0);CKEDITOR.ui.dialog.textarea.prototype= +new CKEDITOR.ui.dialog.textInput;CKEDITOR.ui.dialog.select.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return this._.select.getElement()},add:function(a,b,c){var d=new CKEDITOR.dom.element("option",this.getDialog().getParentEditor().document),g=this.getInputElement().$;d.$.text=a;d.$.value=void 0===b||null===b?a:b;void 0===c||null===c?CKEDITOR.env.ie?g.add(d.$):g.add(d.$,null):g.add(d.$,c);return this},remove:function(a){this.getInputElement().$.remove(a); +return this},clear:function(){for(var a=this.getInputElement().$;0b-a;c--)if(this._.tabs[this._.tabIdList[c%a]][0].$.offsetHeight)return this._.tabIdList[c%a];return null}function f(){for(var a=this._.tabIdList.length,b=CKEDITOR.tools.indexOf(this._.tabIdList,this._.currentTabId),c=b+1;cl.width-k.width-f?l.width-k.width+("rtl"==g.lang.dir?0:h[1]):d.x,d.y+h[0]l.height-k.height-f?l.height-k.height+h[2]:d.y,1);c.data.preventDefault()} +function c(){CKEDITOR.document.removeListener("mousemove",b);CKEDITOR.document.removeListener("mouseup",c);if(CKEDITOR.env.ie6Compat){var a=B.getChild(0).getFrameDocument();a.removeListener("mousemove",b);a.removeListener("mouseup",c)}}var e=null,d=null,g=a.getParentEditor(),f=g.config.dialog_magnetDistance,h=CKEDITOR.skin.margins||[0,0,0,0];"undefined"==typeof f&&(f=20);a.parts.title.on("mousedown",function(g){e={x:g.data.$.screenX,y:g.data.$.screenY};CKEDITOR.document.on("mousemove",b);CKEDITOR.document.on("mouseup", +c);d=a.getPosition();if(CKEDITOR.env.ie6Compat){var f=B.getChild(0).getFrameDocument();f.on("mousemove",b);f.on("mouseup",c)}g.data.preventDefault()},a)}function e(a){function b(c){var n="rtl"==g.lang.dir,r=m.width,t=m.height,v=r+(c.data.$.screenX-l.x)*(n?-1:1)*(a._.moved?1:2),q=t+(c.data.$.screenY-l.y)*(a._.moved?1:2),w=a._.element.getFirst(),w=n&&w.getComputedStyle("right"),z=a.getPosition();z.y+q>k.height&&(q=k.height-z.y);(n?w:z.x)+v>k.width&&(v=k.width-(n?w:z.x));if(d==CKEDITOR.DIALOG_RESIZE_WIDTH|| +d==CKEDITOR.DIALOG_RESIZE_BOTH)r=Math.max(e.minWidth||0,v-f);if(d==CKEDITOR.DIALOG_RESIZE_HEIGHT||d==CKEDITOR.DIALOG_RESIZE_BOTH)t=Math.max(e.minHeight||0,q-h);a.resize(r,t);a._.moved||a.layout();c.data.preventDefault()}function c(){CKEDITOR.document.removeListener("mouseup",c);CKEDITOR.document.removeListener("mousemove",b);n&&(n.remove(),n=null);if(CKEDITOR.env.ie6Compat){var a=B.getChild(0).getFrameDocument();a.removeListener("mouseup",c);a.removeListener("mousemove",b)}}var e=a.definition,d=e.resizable; +if(d!=CKEDITOR.DIALOG_RESIZE_NONE){var g=a.getParentEditor(),f,h,k,l,m,n,r=CKEDITOR.tools.addFunction(function(e){m=a.getSize();var d=a.parts.contents;d.$.getElementsByTagName("iframe").length&&(n=CKEDITOR.dom.element.createFromHtml('\x3cdiv class\x3d"cke_dialog_resize_cover" style\x3d"height: 100%; position: absolute; width: 100%;"\x3e\x3c/div\x3e'),d.append(n));h=m.height-a.parts.contents.getSize("height",!(CKEDITOR.env.gecko||CKEDITOR.env.ie&&CKEDITOR.env.quirks));f=m.width-a.parts.contents.getSize("width", +1);l={x:e.screenX,y:e.screenY};k=CKEDITOR.document.getWindow().getViewPaneSize();CKEDITOR.document.on("mousemove",b);CKEDITOR.document.on("mouseup",c);CKEDITOR.env.ie6Compat&&(d=B.getChild(0).getFrameDocument(),d.on("mousemove",b),d.on("mouseup",c));e.preventDefault&&e.preventDefault()});a.on("load",function(){var b="";d==CKEDITOR.DIALOG_RESIZE_WIDTH?b=" cke_resizer_horizontal":d==CKEDITOR.DIALOG_RESIZE_HEIGHT&&(b=" cke_resizer_vertical");b=CKEDITOR.dom.element.createFromHtml('\x3cdiv class\x3d"cke_resizer'+ +b+" cke_resizer_"+g.lang.dir+'" title\x3d"'+CKEDITOR.tools.htmlEncode(g.lang.common.resize)+'" onmousedown\x3d"CKEDITOR.tools.callFunction('+r+', event )"\x3e'+("ltr"==g.lang.dir?"◢":"◣")+"\x3c/div\x3e");a.parts.footer.append(b,1)});g.on("destroy",function(){CKEDITOR.tools.removeFunction(r)})}}function n(a){a.data.preventDefault(1)}function q(a){var b=CKEDITOR.document.getWindow(),c=a.config,e=CKEDITOR.skinName||a.config.skin,d=c.dialog_backgroundCoverColor||("moono-lisa"==e?"black":"white"),e=c.dialog_backgroundCoverOpacity, +g=c.baseFloatZIndex,c=CKEDITOR.tools.genKey(d,e,g),f=A[c];f?f.show():(g=['\x3cdiv tabIndex\x3d"-1" style\x3d"position: ',CKEDITOR.env.ie6Compat?"absolute":"fixed","; z-index: ",g,"; top: 0px; left: 0px; ",CKEDITOR.env.ie6Compat?"":"background-color: "+d,'" class\x3d"cke_dialog_background_cover"\x3e'],CKEDITOR.env.ie6Compat&&(d="\x3chtml\x3e\x3cbody style\x3d\\'background-color:"+d+";\\'\x3e\x3c/body\x3e\x3c/html\x3e",g.push('\x3ciframe hidefocus\x3d"true" frameborder\x3d"0" id\x3d"cke_dialog_background_iframe" src\x3d"javascript:'), +g.push("void((function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.write( '"+d+"' );document.close();")+"})())"),g.push('" style\x3d"position:absolute;left:0;top:0;width:100%;height: 100%;filter: progid:DXImageTransform.Microsoft.Alpha(opacity\x3d0)"\x3e\x3c/iframe\x3e')),g.push("\x3c/div\x3e"),f=CKEDITOR.dom.element.createFromHtml(g.join("")),f.setOpacity(void 0!==e?e:.5),f.on("keydown",n),f.on("keypress",n),f.on("keyup",n),f.appendTo(CKEDITOR.document.getBody()), +A[c]=f);a.focusManager.add(f);B=f;a=function(){var a=b.getViewPaneSize();f.setStyles({width:a.width+"px",height:a.height+"px"})};var h=function(){var a=b.getScrollPosition(),c=CKEDITOR.dialog._.currentTop;f.setStyles({left:a.x+"px",top:a.y+"px"});if(c){do a=c.getPosition(),c.move(a.x,a.y);while(c=c._.parentDialog)}};x=a;b.on("resize",a);a();CKEDITOR.env.mac&&CKEDITOR.env.webkit||f.focus();if(CKEDITOR.env.ie6Compat){var k=function(){h();k.prevScrollHandler.apply(this,arguments)};b.$.setTimeout(function(){k.prevScrollHandler= +window.onscroll||function(){};window.onscroll=k},0);h()}}function y(a){B&&(a.focusManager.remove(B),a=CKEDITOR.document.getWindow(),B.hide(),B=null,a.removeListener("resize",x),CKEDITOR.env.ie6Compat&&a.$.setTimeout(function(){window.onscroll=window.onscroll&&window.onscroll.prevScrollHandler||null},0),x=null)}var u=CKEDITOR.tools.cssLength,p='\x3cdiv class\x3d"cke_reset_all {editorId} {editorDialogClass} {hidpi}" dir\x3d"{langDir}" lang\x3d"{langCode}" role\x3d"dialog" aria-labelledby\x3d"cke_dialog_title_{id}"\x3e\x3ctable class\x3d"cke_dialog '+ +CKEDITOR.env.cssClass+' cke_{langDir}" style\x3d"position:absolute" role\x3d"presentation"\x3e\x3ctr\x3e\x3ctd role\x3d"presentation"\x3e\x3cdiv class\x3d"cke_dialog_body" role\x3d"presentation"\x3e\x3cdiv id\x3d"cke_dialog_title_{id}" class\x3d"cke_dialog_title" role\x3d"presentation"\x3e\x3c/div\x3e\x3ca id\x3d"cke_dialog_close_button_{id}" class\x3d"cke_dialog_close_button" href\x3d"javascript:void(0)" title\x3d"{closeTitle}" role\x3d"button"\x3e\x3cspan class\x3d"cke_label"\x3eX\x3c/span\x3e\x3c/a\x3e\x3cdiv id\x3d"cke_dialog_tabs_{id}" class\x3d"cke_dialog_tabs" role\x3d"tablist"\x3e\x3c/div\x3e\x3ctable class\x3d"cke_dialog_contents" role\x3d"presentation"\x3e\x3ctr\x3e\x3ctd id\x3d"cke_dialog_contents_{id}" class\x3d"cke_dialog_contents_body" role\x3d"presentation"\x3e\x3c/td\x3e\x3c/tr\x3e\x3ctr\x3e\x3ctd id\x3d"cke_dialog_footer_{id}" class\x3d"cke_dialog_footer" role\x3d"presentation"\x3e\x3c/td\x3e\x3c/tr\x3e\x3c/table\x3e\x3c/div\x3e\x3c/td\x3e\x3c/tr\x3e\x3c/table\x3e\x3c/div\x3e'; +CKEDITOR.dialog=function(b,g){function h(){var a=x._.focusList;a.sort(function(a,b){return a.tabIndex!=b.tabIndex?b.tabIndex-a.tabIndex:a.focusIndex-b.focusIndex});for(var b=a.length,c=0;cb.length)){var c=x._.currentFocusIndex;x._.tabBarMode&&0>a&&(c=0);try{b[c].getInputElement().$.blur()}catch(e){}var d=c,g=1c.height||b.width+(0c.width?a.setStyle("position","absolute"):a.setStyle("position","fixed"));this.move(this._.moved?this._.position.x:e,this._.moved?this._.position.y:d)},foreach:function(a){for(var b in this._.contents)for(var c in this._.contents[b])a.call(this,this._.contents[b][c]);return this},reset:function(){var a=function(a){a.reset&&a.reset(1)};return function(){this.foreach(a);return this}}(), +setupContent:function(){var a=arguments;this.foreach(function(b){b.setup&&b.setup.apply(b,a)})},commitContent:function(){var a=arguments;this.foreach(function(b){CKEDITOR.env.ie&&this._.currentFocusIndex==b.focusIndex&&b.getInputElement().$.blur();b.commit&&b.commit.apply(b,a)})},hide:function(){if(this.parts.dialog.isVisible()){this.fire("hide",{});this._.editor.fire("dialogHide",this);this.selectPage(this._.tabIdList[0]);var a=this._.element;a.setStyle("display","none");this.parts.dialog.setStyle("visibility", +"hidden");for(J(this);CKEDITOR.dialog._.currentTop!=this;)CKEDITOR.dialog._.currentTop.hide();if(this._.parentDialog){var b=this._.parentDialog.getElement().getFirst();b.setStyle("z-index",parseInt(b.$.style.zIndex,10)+Math.floor(this._.editor.config.baseFloatZIndex/2))}else y(this._.editor);if(CKEDITOR.dialog._.currentTop=this._.parentDialog)CKEDITOR.dialog._.currentZIndex-=10;else{CKEDITOR.dialog._.currentZIndex=null;a.removeListener("keydown",H);a.removeListener("keyup",F);var c=this._.editor; +c.focus();setTimeout(function(){c.focusManager.unlock();CKEDITOR.env.iOS&&c.window.focus()},0)}delete this._.parentDialog;this.foreach(function(a){a.resetInitValue&&a.resetInitValue()});this.setState(CKEDITOR.DIALOG_STATE_IDLE)}},addPage:function(a){if(!a.requiredContent||this._.editor.filter.check(a.requiredContent)){for(var b=[],c=a.label?' title\x3d"'+CKEDITOR.tools.htmlEncode(a.label)+'"':"",e=CKEDITOR.dialog._.uiElementBuilders.vbox.build(this,{type:"vbox",className:"cke_dialog_page_contents", +children:a.elements,expand:!!a.expand,padding:a.padding,style:a.style||"width: 100%;"},b),d=this._.contents[a.id]={},g=e.getChild(),f=0;e=g.shift();)e.notAllowed||"hbox"==e.type||"vbox"==e.type||f++,d[e.id]=e,"function"==typeof e.getChild&&g.push.apply(g,e.getChild());f||(a.hidden=!0);b=CKEDITOR.dom.element.createFromHtml(b.join(""));b.setAttribute("role","tabpanel");e=CKEDITOR.env;d="cke_"+a.id+"_"+CKEDITOR.tools.getNextNumber();c=CKEDITOR.dom.element.createFromHtml(['\x3ca class\x3d"cke_dialog_tab"', +0arguments.length)){var h=(e.call?e(b):e)||"div",k=["\x3c",h," "],l=(d&&d.call?d(b):d)||{},m=(g&&g.call?g(b):g)||{},n=(f&&f.call?f.call(this,a,b):f)||"",r=this.domId=m.id||CKEDITOR.tools.getNextId()+"_uiElement";b.requiredContent&&!a.getParentEditor().filter.check(b.requiredContent)&&(l.display="none",this.notAllowed=!0);m.id=r;var q={};b.type&&(q["cke_dialog_ui_"+ +b.type]=1);b.className&&(q[b.className]=1);b.disabled&&(q.cke_disabled=1);for(var t=m["class"]&&m["class"].split?m["class"].split(" "):[],r=0;rCKEDITOR.env.version?"cke_dialog_ui_focused":"";b.on("focus",function(){a._.tabBarMode=!1;a._.hasFocus=!0;v.fire("focus"); +c&&this.addClass(c)});b.on("blur",function(){v.fire("blur");c&&this.removeClass(c)})}});CKEDITOR.tools.extend(this,b);this.keyboardFocusable&&(this.tabIndex=b.tabIndex||0,this.focusIndex=a._.focusList.push(this)-1,this.on("focus",function(){a._.currentFocusIndex=v.focusIndex}))}},hbox:function(a,b,c,e,d){if(!(4>arguments.length)){this._||(this._={});var g=this._.children=b,f=d&&d.widths||null,h=d&&d.height||null,k,l={role:"presentation"};d&&d.align&&(l.align=d.align);CKEDITOR.ui.dialog.uiElement.call(this, +a,d||{type:"hbox"},e,"table",{},l,function(){var a=['\x3ctbody\x3e\x3ctr class\x3d"cke_dialog_ui_hbox"\x3e'];for(k=0;karguments.length)){this._||(this._={});var g=this._.children=b,f=d&&d.width||null,h=d&&d.heights||null;CKEDITOR.ui.dialog.uiElement.call(this,a,d||{type:"vbox"},e,"div",null,{role:"presentation"},function(){var b=['\x3ctable role\x3d"presentation" cellspacing\x3d"0" border\x3d"0" ']; +b.push('style\x3d"');d&&d.expand&&b.push("height:100%;");b.push("width:"+u(f||"100%"),";");CKEDITOR.env.webkit&&b.push("float:none;");b.push('"');b.push('align\x3d"',CKEDITOR.tools.htmlEncode(d&&d.align||("ltr"==a.getParentEditor().lang.dir?"left":"right")),'" ');b.push("\x3e\x3ctbody\x3e");for(var e=0;earguments.length)return this._.children.concat();a.splice||(a=[a]);return 2>a.length?this._.children[a[0]]:this._.children[a[0]]&&this._.children[a[0]].getChild?this._.children[a[0]].getChild(a.slice(1,a.length)):null}},!0);CKEDITOR.ui.dialog.vbox.prototype=new CKEDITOR.ui.dialog.hbox;(function(){var a={build:function(a,b,c){for(var e=b.children,d,g=[],f=[],h=0;hk.length&&(b=a.document.createElement(a.config.enterMode==CKEDITOR.ENTER_P?"p":"div"),g=l.shift(),d.insertNode(b),b.append(new CKEDITOR.dom.text("",a.document)),d.moveToBookmark(g),d.selectNodeContents(b),d.collapse(!0),g=d.createBookmark(),k.push(b),l.unshift(g)); +h=k[0].getParent();d=[];for(g=0;gc||(this.notifications.splice(c,1),a.element.remove(), +this.element.getChildCount()||(this._removeListeners(),this.element.remove()))},_createElement:function(){var a=this.editor,c=a.config,d=new CKEDITOR.dom.element("div");d.addClass("cke_notifications_area");d.setAttribute("id","cke_notifications_area_"+a.name);d.setStyle("z-index",c.baseFloatZIndex-2);return d},_attachListeners:function(){var a=CKEDITOR.document.getWindow(),c=this.editor;a.on("scroll",this._uiBuffer.input);a.on("resize",this._uiBuffer.input);c.on("change",this._changeBuffer.input); +c.on("floatingSpaceLayout",this._layout,this,null,20);c.on("blur",this._layout,this,null,20)},_removeListeners:function(){var a=CKEDITOR.document.getWindow(),c=this.editor;a.removeListener("scroll",this._uiBuffer.input);a.removeListener("resize",this._uiBuffer.input);c.removeListener("change",this._changeBuffer.input);c.removeListener("floatingSpaceLayout",this._layout);c.removeListener("blur",this._layout)},_layout:function(){function a(){c.setStyle("left",w(r+f.width-n-q))}var c=this.element,d= +this.editor,f=d.ui.contentsElement.getClientRect(),k=d.ui.contentsElement.getDocumentPosition(),g,h,m=c.getClientRect(),e,n=this._notificationWidth,q=this._notificationMargin;e=CKEDITOR.document.getWindow();var y=e.getScrollPosition(),u=e.getViewPaneSize(),p=CKEDITOR.document.getBody(),v=p.getDocumentPosition(),w=CKEDITOR.tools.cssLength;n&&q||(e=this.element.getChild(0),n=this._notificationWidth=e.getClientRect().width,q=this._notificationMargin=parseInt(e.getComputedStyle("margin-left"),10)+parseInt(e.getComputedStyle("margin-right"), +10));d.toolbar&&(g=d.ui.space("top"),h=g.getClientRect());g&&g.isVisible()&&h.bottom>f.top&&h.bottomy.y?c.setStyles({position:"fixed",top:0}):c.setStyles({position:"absolute",top:w(k.y+f.height-m.height)});var r="fixed"==c.getStyle("position")?f.left:"static"!=p.getComputedStyle("position")?k.x-v.x:k.x;f.widthy.x+u.width?a():c.setStyle("left", +w(r)):k.x+n+q>y.x+u.width?c.setStyle("left",w(r)):k.x+f.width/2+n/2+q>y.x+u.width?c.setStyle("left",w(r-k.x+y.x+u.width-n-q)):0>f.left+f.width-n-q?a():0>f.left+f.width/2-n/2?c.setStyle("left",w(r-k.x+y.x)):c.setStyle("left",w(r+f.width/2-n/2-q/2))}};CKEDITOR.plugins.notification=a}(),function(){var a='\x3ca id\x3d"{id}" class\x3d"cke_button cke_button__{name} cke_button_{state} {cls}"'+(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href\x3d\"javascript:void('{titleJs}')\"")+' title\x3d"{title}" tabindex\x3d"-1" hidefocus\x3d"true" role\x3d"button" aria-labelledby\x3d"{id}_label" aria-describedby\x3d"{id}_description" aria-haspopup\x3d"{hasArrow}" aria-disabled\x3d"{ariaDisabled}"'; +CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(a+=' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(a+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;"');var f="";CKEDITOR.env.ie&&(f='return false;" onmouseup\x3d"CKEDITOR.tools.getMouseButton(event)\x3d\x3dCKEDITOR.MOUSE_BUTTON_LEFT\x26\x26');var a=a+(' onkeydown\x3d"return CKEDITOR.tools.callFunction({keydownFn},event);" onfocus\x3d"return CKEDITOR.tools.callFunction({focusFn},event);" onclick\x3d"'+f+'CKEDITOR.tools.callFunction({clickFn},this);return false;"\x3e\x3cspan class\x3d"cke_button_icon cke_button__{iconName}_icon" style\x3d"{style}"')+ +'\x3e\x26nbsp;\x3c/span\x3e\x3cspan id\x3d"{id}_label" class\x3d"cke_button_label cke_button__{name}_label" aria-hidden\x3d"false"\x3e{label}\x3c/span\x3e\x3cspan id\x3d"{id}_description" class\x3d"cke_button_label" aria-hidden\x3d"false"\x3e{ariaShortcut}\x3c/span\x3e{arrowHtml}\x3c/a\x3e',b=CKEDITOR.addTemplate("buttonArrow",'\x3cspan class\x3d"cke_button_arrow"\x3e'+(CKEDITOR.env.hc?"\x26#9660;":"")+"\x3c/span\x3e"),c=CKEDITOR.addTemplate("button",a);CKEDITOR.plugins.add("button",{beforeInit:function(a){a.ui.addHandler(CKEDITOR.UI_BUTTON, +CKEDITOR.ui.button.handler)}});CKEDITOR.UI_BUTTON="button";CKEDITOR.ui.button=function(a){CKEDITOR.tools.extend(this,a,{title:a.label,click:a.click||function(b){b.execCommand(a.command)}});this._={}};CKEDITOR.ui.button.handler={create:function(a){return new CKEDITOR.ui.button(a)}};CKEDITOR.ui.button.prototype={render:function(a,f){function k(){var b=a.mode;b&&(b=this.modes[b]?void 0!==g[b]?g[b]:CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,b=a.readOnly&&!this.readOnly?CKEDITOR.TRISTATE_DISABLED: +b,this.setState(b),this.refresh&&this.refresh())}var g=null,h=CKEDITOR.env,m=this._.id=CKEDITOR.tools.getNextId(),e="",n=this.command,q,y,u;this._.editor=a;var p={id:m,button:this,editor:a,focus:function(){CKEDITOR.document.getById(m).focus()},execute:function(){this.button.click(a)},attach:function(a){this.button.attach(a)}},v=CKEDITOR.tools.addFunction(function(a){if(p.onkey)return a=new CKEDITOR.dom.event(a),!1!==p.onkey(p,a.getKeystroke())}),w=CKEDITOR.tools.addFunction(function(a){var b;p.onfocus&& +(b=!1!==p.onfocus(p,new CKEDITOR.dom.event(a)));return b}),r=0;p.clickFn=q=CKEDITOR.tools.addFunction(function(){r&&(a.unlockSelection(1),r=0);p.execute();h.iOS&&a.focus()});this.modes?(g={},a.on("beforeModeUnload",function(){a.mode&&this._.state!=CKEDITOR.TRISTATE_DISABLED&&(g[a.mode]=this._.state)},this),a.on("activeFilterChange",k,this),a.on("mode",k,this),!this.readOnly&&a.on("readOnly",k,this)):n&&(n=a.getCommand(n))&&(n.on("state",function(){this.setState(n.state)},this),e+=n.state==CKEDITOR.TRISTATE_ON? +"on":n.state==CKEDITOR.TRISTATE_DISABLED?"disabled":"off");var z;if(this.directional)a.on("contentDirChanged",function(b){var c=CKEDITOR.document.getById(this._.id),e=c.getFirst();b=b.data;b!=a.lang.dir?c.addClass("cke_"+b):c.removeClass("cke_ltr").removeClass("cke_rtl");e.setAttribute("style",CKEDITOR.skin.getIconStyle(z,"rtl"==b,this.icon,this.iconOffset))},this);n?(y=a.getCommandKeystroke(n))&&(u=CKEDITOR.tools.keystrokeToString(a.lang.common.keyboard,y)):e+="off";y=this.name||this.command;var t= +null,x=this.icon;z=y;this.icon&&!/\./.test(this.icon)?(z=this.icon,x=null):(this.icon&&(t=this.icon),CKEDITOR.env.hidpi&&this.iconHiDpi&&(t=this.iconHiDpi));t?(CKEDITOR.skin.addIcon(t,t),x=null):t=z;e={id:m,name:y,iconName:z,label:this.label,cls:(this.hasArrow?"cke_button_expandable ":"")+(this.className||""),state:e,ariaDisabled:"disabled"==e?"true":"false",title:this.title+(u?" ("+u.display+")":""),ariaShortcut:u?a.lang.common.keyboardShortcut+" "+u.aria:"",titleJs:h.gecko&&!h.hc?"":(this.title|| +"").replace("'",""),hasArrow:"string"===typeof this.hasArrow&&this.hasArrow||(this.hasArrow?"true":"false"),keydownFn:v,focusFn:w,clickFn:q,style:CKEDITOR.skin.getIconStyle(t,"rtl"==a.lang.dir,x,this.iconOffset),arrowHtml:this.hasArrow?b.output():""};c.output(e,f);if(this.onRender)this.onRender();return p},setState:function(a){if(this._.state==a)return!1;this._.state=a;var b=CKEDITOR.document.getById(this._.id);return b?(b.setState(a,"cke_button"),b.setAttribute("aria-disabled",a==CKEDITOR.TRISTATE_DISABLED), +this.hasArrow?b.setAttribute("aria-expanded",a==CKEDITOR.TRISTATE_ON):a===CKEDITOR.TRISTATE_ON?b.setAttribute("aria-pressed",!0):b.removeAttribute("aria-pressed"),!0):!1},getState:function(){return this._.state},toFeature:function(a){if(this._.feature)return this._.feature;var b=this;this.allowedContent||this.requiredContent||!this.command||(b=a.getCommand(this.command)||b);return this._.feature=b}};CKEDITOR.ui.prototype.addButton=function(a,b){this.add(a,CKEDITOR.UI_BUTTON,b)}}(),function(){function a(a){function b(){for(var e= +c(),h=CKEDITOR.tools.clone(a.config.toolbarGroups)||f(a),m=0;mb.order?-1:0>a.order? +1:a.order]+data-cke-bookmark[^<]*?<\/span>/ig,"");g&&a(b,d)})}function A(){if("wysiwyg"==b.mode){var a=B("paste");b.getCommand("cut").setState(B("cut"));b.getCommand("copy").setState(B("copy"));b.getCommand("paste").setState(a);b.fire("pasteState",a)}}function B(a){var c= +b.getSelection(),c=c&&c.getRanges()[0];if((b.readOnly||c&&c.checkReadOnly())&&a in{paste:1,cut:1})return CKEDITOR.TRISTATE_DISABLED;if("paste"==a)return CKEDITOR.TRISTATE_OFF;a=b.getSelection();c=a.getRanges();return a.getType()==CKEDITOR.SELECTION_NONE||1==c.length&&c[0].collapsed?CKEDITOR.TRISTATE_DISABLED:CKEDITOR.TRISTATE_OFF}var C=CKEDITOR.plugins.clipboard,H=0,F=0;(function(){b.on("key",t);b.on("contentDom",c);b.on("selectionChange",A);if(b.contextMenu){b.contextMenu.addListener(function(){return{cut:B("cut"), +copy:B("copy"),paste:B("paste")}});var a=null;b.on("menuShow",function(){a&&(a.removeListener(),a=null);var c=b.contextMenu.findItemByCommandName("paste");c&&c.element&&(a=c.element.on("touchend",function(){b._.forcePasteDialog=!0}))})}if(b.ui.addButton)b.once("instanceReady",function(){b._.pasteButtons&&CKEDITOR.tools.array.forEach(b._.pasteButtons,function(a){if(a=b.ui.get(a))if(a=CKEDITOR.document.getById(a._.id))a.on("touchend",function(){b._.forcePasteDialog=!0})})})})();(function(){function a(c, +d,g,f,h){var k=b.lang.clipboard[d];b.addCommand(d,g);b.ui.addButton&&b.ui.addButton(c,{label:k,command:d,toolbar:"clipboard,"+f});b.addMenuItems&&b.addMenuItem(d,{label:k,command:d,group:"clipboard",order:h})}a("Cut","cut",d("cut"),10,1);a("Copy","copy",d("copy"),20,4);a("Paste","paste",g(),30,8);b._.pasteButtons||(b._.pasteButtons=[]);b._.pasteButtons.push("Paste")})();b.getClipboardData=function(a,c){function d(a){a.removeListener();a.cancel();c(a.data)}function g(a){a.removeListener();a.cancel(); +c({type:h,dataValue:a.data.dataValue,dataTransfer:a.data.dataTransfer,method:"paste"})}var f=!1,h="auto";c||(c=a,a=null);b.on("beforePaste",function(a){a.removeListener();f=!0;h=a.data.type},null,null,1E3);b.on("paste",d,null,null,0);!1===z()&&(b.removeListener("paste",d),b._.forcePasteDialog&&f&&b.fire("pasteDialog")?(b.on("pasteDialogCommit",g),b.on("dialogHide",function(a){a.removeListener();a.data.removeListener("pasteDialogCommit",g);a.data._.committed||c(null)})):c(null))}}function b(a){if(CKEDITOR.env.webkit){if(!a.match(/^[^<]*$/g)&& +!a.match(/^(
<\/div>|
[^<]*<\/div>)*$/gi))return"html"}else if(CKEDITOR.env.ie){if(!a.match(/^([^<]|)*$/gi)&&!a.match(/^(

([^<]|)*<\/p>|(\r\n))*$/gi))return"html"}else if(CKEDITOR.env.gecko){if(!a.match(/^([^<]|)*$/gi))return"html"}else return"html";return"htmlifiedtext"}function c(a,b){function c(a){return CKEDITOR.tools.repeat("\x3c/p\x3e\x3cp\x3e",~~(a/2))+(1==a%2?"\x3cbr\x3e":"")}b=b.replace(/(?!\u3000)\s+/g," ").replace(/> +/gi, +"\x3cbr\x3e");b=b.replace(/<\/?[A-Z]+>/g,function(a){return a.toLowerCase()});if(b.match(/^[^<]$/))return b;CKEDITOR.env.webkit&&-1(
|)<\/div>)(?!$|(

(
|)<\/div>))/g,"\x3cbr\x3e").replace(/^(
(
|)<\/div>){2}(?!$)/g,"\x3cdiv\x3e\x3c/div\x3e"),b.match(/
(
|)<\/div>/)&&(b="\x3cp\x3e"+b.replace(/(
(
|)<\/div>)+/g,function(a){return c(a.split("\x3c/div\x3e\x3cdiv\x3e").length+1)})+"\x3c/p\x3e"),b=b.replace(/<\/div>
/g,"\x3cbr\x3e"), +b=b.replace(/<\/?div>/g,""));CKEDITOR.env.gecko&&a.enterMode!=CKEDITOR.ENTER_BR&&(CKEDITOR.env.gecko&&(b=b.replace(/^

$/,"\x3cbr\x3e")),-1){2,}/g,function(a){return c(a.length/4)})+"\x3c/p\x3e"));return k(a,b)}function d(a){function b(){var a={},c;for(c in CKEDITOR.dtd)"$"!=c.charAt(0)&&"div"!=c&&"span"!=c&&(a[c]=1);return a}var c={};return{get:function(d){return"plain-text"==d?c.plainText||(c.plainText=new CKEDITOR.filter(a, +"br")):"semantic-content"==d?((d=c.semanticContent)||(d=new CKEDITOR.filter(a,{}),d.allow({$1:{elements:b(),attributes:!0,styles:!1,classes:!1}}),d=c.semanticContent=d),d):d?new CKEDITOR.filter(a,d):null}}}function l(a,b,c){b=CKEDITOR.htmlParser.fragment.fromHtml(b);var d=new CKEDITOR.htmlParser.basicWriter;c.applyTo(b,!0,!1,a.activeEnterMode);b.writeHtml(d);return d.getHtml()}function k(a,b){a.enterMode==CKEDITOR.ENTER_BR?b=b.replace(/(<\/p>

)+/g,function(a){return CKEDITOR.tools.repeat("\x3cbr\x3e", +a.length/7*2)}).replace(/<\/?p>/g,""):a.enterMode==CKEDITOR.ENTER_DIV&&(b=b.replace(/<(\/)?p>/g,"\x3c$1div\x3e"));return b}function g(a){a.data.preventDefault();a.data.$.dataTransfer.dropEffect="none"}function h(b){var c=CKEDITOR.plugins.clipboard;b.on("contentDom",function(){function d(c,g,f){g.select();a(b,{dataTransfer:f,method:"drop"},1);f.sourceEditor.fire("saveSnapshot");f.sourceEditor.editable().extractHtmlFromRange(c);f.sourceEditor.getSelection().selectRanges([c]);f.sourceEditor.fire("saveSnapshot")} +function g(d,f){d.select();a(b,{dataTransfer:f,method:"drop"},1);c.resetDragDataTransfer()}function f(a,c,d){var g={$:a.data.$,target:a.data.getTarget()};c&&(g.dragRange=c);d&&(g.dropRange=d);!1===b.fire(a.name,g)&&a.data.preventDefault()}function h(a){a.type!=CKEDITOR.NODE_ELEMENT&&(a=a.getParent());return a.getChildCount()}var k=b.editable(),l=CKEDITOR.plugins.clipboard.getDropTarget(b),m=b.ui.space("top"),z=b.ui.space("bottom");c.preventDefaultDropOnElement(m);c.preventDefaultDropOnElement(z); +k.attachListener(l,"dragstart",f);k.attachListener(b,"dragstart",c.resetDragDataTransfer,c,null,1);k.attachListener(b,"dragstart",function(a){c.initDragDataTransfer(a,b)},null,null,2);k.attachListener(b,"dragstart",function(){var a=c.dragRange=b.getSelection().getRanges()[0];CKEDITOR.env.ie&&10>CKEDITOR.env.version&&(c.dragStartContainerChildCount=a?h(a.startContainer):null,c.dragEndContainerChildCount=a?h(a.endContainer):null)},null,null,100);k.attachListener(l,"dragend",f);k.attachListener(b,"dragend", +c.initDragDataTransfer,c,null,1);k.attachListener(b,"dragend",c.resetDragDataTransfer,c,null,100);k.attachListener(l,"dragover",function(a){if(CKEDITOR.env.edge)a.data.preventDefault();else{var b=a.data.getTarget();b&&b.is&&b.is("html")?a.data.preventDefault():CKEDITOR.env.ie&&CKEDITOR.plugins.clipboard.isFileApiSupported&&a.data.$.dataTransfer.types.contains("Files")&&a.data.preventDefault()}});k.attachListener(l,"drop",function(a){if(!a.data.$.defaultPrevented){a.data.preventDefault();var d=a.data.getTarget(); +if(!d.isReadOnly()||d.type==CKEDITOR.NODE_ELEMENT&&d.is("html")){var d=c.getRangeAtDropPosition(a,b),g=c.dragRange;d&&f(a,g,d)}}},null,null,9999);k.attachListener(b,"drop",c.initDragDataTransfer,c,null,1);k.attachListener(b,"drop",function(a){if(a=a.data){var f=a.dropRange,h=a.dragRange,k=a.dataTransfer;k.getTransferType(b)==CKEDITOR.DATA_TRANSFER_INTERNAL?setTimeout(function(){c.internalDrop(h,f,k,b)},0):k.getTransferType(b)==CKEDITOR.DATA_TRANSFER_CROSS_EDITORS?d(h,f,k):g(f,k)}},null,null,9999)})} +var m;CKEDITOR.plugins.add("clipboard",{requires:"dialog,notification,toolbar",init:function(a){var g,k=d(a);a.config.forcePasteAsPlainText?g="plain-text":a.config.pasteFilter?g=a.config.pasteFilter:!CKEDITOR.env.webkit||"pasteFilter"in a.config||(g="semantic-content");a.pasteFilter=k.get(g);f(a);h(a);CKEDITOR.dialog.add("paste",CKEDITOR.getUrl(this.path+"dialogs/paste.js"));if(CKEDITOR.env.gecko){var m=["image/png","image/jpeg","image/gif"],u;a.on("paste",function(b){var c=b.data,d=c.dataTransfer; +if(!c.dataValue&&"paste"==c.method&&d&&1==d.getFilesCount()&&u!=d.id&&(d=d.getFile(0),-1!=CKEDITOR.tools.indexOf(m,d.type))){var g=new FileReader;g.addEventListener("load",function(){b.data.dataValue='\x3cimg src\x3d"'+g.result+'" /\x3e';a.fire("paste",b.data)},!1);g.addEventListener("abort",function(){a.fire("paste",b.data)},!1);g.addEventListener("error",function(){a.fire("paste",b.data)},!1);g.readAsDataURL(d);u=c.dataTransfer.id;b.stop()}},null,null,1)}a.on("paste",function(b){b.data.dataTransfer|| +(b.data.dataTransfer=new CKEDITOR.plugins.clipboard.dataTransfer);if(!b.data.dataValue){var c=b.data.dataTransfer,d=c.getData("text/html");if(d)b.data.dataValue=d,b.data.type="html";else if(d=c.getData("text/plain"))b.data.dataValue=a.editable().transformPlainTextToHtml(d),b.data.type="text"}},null,null,1);a.on("paste",function(a){var b=a.data.dataValue,c=CKEDITOR.dtd.$block;-1 <\/span>/gi," "),"html"!=a.data.type&&(b=b.replace(/]*>([^<]*)<\/span>/gi, +function(a,b){return b.replace(/\t/g,"\x26nbsp;\x26nbsp; \x26nbsp;")})),-1/,"")),b=b.replace(/(<[^>]+) class="Apple-[^"]*"/gi,"$1"));if(b.match(/^<[^<]+cke_(editable|contents)/i)){var d,e,g=new CKEDITOR.dom.element("div");for(g.setHtml(b);1==g.getChildCount()&&(d=g.getFirst())&&d.type==CKEDITOR.NODE_ELEMENT&&(d.hasClass("cke_editable")|| +d.hasClass("cke_contents"));)g=e=d;e&&(b=e.getHtml().replace(/
$/i,""))}CKEDITOR.env.ie?b=b.replace(/^ (?: |\r\n)?<(\w+)/g,function(b,d){return d.toLowerCase()in c?(a.data.preSniffing="html","\x3c"+d):b}):CKEDITOR.env.webkit?b=b.replace(/<\/(\w+)>


<\/div>$/,function(b,d){return d in c?(a.data.endsWithEOL=1,"\x3c/"+d+"\x3e"):b}):CKEDITOR.env.gecko&&(b=b.replace(/(\s)
$/,"$1"));a.data.dataValue=b},null,null,3);a.on("paste",function(d){d=d.data;var g=a._.nextPasteType||d.type,f=d.dataValue, +h,m=a.config.clipboard_defaultContentType||"html",n=d.dataTransfer.getTransferType(a)==CKEDITOR.DATA_TRANSFER_EXTERNAL,u=!0===a.config.forcePasteAsPlainText;h="html"==g||"html"==d.preSniffing?"html":b(f);delete a._.nextPasteType;"htmlifiedtext"==h&&(f=c(a.config,f));if("text"==g&&"html"==h)f=l(a,f,k.get("plain-text"));else if(n&&a.pasteFilter&&!d.dontFilter||u)f=l(a,f,a.pasteFilter);d.startsWithEOL&&(f='\x3cbr data-cke-eol\x3d"1"\x3e'+f);d.endsWithEOL&&(f+='\x3cbr data-cke-eol\x3d"1"\x3e');"auto"== +g&&(g="html"==h||"html"==m?"html":"text");d.type=g;d.dataValue=f;delete d.preSniffing;delete d.startsWithEOL;delete d.endsWithEOL},null,null,6);a.on("paste",function(b){b=b.data;b.dataValue&&(a.insertHtml(b.dataValue,b.type,b.range),setTimeout(function(){a.fire("afterPaste")},0))},null,null,1E3);a.on("pasteDialog",function(b){setTimeout(function(){a.openDialog("paste",b.data)},0)})}});CKEDITOR.plugins.clipboard={isCustomCopyCutSupported:(!CKEDITOR.env.ie||16<=CKEDITOR.env.version)&&!CKEDITOR.env.iOS, +isCustomDataTypesSupported:!CKEDITOR.env.ie||16<=CKEDITOR.env.version,isFileApiSupported:!CKEDITOR.env.ie||9CKEDITOR.env.version||b.isInline()?b:a.document},fixSplitNodesAfterDrop:function(a,b,c,d){function g(a,c,d){var e=a;e.type==CKEDITOR.NODE_TEXT&&(e=a.getParent());if(e.equals(c)&&d!=c.getChildCount())return a=b.startContainer.getChild(b.startOffset-1),c=b.startContainer.getChild(b.startOffset), +a&&a.type==CKEDITOR.NODE_TEXT&&c&&c.type==CKEDITOR.NODE_TEXT&&(d=a.getLength(),a.setText(a.getText()+c.getText()),c.remove(),b.setStart(a,d),b.collapse(!0)),!0}var f=b.startContainer;"number"==typeof d&&"number"==typeof c&&f.type==CKEDITOR.NODE_ELEMENT&&(g(a.startContainer,f,c)||g(a.endContainer,f,d))},isDropRangeAffectedByDragRange:function(a,b){var c=b.startContainer,d=b.endOffset;return a.endContainer.equals(c)&&a.endOffset<=d||a.startContainer.getParent().equals(c)&&a.startContainer.getIndex()< +d||a.endContainer.getParent().equals(c)&&a.endContainer.getIndex()CKEDITOR.env.version&&this.fixSplitNodesAfterDrop(b,c,f.dragStartContainerChildCount,f.dragEndContainerChildCount);(l=this.isDropRangeAffectedByDragRange(b,c))||(k=b.createBookmark(!1));f=c.clone().createBookmark(!1);l&&(k=b.createBookmark(!1));b=k.startNode;c= +k.endNode;l=f.startNode;c&&b.getPosition(l)&CKEDITOR.POSITION_PRECEDING&&c.getPosition(l)&CKEDITOR.POSITION_FOLLOWING&&l.insertBefore(b);b=g.createRange();b.moveToBookmark(k);h.extractHtmlFromRange(b,1);c=g.createRange();f.startNode.getCommonAncestor(h)||(f=g.getSelection().createBookmarks()[0]);c.moveToBookmark(f);a(g,{dataTransfer:d,method:"drop",range:c},1);g.fire("unlockSnapshot")},getRangeAtDropPosition:function(a,b){var c=a.data.$,d=c.clientX,g=c.clientY,f=b.getSelection(!0).getRanges()[0], +h=b.createRange();if(a.data.testRange)return a.data.testRange;if(document.caretRangeFromPoint&&b.document.$.caretRangeFromPoint(d,g))c=b.document.$.caretRangeFromPoint(d,g),h.setStart(CKEDITOR.dom.node(c.startContainer),c.startOffset),h.collapse(!0);else if(c.rangeParent)h.setStart(CKEDITOR.dom.node(c.rangeParent),c.rangeOffset),h.collapse(!0);else{if(CKEDITOR.env.ie&&8l&&!k;l++){if(!k)try{c.moveToPoint(d,g-l),k=!0}catch(m){}if(!k)try{c.moveToPoint(d,g+l),k=!0}catch(t){}}if(k){var x="cke-temp-"+(new Date).getTime();c.pasteHTML('\x3cspan id\x3d"'+x+'"\x3e​\x3c/span\x3e');var A=b.document.getById(x);h.moveToPosition(A,CKEDITOR.POSITION_BEFORE_START);A.remove()}else{var B=b.document.$.elementFromPoint(d,g),C=new CKEDITOR.dom.element(B),H;if(C.equals(b.editable())||"html"==C.getName())return f&&f.startContainer&&!f.startContainer.equals(b.editable())? +f:null;H=C.getClientRect();d/i,bodyRegExp:/([\s\S]*)<\/body>/i,fragmentRegExp:/\x3c!--(?:Start|End)Fragment--\x3e/g,data:{},files:[],nativeHtmlCache:"",normalizeType:function(a){a=a.toLowerCase();return"text"==a||"text/plain"==a?"Text":"url"==a?"URL":a}};this._.fallbackDataTransfer=new CKEDITOR.plugins.clipboard.fallbackDataTransfer(this);this.id=this.getData(m);this.id||(this.id="Text"==m?"":"cke-"+ +CKEDITOR.tools.getUniqueId());b&&(this.sourceEditor=b,this.setData("text/html",b.getSelectedHtml(1)),"Text"==m||this.getData("text/plain")||this.setData("text/plain",b.getSelection().getSelectedText()))};CKEDITOR.DATA_TRANSFER_INTERNAL=1;CKEDITOR.DATA_TRANSFER_CROSS_EDITORS=2;CKEDITOR.DATA_TRANSFER_EXTERNAL=3;CKEDITOR.plugins.clipboard.dataTransfer.prototype={getData:function(a,b){a=this._.normalizeType(a);var c="text/html"==a&&b?this._.nativeHtmlCache:this._.data[a];if(void 0===c||null===c||""=== +c){if(this._.fallbackDataTransfer.isRequired())c=this._.fallbackDataTransfer.getData(a,b);else try{c=this.$.getData(a)||""}catch(d){c=""}"text/html"!=a||b||(c=this._stripHtml(c))}"Text"==a&&CKEDITOR.env.gecko&&this.getFilesCount()&&"file://"==c.substring(0,7)&&(c="");if("string"===typeof c)var g=c.indexOf("\x3c/html\x3e"),c=-1!==g?c.substring(0,g+7):c;return c},setData:function(a,b){a=this._.normalizeType(a);"text/html"==a?(this._.data[a]=this._stripHtml(b),this._.nativeHtmlCache=b):this._.data[a]= +b;if(CKEDITOR.plugins.clipboard.isCustomDataTypesSupported||"URL"==a||"Text"==a)if("Text"==m&&"Text"==a&&(this.id=b),this._.fallbackDataTransfer.isRequired())this._.fallbackDataTransfer.setData(a,b);else try{this.$.setData(a,b)}catch(c){}},storeId:function(){"Text"!==m&&this.setData(m,this.id)},getTransferType:function(a){return this.sourceEditor?this.sourceEditor==a?CKEDITOR.DATA_TRANSFER_INTERNAL:CKEDITOR.DATA_TRANSFER_CROSS_EDITORS:CKEDITOR.DATA_TRANSFER_EXTERNAL},cacheData:function(){function a(c){c= +b._.normalizeType(c);var d=b.getData(c);"text/html"==c&&(b._.nativeHtmlCache=b.getData(c,!0),d=b._stripHtml(d));d&&(b._.data[c]=d)}if(this.$){var b=this,c,d;if(CKEDITOR.plugins.clipboard.isCustomDataTypesSupported){if(this.$.types)for(c=0;cc?v+c:b.width>c?v-a.left:v-a.right+b.width):ec?v-c:b.width>c?v-a.right+b.width:v-a.left);c=a.top;b.height-a.topd?w-d:b.height>d?w-a.bottom+b.height:w-a.top);CKEDITOR.env.ie&&!CKEDITOR.env.edge&&(b=a=new CKEDITOR.dom.element(n.$.offsetParent),"html"==b.getName()&&(b=b.getDocument().getBody()),"rtl"==b.getComputedStyle("direction")&&(v=CKEDITOR.env.ie8Compat?v-2*n.getDocument().getDocumentElement().$.scrollLeft:v-(a.$.scrollWidth- +a.$.clientWidth)));var a=n.getFirst(),k;(k=a.getCustomData("activePanel"))&&k.onHide&&k.onHide.call(this,1);a.setCustomData("activePanel",this);n.setStyles({top:w+"px",left:v+"px"});n.setOpacity(1);g&&g()},this);h.isLoaded?a():h.onLoad=a;CKEDITOR.tools.setTimeout(function(){var a=CKEDITOR.env.webkit&&CKEDITOR.document.getWindow().getScrollPosition().y;this.focus();m.element.focus();CKEDITOR.env.webkit&&(CKEDITOR.document.getBody().$.scrollTop=a);this.allowBlur(!0);this._.markFirst&&(CKEDITOR.env.ie? +CKEDITOR.tools.setTimeout(function(){m.markFirstDisplayed?m.markFirstDisplayed():m._.markFirstDisplayed()},0):m.markFirstDisplayed?m.markFirstDisplayed():m._.markFirstDisplayed());this._.editor.fire("panelShow",this)},0,this)},CKEDITOR.env.air?200:0,this);this.visible=1;this.onShow&&this.onShow.call(this)},reposition:function(){var a=this._.showBlockParams;this.visible&&this._.showBlockParams&&(this.hide(),this.showBlock.apply(this,a))},focus:function(){if(CKEDITOR.env.webkit){var a=CKEDITOR.document.getActive(); +a&&!a.equals(this._.iframe)&&a.$.blur()}(this._.lastFocused||this._.iframe.getFrameDocument().getWindow()).focus()},blur:function(){var a=this._.iframe.getFrameDocument().getActive();a&&a.is("a")&&(this._.lastFocused=a)},hide:function(a){if(this.visible&&(!this.onHide||!0!==this.onHide.call(this))){this.hideChild();CKEDITOR.env.gecko&&this._.iframe.getFrameDocument().$.activeElement.blur();this.element.setStyle("display","none");this.visible=0;this.element.getFirst().removeCustomData("activePanel"); +if(a=a&&this._.returnFocus)CKEDITOR.env.webkit&&a.type&&a.getWindow().$.focus(),a.focus();delete this._.lastFocused;this._.showBlockParams=null;this._.editor.fire("panelHide",this)}},allowBlur:function(a){var c=this._.panel;void 0!==a&&(c.allowBlur=a);return c.allowBlur},showAsChild:function(a,c,d,f,k,g){if(this._.activeChild!=a||a._.panel._.offsetParentId!=d.getId())this.hideChild(),a.onHide=CKEDITOR.tools.bind(function(){CKEDITOR.tools.setTimeout(function(){this._.focused||this.hide()},0,this)}, +this),this._.activeChild=a,this._.focused=!1,a.showBlock(c,d,f,k,g),this.blur(),(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)&&setTimeout(function(){a.element.getChild(0).$.style.cssText+=""},100)},hideChild:function(a){var c=this._.activeChild;c&&(delete c.onHide,delete this._.activeChild,c.hide(),a&&this.focus())}}});CKEDITOR.on("instanceDestroyed",function(){var a=CKEDITOR.tools.isEmpty(CKEDITOR.instances),c;for(c in f){var d=f[c];a?d.destroy():d.element.hide()}a&&(f={})})}(),CKEDITOR.plugins.add("menu", +{requires:"floatpanel",beforeInit:function(a){for(var f=a.config.menu_groups.split(","),b=a._.menuGroups={},c=a._.menuItems={},d=0;db.group?1:a.orderb.order?1:0})}var f='\x3cspan class\x3d"cke_menuitem"\x3e\x3ca id\x3d"{id}" class\x3d"cke_menubutton cke_menubutton__{name} cke_menubutton_{state} {cls}" href\x3d"{href}" title\x3d"{title}" tabindex\x3d"-1" _cke_focus\x3d1 hidefocus\x3d"true" role\x3d"{role}" aria-label\x3d"{label}" aria-describedby\x3d"{id}_description" aria-haspopup\x3d"{hasPopup}" aria-disabled\x3d"{disabled}" {ariaChecked} draggable\x3d"false"';CKEDITOR.env.gecko&&CKEDITOR.env.mac&& +(f+=' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(f+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;" ondragstart\x3d"return false;"');var f=f+(' onmouseover\x3d"CKEDITOR.tools.callFunction({hoverFn},{index});" onmouseout\x3d"CKEDITOR.tools.callFunction({moveOutFn},{index});" '+(CKEDITOR.env.ie?'onclick\x3d"return false;" onmouseup':"onclick")+'\x3d"CKEDITOR.tools.callFunction({clickFn},{index}); return false;"\x3e'),b=CKEDITOR.addTemplate("menuItem",f+'\x3cspan class\x3d"cke_menubutton_inner"\x3e\x3cspan class\x3d"cke_menubutton_icon"\x3e\x3cspan class\x3d"cke_button_icon cke_button__{iconName}_icon" style\x3d"{iconStyle}"\x3e\x3c/span\x3e\x3c/span\x3e\x3cspan class\x3d"cke_menubutton_label"\x3e{label}\x3c/span\x3e{shortcutHtml}{arrowHtml}\x3c/span\x3e\x3c/a\x3e\x3cspan id\x3d"{id}_description" class\x3d"cke_voice_label" aria-hidden\x3d"false"\x3e{ariaShortcut}\x3c/span\x3e\x3c/span\x3e'), +c=CKEDITOR.addTemplate("menuArrow",'\x3cspan class\x3d"cke_menuarrow"\x3e\x3cspan\x3e{label}\x3c/span\x3e\x3c/span\x3e'),d=CKEDITOR.addTemplate("menuShortcut",'\x3cspan class\x3d"cke_menubutton_label cke_menubutton_shortcut"\x3e{shortcut}\x3c/span\x3e');CKEDITOR.menu=CKEDITOR.tools.createClass({$:function(a,b){b=this._.definition=b||{};this.id=CKEDITOR.tools.getNextId();this.editor=a;this.items=[];this._.listeners=[];this._.level=b.level||1;var c=CKEDITOR.tools.extend({},b.panel,{css:[CKEDITOR.skin.getPath("editor")], +level:this._.level-1,block:{}}),d=c.block.attributes=c.attributes||{};!d.role&&(d.role="menu");this._.panelDefinition=c},_:{onShow:function(){var a=this.editor.getSelection(),b=a&&a.getStartElement(),c=this.editor.elementPath(),d=this._.listeners;this.removeAll();for(var f=0;fz.length)return!1;h=f.getParents(!0);for(r=0;rx;r++)t[r].indent+=h;h=CKEDITOR.plugins.list.arrayToList(t,e,null,a.config.enterMode,f.getDirection());if(!d.isIndent){var C;if((C=f.getParent())&&C.is("li"))for(var z=h.listNode.getChildren(),u=[],p,r=z.count()-1;0<=r;r--)(p=z.getItem(r))&&p.is&&p.is("li")&&u.push(p)}h&&h.listNode.replace(f);if(u&&u.length)for(r=0;rg.length)){d=g[g.length-1].getNext();f=e.createElement(this.type);for(c.push(f);g.length;)c=g.shift(),a=e.createElement("li"),l=c,l.is("pre")||u.test(l.getName())||"false"==l.getAttribute("contenteditable")?c.appendTo(a):(c.copyAttributes(a),h&&c.getDirection()&&(a.removeStyle("direction"),a.removeAttribute("dir")),c.moveChildren(a),c.remove()),a.appendTo(f);h&&k&&f.setAttribute("dir",h);d?f.insertBefore(d):f.appendTo(b)}}function b(a,b,c){function d(c){if(!(!(l= +k[c?"getFirst":"getLast"]())||l.is&&l.isBlockBoundary()||!(m=b.root[c?"getPrevious":"getNext"](CKEDITOR.dom.walker.invisible(!0)))||m.is&&m.isBlockBoundary({br:1})))a.document.createElement("br")[c?"insertBefore":"insertAfter"](l)}for(var e=CKEDITOR.plugins.list.listToArray(b.root,c),g=[],f=0;fe[f-1].indent+1){g=e[f-1].indent+1-e[f].indent;for(h=e[f].indent;e[f]&&e[f].indent>=h;)e[f].indent+=g,f++;f--}var k=CKEDITOR.plugins.list.arrayToList(e,c,null,a.config.enterMode,b.root.getAttribute("dir")).listNode,l,m;d(!0);d();k.replace(b.root);a.fire("contentDomInvalidated")}function c(a,b){this.name=a;this.context=this.type=b;this.allowedContent=b+" li";this.requiredContent=b}function d(a,b,c,d){for(var e, +g;e=a[d?"getLast":"getFirst"](p);)(g=e.getDirection(1))!==b.getDirection(1)&&e.setAttribute("dir",g),e.remove(),c?e[d?"insertBefore":"insertAfter"](c):b.append(e,d),c=e}function l(a){function b(c){var e=a[c?"getPrevious":"getNext"](q);e&&e.type==CKEDITOR.NODE_ELEMENT&&e.is(a.getName())&&(d(a,e,null,!c),a.remove(),a=e)}b();b(1)}function k(a){return a.type==CKEDITOR.NODE_ELEMENT&&(a.getName()in CKEDITOR.dtd.$block||a.getName()in CKEDITOR.dtd.$listItem)&&CKEDITOR.dtd[a.getName()]["#"]}function g(a,b, +c){a.fire("saveSnapshot");c.enlarge(CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS);var e=c.extractContents();b.trim(!1,!0);var g=b.createBookmark(),f=new CKEDITOR.dom.elementPath(b.startContainer),k=f.block,f=f.lastElement.getAscendant("li",1)||k,m=new CKEDITOR.dom.elementPath(c.startContainer),n=m.contains(CKEDITOR.dtd.$listItem),m=m.contains(CKEDITOR.dtd.$list);k?(k=k.getBogus())&&k.remove():m&&(k=m.getPrevious(q))&&y(k)&&k.remove();(k=e.getLast())&&k.type==CKEDITOR.NODE_ELEMENT&&k.is("br")&&k.remove();(k= +b.startContainer.getChild(b.startOffset))?e.insertBefore(k):b.startContainer.append(e);n&&(e=h(n))&&(f.contains(n)?(d(e,n.getParent(),n),e.remove()):f.append(e));for(;c.checkStartOfBlock()&&c.checkEndOfBlock();){m=c.startPath();e=m.block;if(!e)break;e.is("li")&&(f=e.getParent(),e.equals(f.getLast(q))&&e.equals(f.getFirst(q))&&(e=f));c.moveToPosition(e,CKEDITOR.POSITION_BEFORE_START);e.remove()}c=c.clone();e=a.editable();c.setEndAt(e,CKEDITOR.POSITION_BEFORE_END);c=new CKEDITOR.dom.walker(c);c.evaluator= +function(a){return q(a)&&!y(a)};(c=c.next())&&c.type==CKEDITOR.NODE_ELEMENT&&c.getName()in CKEDITOR.dtd.$list&&l(c);b.moveToBookmark(g);b.select();a.fire("saveSnapshot")}function h(a){return(a=a.getLast(q))&&a.type==CKEDITOR.NODE_ELEMENT&&a.getName()in m?a:null}var m={ol:1,ul:1},e=CKEDITOR.dom.walker.whitespaces(),n=CKEDITOR.dom.walker.bookmark(),q=function(a){return!(e(a)||n(a))},y=CKEDITOR.dom.walker.bogus();CKEDITOR.plugins.list={listToArray:function(a,b,c,d,e){if(!m[a.getName()])return[];d||(d= +0);c||(c=[]);for(var g=0,f=a.getChildCount();g=f.$.documentMode&&p.append(f.createText(" ")),p.append(l.listNode),l=l.nextIndex;else if(-1== +G.indent&&!c&&g){m[g.getName()]?(p=G.element.clone(!1,!0),y!=g.getDirection(1)&&p.setAttribute("dir",y)):p=new CKEDITOR.dom.documentFragment(f);var k=g.getDirection(1)!=y,D=G.element,N=D.getAttribute("class"),Q=D.getAttribute("style"),O=p.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT&&(d!=CKEDITOR.ENTER_BR||k||Q||N),K,W=G.contents.length,R;for(g=0;gCKEDITOR.env.version? +k.createText("\r"):k.createElement("br"),c.deleteContents(),c.insertNode(a),CKEDITOR.env.needsBrFiller?(k.createText("").insertAfter(a),l&&(v||p.blockLimit).appendBogus(),a.getNext().$.nodeValue="",c.setStartAt(a.getNext(),CKEDITOR.POSITION_AFTER_START)):c.setStartAt(a,CKEDITOR.POSITION_AFTER_END)),c.collapse(!0),c.select(),c.scrollIntoView()):g(a,b,c,d)}}};l=CKEDITOR.plugins.enterkey;k=l.enterBr;g=l.enterBlock;h=/^h[1-6]$/}(),function(){function a(a,b){var c={},d=[],l={nbsp:" ",shy:"­",gt:"\x3e", +lt:"\x3c",amp:"\x26",apos:"'",quot:'"'};a=a.replace(/\b(nbsp|shy|gt|lt|amp|apos|quot)(?:,|$)/g,function(a,e){var g=b?"\x26"+e+";":l[e];c[g]=b?l[e]:"\x26"+e+";";d.push(g);return""});a=a.replace(/,$/,"");if(!b&&a){a=a.split(",");var k=document.createElement("div"),g;k.innerHTML="\x26"+a.join(";\x26")+";";g=k.innerHTML;k=null;for(k=0;kf&&(f=640);420>b&&(b=420);var d=parseInt((window.screen.height-b)/2,10),l=parseInt((window.screen.width- +f)/2,10);c=(c||"location\x3dno,menubar\x3dno,toolbar\x3dno,dependent\x3dyes,minimizable\x3dno,modal\x3dyes,alwaysRaised\x3dyes,resizable\x3dyes,scrollbars\x3dyes")+",width\x3d"+f+",height\x3d"+b+",top\x3d"+d+",left\x3d"+l;var k=window.open("",null,c,!0);if(!k)return!1;try{-1==navigator.userAgent.toLowerCase().indexOf(" chrome/")&&(k.moveTo(l,d),k.resizeTo(f,b)),k.focus(),k.location.href=a}catch(g){window.open(a,null,c,!0)}return!0}}),"use strict",function(){function a(a){this.editor=a;this.loaders= +[]}function f(a,c,f){var g=a.config.fileTools_defaultFileName;this.editor=a;this.lang=a.lang;"string"===typeof c?(this.data=c,this.file=b(this.data),this.loaded=this.total=this.file.size):(this.data=null,this.file=c,this.total=this.file.size,this.loaded=0);f?this.fileName=f:this.file.name?this.fileName=this.file.name:(a=this.file.type.split("/"),g&&(a[0]=g),this.fileName=a.join("."));this.uploaded=0;this.responseData=this.uploadTotal=null;this.status="created";this.abort=function(){this.changeStatus("abort")}} +function b(a){var b=a.match(c)[1];a=a.replace(c,"");a=atob(a);var f=[],g,h,m,e;for(g=0;gg.status||299w.height-v.bottom?g("pin"):g("bottom"),e=w.width/2,e=d.floatSpacePreferRight?"right":0p.width?"rtl"==d.contentsLangDirection?"right":"left":e-v.left>v.right-e?"left":"right",p.width>w.width?(e="left",n=0):(n="left"==e?0w.width&&(e="left"==e?"right":"left",n=0)),h.setStyle(e,b(("pin"==l?A:t)+n+("pin"==l?0:"left"==e?z:-z)))):(l="pin",g("pin"),k(e))}}}();if(l){var g=new CKEDITOR.template('\x3cdiv id\x3d"cke_{name}" class\x3d"cke {id} cke_reset_all cke_chrome cke_editor_{name} cke_float cke_{langDir} '+ +CKEDITOR.env.cssClass+'" dir\x3d"{langDir}" title\x3d"'+(CKEDITOR.env.gecko?" ":"")+'" lang\x3d"{langCode}" role\x3d"application" style\x3d"{style}"'+(a.title?' aria-labelledby\x3d"cke_{name}_arialbl"':" ")+"\x3e"+(a.title?'\x3cspan id\x3d"cke_{name}_arialbl" class\x3d"cke_voice_label"\x3e{voiceLabel}\x3c/span\x3e':" ")+'\x3cdiv class\x3d"cke_inner"\x3e\x3cdiv id\x3d"{topId}" class\x3d"cke_top" role\x3d"presentation"\x3e{content}\x3c/div\x3e\x3c/div\x3e\x3c/div\x3e'),h=CKEDITOR.document.getBody().append(CKEDITOR.dom.element.createFromHtml(g.output({content:l, +id:a.id,langDir:a.lang.dir,langCode:a.langCode,name:a.name,style:"display:none;z-index:"+(d.baseFloatZIndex-1),topId:a.ui.spaceId("top"),voiceLabel:a.title}))),m=CKEDITOR.tools.eventsBuffer(500,k),e=CKEDITOR.tools.eventsBuffer(100,k);h.unselectable();h.on("mousedown",function(a){a=a.data;a.getTarget().hasAscendant("a",1)||a.preventDefault()});a.on("focus",function(b){k(b);a.on("change",m.input);f.on("scroll",e.input);f.on("resize",e.input)});a.on("blur",function(){h.hide();a.removeListener("change", +m.input);f.removeListener("scroll",e.input);f.removeListener("resize",e.input)});a.on("destroy",function(){f.removeListener("scroll",e.input);f.removeListener("resize",e.input);h.clearCustomData();h.remove()});a.focusManager.hasFocus&&h.show();a.focusManager.add(h,1)}}var f=CKEDITOR.document.getWindow(),b=CKEDITOR.tools.cssLength;CKEDITOR.plugins.add("floatingspace",{init:function(b){b.on("loaded",function(){a(this)},null,null,20)}})}(),CKEDITOR.plugins.add("listblock",{requires:"panel",onLoad:function(){var a= +CKEDITOR.addTemplate("panel-list",'\x3cul role\x3d"presentation" class\x3d"cke_panel_list"\x3e{items}\x3c/ul\x3e'),f=CKEDITOR.addTemplate("panel-list-item",'\x3cli id\x3d"{id}" class\x3d"cke_panel_listItem" role\x3dpresentation\x3e\x3ca id\x3d"{id}_option" _cke_focus\x3d1 hidefocus\x3dtrue title\x3d"{title}" draggable\x3d"false" ondragstart\x3d"return false;" href\x3d"javascript:void(\'{val}\')" {onclick}\x3d"CKEDITOR.tools.callFunction({clickFn},\'{val}\'); return false;" role\x3d"option"\x3e{text}\x3c/a\x3e\x3c/li\x3e'), +b=CKEDITOR.addTemplate("panel-list-group",'\x3ch1 id\x3d"{id}" draggable\x3d"false" ondragstart\x3d"return false;" class\x3d"cke_panel_grouptitle" role\x3d"presentation" \x3e{label}\x3c/h1\x3e'),c=/\'/g;CKEDITOR.ui.panel.prototype.addListBlock=function(a,b){return this.addBlock(a,new CKEDITOR.ui.listBlock(this.getHolderElement(),b))};CKEDITOR.ui.listBlock=CKEDITOR.tools.createClass({base:CKEDITOR.ui.panel.block,$:function(a,b){b=b||{};var c=b.attributes||(b.attributes={});(this.multiSelect=!!b.multiSelect)&& +(c["aria-multiselectable"]=!0);!c.role&&(c.role="listbox");this.base.apply(this,arguments);this.element.setAttribute("role",c.role);c=this.keys;c[40]="next";c[9]="next";c[38]="prev";c[CKEDITOR.SHIFT+9]="prev";c[32]=CKEDITOR.env.ie?"mouseup":"click";CKEDITOR.env.ie&&(c[13]="mouseup");this._.pendingHtml=[];this._.pendingList=[];this._.items={};this._.groups={}},_:{close:function(){if(this._.started){var b=a.output({items:this._.pendingList.join("")});this._.pendingList=[];this._.pendingHtml.push(b); +delete this._.started}},getClick:function(){this._.click||(this._.click=CKEDITOR.tools.addFunction(function(a){var b=this.toggle(a);if(this.onClick)this.onClick(a,b)},this));return this._.click}},proto:{add:function(a,b,k){var g=CKEDITOR.tools.getNextId();this._.started||(this._.started=1,this._.size=this._.size||0);this._.items[a]=g;var h;h=CKEDITOR.tools.htmlEncodeAttr(a).replace(c,"\\'");a={id:g,val:h,onclick:CKEDITOR.env.ie?'onclick\x3d"return false;" onmouseup':"onclick",clickFn:this._.getClick(), +title:CKEDITOR.tools.htmlEncodeAttr(k||a),text:b||a};this._.pendingList.push(f.output(a))},startGroup:function(a){this._.close();var c=CKEDITOR.tools.getNextId();this._.groups[a]=c;this._.pendingHtml.push(b.output({id:c,label:a}))},commit:function(){this._.close();this.element.appendHtml(this._.pendingHtml.join(""));delete this._.size;this._.pendingHtml=[]},toggle:function(a){var b=this.isMarked(a);b?this.unmark(a):this.mark(a);return!b},hideGroup:function(a){var b=(a=this.element.getDocument().getById(this._.groups[a]))&& +a.getNext();a&&(a.setStyle("display","none"),b&&"ul"==b.getName()&&b.setStyle("display","none"))},hideItem:function(a){this.element.getDocument().getById(this._.items[a]).setStyle("display","none")},showAll:function(){var a=this._.items,b=this._.groups,c=this.element.getDocument(),g;for(g in a)c.getById(a[g]).setStyle("display","");for(var f in b)a=c.getById(b[f]),g=a.getNext(),a.setStyle("display",""),g&&"ul"==g.getName()&&g.setStyle("display","")},mark:function(a){this.multiSelect||this.unmarkAll(); +a=this._.items[a];var b=this.element.getDocument().getById(a);b.addClass("cke_selected");this.element.getDocument().getById(a+"_option").setAttribute("aria-selected",!0);this.onMark&&this.onMark(b)},markFirstDisplayed:function(){var a=this;this._.markFirstDisplayed(function(){a.multiSelect||a.unmarkAll()})},unmark:function(a){var b=this.element.getDocument();a=this._.items[a];var c=b.getById(a);c.removeClass("cke_selected");b.getById(a+"_option").removeAttribute("aria-selected");this.onUnmark&&this.onUnmark(c)}, +unmarkAll:function(){var a=this._.items,b=this.element.getDocument(),c;for(c in a){var g=a[c];b.getById(g).removeClass("cke_selected");b.getById(g+"_option").removeAttribute("aria-selected")}this.onUnmark&&this.onUnmark()},isMarked:function(a){return this.element.getDocument().getById(this._.items[a]).hasClass("cke_selected")},focus:function(a){this._.focusIndex=-1;var b=this.element.getElementsByTag("a"),c,g=-1;if(a)for(c=this.element.getDocument().getById(this._.items[a]).getFirst();a=b.getItem(++g);){if(a.equals(c)){this._.focusIndex= +g;break}}else this.element.focus();c&&setTimeout(function(){c.focus()},0)}}})}}),CKEDITOR.plugins.add("richcombo",{requires:"floatpanel,listblock,button",beforeInit:function(a){a.ui.addHandler(CKEDITOR.UI_RICHCOMBO,CKEDITOR.ui.richCombo.handler)}}),function(){var a='\x3cspan id\x3d"{id}" class\x3d"cke_combo cke_combo__{name} {cls}" role\x3d"presentation"\x3e\x3cspan id\x3d"{id}_label" class\x3d"cke_combo_label"\x3e{label}\x3c/span\x3e\x3ca class\x3d"cke_combo_button" title\x3d"{title}" tabindex\x3d"-1"'+ +(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href\x3d\"javascript:void('{titleJs}')\"")+' hidefocus\x3d"true" role\x3d"button" aria-labelledby\x3d"{id}_label" aria-haspopup\x3d"listbox"';CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(a+=' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(a+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;"');var a=a+(' onkeydown\x3d"return CKEDITOR.tools.callFunction({keydownFn},event,this);" onfocus\x3d"return CKEDITOR.tools.callFunction({focusFn},event);" '+(CKEDITOR.env.ie? +'onclick\x3d"return false;" onmouseup':"onclick")+'\x3d"CKEDITOR.tools.callFunction({clickFn},this);return false;"\x3e\x3cspan id\x3d"{id}_text" class\x3d"cke_combo_text cke_combo_inlinelabel"\x3e{label}\x3c/span\x3e\x3cspan class\x3d"cke_combo_open"\x3e\x3cspan class\x3d"cke_combo_arrow"\x3e'+(CKEDITOR.env.hc?"\x26#9660;":CKEDITOR.env.air?"\x26nbsp;":"")+"\x3c/span\x3e\x3c/span\x3e\x3c/a\x3e\x3c/span\x3e"),f=CKEDITOR.addTemplate("combo",a);CKEDITOR.UI_RICHCOMBO="richcombo";CKEDITOR.ui.richCombo= +CKEDITOR.tools.createClass({$:function(a){CKEDITOR.tools.extend(this,a,{canGroup:!1,title:a.label,modes:{wysiwyg:1},editorFocus:1});a=this.panel||{};delete this.panel;this.id=CKEDITOR.tools.getNextNumber();this.document=a.parent&&a.parent.getDocument()||CKEDITOR.document;a.className="cke_combopanel";a.block={multiSelect:a.multiSelect,attributes:a.attributes};a.toolbarRelated=!0;this._={panelDefinition:a,items:{},listeners:[]}},proto:{renderHtml:function(a){var c=[];this.render(a,c);return c.join("")}, +render:function(a,c){function d(){if(this.getState()!=CKEDITOR.TRISTATE_ON){var c=this.modes[a.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED;a.readOnly&&!this.readOnly&&(c=CKEDITOR.TRISTATE_DISABLED);this.setState(c);this.setValue("");c!=CKEDITOR.TRISTATE_DISABLED&&this.refresh&&this.refresh()}}var l=CKEDITOR.env,k="cke_"+this.id,g=CKEDITOR.tools.addFunction(function(c){q&&(a.unlockSelection(1),q=0);m.execute(c)},this),h=this,m={id:k,combo:this,focus:function(){CKEDITOR.document.getById(k).getChild(1).focus()}, +execute:function(c){var d=h._;if(d.state!=CKEDITOR.TRISTATE_DISABLED)if(h.createPanel(a),d.on)d.panel.hide();else{h.commit();var e=h.getValue();e?d.list.mark(e):d.list.unmarkAll();d.panel.showBlock(h.id,new CKEDITOR.dom.element(c),4)}},clickFn:g};this._.listeners.push(a.on("activeFilterChange",d,this));this._.listeners.push(a.on("mode",d,this));this._.listeners.push(a.on("selectionChange",d,this));!this.readOnly&&this._.listeners.push(a.on("readOnly",d,this));var e=CKEDITOR.tools.addFunction(function(a, +b){a=new CKEDITOR.dom.event(a);var c=a.getKeystroke();switch(c){case 13:case 32:case 40:CKEDITOR.tools.callFunction(g,b);break;default:m.onkey(m,c)}a.preventDefault()}),n=CKEDITOR.tools.addFunction(function(){m.onfocus&&m.onfocus()}),q=0;m.keyDownFn=e;l={id:k,name:this.name||this.command,label:this.label,title:this.title,cls:this.className||"",titleJs:l.gecko&&!l.hc?"":(this.title||"").replace("'",""),keydownFn:e,focusFn:n,clickFn:g};f.output(l,c);if(this.onRender)this.onRender();return m},createPanel:function(a){if(!this._.panel){var c= +this._.panelDefinition,d=this._.panelDefinition.block,f=c.parent||CKEDITOR.document.getBody(),k="cke_combopanel__"+this.name,g=new CKEDITOR.ui.floatPanel(a,f,c),c=g.addListBlock(this.id,d),h=this;g.onShow=function(){this.element.addClass(k);h.setState(CKEDITOR.TRISTATE_ON);h._.on=1;h.editorFocus&&!a.focusManager.hasFocus&&a.focus();if(h.onOpen)h.onOpen()};g.onHide=function(c){this.element.removeClass(k);h.setState(h.modes&&h.modes[a.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED);h._.on=0; +if(!c&&h.onClose)h.onClose()};g.onEscape=function(){g.hide(1)};c.onClick=function(a,b){h.onClick&&h.onClick.call(h,a,b);g.hide()};this._.panel=g;this._.list=c;g.getBlock(this.id).onHide=function(){h._.on=0;h.setState(CKEDITOR.TRISTATE_OFF)};this.init&&this.init()}},setValue:function(a,c){this._.value=a;var d=this.document.getById("cke_"+this.id+"_text");d&&(a||c?d.removeClass("cke_combo_inlinelabel"):(c=this.label,d.addClass("cke_combo_inlinelabel")),d.setText("undefined"!=typeof c?c:a))},getValue:function(){return this._.value|| +""},unmarkAll:function(){this._.list.unmarkAll()},mark:function(a){this._.list.mark(a)},hideItem:function(a){this._.list.hideItem(a)},hideGroup:function(a){this._.list.hideGroup(a)},showAll:function(){this._.list.showAll()},add:function(a,c,d){this._.items[a]=d||a;this._.list.add(a,c,d)},startGroup:function(a){this._.list.startGroup(a)},commit:function(){this._.committed||(this._.list.commit(),this._.committed=1,CKEDITOR.ui.fire("ready",this));this._.committed=1},setState:function(a){if(this._.state!= +a){var c=this.document.getById("cke_"+this.id);c.setState(a,"cke_combo");a==CKEDITOR.TRISTATE_DISABLED?c.setAttribute("aria-disabled",!0):c.removeAttribute("aria-disabled");this._.state=a}},getState:function(){return this._.state},enable:function(){this._.state==CKEDITOR.TRISTATE_DISABLED&&this.setState(this._.lastState)},disable:function(){this._.state!=CKEDITOR.TRISTATE_DISABLED&&(this._.lastState=this._.state,this.setState(CKEDITOR.TRISTATE_DISABLED))},destroy:function(){CKEDITOR.tools.array.forEach(this._.listeners, +function(a){a.removeListener()});this._.listeners=[]}},statics:{handler:{create:function(a){return new CKEDITOR.ui.richCombo(a)}}}});CKEDITOR.ui.prototype.addRichCombo=function(a,c){this.add(a,CKEDITOR.UI_RICHCOMBO,c)}}(),CKEDITOR.plugins.add("format",{requires:"richcombo",init:function(a){if(!a.blockless){for(var f=a.config,b=a.lang.format,c=f.format_tags.split(";"),d={},l=0,k=[],g=0;gb&&aI.version?" ":R,g=a.hotNode&&a.hotNode.getText()==d&&a.element.equals(a.hotNode)&&a.lastCmdDirection===!!c;h(a,function(d){g&&a.hotNode&&a.hotNode.remove();d[c?"insertAfter":"insertBefore"](b);d.setAttributes({"data-cke-magicline-hot":1, +"data-cke-magicline-dir":!!c});a.lastCmdDirection=!!c});I.ie||a.enterMode==CKEDITOR.ENTER_BR||a.hotNode.scrollIntoView();a.line.detach()}return function(g){g=g.getSelection().getStartElement();var f;g=g.getAscendant(U,1);if(!p(a,g)&&g&&!g.equals(a.editable)&&!g.contains(a.editable)){(f=k(g))&&"false"==f.getAttribute("contenteditable")&&(g=f);a.element=g;f=d(a,g,!c);var h;n(f)&&f.is(a.triggers)&&f.is(X)&&(!d(a,f,!c)||(h=d(a,f,!c))&&n(h)&&h.is(a.triggers))?e(f):(h=b(a,g),n(h)&&(d(a,h,!c)?(g=d(a,h,!c))&& +n(g)&&g.is(a.triggers)&&e(h):e(h)))}}}()}}function e(a,b){if(!b||b.type!=CKEDITOR.NODE_ELEMENT||!b.$)return!1;var c=a.line;return c.wrap.equals(b)||c.wrap.contains(b)}function n(a){return a&&a.type==CKEDITOR.NODE_ELEMENT&&a.$}function q(a){if(!n(a))return!1;var b;(b=y(a))||(n(a)?(b={left:1,right:1,center:1},b=!(!b[a.getComputedStyle("float")]&&!b[a.getAttribute("align")])):b=!1);return b}function y(a){return!!{absolute:1,fixed:1}[a.getComputedStyle("position")]}function u(a,b){return n(b)?b.is(a.triggers): +null}function p(a,b){if(!b)return!1;for(var c=b.getParents(1),d=c.length;d--;)for(var e=a.tabuList.length;e--;)if(c[d].hasAttribute(a.tabuList[e]))return!0;return!1}function v(a,b,c){b=b[c?"getLast":"getFirst"](function(b){return a.isRelevant(b)&&!b.is(ha)});if(!b)return!1;t(a,b);return c?b.size.top>a.mouse.y:b.size.bottom(a.inInlineMode?d.editable.top+d.editable.height/2:Math.min(d.editable.height,d.pane.height)/ +2),b=b[h?"getLast":"getFirst"](function(a){return!(P(a)||S(a))});if(!b)return null;e(a,b)&&(b=a.line.wrap[h?"getPrevious":"getNext"](function(a){return!(P(a)||S(a))}));if(!n(b)||q(b)||!u(a,b))return null;t(a,b);return!h&&0<=b.size.top&&l(c.y,0,b.size.top+g)?(a=a.inInlineMode||0===d.scroll.y?O:W,new f([null,b,G,Q,a])):h&&b.size.bottom<=d.pane.height&&l(c.y,b.size.bottom-g,d.pane.height)?(a=a.inInlineMode||l(b.size.bottom,d.pane.height-g,d.pane.height)?K:W,new f([b,null,D,Q,a])):null}function r(a){var c= +a.mouse,e=a.view,g=a.triggerOffset,h=b(a);if(!h)return null;t(a,h);var g=Math.min(g,0|h.size.outerHeight/2),k=[],m,r;if(l(c.y,h.size.top-1,h.size.top+g))r=!1;else if(l(c.y,h.size.bottom-g,h.size.bottom+1))r=!0;else return null;if(q(h)||v(a,h,r)||h.getParent().is(Z))return null;var M=d(a,h,!r);if(M){if(M&&M.type==CKEDITOR.NODE_TEXT)return null;if(n(M)){if(q(M)||!u(a,M)||M.getParent().is(Z))return null;k=[M,h][r?"reverse":"concat"]().concat([N,Q])}}else h.equals(a.editable[r?"getLast":"getFirst"](a.isRelevant))? +(x(a),r&&l(c.y,h.size.bottom-g,e.pane.height)&&l(h.size.bottom,e.pane.height-g,e.pane.height)?m=K:l(c.y,0,h.size.top+g)&&(m=O)):m=W,k=[null,h][r?"reverse":"concat"]().concat([r?D:G,Q,m,h.equals(a.editable[r?"getLast":"getFirst"](a.isRelevant))?r?K:O:W]);return 0 in k?new f(k):null}function z(a,b,c,d){for(var e=b.getDocumentPosition(),g={},f={},h={},k={},l=Y.length;l--;)g[Y[l]]=parseInt(b.getComputedStyle.call(b,"border-"+Y[l]+"-width"),10)||0,h[Y[l]]=parseInt(b.getComputedStyle.call(b,"padding-"+ +Y[l]),10)||0,f[Y[l]]=parseInt(b.getComputedStyle.call(b,"margin-"+Y[l]),10)||0;c&&!d||A(a,d);k.top=e.y-(c?0:a.view.scroll.y);k.left=e.x-(c?0:a.view.scroll.x);k.outerWidth=b.$.offsetWidth;k.outerHeight=b.$.offsetHeight;k.height=k.outerHeight-(h.top+h.bottom+g.top+g.bottom);k.width=k.outerWidth-(h.left+h.right+g.left+g.right);k.bottom=k.top+k.outerHeight;k.right=k.left+k.outerWidth;a.inInlineMode&&(k.scroll={top:b.$.scrollTop,left:b.$.scrollLeft});return C({border:g,padding:h,margin:f,ignoreScroll:c}, +k,!0)}function t(a,b,c){if(!n(b))return b.size=null;if(!b.size)b.size={};else if(b.size.ignoreScroll==c&&b.size.date>new Date-ba)return null;return C(b.size,z(a,b,c),{date:+new Date},!0)}function x(a,b){a.view.editable=z(a,a.editable,b,!0)}function A(a,b){a.view||(a.view={});var c=a.view;if(!(!b&&c&&c.date>new Date-ba)){var d=a.win,c=d.getScrollPosition(),d=d.getViewPaneSize();C(a.view,{scroll:{x:c.x,y:c.y,width:a.doc.$.documentElement.scrollWidth-d.width,height:a.doc.$.documentElement.scrollHeight- +d.height},pane:{width:d.width,height:d.height,bottom:d.height+c.y},date:+new Date},!0)}}function B(a,b,c,d){for(var e=d,g=d,h=0,k=!1,l=!1,m=a.view.pane.height,n=a.mouse;n.y+hd.left-e.x&&cd.top-e.y&&bCKEDITOR.env.version,E=CKEDITOR.dtd,L={},G=128,D=64,N=32,Q=16, +O=4,K=2,W=1,R=" ",Z=E.$listItem,ha=E.$tableContent,X=C({},E.$nonEditable,E.$empty),U=E.$block,ba=100,ca="width:0px;height:0px;padding:0px;margin:0px;display:block;z-index:9999;color:#fff;position:absolute;font-size: 0px;line-height:0px;",V=ca+"border-color:transparent;display:block;border-style:solid;",M="\x3cspan\x3e"+R+"\x3c/span\x3e";L[CKEDITOR.ENTER_BR]="br";L[CKEDITOR.ENTER_P]="p";L[CKEDITOR.ENTER_DIV]="div";f.prototype={set:function(a,b,c){this.properties=a+b+(c||W);return this},is:function(a){return(this.properties& +a)==a}};var aa=function(){function a(b,c){var d=b.$.elementFromPoint(c.x,c.y);return d&&d.nodeType?new CKEDITOR.dom.element(d):null}return function(b,c,d){if(!b.mouse)return null;var g=b.doc,f=b.line.wrap;d=d||b.mouse;var h=a(g,d);c&&e(b,h)&&(f.hide(),h=a(g,d),f.show());return!h||h.type!=CKEDITOR.NODE_ELEMENT||!h.$||I.ie&&9>I.version&&!b.boundary.equals(h)&&!b.boundary.contains(h)?null:h}}(),P=CKEDITOR.dom.walker.whitespaces(),S=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_COMMENT),T=function(){function b(e){var g= +e.element,f,h,k;if(!n(g)||g.contains(e.editable)||g.isReadOnly())return null;k=B(e,function(a,b){return!b.equals(a)},function(a,b){return aa(a,!0,b)},g);f=k.upper;h=k.lower;if(a(e,f,h))return k.set(N,8);if(f&&g.contains(f))for(;!f.getParent().equals(g);)f=f.getParent();else f=g.getFirst(function(a){return d(e,a)});if(h&&g.contains(h))for(;!h.getParent().equals(g);)h=h.getParent();else h=g.getLast(function(a){return d(e,a)});if(!f||!h)return null;t(e,f);t(e,h);if(!l(e.mouse.y,f.size.top,h.size.bottom))return null; +for(var g=Number.MAX_VALUE,m,r,M,q;h&&!h.equals(f)&&(r=f.getNext(e.isRelevant));)m=Math.abs(c(e,f,r)-e.mouse.y),m|<\/font>)/,l=/h.width&&(c.resize_minWidth=h.width);c.resize_minHeight> +h.height&&(c.resize_minHeight=h.height);CKEDITOR.document.on("mousemove",f);CKEDITOR.document.on("mouseup",b);a.document&&(a.document.on("mousemove",f),a.document.on("mouseup",b));d.preventDefault&&d.preventDefault()});a.on("destroy",function(){CKEDITOR.tools.removeFunction(n)});a.on("uiSpace",function(b){if("bottom"==b.data.space){var c="";m&&!e&&(c=" cke_resizer_horizontal");!m&&e&&(c=" cke_resizer_vertical");var g='\x3cspan id\x3d"'+d+'" class\x3d"cke_resizer'+c+" cke_resizer_"+l+'" title\x3d"'+ +CKEDITOR.tools.htmlEncode(a.lang.common.resize)+'" onmousedown\x3d"CKEDITOR.tools.callFunction('+n+', event)"\x3e'+("ltr"==l?"◢":"◣")+"\x3c/span\x3e";"ltr"==l&&"ltr"==c?b.data.html+=g:b.data.html=g+b.data.html}},a,null,100);a.on("maximize",function(b){a.ui.space("resizer")[b.data==CKEDITOR.TRISTATE_ON?"hide":"show"]()})}}}),CKEDITOR.plugins.add("menubutton",{requires:"button,menu",onLoad:function(){var a=function(a){var b=this._,c=b.menu;b.state!==CKEDITOR.TRISTATE_DISABLED&&(b.on&&c?c.hide():(b.previousState= +b.state,c||(c=b.menu=new CKEDITOR.menu(a,{panel:{className:"cke_menu_panel",attributes:{"aria-label":a.lang.common.options}}}),c.onHide=CKEDITOR.tools.bind(function(){var c=this.command?a.getCommand(this.command).modes:this.modes;this.setState(!c||c[a.mode]?b.previousState:CKEDITOR.TRISTATE_DISABLED);b.on=0},this),this.onMenu&&c.addListener(this.onMenu)),this.setState(CKEDITOR.TRISTATE_ON),b.on=1,setTimeout(function(){c.show(CKEDITOR.document.getById(b.id),4)},0)))};CKEDITOR.ui.menuButton=CKEDITOR.tools.createClass({base:CKEDITOR.ui.button, +$:function(f){delete f.panel;this.base(f);this.hasArrow="menu";this.click=a},statics:{handler:{create:function(a){return new CKEDITOR.ui.menuButton(a)}}}})},beforeInit:function(a){a.ui.addHandler(CKEDITOR.UI_MENUBUTTON,CKEDITOR.ui.menuButton.handler)}}),CKEDITOR.UI_MENUBUTTON="menubutton","use strict",CKEDITOR.plugins.add("scayt",{requires:"menubutton,dialog",tabToOpen:null,dialogName:"scaytDialog",onLoad:function(a){"moono-lisa"==(CKEDITOR.skinName||a.config.skin)&&CKEDITOR.document.appendStyleSheet(CKEDITOR.getUrl(this.path+ +"skins/"+CKEDITOR.skin.name+"/scayt.css"));CKEDITOR.document.appendStyleSheet(CKEDITOR.getUrl(this.path+"dialogs/dialog.css"))},init:function(a){var f=this,b=CKEDITOR.plugins.scayt;this.bindEvents(a);this.parseConfig(a);this.addRule(a);CKEDITOR.dialog.add(this.dialogName,CKEDITOR.getUrl(this.path+"dialogs/options.js"));this.addMenuItems(a);var c=a.lang.scayt,d=CKEDITOR.env;a.ui.add("Scayt",CKEDITOR.UI_MENUBUTTON,{label:c.text_title,title:a.plugins.wsc?a.lang.wsc.title:c.text_title,modes:{wysiwyg:!(d.ie&& +(8>d.version||d.quirks))},toolbar:"spellchecker,20",refresh:function(){var c=a.ui.instances.Scayt.getState();a.scayt&&(c=b.state.scayt[a.name]?CKEDITOR.TRISTATE_ON:CKEDITOR.TRISTATE_OFF);a.fire("scaytButtonState",c)},onRender:function(){var b=this;a.on("scaytButtonState",function(a){void 0!==typeof a.data&&b.setState(a.data)})},onMenu:function(){var c=a.scayt;a.getMenuItem("scaytToggle").label=a.lang.scayt[c&&b.state.scayt[a.name]?"btn_disable":"btn_enable"];var d={scaytToggle:CKEDITOR.TRISTATE_OFF, +scaytOptions:c?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,scaytLangs:c?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,scaytDict:c?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,scaytAbout:c?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,WSC:a.plugins.wsc?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED};a.config.scayt_uiTabs[0]||delete d.scaytOptions;a.config.scayt_uiTabs[1]||delete d.scaytLangs;a.config.scayt_uiTabs[2]||delete d.scaytDict;c&&!CKEDITOR.plugins.scayt.isNewUdSupported(c)&& +(delete d.scaytDict,a.config.scayt_uiTabs[2]=0,CKEDITOR.plugins.scayt.alarmCompatibilityMessage());return d}});a.contextMenu&&a.addMenuItems&&(a.contextMenu.addListener(function(b,c){var d=a.scayt,h,m;d&&(m=d.getSelectionNode())&&(h=f.menuGenerator(a,m),d.showBanner("."+a.contextMenu._.definition.panel.className.split(" ").join(" .")));return h}),a.contextMenu._.onHide=CKEDITOR.tools.override(a.contextMenu._.onHide,function(b){return function(){var c=a.scayt;c&&c.hideBanner();return b.apply(this)}}))}, +addMenuItems:function(a){var f=this,b=CKEDITOR.plugins.scayt;a.addMenuGroup("scaytButton");for(var c=a.config.scayt_contextMenuItemsOrder.split("|"),d=0;da.config.scayt_maxSuggestions)a.config.scayt_maxSuggestions=3;if(void 0===a.config.scayt_minWordLength||"number"!=typeof a.config.scayt_minWordLength||1>a.config.scayt_minWordLength)a.config.scayt_minWordLength=3;if(void 0===a.config.scayt_customDictionaryIds||"string"!==typeof a.config.scayt_customDictionaryIds)a.config.scayt_customDictionaryIds="";if(void 0=== +a.config.scayt_userDictionaryName||"string"!==typeof a.config.scayt_userDictionaryName)a.config.scayt_userDictionaryName=null;if("string"===typeof a.config.scayt_uiTabs&&3===a.config.scayt_uiTabs.split(",").length){var b=[],c=[];a.config.scayt_uiTabs=a.config.scayt_uiTabs.split(",");CKEDITOR.tools.search(a.config.scayt_uiTabs,function(a){1===Number(a)||0===Number(a)?(c.push(!0),b.push(Number(a))):c.push(!1)});null===CKEDITOR.tools.search(c,!1)?a.config.scayt_uiTabs=b:a.config.scayt_uiTabs=[1,1,1]}else a.config.scayt_uiTabs= +[1,1,1];"string"!=typeof a.config.scayt_serviceProtocol&&(a.config.scayt_serviceProtocol=null);"string"!=typeof a.config.scayt_serviceHost&&(a.config.scayt_serviceHost=null);"string"!=typeof a.config.scayt_servicePort&&(a.config.scayt_servicePort=null);"string"!=typeof a.config.scayt_servicePath&&(a.config.scayt_servicePath=null);a.config.scayt_moreSuggestions||(a.config.scayt_moreSuggestions="on");"string"!==typeof a.config.scayt_customerId&&(a.config.scayt_customerId="1:WvF0D4-UtPqN1-43nkD4-NKvUm2-daQqk3-LmNiI-z7Ysb4-mwry24-T8YrS3-Q2tpq2"); +"string"!==typeof a.config.scayt_customPunctuation&&(a.config.scayt_customPunctuation="-");"string"!==typeof a.config.scayt_srcUrl&&(f=document.location.protocol,f=-1!=f.search(/https?:/)?f:"http:",a.config.scayt_srcUrl=f+"//svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js");"boolean"!==typeof CKEDITOR.config.scayt_handleCheckDirty&&(CKEDITOR.config.scayt_handleCheckDirty=!0);"boolean"!==typeof CKEDITOR.config.scayt_handleUndoRedo&&(CKEDITOR.config.scayt_handleUndoRedo=!0);CKEDITOR.config.scayt_handleUndoRedo= +CKEDITOR.plugins.undo?CKEDITOR.config.scayt_handleUndoRedo:!1;"boolean"!==typeof a.config.scayt_multiLanguageMode&&(a.config.scayt_multiLanguageMode=!1);"object"!==typeof a.config.scayt_multiLanguageStyles&&(a.config.scayt_multiLanguageStyles={});a.config.scayt_ignoreAllCapsWords&&"boolean"!==typeof a.config.scayt_ignoreAllCapsWords&&(a.config.scayt_ignoreAllCapsWords=!1);a.config.scayt_ignoreDomainNames&&"boolean"!==typeof a.config.scayt_ignoreDomainNames&&(a.config.scayt_ignoreDomainNames=!1);a.config.scayt_ignoreWordsWithMixedCases&& +"boolean"!==typeof a.config.scayt_ignoreWordsWithMixedCases&&(a.config.scayt_ignoreWordsWithMixedCases=!1);a.config.scayt_ignoreWordsWithNumbers&&"boolean"!==typeof a.config.scayt_ignoreWordsWithNumbers&&(a.config.scayt_ignoreWordsWithNumbers=!1);if(a.config.scayt_disableOptionsStorage){var f=CKEDITOR.tools.isArray(a.config.scayt_disableOptionsStorage)?a.config.scayt_disableOptionsStorage:"string"===typeof a.config.scayt_disableOptionsStorage?[a.config.scayt_disableOptionsStorage]:void 0,d="all options lang ignore-all-caps-words ignore-domain-names ignore-words-with-mixed-cases ignore-words-with-numbers".split(" "), +l=["lang","ignore-all-caps-words","ignore-domain-names","ignore-words-with-mixed-cases","ignore-words-with-numbers"],k=CKEDITOR.tools.search,g=CKEDITOR.tools.indexOf;a.config.scayt_disableOptionsStorage=function(a){for(var b=[],c=0;ca.config.scayt_cacheSize)a.config.scayt_cacheSize=4E3},addRule:function(a){var f=CKEDITOR.plugins.scayt,b=a.dataProcessor,c=b&&b.htmlFilter,d=a._.elementsPath&&a._.elementsPath.filters,b=b&&b.dataFilter,l=a.addRemoveFormatFilter,k=function(b){if(a.scayt&&(b.hasAttribute(f.options.data_attribute_name)||b.hasAttribute(f.options.problem_grammar_data_attribute)))return!1},g=function(b){var c= +!0;a.scayt&&(b.hasAttribute(f.options.data_attribute_name)||b.hasAttribute(f.options.problem_grammar_data_attribute))&&(c=!1);return c};d&&d.push(k);b&&b.addRules({elements:{span:function(a){var b=a.hasClass(f.options.misspelled_word_class)&&a.attributes[f.options.data_attribute_name],c=a.hasClass(f.options.problem_grammar_class)&&a.attributes[f.options.problem_grammar_data_attribute];f&&(b||c)&&delete a.name;return a}}});c&&c.addRules({elements:{span:function(a){var b=a.hasClass(f.options.misspelled_word_class)&& +a.attributes[f.options.data_attribute_name],c=a.hasClass(f.options.problem_grammar_class)&&a.attributes[f.options.problem_grammar_data_attribute];f&&(b||c)&&delete a.name;return a}}});l&&l.call(a,g)},scaytMenuDefinition:function(a){var f=this,b=CKEDITOR.plugins.scayt;a=a.scayt;return{scayt:{scayt_ignore:{label:a.getLocal("btn_ignore"),group:"scayt_control",order:1,exec:function(a){a.scayt.ignoreWord()}},scayt_ignoreall:{label:a.getLocal("btn_ignoreAll"),group:"scayt_control",order:2,exec:function(a){a.scayt.ignoreAllWords()}}, +scayt_add:{label:a.getLocal("btn_addWord"),group:"scayt_control",order:3,exec:function(a){var b=a.scayt;setTimeout(function(){b.addWordToUserDictionary()},10)}},scayt_option:{label:a.getLocal("btn_options"),group:"scayt_control",order:4,exec:function(a){a.scayt.tabToOpen="options";b.openDialog(f.dialogName,a)},verification:function(a){return 1==a.config.scayt_uiTabs[0]?!0:!1}},scayt_language:{label:a.getLocal("btn_langs"),group:"scayt_control",order:5,exec:function(a){a.scayt.tabToOpen="langs";b.openDialog(f.dialogName, +a)},verification:function(a){return 1==a.config.scayt_uiTabs[1]?!0:!1}},scayt_dictionary:{label:a.getLocal("btn_dictionaries"),group:"scayt_control",order:6,exec:function(a){a.scayt.tabToOpen="dictionaries";b.openDialog(f.dialogName,a)},verification:function(a){return 1==a.config.scayt_uiTabs[2]?!0:!1}},scayt_about:{label:a.getLocal("btn_about"),group:"scayt_control",order:7,exec:function(a){a.scayt.tabToOpen="about";b.openDialog(f.dialogName,a)}}},grayt:{grayt_problemdescription:{label:"Grammar problem description", +group:"grayt_description",order:1,state:CKEDITOR.TRISTATE_DISABLED,exec:function(a){}},grayt_ignore:{label:a.getLocal("btn_ignore"),group:"grayt_control",order:2,exec:function(a){a.scayt.ignorePhrase()}},grayt_ignoreall:{label:a.getLocal("btn_ignoreAll"),group:"grayt_control",order:3,exec:function(a){a.scayt.ignoreAllPhrases()}}}}},buildSuggestionMenuItems:function(a,f,b){var c={},d={},l=b?"word":"phrase",k=b?"startGrammarCheck":"startSpellCheck",g=a.scayt;if(0=parseInt(f)*Math.pow(10,b)}return f?Array(7).join(String.fromCharCode(8203)):String.fromCharCode(8203)}()}],state:{scayt:{},grayt:{}},warningCounter:0,suggestions:[],options:{disablingCommandExec:{source:!0,newpage:!0,templates:!0},data_attribute_name:"data-scayt-word",misspelled_word_class:"scayt-misspell-word",problem_grammar_data_attribute:"data-grayt-phrase",problem_grammar_class:"gramm-problem"},backCompatibilityMap:{scayt_service_protocol:"scayt_serviceProtocol",scayt_service_host:"scayt_serviceHost", +scayt_service_port:"scayt_servicePort",scayt_service_path:"scayt_servicePath",scayt_customerid:"scayt_customerId"},openDialog:function(a,f){var b=f.scayt;b.isAllModulesReady&&!1===b.isAllModulesReady()||(f.lockSelection(),f.openDialog(a))},alarmCompatibilityMessage:function(){5>this.warningCounter&&(console.warn("You are using the latest version of SCAYT plugin for CKEditor with the old application version. In order to have access to the newest features, it is recommended to upgrade the application version to latest one as well. Contact us for more details at support@webspellchecker.net."), +this.warningCounter+=1)},isNewUdSupported:function(a){return a.getUserDictionary?!0:!1},reloadMarkup:function(a){var f;a&&(f=a.getScaytLangList(),a.reloadMarkup?a.reloadMarkup():(this.alarmCompatibilityMessage(),f&&f.ltr&&f.rtl&&a.fire("startSpellCheck, startGrammarCheck")))},replaceOldOptionsNames:function(a){for(var f in a)f in this.backCompatibilityMap&&(a[this.backCompatibilityMap[f]]=a[f],delete a[f])},createScayt:function(a){var f=this,b=CKEDITOR.plugins.scayt;this.loadScaytLibrary(a,function(a){function d(a){return new SCAYT.CKSCAYT(a, +function(){},function(){})}var l;a.window&&(l="BODY"==a.editable().$.nodeName?a.window.getFrame():a.editable());if(l){l={lang:a.config.scayt_sLang,container:l.$,customDictionary:a.config.scayt_customDictionaryIds,userDictionaryName:a.config.scayt_userDictionaryName,localization:a.langCode,customer_id:a.config.scayt_customerId,customPunctuation:a.config.scayt_customPunctuation,debug:a.config.scayt_debug,data_attribute_name:f.options.data_attribute_name,misspelled_word_class:f.options.misspelled_word_class, +problem_grammar_data_attribute:f.options.problem_grammar_data_attribute,problem_grammar_class:f.options.problem_grammar_class,"options-to-restore":a.config.scayt_disableOptionsStorage,focused:a.editable().hasFocus,ignoreElementsRegex:a.config.scayt_elementsToIgnore,ignoreGraytElementsRegex:a.config.grayt_elementsToIgnore,minWordLength:a.config.scayt_minWordLength,multiLanguageMode:a.config.scayt_multiLanguageMode,multiLanguageStyles:a.config.scayt_multiLanguageStyles,graytAutoStartup:a.config.grayt_autoStartup, +disableCache:a.config.scayt_disableCache,cacheSize:a.config.scayt_cacheSize,charsToObserve:b.charsToObserve};a.config.scayt_serviceProtocol&&(l.service_protocol=a.config.scayt_serviceProtocol);a.config.scayt_serviceHost&&(l.service_host=a.config.scayt_serviceHost);a.config.scayt_servicePort&&(l.service_port=a.config.scayt_servicePort);a.config.scayt_servicePath&&(l.service_path=a.config.scayt_servicePath);"boolean"===typeof a.config.scayt_ignoreAllCapsWords&&(l["ignore-all-caps-words"]=a.config.scayt_ignoreAllCapsWords); +"boolean"===typeof a.config.scayt_ignoreDomainNames&&(l["ignore-domain-names"]=a.config.scayt_ignoreDomainNames);"boolean"===typeof a.config.scayt_ignoreWordsWithMixedCases&&(l["ignore-words-with-mixed-cases"]=a.config.scayt_ignoreWordsWithMixedCases);"boolean"===typeof a.config.scayt_ignoreWordsWithNumbers&&(l["ignore-words-with-numbers"]=a.config.scayt_ignoreWordsWithNumbers);var k;try{k=d(l)}catch(g){f.alarmCompatibilityMessage(),delete l.charsToObserve,k=d(l)}k.subscribe("suggestionListSend", +function(a){for(var b={},c=[],d=0;d=parseInt(a.getAttribute("border"),10))&&a.addClass("cke_show_border")})},afterInit:function(a){var b=a.dataProcessor;a=b&&b.dataFilter;b=b&&b.htmlFilter;a&&a.addRules({elements:{table:function(a){a=a.attributes;var b=a["class"],f=parseInt(a.border,10);f&&!(0>=f)||b&&-1!=b.indexOf("cke_show_border")||(a["class"]=(b||"")+" cke_show_border")}}});b&&b.addRules({elements:{table:function(a){a=a.attributes;var b=a["class"];b&&(a["class"]=b.replace("cke_show_border","").replace(/\s{2}/," ").replace(/^\s+|\s+$/, +""))}}})}});CKEDITOR.on("dialogDefinition",function(a){var b=a.data.name;if("table"==b||"tableProperties"==b)if(a=a.data.definition,b=a.getContents("info").get("txtBorder"),b.commit=CKEDITOR.tools.override(b.commit,function(a){return function(b,f){a.apply(this,arguments);var k=parseInt(this.getValue(),10);f[!k||0>=k?"addClass":"removeClass"]("cke_show_border")}}),a=(a=a.getContents("advanced"))&&a.get("advCSSClasses"))a.setup=CKEDITOR.tools.override(a.setup,function(a){return function(){a.apply(this, +arguments);this.setValue(this.getValue().replace(/cke_show_border/,""))}}),a.commit=CKEDITOR.tools.override(a.commit,function(a){return function(b,f){a.apply(this,arguments);parseInt(f.getAttribute("border"),10)||f.addClass("cke_show_border")}})})}(),function(){CKEDITOR.plugins.add("sourcearea",{init:function(f){function b(){var a=d&&this.equals(CKEDITOR.document.getActive());this.hide();this.setStyle("height",this.getParent().$.clientHeight+"px");this.setStyle("width",this.getParent().$.clientWidth+ +"px");this.show();a&&this.focus()}if(f.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE){var c=CKEDITOR.plugins.sourcearea;f.addMode("source",function(c){var d=f.ui.space("contents").getDocument().createElement("textarea");d.setStyles(CKEDITOR.tools.extend({width:CKEDITOR.env.ie7Compat?"99%":"100%",height:"100%",resize:"none",outline:"none","text-align":"left"},CKEDITOR.tools.cssVendorPrefix("tab-size",f.config.sourceAreaTabSize||4)));d.setAttribute("dir","ltr");d.addClass("cke_source").addClass("cke_reset").addClass("cke_enable_context_menu"); +f.ui.space("contents").append(d);d=f.editable(new a(f,d));d.setData(f.getData(1));CKEDITOR.env.ie&&(d.attachListener(f,"resize",b,d),d.attachListener(CKEDITOR.document.getWindow(),"resize",b,d),CKEDITOR.tools.setTimeout(b,0,d));f.fire("ariaWidget",this);c()});f.addCommand("source",c.commands.source);f.ui.addButton&&f.ui.addButton("Source",{label:f.lang.sourcearea.toolbar,command:"source",toolbar:"mode,10"});f.on("mode",function(){f.getCommand("source").setState("source"==f.mode?CKEDITOR.TRISTATE_ON: +CKEDITOR.TRISTATE_OFF)});var d=CKEDITOR.env.ie&&9==CKEDITOR.env.version}}});var a=CKEDITOR.tools.createClass({base:CKEDITOR.editable,proto:{setData:function(a){this.setValue(a);this.status="ready";this.editor.fire("dataReady")},getData:function(){return this.getValue()},insertHtml:function(){},insertElement:function(){},insertText:function(){},setReadOnly:function(a){this[(a?"set":"remove")+"Attribute"]("readOnly","readonly")},detach:function(){a.baseProto.detach.call(this);this.clearCustomData(); +this.remove()}}})}(),CKEDITOR.plugins.sourcearea={commands:{source:{modes:{wysiwyg:1,source:1},editorFocus:!1,readOnly:1,exec:function(a){"wysiwyg"==a.mode&&a.fire("saveSnapshot");a.getCommand("source").setState(CKEDITOR.TRISTATE_DISABLED);a.setMode("source"==a.mode?"wysiwyg":"source")},canUndo:!1}}},CKEDITOR.plugins.add("specialchar",{availableLangs:{af:1,ar:1,az:1,bg:1,ca:1,cs:1,cy:1,da:1,de:1,"de-ch":1,el:1,en:1,"en-au":1,"en-ca":1,"en-gb":1,eo:1,es:1,"es-mx":1,et:1,eu:1,fa:1,fi:1,fr:1,"fr-ca":1, +gl:1,he:1,hr:1,hu:1,id:1,it:1,ja:1,km:1,ko:1,ku:1,lt:1,lv:1,nb:1,nl:1,no:1,oc:1,pl:1,pt:1,"pt-br":1,ro:1,ru:1,si:1,sk:1,sl:1,sq:1,sr:1,"sr-latn":1,sv:1,th:1,tr:1,tt:1,ug:1,uk:1,vi:1,zh:1,"zh-cn":1},requires:"dialog",init:function(a){var f=this;CKEDITOR.dialog.add("specialchar",this.path+"dialogs/specialchar.js");a.addCommand("specialchar",{exec:function(){var b=a.langCode,b=f.availableLangs[b]?b:f.availableLangs[b.replace(/-.*/,"")]?b.replace(/-.*/,""):"en";CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(f.path+ +"dialogs/lang/"+b+".js"),function(){CKEDITOR.tools.extend(a.lang.specialchar,f.langEntries[b]);a.openDialog("specialchar")})},modes:{wysiwyg:1},canUndo:!1});a.ui.addButton&&a.ui.addButton("SpecialChar",{label:a.lang.specialchar.toolbar,command:"specialchar",toolbar:"insert,50"})}}),CKEDITOR.config.specialChars="! \x26quot; # $ % \x26amp; ' ( ) * + - . / 0 1 2 3 4 5 6 7 8 9 : ; \x26lt; \x3d \x26gt; ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ \x26euro; \x26lsquo; \x26rsquo; \x26ldquo; \x26rdquo; \x26ndash; \x26mdash; \x26iexcl; \x26cent; \x26pound; \x26curren; \x26yen; \x26brvbar; \x26sect; \x26uml; \x26copy; \x26ordf; \x26laquo; \x26not; \x26reg; \x26macr; \x26deg; \x26sup2; \x26sup3; \x26acute; \x26micro; \x26para; \x26middot; \x26cedil; \x26sup1; \x26ordm; \x26raquo; \x26frac14; \x26frac12; \x26frac34; \x26iquest; \x26Agrave; \x26Aacute; \x26Acirc; \x26Atilde; \x26Auml; \x26Aring; \x26AElig; \x26Ccedil; \x26Egrave; \x26Eacute; \x26Ecirc; \x26Euml; \x26Igrave; \x26Iacute; \x26Icirc; \x26Iuml; \x26ETH; \x26Ntilde; \x26Ograve; \x26Oacute; \x26Ocirc; \x26Otilde; \x26Ouml; \x26times; \x26Oslash; \x26Ugrave; \x26Uacute; \x26Ucirc; \x26Uuml; \x26Yacute; \x26THORN; \x26szlig; \x26agrave; \x26aacute; \x26acirc; \x26atilde; \x26auml; \x26aring; \x26aelig; \x26ccedil; \x26egrave; \x26eacute; \x26ecirc; \x26euml; \x26igrave; \x26iacute; \x26icirc; \x26iuml; \x26eth; \x26ntilde; \x26ograve; \x26oacute; \x26ocirc; \x26otilde; \x26ouml; \x26divide; \x26oslash; \x26ugrave; \x26uacute; \x26ucirc; \x26uuml; \x26yacute; \x26thorn; \x26yuml; \x26OElig; \x26oelig; \x26#372; \x26#374 \x26#373 \x26#375; \x26sbquo; \x26#8219; \x26bdquo; \x26hellip; \x26trade; \x26#9658; \x26bull; \x26rarr; \x26rArr; \x26hArr; \x26diams; \x26asymp;".split(" "), +function(){CKEDITOR.plugins.add("stylescombo",{requires:"richcombo",init:function(a){var f=a.config,b=a.lang.stylescombo,c={},d=[],l=[];a.on("stylesSet",function(b){if(b=b.data.styles){for(var g,h,m,e=0,n=b.length;e=b)for(g=this.getNextSourceNode(a,CKEDITOR.NODE_ELEMENT);g;){if(g.isVisible()&&0===g.getTabIndex()){l=g;break}g=g.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT)}else for(g=this.getDocument().getBody().getFirst();g=g.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT);){if(!c)if(!d&&g.equals(this)){if(d=!0,a){if(!(g=g.getNextSourceNode(!0,CKEDITOR.NODE_ELEMENT)))break;c=1}}else d&&!this.contains(g)&&(c=1);if(g.isVisible()&&!(0>(h=g.getTabIndex()))){if(c&&h==b){l= +g;break}h>b&&(!l||!k||h(g=h.getTabIndex())))if(0>=b){if(c&&0===g){l=h;break}g>k&& +(l=h,k=g)}else{if(c&&g==b){l=h;break}gk)&&(l=h,k=g)}}l&&l.focus()},CKEDITOR.plugins.add("table",{requires:"dialog",init:function(a){function f(a){return CKEDITOR.tools.extend(a||{},{contextSensitive:1,refresh:function(a,b){this.setState(b.contains("table",1)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)}})}if(!a.blockless){var b=a.lang.table;a.addCommand("table",new CKEDITOR.dialogCommand("table",{context:"table",allowedContent:"table{width,height,border-collapse}[align,border,cellpadding,cellspacing,summary];caption tbody thead tfoot;th td tr[scope];td{border*,background-color,vertical-align,width,height}[colspan,rowspan];"+ +(a.plugins.dialogadvtab?"table"+a.plugins.dialogadvtab.allowedContent():""),requiredContent:"table",contentTransformations:[["table{width}: sizeToStyle","table[width]: sizeToAttribute"],["td: splitBorderShorthand"],[{element:"table",right:function(a){if(a.styles){var b;if(a.styles.border)b=CKEDITOR.tools.style.parse.border(a.styles.border);else if(CKEDITOR.env.ie&&8===CKEDITOR.env.version){var f=a.styles;f["border-left"]&&f["border-left"]===f["border-right"]&&f["border-right"]===f["border-top"]&& +f["border-top"]===f["border-bottom"]&&(b=CKEDITOR.tools.style.parse.border(f["border-top"]))}b&&b.style&&"solid"===b.style&&b.width&&0!==parseFloat(b.width)&&(a.attributes.border=1);"collapse"==a.styles["border-collapse"]&&(a.attributes.cellspacing=0)}}}]]}));a.addCommand("tableProperties",new CKEDITOR.dialogCommand("tableProperties",f()));a.addCommand("tableDelete",f({exec:function(a){var b=a.elementPath().contains("table",1);if(b){var f=b.getParent(),k=a.editable();1!=f.getChildCount()||f.is("td", +"th")||f.equals(k)||(b=f);a=a.createRange();a.moveToPosition(b,CKEDITOR.POSITION_BEFORE_START);b.remove();a.select()}}}));a.ui.addButton&&a.ui.addButton("Table",{label:b.toolbar,command:"table",toolbar:"insert,30"});CKEDITOR.dialog.add("table",this.path+"dialogs/table.js");CKEDITOR.dialog.add("tableProperties",this.path+"dialogs/table.js");a.addMenuItems&&a.addMenuItems({table:{label:b.menu,command:"tableProperties",group:"table",order:5},tabledelete:{label:b.deleteTable,command:"tableDelete",group:"table", +order:1}});a.on("doubleclick",function(a){a.data.element.is("table")&&(a.data.dialog="tableProperties")});a.contextMenu&&a.contextMenu.addListener(function(){return{tabledelete:CKEDITOR.TRISTATE_OFF,table:CKEDITOR.TRISTATE_OFF}})}}}),function(){function a(a,b){function c(a){return b?b.contains(a)&&a.getAscendant("table",!0).equals(b):!0}function d(a){0d)d=g}return d}function l(b,c){for(var e=p(b)?b:a(b),g=e[0].getAscendant("table"),f=d(e,1),e=d(e),h=c?f:e,k=CKEDITOR.tools.buildTableMap(g),g=[],f=[],e=[],l=k.length,m=0;ml?new CKEDITOR.dom.element(h[0][l+1]):k&&-1!==h[0][k-1].cellIndex?new CKEDITOR.dom.element(h[0][k-1]):new CKEDITOR.dom.element(e.$.parentNode);m.length==b&&(d[0].moveToPosition(e,CKEDITOR.POSITION_AFTER_END),d[0].select(),e.remove());return k}function g(a,b){var c=a.getStartElement().getAscendant({td:1,th:1},!0);if(c){var d=c.clone();d.appendBogus();b?d.insertBefore(c):d.insertAfter(c)}}function h(b){if(b instanceof CKEDITOR.dom.selection){var c= +b.getRanges(),d=a(b),e=d[0]&&d[0].getAscendant("table"),g;a:{var f=0;g=d.length-1;for(var k={},l,n;l=d[f++];)CKEDITOR.dom.element.setMarker(k,l,"delete_cell",!0);for(f=0;l=d[f++];)if((n=l.getPrevious())&&!n.getCustomData("delete_cell")||(n=l.getNext())&&!n.getCustomData("delete_cell")){CKEDITOR.dom.element.clearAllMarkers(k);g=n;break a}CKEDITOR.dom.element.clearAllMarkers(k);f=d[0].getParent();(f=f.getPrevious())?g=f.getLast():(f=d[g].getParent(),g=(f=f.getNext())?f.getChild(0):null)}b.reset();for(b= +d.length-1;0<=b;b--)h(d[b]);g?m(g,!0):e&&(c[0].moveToPosition(e,CKEDITOR.POSITION_BEFORE_START),c[0].select(),e.remove())}else b instanceof CKEDITOR.dom.element&&(c=b.getParent(),1==c.getChildCount()?c.remove():b.remove())}function m(a,b){var c=a.getDocument(),d=CKEDITOR.document;CKEDITOR.env.ie&&10==CKEDITOR.env.version&&(d.focus(),c.focus());c=new CKEDITOR.dom.range(c);c["moveToElementEdit"+(b?"End":"Start")](a)||(c.selectNodeContents(a),c.collapse(b?!1:!0));c.select(!0)}function e(a,b,c){a=a[b]; +if("undefined"==typeof c)return a;for(b=0;a&&bg.length)||(f=b.getCommonAncestor())&&f.type==CKEDITOR.NODE_ELEMENT&&f.is("table"))return!1;var h;b=g[0];f=b.getAscendant("table");var k=CKEDITOR.tools.buildTableMap(f),l=k.length,m=k[0].length,n=b.getParent().$.rowIndex,q=e(k,n,b);if(c){var p;try{var u=parseInt(b.getAttribute("rowspan"),10)||1; +h=parseInt(b.getAttribute("colspan"),10)||1;p=k["up"==c?n-u:"down"==c?n+u:n]["left"==c?q-h:"right"==c?q+h:q]}catch(y){return!1}if(!p||b.$==p)return!1;g["up"==c||"left"==c?"unshift":"push"](new CKEDITOR.dom.element(p))}c=b.getDocument();var L=n,u=p=0,G=!d&&new CKEDITOR.dom.documentFragment(c),D=0;for(c=0;c=m?b.removeAttribute("rowSpan"):b.$.rowSpan=p;p>=l?b.removeAttribute("colSpan"):b.$.colSpan=u;d=new CKEDITOR.dom.nodeList(f.$.rows);g=d.count();for(c=g-1;0<=c;c--)f=d.getItem(c),f.$.cells.length||(f.remove(),g++);return b} +function q(b,c){var d=a(b);if(1l){g.insertBefore(new CKEDITOR.dom.element(q));break}else q=null;q||f.append(g)}else for(m=n=1,f=g.clone(),f.insertAfter(g),f.append(g=d.clone()), +q=e(h,k),l=0;lc);n++){k[l+n]||(k[l+n]=[]);for(var q=0;q=d)break}}return k},function(){function a(a){return CKEDITOR.plugins.widget&&CKEDITOR.plugins.widget.isDomWidget(a)}function f(a, +b){var c=a.getAscendant("table"),d=b.getAscendant("table"),e=CKEDITOR.tools.buildTableMap(c),g=m(a),f=m(b),h=[],k={},l,n;c.contains(d)&&(b=b.getAscendant({td:1,th:1}),f=m(b));g>f&&(c=g,g=f,f=c,c=a,a=b,b=c);for(c=0;cn&&(c=l,l=n,n=c);for(c=g;c<=f;c++)for(g=l;g<=n;g++)d=new CKEDITOR.dom.element(e[c][g]),d.$&&!d.getCustomData("selected_cell")&&(h.push(d),CKEDITOR.dom.element.setMarker(k,d,"selected_cell", +!0));CKEDITOR.dom.element.clearAllMarkers(k);return h}function b(a){if(a)return a=a.clone(),a.enlarge(CKEDITOR.ENLARGE_ELEMENT),(a=a.getEnclosedNode())&&a.is&&a.is(CKEDITOR.dtd.$tableContent)}function c(a){return(a=a.editable().findOne(".cke_table-faked-selection"))&&a.getAscendant("table")}function d(a,b){var c=a.editable().find(".cke_table-faked-selection"),d=a.editable().findOne("[data-cke-table-faked-selection-table]"),e;a.fire("lockSnapshot");a.editable().removeClass("cke_table-faked-selection-editor"); +for(e=0;eb.count()||(b=f(b.getItem(0),b.getItem(b.count()-1)), +l(a,b))}function g(b,c,e){var g=r(b.getSelection(!0));c=c.is("table")?null:c;var h;(h=v.active&&!v.first)&&!(h=c)&&(h=b.getSelection().getRanges(),h=1CKEDITOR.env.version,l=a.blockless||CKEDITOR.env.ie?"span":"div",m,n,p,q;g.getById("cke_table_copybin")||(m=g.createElement(l),n=g.createElement(l),n.setAttributes({id:"cke_table_copybin","data-cke-temp":"1"}),m.setStyles({position:"absolute",width:"1px", +height:"1px",overflow:"hidden"}),m.setStyle("ltr"==a.config.contentsLangDirection?"left":"right","-5000px"),m.setHtml(a.getSelectedHtml(!0)),a.fire("lockSnapshot"),n.append(m),a.editable().append(n),q=a.on("selectionChange",c,null,null,0),k&&(p=h.scrollTop),f.selectNodeContents(m),f.select(),k&&(h.scrollTop=p),setTimeout(function(){n.remove();d.selectBookmarks(e);q.removeListener();a.fire("unlockSnapshot");b&&(a.extractSelectedHtml(),a.fire("saveSnapshot"))},100))}function y(a){var b=a.editor||a.sender.editor, +c=b.getSelection();c.isInTable()&&(c.getRanges()[0]._getTableElement({table:1}).hasAttribute("data-cke-tableselection-ignored")||q(b,"cut"===a.name))}function u(a){this._reset();a&&this.setSelectedCells(a)}function p(a,b,c){a.on("beforeCommandExec",function(c){-1!==CKEDITOR.tools.array.indexOf(b,c.data.name)&&(c.data.selectedCells=r(a.getSelection()))});a.on("afterCommandExec",function(d){-1!==CKEDITOR.tools.array.indexOf(b,d.data.name)&&c(a,d.data)})}var v={active:!1},w,r,z,t,x;u.prototype={};u.prototype._reset= +function(){this.cells={first:null,last:null,all:[]};this.rows={first:null,last:null}};u.prototype.setSelectedCells=function(a){this._reset();a=a.slice(0);this._arraySortByDOMOrder(a);this.cells.all=a;this.cells.first=a[0];this.cells.last=a[a.length-1];this.rows.first=a[0].getAscendant("tr");this.rows.last=this.cells.last.getAscendant("tr")};u.prototype.getTableMap=function(){var a=z(this.cells.first),b;a:{b=this.cells.last;var c=b.getAscendant("table"),d=m(b),c=CKEDITOR.tools.buildTableMap(c),e;for(e= +0;e=a)return;for(var d=this.cells.first.$.cellIndex,e=this.cells.last.$.cellIndex,g=c?[]:this.cells.all,f,h=0;h=d&&a.$.cellIndex<=e}),g=b?f.concat(g):g.concat(f);this.setSelectedCells(g)};u.prototype.insertColumn=function(a){function b(a){a=m(a);return a>=e&&a<=g}if("undefined"===typeof a)a=1;else if(0>=a)return;for(var c=this.cells,d=c.all,e=m(c.first),g=m(c.last),c=0;cg)l[0].moveToElementEditablePosition(k?m:n,!k),h.selectRanges([l[0]]); +else if(13!==g||13===f||f===CKEDITOR.SHIFT+13){for(e=0;eCKEDITOR.env.version)},onLoad:function(){w=CKEDITOR.plugins.tabletools;r=w.getSelectedCells;z=w.getCellColIndex;t=w.insertRow;x=w.insertColumn; +CKEDITOR.document.appendStyleSheet(this.path+"styles/tableselection.css")},init:function(a){this.isSupportedEnvironment()&&(a.addContentsCss&&a.addContentsCss(this.path+"styles/tableselection.css"),a.on("contentDom",function(){var b=a.editable(),c=b.isInline()?b:a.document,d={editor:a};b.attachListener(c,"mousedown",e,null,d);b.attachListener(c,"mousemove",e,null,d);b.attachListener(c,"mouseup",e,null,d);b.attachListener(b,"dragstart",n);b.attachListener(a,"selectionCheck",h);CKEDITOR.plugins.tableselection.keyboardIntegration(a); +CKEDITOR.plugins.clipboard&&!CKEDITOR.plugins.clipboard.isCustomCopyCutSupported&&(b.attachListener(b,"cut",y),b.attachListener(b,"copy",y))}),a.on("paste",A.onPaste,A),p(a,"rowInsertBefore rowInsertAfter columnInsertBefore columnInsertAfter cellInsertBefore cellInsertAfter".split(" "),function(a,b){l(a,b.selectedCells)}),p(a,["cellMerge","cellMergeRight","cellMergeDown"],function(a,b){l(a,[b.commandData.cell])}),p(a,["cellDelete"],function(a){d(a,!0)}))}})}(),"use strict",function(){var a=[CKEDITOR.CTRL+ +90,CKEDITOR.CTRL+89,CKEDITOR.CTRL+CKEDITOR.SHIFT+90],f={8:1,46:1};CKEDITOR.plugins.add("undo",{init:function(c){function d(a){e.enabled&&!1!==a.data.command.canUndo&&e.save()}function f(){e.enabled=c.readOnly?!1:"wysiwyg"==c.mode;e.onChange()}var e=c.undoManager=new b(c),k=e.editingHandler=new l(e),q=c.addCommand("undo",{exec:function(){e.undo()&&(c.selectionChange(),this.fire("afterUndo"))},startDisabled:!0,canUndo:!1}),y=c.addCommand("redo",{exec:function(){e.redo()&&(c.selectionChange(),this.fire("afterRedo"))}, +startDisabled:!0,canUndo:!1});c.setKeystroke([[a[0],"undo"],[a[1],"redo"],[a[2],"redo"]]);e.onChange=function(){q.setState(e.undoable()?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED);y.setState(e.redoable()?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)};c.on("beforeCommandExec",d);c.on("afterCommandExec",d);c.on("saveSnapshot",function(a){e.save(a.data&&a.data.contentOnly)});c.on("contentDom",k.attachListeners,k);c.on("instanceReady",function(){c.fire("saveSnapshot")});c.on("beforeModeUnload", +function(){"wysiwyg"==c.mode&&e.save(!0)});c.on("mode",f);c.on("readOnly",f);c.ui.addButton&&(c.ui.addButton("Undo",{label:c.lang.undo.undo,command:"undo",toolbar:"undo,10"}),c.ui.addButton("Redo",{label:c.lang.undo.redo,command:"redo",toolbar:"undo,20"}));c.resetUndo=function(){e.reset();c.fire("saveSnapshot")};c.on("updateSnapshot",function(){e.currentImage&&e.update()});c.on("lockSnapshot",function(a){a=a.data;e.lock(a&&a.dontUpdate,a&&a.forceUpdate)});c.on("unlockSnapshot",e.unlock,e)}});CKEDITOR.plugins.undo= +{};var b=CKEDITOR.plugins.undo.UndoManager=function(a){this.strokesRecorded=[0,0];this.locked=null;this.previousKeyGroup=-1;this.limit=a.config.undoStackSize||20;this.strokesLimit=25;this.editor=a;this.reset()};b.prototype={type:function(a,c){var d=b.getKeyGroup(a),e=this.strokesRecorded[d]+1;c=c||e>=this.strokesLimit;this.typing||(this.hasUndo=this.typing=!0,this.hasRedo=!1,this.onChange());c?(e=0,this.editor.fire("saveSnapshot")):this.editor.fire("change");this.strokesRecorded[d]=e;this.previousKeyGroup= +d},keyGroupChanged:function(a){return b.getKeyGroup(a)!=this.previousKeyGroup},reset:function(){this.snapshots=[];this.index=-1;this.currentImage=null;this.hasRedo=this.hasUndo=!1;this.locked=null;this.resetType()},resetType:function(){this.strokesRecorded=[0,0];this.typing=!1;this.previousKeyGroup=-1},refreshState:function(){this.hasUndo=!!this.getNextImage(!0);this.hasRedo=!!this.getNextImage(!1);this.resetType();this.onChange()},save:function(a,b,d){var e=this.editor;if(this.locked||"ready"!=e.status|| +"wysiwyg"!=e.mode)return!1;var f=e.editable();if(!f||"ready"!=f.status)return!1;f=this.snapshots;b||(b=new c(e));if(!1===b.contents)return!1;if(this.currentImage)if(b.equalsContent(this.currentImage)){if(a||b.equalsSelection(this.currentImage))return!1}else!1!==d&&e.fire("change");f.splice(this.index+1,f.length-this.index-1);f.length==this.limit&&f.shift();this.index=f.push(b)-1;this.currentImage=b;!1!==d&&this.refreshState();return!0},restoreImage:function(a){var b=this.editor,c;a.bookmarks&&(b.focus(), +c=b.getSelection());this.locked={level:999};this.editor.loadSnapshot(a.contents);a.bookmarks?c.selectBookmarks(a.bookmarks):CKEDITOR.env.ie&&(c=this.editor.document.getBody().$.createTextRange(),c.collapse(!0),c.select());this.locked=null;this.index=a.index;this.currentImage=this.snapshots[this.index];this.update();this.refreshState();b.fire("change")},getNextImage:function(a){var b=this.snapshots,c=this.currentImage,d;if(c)if(a)for(d=this.index-1;0<=d;d--){if(a=b[d],!c.equalsContent(a))return a.index= +d,a}else for(d=this.index+1;d=this.undoManager.strokesLimit&&(this.undoManager.type(a.keyCode,!0),this.keyEventsStack.resetInputs())}},onKeyup:function(a){var d=this.undoManager; +a=a.data.getKey();var f=this.keyEventsStack.getTotalInputs();this.keyEventsStack.remove(a);if(!(b.ieFunctionalKeysBug(a)&&this.lastKeydownImage&&this.lastKeydownImage.equalsContent(new c(d.editor,!0))))if(0=this.rect.right||a<=this.rect.top||a>=this.rect.bottom)&&this.hideVisible();(0>=b||b>=this.winTopPane.width||0>=a||a>=this.winTopPane.height)&&this.hideVisible()},this);c.attachListener(a,"resize",f);c.attachListener(a,"mode",k);a.on("destroy",k);this.lineTpl=(new CKEDITOR.template('\x3cdiv data-cke-lineutils-line\x3d"1" class\x3d"cke_reset_all" style\x3d"{lineStyle}"\x3e\x3cspan style\x3d"{tipLeftStyle}"\x3e\x26nbsp;\x3c/span\x3e\x3cspan style\x3d"{tipRightStyle}"\x3e\x26nbsp;\x3c/span\x3e\x3c/div\x3e')).output({lineStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({}, +l,this.lineStyle,!0)),tipLeftStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({},d,{left:"0px","border-left-color":"red","border-width":"6px 0 6px 6px"},this.tipCss,this.tipLeftStyle,!0)),tipRightStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({},d,{right:"0px","border-right-color":"red","border-width":"6px 6px 6px 0"},this.tipCss,this.tipRightStyle,!0))})}function c(a){var b;if(b=a&&a.type==CKEDITOR.NODE_ELEMENT)b=!(k[a.getComputedStyle("float")]||k[a.getAttribute("align")]);return b&& +!g[a.getComputedStyle("position")]}CKEDITOR.plugins.add("lineutils");CKEDITOR.LINEUTILS_BEFORE=1;CKEDITOR.LINEUTILS_AFTER=2;CKEDITOR.LINEUTILS_INSIDE=4;a.prototype={start:function(a){var b=this,c=this.editor,d=this.doc,f,g,k,l,v=CKEDITOR.tools.eventsBuffer(50,function(){c.readOnly||"wysiwyg"!=c.mode||(b.relations={},(g=d.$.elementFromPoint(k,l))&&g.nodeType&&(f=new CKEDITOR.dom.element(g),b.traverseSearch(f),isNaN(k+l)||b.pixelSearch(f,k,l),a&&a(b.relations,k,l)))});this.listener=this.editable.attachListener(this.target, +"mousemove",function(a){k=a.data.$.clientX;l=a.data.$.clientY;v.input()});this.editable.attachListener(this.inline?this.editable:this.frame,"mouseout",function(){v.reset()})},stop:function(){this.listener&&this.listener.removeListener()},getRange:function(){var a={};a[CKEDITOR.LINEUTILS_BEFORE]=CKEDITOR.POSITION_BEFORE_START;a[CKEDITOR.LINEUTILS_AFTER]=CKEDITOR.POSITION_AFTER_END;a[CKEDITOR.LINEUTILS_INSIDE]=CKEDITOR.POSITION_AFTER_START;return function(b){var c=this.editor.createRange();c.moveToPosition(this.relations[b.uid].element, +a[b.type]);return c}}(),store:function(){function a(b,c,d){var f=b.getUniqueId();f in d?d[f].type|=c:d[f]={element:b,type:c}}return function(b,d){var f;d&CKEDITOR.LINEUTILS_AFTER&&c(f=b.getNext())&&f.isVisible()&&(a(f,CKEDITOR.LINEUTILS_BEFORE,this.relations),d^=CKEDITOR.LINEUTILS_AFTER);d&CKEDITOR.LINEUTILS_INSIDE&&c(f=b.getFirst())&&f.isVisible()&&(a(f,CKEDITOR.LINEUTILS_BEFORE,this.relations),d^=CKEDITOR.LINEUTILS_INSIDE);a(b,d,this.relations)}}(),traverseSearch:function(a){var b,d,f;do if(f=a.$["data-cke-expando"], +!(f&&f in this.relations)){if(a.equals(this.editable))break;if(c(a))for(b in this.lookups)(d=this.lookups[b](a))&&this.store(a,d)}while((!a||a.type!=CKEDITOR.NODE_ELEMENT||"true"!=a.getAttribute("contenteditable"))&&(a=a.getParent()))},pixelSearch:function(){function a(d,f,g,h,k){for(var l=0,v;k(g);){g+=h;if(25==++l)break;if(v=this.doc.$.elementFromPoint(f,g))if(v==d)l=0;else if(b(d,v)&&(l=0,c(v=new CKEDITOR.dom.element(v))))return v}}var b=CKEDITOR.env.ie||CKEDITOR.env.webkit?function(a,b){return a.contains(b)}: +function(a,b){return!!(a.compareDocumentPosition(b)&16)};return function(b,d,f){var g=this.win.getViewPaneSize().height,k=a.call(this,b.$,d,f,-1,function(a){return 0this.rect.bottom)return!1;this.inline? +f.left=c.elementRect.left-this.rect.relativeX:(0[^<]*e});0>f&&(f=a._.upcasts.length);a._.upcasts.splice(f,0,[CKEDITOR.tools.bind(b,c),c.name,e])}var e=b.upcast,f=b.upcastPriority||10;e&&("string"==typeof e?d(c, +b,f):d(e,b,f))}function l(a,b){a.focused=null;if(b.isInited()){var c=b.editor.checkDirty();a.fire("widgetBlurred",{widget:b});b.setFocused(!1);!c&&b.editor.resetDirty()}}function k(a){a=a.data;if("wysiwyg"==this.editor.mode){var b=this.editor.editable(),c=this.instances,d,e,g,h;if(b){for(d in c)c[d].isReady()&&!b.contains(c[d].wrapper)&&this.destroy(c[d],!0);if(a&&a.initOnlyNew)c=this.initOnAll();else{var k=b.find(".cke_widget_wrapper"),c=[];d=0;for(e=k.count();dCKEDITOR.tools.indexOf(b,a)&&c.push(a);a=CKEDITOR.tools.indexOf(d,a);0<=a&&d.splice(a,1);return this},focus:function(a){e=a;return this}, +commit:function(){var f=a.focused!==e,g,h;a.editor.fire("lockSnapshot");for(f&&(g=a.focused)&&l(a,g);g=d.pop();)b.splice(CKEDITOR.tools.indexOf(b,g),1),g.isInited()&&(h=g.editor.checkDirty(),g.setSelected(!1),!h&&g.editor.resetDirty());f&&e&&(h=a.editor.checkDirty(),a.focused=e,a.fire("widgetFocused",{widget:e}),e.setFocused(!0),!h&&a.editor.resetDirty());for(;g=c.pop();)b.push(g),g.setSelected(!0);a.editor.fire("unlockSnapshot")}}}function I(a,b,c){var d=0;b=L(b);var e=a.data.classes||{},f;if(b){for(e= +CKEDITOR.tools.clone(e);f=b.pop();)c?e[f]||(d=e[f]=1):e[f]&&(delete e[f],d=1);d&&a.setData("classes",e)}}function J(a){a.cancel()}function E(a,b){var c=a.editor,d=c.document,e=CKEDITOR.env.edge&&16<=CKEDITOR.env.version;if(!d.getById("cke_copybin")){var f=!c.blockless&&!CKEDITOR.env.ie||e?"div":"span",e=d.createElement(f),g=d.createElement(f),f=CKEDITOR.env.ie&&9>CKEDITOR.env.version;g.setAttributes({id:"cke_copybin","data-cke-temp":"1"});e.setStyles({position:"absolute",width:"1px",height:"1px", +overflow:"hidden"});e.setStyle("ltr"==c.config.contentsLangDirection?"left":"right","-5000px");var h=c.createRange();h.setStartBefore(a.wrapper);h.setEndAfter(a.wrapper);e.setHtml('\x3cspan data-cke-copybin-start\x3d"1"\x3e​\x3c/span\x3e'+c.editable().getHtmlFromRange(h).getHtml()+'\x3cspan data-cke-copybin-end\x3d"1"\x3e​\x3c/span\x3e');c.fire("saveSnapshot");c.fire("lockSnapshot");g.append(e);c.editable().append(g);var k=c.on("selectionChange",J,null,null,0),l=a.repository.on("checkSelection",J, +null,null,0);if(f)var m=d.getDocumentElement().$,n=m.scrollTop;h=c.createRange();h.selectNodeContents(e);h.select();f&&(m.scrollTop=n);setTimeout(function(){b||a.focus();g.remove();k.removeListener();l.removeListener();c.fire("unlockSnapshot");b&&!c.readOnly&&(a.repository.del(a),c.fire("saveSnapshot"))},100)}}function L(a){return(a=(a=a.getDefinition().attributes)&&a["class"])?a.split(/\s+/):null}function G(){var a=CKEDITOR.document.getActive(),b=this.editor,c=b.editable();(c.isInline()?c:b.document.getWindow().getFrame()).equals(a)&& +b.focusManager.focus(c)}function D(){CKEDITOR.env.gecko&&this.editor.unlockSelection();CKEDITOR.env.webkit||(this.editor.forceNextSelectionCheck(),this.editor.selectionChange(1))}function N(a){var b=null;a.on("data",function(){var a=this.data.classes,c;if(b!=a){for(c in b)a&&a[c]||this.removeClass(c);for(c in a)this.addClass(c);b=a}})}function Q(a){a.on("data",function(){if(a.wrapper){var b=this.getLabel?this.getLabel():this.editor.lang.widget.label.replace(/%1/,this.pathName||this.element.getName()); +a.wrapper.setAttribute("role","region");a.wrapper.setAttribute("aria-label",b)}},null,null,9999)}function O(a){if(a.draggable){var b=a.editor,c=a.wrapper.getLast(f.isDomDragHandlerContainer),d;c?d=c.findOne("img"):(c=new CKEDITOR.dom.element("span",b.document),c.setAttributes({"class":"cke_reset cke_widget_drag_handler_container",style:"background:rgba(220,220,220,0.5);background-image:url("+b.plugins.widget.path+"images/handle.png)"}),d=new CKEDITOR.dom.element("img",b.document),d.setAttributes({"class":"cke_reset cke_widget_drag_handler", +"data-cke-widget-drag-handler":"1",src:CKEDITOR.tools.transparentImageData,width:15,title:b.lang.widget.move,height:15,role:"presentation"}),a.inline&&d.setAttribute("draggable","true"),c.append(d),a.wrapper.append(c));a.wrapper.on("dragover",function(a){a.data.preventDefault()});a.wrapper.on("mouseenter",a.updateDragHandlerPosition,a);setTimeout(function(){a.on("data",a.updateDragHandlerPosition,a)},50);if(!a.inline&&(d.on("mousedown",K,a),CKEDITOR.env.ie&&9>CKEDITOR.env.version))d.on("dragstart", +function(a){a.data.preventDefault(!0)});a.dragHandlerContainer=c}}function K(a){function b(){var c;for(p.reset();c=h.pop();)c.removeListener();var d=k;c=a.sender;var e=this.repository.finder,f=this.repository.liner,g=this.editor,l=this.editor.editable();CKEDITOR.tools.isEmpty(f.visible)||(d=e.getRange(d[0]),this.focus(),g.fire("drop",{dropRange:d,target:d.startContainer}));l.removeClass("cke_widget_dragging");f.hideVisible();g.fire("dragend",{target:c})}if(CKEDITOR.tools.getMouseButton(a)===CKEDITOR.MOUSE_BUTTON_LEFT){var c= +this.repository.finder,d=this.repository.locator,e=this.repository.liner,f=this.editor,g=f.editable(),h=[],k=[],l,m;this.repository._.draggedWidget=this;var n=c.greedySearch(),p=CKEDITOR.tools.eventsBuffer(50,function(){l=d.locate(n);k=d.sort(m,1);k.length&&(e.prepare(n,l),e.placeLine(k[0]),e.cleanup())});g.addClass("cke_widget_dragging");h.push(g.on("mousemove",function(a){m=a.data.$.clientY;p.input()}));f.fire("dragstart",{target:a.sender});h.push(f.document.once("mouseup",b,this));g.isInline()|| +h.push(CKEDITOR.document.once("mouseup",b,this))}}function W(a){var b,c,d=a.editables;a.editables={};if(a.editables)for(b in d)c=d[b],a.initEditable(b,"string"==typeof c?{selector:c}:c)}function R(a){if(a.mask){var b=a.wrapper.findOne(".cke_widget_mask");b||(b=new CKEDITOR.dom.element("img",a.editor.document),b.setAttributes({src:CKEDITOR.tools.transparentImageData,"class":"cke_reset cke_widget_mask"}),a.wrapper.append(b));a.mask=b}}function Z(a){if(a.parts){var b={},c,d;for(d in a.parts)c=a.wrapper.findOne(a.parts[d]), +b[d]=c;a.parts=b}}function ha(a,b){X(a);Z(a);W(a);R(a);O(a);N(a);Q(a);if(CKEDITOR.env.ie&&9>CKEDITOR.env.version)a.wrapper.on("dragstart",function(b){var c=b.data.getTarget();f.getNestedEditable(a,c)||a.inline&&f.isDomDragHandler(c)||b.data.preventDefault()});a.wrapper.removeClass("cke_widget_new");a.element.addClass("cke_widget_element");a.on("key",function(b){b=b.data.keyCode;if(13==b)a.edit();else{if(b==CKEDITOR.CTRL+67||b==CKEDITOR.CTRL+88){E(a,b==CKEDITOR.CTRL+88);return}if(b in V||CKEDITOR.CTRL& +b||CKEDITOR.ALT&b)return}return!1},null,null,999);a.on("doubleclick",function(b){a.edit()&&b.cancel()});if(b.data)a.on("data",b.data);if(b.edit)a.on("edit",b.edit)}function X(a){(a.wrapper=a.element.getParent()).setAttribute("data-cke-widget-id",a.id)}function U(a){a.element.data("cke-widget-data",encodeURIComponent(JSON.stringify(a.data)))}function ba(){function a(){}function b(a,c,d){return d&&this.checkElement(a)?(a=d.widgets.getByElement(a,!0))&&a.checkStyleActive(this):!1}function c(a){function b(a, +c,d){for(var e=a.length,f=0;f)?(?:<(?:div|span)(?: style="[^"]+")?>)?]*data-cke-copybin-start="1"[^>]*>.?<\/span>([\s\S]+)]*data-cke-copybin-end="1"[^>]*>.?<\/span>(?:<\/(?:div|span)>)?(?:<\/(?:div|span)>)?$/i,V={37:1,38:1,39:1,40:1,8:1,46:1};V[CKEDITOR.SHIFT+121]=1;CKEDITOR.plugins.widget=f;f.repository=a;f.nestedEditable=b}(),function(){function a(a,c,d){this.editor= +a;this.notification=null;this._message=new CKEDITOR.template(c);this._singularMessage=d?new CKEDITOR.template(d):null;this._tasks=[];this._doneTasks=this._doneWeights=this._totalWeights=0}function f(a){this._weight=a||1;this._doneWeight=0;this._isCanceled=!1}CKEDITOR.plugins.add("notificationaggregator",{requires:"notification"});a.prototype={createTask:function(a){a=a||{};var c=!this.notification,d;c&&(this.notification=this._createNotification());d=this._addTask(a);d.on("updated",this._onTaskUpdate, +this);d.on("done",this._onTaskDone,this);d.on("canceled",function(){this._removeTask(d)},this);this.update();c&&this.notification.show();return d},update:function(){this._updateNotification();this.isFinished()&&this.fire("finished")},getPercentage:function(){return 0===this.getTaskCount()?1:this._doneWeights/this._totalWeights},isFinished:function(){return this.getDoneTaskCount()===this.getTaskCount()},getTaskCount:function(){return this._tasks.length},getDoneTaskCount:function(){return this._doneTasks}, +_updateNotification:function(){this.notification.update({message:this._getNotificationMessage(),progress:this.getPercentage()})},_getNotificationMessage:function(){var a=this.getTaskCount(),c={current:this.getDoneTaskCount(),max:a,percentage:Math.round(100*this.getPercentage())};return(1==a&&this._singularMessage?this._singularMessage:this._message).output(c)},_createNotification:function(){return new CKEDITOR.plugins.notification(this.editor,{type:"progress"})},_addTask:function(a){a=new f(a.weight); +this._tasks.push(a);this._totalWeights+=a._weight;return a},_removeTask:function(a){var c=CKEDITOR.tools.indexOf(this._tasks,a);-1!==c&&(a._doneWeight&&(this._doneWeights-=a._doneWeight),this._totalWeights-=a._weight,this._tasks.splice(c,1),this.update())},_onTaskUpdate:function(a){this._doneWeights+=a.data;this.update()},_onTaskDone:function(){this._doneTasks+=1;this.update()}};CKEDITOR.event.implementOn(a.prototype);f.prototype={done:function(){this.update(this._weight)},update:function(a){if(!this.isDone()&& +!this.isCanceled()){a=Math.min(this._weight,a);var c=a-this._doneWeight;this._doneWeight=a;this.fire("updated",c);this.isDone()&&this.fire("done")}},cancel:function(){this.isDone()||this.isCanceled()||(this._isCanceled=!0,this.fire("canceled"))},isDone:function(){return this._weight===this._doneWeight},isCanceled:function(){return this._isCanceled}};CKEDITOR.event.implementOn(f.prototype);CKEDITOR.plugins.notificationAggregator=a;CKEDITOR.plugins.notificationAggregator.task=f}(),"use strict",function(){CKEDITOR.plugins.add("uploadwidget", +{requires:"widget,clipboard,filetools,notificationaggregator",init:function(a){a.filter.allow("*[!data-widget,!data-cke-upload-id]")},isSupportedEnvironment:function(){return CKEDITOR.plugins.clipboard.isFileApiSupported}});CKEDITOR.fileTools||(CKEDITOR.fileTools={});CKEDITOR.tools.extend(CKEDITOR.fileTools,{addUploadWidget:function(a,f,b){var c=CKEDITOR.fileTools,d=a.uploadRepository,l=b.supportedTypes?10:20;if(b.fileToElement)a.on("paste",function(b){b=b.data;var g=a.widgets.registered[f],h=b.dataTransfer, +l=h.getFilesCount(),e=g.loadMethod||"loadAndUpload",n,q;if(!b.dataValue&&l)for(q=0;q=a&&(a="0"+a);return String(a)}function f(c){var d=new Date,d=[d.getFullYear(),d.getMonth()+1,d.getDate(),d.getHours(),d.getMinutes(),d.getSeconds()];b+=1;return"image-"+CKEDITOR.tools.array.map(d,a).join("")+"-"+b+"."+c}var b=0;CKEDITOR.plugins.add("uploadimage", +{requires:"uploadwidget",onLoad:function(){CKEDITOR.addCss(".cke_upload_uploading img{opacity: 0.3}")},isSupportedEnvironment:function(){return CKEDITOR.plugins.clipboard.isFileApiSupported},init:function(a){if(this.isSupportedEnvironment()){var b=CKEDITOR.fileTools,l=b.getUploadUrl(a.config,"image");l&&(b.addUploadWidget(a,"uploadimage",{supportedTypes:/image\/(jpeg|png|gif|bmp)/,uploadUrl:l,fileToElement:function(){var a=new CKEDITOR.dom.element("img");a.setAttribute("src","data:image/gif;base64,R0lGODlhDgAOAIAAAAAAAP///yH5BAAAAAAALAAAAAAOAA4AAAIMhI+py+0Po5y02qsKADs\x3d"); +return a},parts:{img:"img"},onUploading:function(a){this.parts.img.setAttribute("src",a.data)},onUploaded:function(a){var b=this.parts.img.$;this.replaceWith('\x3cimg src\x3d"'+a.url+'" width\x3d"'+(a.responseData.width||b.naturalWidth)+'" height\x3d"'+(a.responseData.height||b.naturalHeight)+'"\x3e')}}),a.on("paste",function(k){if(k.data.dataValue.match(/f.version||f.quirks))};"undefined"==typeof a.plugins.scayt&&a.ui.addButton&&a.ui.addButton("SpellChecker",{label:a.lang.wsc.toolbar,click:function(a){var c=a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?a.container.getText(): +a.document.getBody().getText();(c=c.replace(/\s/g,""))?a.execCommand("checkspell"):alert("Nothing to check!")},toolbar:"spellchecker,10"});CKEDITOR.dialog.add("checkspell",this.path+(CKEDITOR.env.ie&&7>=CKEDITOR.env.version?"dialogs/wsc_ie.js":window.postMessage?"dialogs/wsc.js":"dialogs/wsc_ie.js"))}}),function(){function a(a){function b(a){var c=!1;e.attachListener(e,"keydown",function(){var b=g.getBody().getElementsByTag(a);if(!c){for(var d=0;dCKEDITOR.env.version&&c.enterMode!=CKEDITOR.ENTER_DIV&&b("div");if(CKEDITOR.env.webkit||CKEDITOR.env.ie&&10this.$.offsetHeight){var d=c.createRange();d[33==b?"moveToElementEditStart": +"moveToElementEditEnd"](this);d.select();a.data.preventDefault()}});CKEDITOR.env.ie&&this.attachListener(g,"blur",function(){try{g.$.selection.empty()}catch(a){}});CKEDITOR.env.iOS&&this.attachListener(g,"touchend",function(){a.focus()});h=c.document.getElementsByTag("title").getItem(0);h.data("cke-title",h.getText());CKEDITOR.env.ie&&(c.document.$.title=this._.docTitle);CKEDITOR.tools.setTimeout(function(){"unloaded"==this.status&&(this.status="ready");c.fire("contentDom");this._.isPendingFocus&& +(c.focus(),this._.isPendingFocus=!1);setTimeout(function(){c.fire("dataReady")},0)},0,this)}function f(a){function b(){var f;a.editable().attachListener(a,"selectionChange",function(){var b=a.getSelection().getSelectedElement();b&&(f&&(f.detachEvent("onresizestart",c),f=null),b.$.attachEvent("onresizestart",c),f=b.$)})}function c(a){a.returnValue=!1}if(CKEDITOR.env.gecko)try{var f=a.document.$;f.execCommand("enableObjectResizing",!1,!a.config.disableObjectResizing);f.execCommand("enableInlineTableEditing", +!1,!a.config.disableNativeTableHandles)}catch(h){}else CKEDITOR.env.ie&&11>CKEDITOR.env.version&&a.config.disableObjectResizing&&b(a)}function b(){var a=[];if(8<=CKEDITOR.document.$.documentMode){a.push("html.CSS1Compat [contenteditable\x3dfalse]{min-height:0 !important}");var b=[],c;for(c in CKEDITOR.dtd.$removeEmpty)b.push("html.CSS1Compat "+c+"[contenteditable\x3dfalse]");a.push(b.join(",")+"{display:inline-block}")}else CKEDITOR.env.gecko&&(a.push("html{height:100% !important}"),a.push("img:-moz-broken{-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}")); +a.push("html{cursor:text;*cursor:auto}");a.push("img,input,textarea{cursor:default}");return a.join("\n")}var c;CKEDITOR.plugins.add("wysiwygarea",{init:function(a){a.config.fullPage&&a.addFeature({allowedContent:"html head title; style [media,type]; body (*)[id]; meta link [*]",requiredContent:"body"});a.addMode("wysiwyg",function(b){function f(e){e&&e.removeListener();a.editable(new c(a,h.$.contentWindow.document.body));a.setData(a.getData(1),b)}var g="document.open();"+(CKEDITOR.env.ie?"("+CKEDITOR.tools.fixDomain+ +")();":"")+"document.close();",g=CKEDITOR.env.air?"javascript:void(0)":CKEDITOR.env.ie&&!CKEDITOR.env.edge?"javascript:void(function(){"+encodeURIComponent(g)+"}())":"",h=CKEDITOR.dom.element.createFromHtml('\x3ciframe src\x3d"'+g+'" frameBorder\x3d"0"\x3e\x3c/iframe\x3e');h.setStyles({width:"100%",height:"100%"});h.addClass("cke_wysiwyg_frame").addClass("cke_reset");g=a.ui.space("contents");g.append(h);var m=CKEDITOR.env.ie&&!CKEDITOR.env.edge||CKEDITOR.env.gecko;if(m)h.on("load",f);var e=a.title, +n=a.fire("ariaEditorHelpLabel",{}).label;e&&(CKEDITOR.env.ie&&n&&(e+=", "+n),h.setAttribute("title",e));if(n){var e=CKEDITOR.tools.getNextId(),q=CKEDITOR.dom.element.createFromHtml('\x3cspan id\x3d"'+e+'" class\x3d"cke_voice_label"\x3e'+n+"\x3c/span\x3e");g.append(q,1);h.setAttribute("aria-describedby",e)}a.on("beforeModeUnload",function(a){a.removeListener();q&&q.remove()});h.setAttributes({tabIndex:a.tabIndex,allowTransparency:"true"});!m&&f();a.fire("ariaWidget",h)})}});CKEDITOR.editor.prototype.addContentsCss= +function(a){var b=this.config,c=b.contentsCss;CKEDITOR.tools.isArray(c)||(b.contentsCss=c?[c]:[]);b.contentsCss.push(a)};c=CKEDITOR.tools.createClass({$:function(){this.base.apply(this,arguments);this._.frameLoadedHandler=CKEDITOR.tools.addFunction(function(b){CKEDITOR.tools.setTimeout(a,0,this,b)},this);this._.docTitle=this.getWindow().getFrame().getAttribute("title")},base:CKEDITOR.editable,proto:{setData:function(a,c){var f=this.editor;if(c)this.setHtml(a),this.fixInitialSelection(),f.fire("dataReady"); +else{this._.isLoadingData=!0;f._.dataStore={id:1};var g=f.config,h=g.fullPage,m=g.docType,e=CKEDITOR.tools.buildStyleHtml(b()).replace(/ + +
+
+
+
+ `); + $('.wrapper').append(skinModal); + + /** + * skin opt + */ + $('#changeSkin').on('click', function(){ + $('#skinModal').modal({backdrop: true, keyboard: false}).modal('show'); + }); + + /** + * skin list + */ + var skins = [ + "skin-blue", + "skin-black", + "skin-purple", + "skin-green", + "skin-red", + "skin-yellow", + "skin-blue-light", + "skin-black-light", + "skin-purple-light", + "skin-green-light", + "skin-red-light", + "skin-yellow-light" + ]; + + /** + * skin change + */ + $("[data-skin]").on('click', function(e) { + var skin = $(this).data('skin'); + $.each(skins, function (i) { + $("body").removeClass(skins[i]); + }); + $("body").addClass(skin); + store('admin_skin', skin); + return false; + }); + + /** + * skin init + */ + let skin = loadStore('admin_skin'); + if (skin) { + $.each(skins, function (i) { + $("body").removeClass(skins[i]); + }); + $("body").addClass(skin); + } + + // ---------------------- localStorage ---------------------- + + /** + * store + */ + function store(name, val) { + if (typeof (Storage) !== "undefined") { + localStorage.setItem(name, val); + } else { + window.alert('Please use a modern browser to properly view this template!'); + } + } + + /** + * get + */ + function loadStore(name) { + if (typeof (Storage) !== "undefined") { + return localStorage.getItem(name); + } else { + window.alert('Please use a modern browser to properly view this template!'); + } + } + + // ---------------------- menu, sidebar-toggle ---------------------- + + // init menu speed + $('.sidebar-menu').attr('data-animation-speed', 1); + + // init menu status + let sidebar = loadStore('admin_sidebar_status'); + if ( 'close' === sidebar ) { + $('body').addClass('sidebar-collapse'); + } else { + $('body').removeClass('sidebar-collapse'); + } + + // change menu status + $('.sidebar-toggle').click(function(){ + if ( 'close' === loadStore('admin_sidebar_status') ) { + store('admin_sidebar_status', 'open') + } else { + store('admin_sidebar_status', 'close') + } + }); + +}); diff --git a/xxl-job-admin/src/main/resources/static/biz/common/admin.tab.css b/xxl-job-admin/src/main/resources/static/biz/common/admin.tab.css new file mode 100644 index 00000000..9c8fd97c --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/biz/common/admin.tab.css @@ -0,0 +1,225 @@ +/* admin tab start */ +html, +body { + height: 100% !important; + min-height: 0px !important; +} + +body .wrapper{ + height: 100% !important; + min-height: 0px !important; +} + +body .content-wrapper{ + /*for footer*/ + height:calc(100% - 36px); +} + +body .sidebar{ + overflow: hidden; +} + +.content-tabs { + position: relative; + height: 35px; + background: #fafafa; + line-height: 35px; +} + +.content-tabs .roll-nav, +.page-tabs-list { + position: absolute; + width: 40px; + height: 35px; + text-align: center; + color: #999; + z-index: 2; + top: 0; +} + +.content-tabs .roll-left { + left: 0; + border-right: solid 1px #eee; +} + +.content-tabs .roll-right { + right: 0; + border-left: solid 1px #eee; +} + +.content-tabs button { + background: #fff; + border: 0; + height: 35px; + width: 40px; + outline: none; +} + +.content-tabs button:hover { + background: #fafafa; +} + +nav.page-tabs { + margin-left: 40px; + width: 100000px; + height: 35px; + overflow: hidden; +} + +nav.page-tabs .page-tabs-content { + float: left; +} + +.page-tabs a { + display: block; + float: left; + border-right: solid 1px #eee; + padding: 0 15px; +} + +.page-tabs a i:hover { + color: #c00; +} + +.page-tabs a:hover, +.content-tabs .roll-nav:hover { + color: #777; + background: #f2f2f2; + cursor: pointer; +} + +.roll-right.J_tabRight { + right: 200px; +} + +.roll-right.btn-group { + right: 120px; + width: 80px; + padding: 0; +} + +.roll-right.btn-group button { + width: 80px; +} + +.roll-right.J_tabExit, .roll-right.tabReload { + right: 60px; + background: #fff; + height: 35px; + width: 60px; + outline: none; +} + +.roll-right.J_tabExit, .roll-right.fullscreen { + background: #fff; + height: 35px; + width: 60px; + outline: none; +} + +.dropdown-menu-right { + left: auto; +} + +#content-main { + height: calc(100% - 50px); + overflow: hidden; +} + +.fixed-nav #content-main { + height: calc(100% - 80px); + overflow: hidden; +} + +.content-tabs { + height: 37px; + border-bottom: solid 2px #d2d6de; +} + +.page-tabs a { + color: #999; +} + +.page-tabs a i { + color: #ccc; +} + +.page-tabs a.active { + background: #2f4050; + color: #ccc; +} + +.page-tabs a.active:hover, +.page-tabs a.active i:hover { + background: #2f4050; + color: #fff; +} + +/** 遮罩层 **/ +.loaderbox { + display: inline-block; + min-width: 125px; + padding: 10px; + margin: 0 auto; + color: #000 !important; + font-size: 13px; + font-weight: 400; + text-align: center; + vertical-align: middle; + border: 1px solid #ddd; + background-color: #eee; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + -o-border-radius: 2px; + border-radius: 2px; + -webkit-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); +} + +.loaderbox .loading-activity { + float: left; + width: 18px; + height: 18px; + border: solid 2px transparent; + border-top-color: #000; + border-left-color: #000; + border-radius: 10px; + -webkit-animation: pace-spinner 400ms linear infinite; + -moz-animation: pace-spinner 400ms linear infinite; + -ms-animation: pace-spinner 400ms linear infinite; + -o-animation: pace-spinner 400ms linear infinite; + animation: pace-spinner 400ms linear infinite; +} + +.dropdown-menu { + min-width: 100px; +} + +.main-footer { + height: 37px; + border-top: solid 2px #d2d6de; + padding: 8px 20px 12px 20px; +} +/* admin tab end */ + +/* nprogress start */ +#nprogress .bar { + background: #FFFFFF !important; +} +#nprogress .peg { + box-shadow: 0 0 10px #FFFFFF, 0 0 5px #FFFFFF !important; +} +#nprogress .spinner-icon { + border-top-color: #FFFFFF !important; + border-left-color: #FFFFFF !important; +} +/* nprogress end */ + +/* menu start */ +.treeview-menu>li>a { + padding-top: 8px; + padding-bottom: 8px; +} +/* menu end */ diff --git a/xxl-job-admin/src/main/resources/static/biz/common/admin.tab.js b/xxl-job-admin/src/main/resources/static/biz/common/admin.tab.js new file mode 100644 index 00000000..4392d19e --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/biz/common/admin.tab.js @@ -0,0 +1,587 @@ +/*! +* Admin Tab for XXL-BOOT +* ================ +* +* 1、menu: +* - J_menuItem: 菜单 +* 2、tab: +* - J_tabLeft: 左移按扭 +* - J_menuTabs: +* - J_menuTab : 菜单Tab +* - J_tabRight: 右移按扭 +* - J_tabCloseOther: 关闭其他Tab +* - J_tabCloseAll : 关闭所有Tab +* 3、contont +* - J_mainContent +* - J_iframe : 页面iframe +* +* @author xuxueli +* @repository https://github.com/xuxueli/xxl-boot +*/ +(function($) { + + $.extend({ + adminTab: { + initTab: function(options) { + + /** + * 点击菜单:打开Tab,展示Menu页面 + */ + $('.J_menuItem').on('click', function (){ + // 获取Tab属性 + let tabSrc = $(this).attr('href'); + let tabName = $.trim($(this).text()); + + // open tab + return openTab(tabSrc, tabName); + }); + + /** + * Tab关闭(x按钮):关闭菜单页面 + */ + $('.J_menuTabs').on('click', '.J_menuTab i', closeTab); + + /** + * Tab单击(切换/激活):展示Tab页面 + 左侧菜单active更新 + */ + $('.J_menuTabs').on('click', '.J_menuTab', activeTab); + + /** + * 选项卡-左移按扭:查看左侧隐藏的选项卡 + */ + $('.J_tabLeft').on('click', scrollTabLeft); + + /** + * 选项卡-右移按扭:查看右侧隐藏的选项卡 + */ + $('.J_tabRight').on('click', scrollTabRight); + + /** + * Tab-刷新按钮:刷新active的 Tab 页面 + */ + $('.tabReload').on('click', refreshTab); + + /** + * 关闭其他选项卡 + */ + $('.J_tabCloseOther').on('click', closeOtherTabs); + + /** + * 关闭当前选项卡 + */ + $('.tabCloseCurrent').on('click', tabCloseCurrent); + + /** + * 关闭全部选项卡 + */ + $('.J_tabCloseAll').on('click', tabCloseAll); + + /** + * 全屏显示 + */ + $('#fullScreen').on('click', function () { + let currentHash = window.location.hash; + $(document).toggleFullScreen(); + + // reset + if (currentHash) { + setTimeout(function (){ + window.location.hash = currentHash; + },50) + } + }); + + /** + * 默认打开菜单Tab:优先尝试打开url路径TAB,兜底打开首个菜单 + */ + openDefaultTab(); + }, + openTab: function(options, isCloseCurrent) { + // 当前页面是否关闭 + if (isCloseCurrent) { + tabCloseCurrent(); + } + // 打开Tab页面 + return openTab(options.tabSrc, options.tabName); + } + } + }); + + // -------------------- tab:open default -------------------- + + /** + * 默认打开菜单Tab:初始化首页菜单,然后 尝试打开url路径TAB + */ + function openDefaultTab() { + // load url + let tabSrc = window.location.hash.slice(1); + + // 1、首页菜单:初始化 + let $firstMenuItem = $(".J_menuItem:first"); + if ($firstMenuItem.length > 0) { + $firstMenuItem.click(); + // 首页菜单特殊逻辑,不允许关闭 + $('.J_menuTab[data-id="' + $firstMenuItem.attr('href') + '"] i').remove(); + } + + // 2、URL匹配到菜单,初始化 + if (tabSrc === '' || tabSrc === undefined || tabSrc === null) { + // URL菜单路径不存在则pass + return; + } + setTimeout(function (){ + var $menuItem = $('.J_menuItem').filter('a[href$="' + decodeURI(tabSrc) + '"]'); + if ($menuItem.length > 0) { + // URL匹配到菜单,初始化 + $menuItem.click(); + return; + } else { + // 匹配失败,兜底直接打开 + openTab(tabSrc, tabSrc); + } + }, 100) + + } + + // -------------------- tab:open、close -------------------- + + /** + * 打开Tab:根据 url + 名称 + * + * @param tabSrc + * @param tabName + * @returns {boolean} + */ + function openTab(tabSrc, tabName) { + // 0、valid dateurl + if (tabSrc === undefined || $.trim(tabSrc).length === 0){ + return false; + } + if (tabName === undefined || $.trim(tabName).length === 0){ + tabName = tabSrc; + } + let tabNameShow = tabName.length > 15 ? tabName.substring(0, 15) + '...' : tabName; + + // 1、菜单Menu联动 + 页面锚点(hash参数)更新 + activeMenuAndPath(tabSrc); + + // 2、匹配已存在Tab,切换/active展示 + let tabExist = false; + $('.J_menuTab').each(function () { + // 匹配成功 + if ($(this).data('id') === tabSrc) { + // Tab是否展示,若否则切换展示 + if (!$(this).hasClass('active')) { + $(this).addClass('active').siblings('.J_menuTab').removeClass('active'); + scrollToTab(this); + // 显示Tab对应的内容区 + $('.J_mainContent .J_iframe').each(function () { + if ($(this).data('id') === tabSrc) { + $(this).show().siblings('.J_iframe').hide(); + return false; + } + }); + } + tabExist = true; + return false; + } + }); + if (tabExist) { + return false; + } + + // 3、Tab不存在,初始化新Tab + IFrame + // build Tab (other tab no-active) + $('.J_menuTab').removeClass('active'); + var tabStr = '' + tabNameShow + ' '; + + // build IFrame (other ifame hide) + var iframeStr = ''; + + // 4、添加Tab + IFrame + // append iframe + $('.J_mainContent').find('iframe.J_iframe').hide(); + $('.J_mainContent').append(iframeStr); + // append tab + $('.J_menuTabs .page-tabs-content').append(tabStr); + + // 添加遮罩层 + NProgress.inc(0.2); + NProgress.configure({ + easing: 'ease', // 动画缓动函数 (默认: 'ease') + speed: 500, // 动画速度(毫秒)(默认: 200) + showSpinner: true // 是否显示旋转图标 (默认: true) + }); + NProgress.start(); + + // load iframe + let $iframe = $('.J_mainContent iframe:visible'); + $iframe.on('load', function () { + NProgress.done(); + }).on('error', function () { + NProgress.done(); + // 处理加载失败情况,防止跳转 + console.error('iframe load error, src = ' + $(this).attr('src')); + }); + + // 5、滚动到已激活的Tab + scrollToTab($('.J_menuTab.active')); + return false; + } + + /** + * 关闭Tab:根据点击 Tab元素 + * + * @returns {boolean} + */ + function closeTab() { + // param + var closeTabId = $(this).parents('.J_menuTab').data('id'); + var currentWidth = $(this).parents('.J_menuTab').width(); + + // process + if ($(this).parents('.J_menuTab').hasClass('active')) { + // 1、当前元素处于活动状态 + + // 当前元素后面有同辈元素,使后面的一个元素处于活动状态 + if ($(this).parents('.J_menuTab').next('.J_menuTab').length > 0) { + // 后一个Tab,激活 Tab + var activeId = $(this).parents('.J_menuTab').next('.J_menuTab:eq(0)').data('id'); + $(this).parents('.J_menuTab').next('.J_menuTab:eq(0)').addClass('active'); + + // 后一个Tab,激活 iframe + $('.J_mainContent .J_iframe').each(function () { + if ($(this).data('id') === activeId) { + $(this).show().siblings('.J_iframe').hide(); + return false; + } + }); + + var marginLeftVal = parseInt($('.page-tabs-content').css('margin-left')); + if (marginLeftVal < 0) { + $('.page-tabs-content').animate({ + marginLeft: (marginLeftVal + currentWidth) + 'px' + }, "fast"); + } + + // 移除 当前 Tab + $(this).parents('.J_menuTab').remove(); + + // 移除 当前 iframe + $('.J_mainContent .J_iframe').each(function () { + if ($(this).data('id') === closeTabId) { + $(this).remove(); + return false; + } + }); + } + + // 当前元素后面没有同辈元素,使当前元素的上一个元素处于活动状态 + if ($(this).parents('.J_menuTab').prev('.J_menuTab').length > 0) { + // 前一个Tab,激活 Tab + var activeId = $(this).parents('.J_menuTab').prev('.J_menuTab:last').data('id'); + $(this).parents('.J_menuTab').prev('.J_menuTab:last').addClass('active'); + + // 前一个Tab,激活 iframe + $('.J_mainContent .J_iframe').each(function () { + if ($(this).data('id') == activeId) { + $(this).show().siblings('.J_iframe').hide(); + return false; + } + }); + + // 移除 当前 Tab + $(this).parents('.J_menuTab').remove(); + + // 移除 当前 iframe + $('.J_mainContent .J_iframe').each(function () { + if ($(this).data('id') === closeTabId) { + $(this).remove(); + return false; + } + }); + } + } else { + // 2、当前元素不处于活动状态 + + // 移除 当前 Tab + $(this).parents('.J_menuTab').remove(); + + // 移除 当前 iframe + $('.J_mainContent .J_iframe').each(function () { + if ($(this).data('id') == closeTabId) { + $(this).remove(); + return false; + } + }); + + // 滚动到active状态 Tab + scrollToTab($('.J_menuTab.active')); + } + + // 3、菜单Menu联动 + 页面锚点(hash参数)更新 + activeMenuAndPath($('.page-tabs-content').find('.active').attr('data-id')); + return false; + } + + /** + * 菜单Menu联动 + 页面锚点(hash参数)更新 + */ + function activeMenuAndPath(tabSrc) { + + // 页面锚点(hash参数)更新 + window.location.hash = tabSrc; + + // 菜单Menu切换/active + $(".sidebar-menu ul li, .sidebar-menu li").removeClass("active"); + $('.J_menuItem').each(function () { + if ($(this).attr('href') === tabSrc) { + + // 菜单Menu切换/active + $(this).parents("li").addClass("active"); + + + return true; + } + }) + return false; + } + + /** + * Tab单击(切换/激活):展示Tab页面 + 左侧菜单active更新 + */ + function activeTab() { + // 是否已激活 active,避免重复处理 + if (!$(this).hasClass('active')) { + // 待激活Tab信息 + let tabSrc = $(this).data('id'); + + // Ifame 切换展示 + $('.J_mainContent .J_iframe').each(function () { + if ($(this).data('id') === tabSrc) { + $(this).show().siblings('.J_iframe').hide(); + return false; + } + }); + + // Tab 激活 + $(this).addClass('active').siblings('.J_menuTab').removeClass('active'); + scrollToTab(this); + + // 菜单Menu联动 + activeMenuAndPath(tabSrc); + } + } + + // -------------------- tab: scroll -------------------- + + /** + * 滚动到指定选项卡 + */ + function scrollToTab(element) { + + // 当前元素左侧宽度 + var marginLeftVal = calSumWidth($(element).prevAll()), marginRightVal = calSumWidth($(element).nextAll()); + // Tab外部区域宽度 + var tabOuterWidth = calSumWidth($(".content-tabs").children().not(".J_menuTabs")); + // 可视区域Tab宽度 + var visibleWidth = $(".content-tabs").outerWidth(true) - tabOuterWidth; + // 实际滚动宽度 + var scrollVal = 0; + if ($(".page-tabs-content").outerWidth() < visibleWidth) { + scrollVal = 0; + } else if (marginRightVal <= (visibleWidth - $(element).outerWidth(true) - $(element).next().outerWidth(true))) { + if ((visibleWidth - $(element).next().outerWidth(true)) > marginRightVal) { + scrollVal = marginLeftVal; + var tabElement = element; + while ((scrollVal - $(tabElement).outerWidth()) > ($(".page-tabs-content").outerWidth() - visibleWidth)) { + scrollVal -= $(tabElement).prev().outerWidth(); + tabElement = $(tabElement).prev(); + } + } + } else if (marginLeftVal > (visibleWidth - $(element).outerWidth(true) - $(element).prev().outerWidth(true))) { + scrollVal = marginLeftVal - $(element).prev().outerWidth(true); + } + + // 滚动处理 + $('.page-tabs-content').animate({ + marginLeft: 0 - scrollVal + 'px' + }, "fast"); + } + + /** + * 计算元素集合的总宽度 + */ + function calSumWidth(elements) { + var width = 0; + $(elements).each(function () { + width += $(this).outerWidth(true); + }); + return width; + } + + // -------------------- tab: scroll-left、scroll-right、 -------------------- + + /** + * 选项卡-左移按扭:查看左侧隐藏的选项卡 + */ + function scrollTabLeft() { + + // 当前元素左侧宽度 + var marginLeftVal = Math.abs(parseInt($('.page-tabs-content').css('margin-left'))); + // Tab外部区域宽度 + var tabOuterWidth = calSumWidth($(".content-tabs").children().not(".J_menuTabs")); + // 可视区域tab宽度 + var visibleWidth = $(".content-tabs").outerWidth(true) - tabOuterWidth; + // 实际滚动宽度 + var scrollVal = 0; + if ($(".page-tabs-content").width() < visibleWidth) { + return false; + } else { + var tabElement = $(".J_menuTab:first"); + var offsetVal = 0; + while ((offsetVal + $(tabElement).outerWidth(true)) <= marginLeftVal) {//找到离当前tab最近的元素 + offsetVal += $(tabElement).outerWidth(true); + tabElement = $(tabElement).next(); + } + offsetVal = 0; + if (calSumWidth($(tabElement).prevAll()) > visibleWidth) { + while ((offsetVal + $(tabElement).outerWidth(true)) < (visibleWidth) && tabElement.length > 0) { + offsetVal += $(tabElement).outerWidth(true); + tabElement = $(tabElement).prev(); + } + scrollVal = calSumWidth($(tabElement).prevAll()); + } + } + + // 滚动处理 + $('.page-tabs-content').animate({ + marginLeft: 0 - scrollVal + 'px' + }, "fast"); + } + + /** + * 选项卡-右移按扭:查看右侧隐藏的选项卡 + */ + function scrollTabRight() { + + // 当前元素左侧宽度 + var marginLeftVal = Math.abs(parseInt($('.page-tabs-content').css('margin-left'))); + // 可视区域非tab宽度 + var tabOuterWidth = calSumWidth($(".content-tabs").children().not(".J_menuTabs")); + // 可视区域tab宽度 + var visibleWidth = $(".content-tabs").outerWidth(true) - tabOuterWidth; + // 实际滚动宽度 + var scrollVal = 0; + if ($(".page-tabs-content").width() < visibleWidth) { + return false; + } else { + var tabElement = $(".J_menuTab:first"); + var offsetVal = 0; + while ((offsetVal + $(tabElement).outerWidth(true)) <= marginLeftVal) {//找到离当前tab最近的元素 + offsetVal += $(tabElement).outerWidth(true); + tabElement = $(tabElement).next(); + } + offsetVal = 0; + while ((offsetVal + $(tabElement).outerWidth(true)) < (visibleWidth) && tabElement.length > 0) { + offsetVal += $(tabElement).outerWidth(true); + tabElement = $(tabElement).next(); + } + scrollVal = calSumWidth($(tabElement).prevAll()); + if (scrollVal > 0) { + $('.page-tabs-content').animate({ + marginLeft: 0 - scrollVal + 'px' + }, "fast"); + } + } + } + + // -------------------- tab: refresh、close-other、close-current、close-all -------------------- + + /** + * Tab-刷新按钮:刷新active的 Tab 页面 + */ + function refreshTab() { + + // 显示进度条 + NProgress.inc(0.2); + NProgress.configure({ + easing: 'ease', // 动画缓动函数 (默认: 'ease') + speed: 500, // 动画速度(毫秒)(默认: 200) + showSpinner: true // 是否显示旋转图标 (默认: true) + }); + NProgress.start(); + + // 1、获取当前激活的 Tab + var tabSrc = $('.page-tabs-content').find('.active').attr('data-id'); + var target = $('.J_iframe[data-id="' + tabSrc + '"]'); + var url = target.attr('src'); + + // 2、重新加载页面 + // target.attr('src', url).ready(); + target.attr('src', url).on('load', function () { + NProgress.done(); + + // 3、菜单Menu联动 + 页面锚点(hash参数)更新 + activeMenuAndPath($('.page-tabs-content').find('.active').attr('data-id')); + + }).on('error', function () { + NProgress.done(); + // 处理加载失败情况,防止跳转 + console.error('iframe load error, src = ' + $(this).attr('src')); + }); + } + + /** + * 选项卡-Tab双击:刷新菜单页面 + */ + /*$('.J_menuTabs').on('dblclick', '.J_menuTab', refreshTab);*/ + + /** + * 滚动到已激活的选项卡 + */ + /*$('.J_tabShowActive').on('click', showActiveTab); + function showActiveTab(){ + scrollToTab($('.J_menuTab.active')); + }*/ + + /** + * 关闭其他选项卡 + */ + function closeOtherTabs(){ + $('.page-tabs-content').children("[data-id]").not(":first").not(".active").each(function () { + $('.J_iframe[data-id="' + $(this).data('id') + '"]').remove(); + $(this).remove(); + }); + $('.page-tabs-content').css("margin-left", "0"); + } + + /** + * 关闭当前选项卡 + */ + function tabCloseCurrent() { + $('.page-tabs-content').find('.active i').trigger("click"); + } + + /** + * 关闭全部选项卡 + */ + function tabCloseAll(){ + // 保留 第一个 Tab,其他删除(Tab+iframe) + $('.page-tabs-content').children("[data-id]").not(":first").each(function () { + $('.J_iframe[data-id="' + $(this).data('id') + '"]').remove(); + $(this).remove(); + }); + // 第一个 Tab 激活/展示(Tab+iframe) + $('.page-tabs-content').children("[data-id]:first").each(function () { + $('.J_iframe[data-id="' + $(this).data('id') + '"]').show(); + $(this).addClass("active"); + }); + $('.page-tabs-content').css("margin-left", "0"); + + // 菜单Menu联动 + 页面锚点(hash参数)更新 + activeMenuAndPath($('.page-tabs-content').find('.active').attr('data-id')); + } + + +})(jQuery); diff --git a/xxl-job-admin/src/main/resources/static/biz/common/admin.table.js b/xxl-job-admin/src/main/resources/static/biz/common/admin.table.js new file mode 100644 index 00000000..21530dd7 --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/biz/common/admin.table.js @@ -0,0 +1,484 @@ +/*! +* Admin Table for XXL-BOOT +* ================ +* +* 1、data_filter: +* searchBtn: 搜索 +* resetBtn: 重置 +* 2、data_operation: +* action: +* - add: 新增 +* - update: 更新 +* - delete: 删除 +* style: +* - selectOnlyOne: 单选 +* - selectAny: 多选 +* 3、data_list +* mainDataTable: 表格 +* +* @author xuxueli +* @repository https://github.com/xuxueli/xxl-boot +*/ +(function($) { + + $.extend({ + adminTable: { + table :null, + options: {}, + selectIds: function () { + // get select rows + let rows = this.table.bootstrapTable('getSelections'); + // find select ids + return (rows && rows.length > 0) ? rows.map(row => row.id) : []; + }, + selectRows: function () { + // get select rows + return this.table.bootstrapTable('getSelections'); + }, + initTable: function(options) { + // parse param + this.table = $(options.table); + this.options = options; + + // init filter + initSearch(this.table, options); + initReset(options); + + // init table + initAdminTable(this.table, options); + }, + initTreeTable: function(options) { + // parse param + this.table = $(options.table); + + // init filter + initSearch(this.table, options); + initReset(options); + + // init tree table + initAdminTreeTable(this.table, options); + }, + initDelete: function(options) { + initDeleteFun(this.table, options); + }, + initAdd: function(options) { + initAddFun(options); + }, + initUpdate: function(options) { + initUpdateFun(this.table, options); + } + } + }); + + /** + * init search + */ + function initSearch(table, options){ + // search + $('#data_filter .searchBtn').on('click', function(){ + + // searchHandler + let searchHandler = options.searchHandler; + if (typeof searchHandler === 'function') { + searchHandler(); + return; + } + + // do search + $(table).bootstrapTable('refresh'); + + }); + } + + /** + * init reset + */ + function initReset(options) { + + // reset + $('#data_filter .resetBtn').on('click', function(){ + + // reset + let resetHandler = options.resetHandler; + if (typeof resetHandler === 'function') { + // resetHandler + resetHandler(); + } else { + // do reset + + // input + $('#data_filter input[type="text"]').val(''); + // select + $('#data_filter select').each(function() { + $(this).prop('selectedIndex', 0); + }); + // checkbox + $('#data_filter input[type="checkbox"]').prop('checked', false); + // radio + $('#data_filter input[type="radio"]').prop('checked', false); + } + + // do search + $('#data_filter .searchBtn').click(); + }); + } + + /** + * init admin table + * + * @param table + * @param url + * @param queryParams + * @param columns + */ + function initAdminTable(table, options) { + + // parse param + let url = options.url; + let queryParams = options.queryParams; + let columns = options.columns; + + // init table + $(table).bootstrapTable({ + url: url, + method: "post", + contentType: "application/x-www-form-urlencoded", + queryParamsType: "limit", + queryParams: queryParams, // bootstrapTable -> queryParams + sidePagination: "server", // server side page + responseHandler: function (result) { + // custome + if (options.responseHandler) { + return options.responseHandler(result); + } + + // valid + if (result.code !== 200) { + layer.msg(result.msg || (I18n.system_opt+I18n.system_fail)); + return { + total: 0, + rows: [] + } + } + + // default + return { + "total": result.data.total, + "rows": result.data.data + }; + }, + columns: columns, + clickToSelect: true, // 是否启用点击选中行 + multipleSelectRow: true, // 启动多选行:点击 选择单行,Shift+点击 选择连续行, Commond+点击 非连续选择多行 + sortable: false, // 是否启用排序 + pagination: true, // 是否显示分页 + pageNumber: 1, // 默认第一页 + pageList: [10, 25, 50, 100] , // 可供选择的每页的行数(*) + smartDisplay: false, // 当总记录数小于分页数,是否显示可选项 + paginationParts: ['pageInfoShort', 'pageSize', 'pageList'], + paginationPreText: '<<', // 跳转页面的 上一页按钮 + paginationNextText: '>>', // 跳转页面的 下一页按钮 + paginationLoop: false, // 是否循环翻页 + showRefresh: true, // 显示刷新按钮 + showColumns: true, // 显示/隐藏列 + minimumCountColumns: 2, // 最少允许的列数 + // onLoadSuccess: function(data) {} + onAll: function(name, args) { + // filter + if (!(['check.bs.table', "uncheck.bs.table", "check-all.bs.table", "uncheck-all.bs.table", 'post-body.bs.table'].indexOf(name) > -1)) { + return false; + } + var rows = $(table).bootstrapTable('getSelections'); + var selectLen = rows.length; + + if (selectLen > 0) { + $("#data_operation .selectAny").removeClass('disabled'); + } else { + $("#data_operation .selectAny").addClass('disabled'); + } + if (selectLen === 1) { + $("#data_operation .selectOnlyOne").removeClass('disabled'); + } else { + $("#data_operation .selectOnlyOne").addClass('disabled'); + } + } + }); + + // toolbar 样式调整; + var toolbarElement = document.querySelector('.fixed-table-toolbar'); + if (toolbarElement) { + toolbarElement.classList.remove('fixed-table-toolbar'); + } + } + + function initAdminTreeTable(table, options) { + + // parse param + let url = options.url; + let queryParams = options.queryParams; + let columns = options.columns; + + // table + var mainDataTable = $("#data_list").bootstrapTable({ + url: url, + method: "post", + contentType: "application/x-www-form-urlencoded", + queryParams: queryParams, + responseHandler: function(result) { + if (result.code !== 200) { + layer.open({ + icon: '2', + content: result.msg + }); + return ; + } + return result.data; + }, + treeEnable:true, + idField: 'id', // 树形id + parentIdField: 'parentId', // 父级字段 + treeShowField: 'name', // 树形字段 + onPostBody: function(data) { + $("#data_list").treegrid({ + treeColumn: 1, // 选择第几列作为树形字段 + initialState: 'expanded', // 默认展开;expanded、collapsed + expanderExpandedClass: 'fa fa-fw fa-minus-square-o', // 树形展开图标 + expanderCollapsedClass: 'fa fa-fw fa-plus-square-o', // 树形折叠图标 + onChange () { + $("#data_list").bootstrapTable('resetView') // 树形表格重绘 + } + }) + }, + columns:columns, + clickToSelect: true, // 是否启用点击选中行 + multipleSelectRow: true, // 启动多选行:点击 选择单行,Shift+点击 选择连续行, Commond+点击 非连续选择多行 + sortable: false, // 是否启用排序 + showRefresh: true, // 显示刷新按钮 + showColumns: true, // 显示/隐藏列 + minimumCountColumns: 2, // 最少允许的列数 + onAll: function(name, args) { + // filter + if (!(['check.bs.table', "uncheck.bs.table", "check-all.bs.table", "uncheck-all.bs.table"].indexOf(name) > -1)) { + return false; + } + var rows = mainDataTable.bootstrapTable('getSelections'); + var selectLen = rows.length; + + if (selectLen > 0) { + $("#data_operation .selectAny").removeClass('disabled'); + } else { + $("#data_operation .selectAny").addClass('disabled'); + } + if (selectLen === 1) { + $("#data_operation .selectOnlyOne").removeClass('disabled'); + } else { + $("#data_operation .selectOnlyOne").addClass('disabled'); + } + } + }); + + // toolbar 样式调整; + var toolbarElement = document.querySelector('.fixed-table-toolbar'); + if (toolbarElement) { + toolbarElement.classList.remove('fixed-table-toolbar'); + } + } + + /** + * init delete + */ + function initDeleteFun(table, options) { + // parse param + let url = options.url; + + // delete + $("#data_operation").on('click', '.delete',function() { + // get select rows + var rows = $(table).bootstrapTable('getSelections'); + + // find select ids + const selectIds = (rows && rows.length > 0) ? rows.map(row => row.id) : []; + if (selectIds.length <= 0) { + layer.msg(I18n.system_please_choose + I18n.system_data); + return; + } + + // do delete + layer.confirm( I18n.system_ok + I18n.system_opt_del + '?', { + icon: 3, + title: I18n.system_tips , + btn: [ I18n.system_ok, I18n.system_cancel ] + }, function(index){ + layer.close(index); + + $.ajax({ + type : 'POST', + url : url, + data : { + "ids" : selectIds + }, + dataType : "json", + success : function(data){ + if (data.code === 200) { + layer.msg( I18n.system_opt_del + I18n.system_success ); + // refresh table + $('#data_filter .searchBtn').click(); + } else { + layer.msg( data.msg || I18n.system_opt_del + I18n.system_fail ); + } + }, + error: function(xhr, status, error) { + // Handle error + console.log("Error: " + error); + layer.open({ + icon: '2', + content: (I18n.system_opt_del + I18n.system_fail) + }); + } + }); + }); + }); + } + + /** + * init add + */ + function initAddFun(options) { + + // parse param + let url = options.url; + let rules = options.rules; + let messages = options.messages; + let writeFormData = options.writeFormData; + let readFormData = options.readFormData; + + // add + $("#data_operation .add").click(function(){ + // reset + addModalValidate.resetForm(); + $("#addModal .form")[0].reset(); + $("#addModal .form .form-group").removeClass("has-error"); + + // write FormData + if (typeof writeFormData === 'function') { + writeFormData(); + } + + // show + $('#addModal').modal({backdrop: false, keyboard: false}).modal('show'); + }); + var addModalValidate = $("#addModal .form").validate({ + errorElement : 'span', + errorClass : 'help-block', + focusInvalid : true, + rules : rules, // jquery.validate -> rules + messages : messages, // jquery.validate -> messages + highlight : function(element) { + $(element).closest('.form-group').addClass('has-error'); + }, + success : function(label) { + label.closest('.form-group').removeClass('has-error'); + label.remove(); + }, + errorPlacement : function(error, element) { + element.parent('div').append(error); + }, + submitHandler : function(form) { + // post + $.post(url, readFormData(), function(data, status) { + if (data.code === 200) { + $('#addModal').modal('hide'); + layer.msg( I18n.system_opt_add + I18n.system_success ); + + // refresh table + $('#data_filter .searchBtn').click(); + } else { + layer.open({ + title: I18n.system_tips , + btn: [ I18n.system_ok ], + content: (data.msg || I18n.system_opt_add + I18n.system_fail ), + icon: '2' + }); + } + }); + } + }); + } + + /** + * init update + */ + function initUpdateFun(table, options) { + + // parse param + let url = options.url; + let rules = options.rules; + let messages = options.messages; + let writeFormData = options.writeFormData; + let readFormData = options.readFormData; + + // update + $("#data_operation .update").click(function(){ + // get select rows + var rows = $(table).bootstrapTable('getSelections'); + + // find select row + if (rows.length !== 1) { + layer.msg(I18n.system_please_choose + I18n.system_one + I18n.system_data); + return; + } + var row = rows[0]; + + // reset + $("#updateModal .form")[0].reset(); + $("#updateModal .form .form-group").removeClass("has-error"); + updateModalValidate.resetForm(); + + // write FormData + writeFormData(row); + + // show + $('#updateModal').modal({backdrop: false, keyboard: false}).modal('show'); + }); + var updateModalValidate = $("#updateModal .form").validate({ + errorElement : 'span', + errorClass : 'help-block', + focusInvalid : true, + highlight : function(element) { + $(element).closest('.form-group').addClass('has-error'); + }, + success : function(label) { + label.closest('.form-group').removeClass('has-error'); + label.remove(); + }, + errorPlacement : function(error, element) { + element.parent('div').append(error); + }, + rules : rules, // jquery.validate -> rules + messages : messages, // jquery.validate -> messages + submitHandler : function(form) { + + // request + var paramData = readFormData(); + + $.post(url, paramData, function(data, status) { + if (data.code === 200) { + $('#updateModal').modal('hide'); + layer.msg( I18n.system_opt_edit + I18n.system_success ); + + // refresh table + $('#data_filter .searchBtn').click(); + } else { + layer.open({ + title: I18n.system_tips , + btn: [ I18n.system_ok ], + content: (data.msg || I18n.system_opt_edit + I18n.system_fail ), + icon: '2' + }); + } + }); + } + }); + } + +})(jQuery); \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/static/biz/common/admin.util.js b/xxl-job-admin/src/main/resources/static/biz/common/admin.util.js new file mode 100644 index 00000000..ec91ba9b --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/biz/common/admin.util.js @@ -0,0 +1,47 @@ +/*! +* Admin Util for XXL-BOOT +* ================ +* +* 1、openTab: 打开新页面,兼容iframe和window.open +* +* @author xuxueli +* @repository https://github.com/xuxueli/xxl-boot +*/ +$(function(){ + + + // ---------------------- openTab ---------------------- + + /** + * 打开新页面,兼容iframe和window.open + * + * @param tabSrc tab访问地址 + * @param tabName tab展示名称 + * @param isCloseCurrent 是否关闭当前页 + */ + window.openTab = function (tabSrc, tabName, isCloseCurrent) { + // open tab + if (window.parent.$.adminTab) { + window.parent.$.adminTab.openTab({ + tabSrc: tabSrc, + tabName: tabName + }, isCloseCurrent) + } else { + if (isCloseCurrent) { + window.open(tabSrc, '_self'); + } else { + window.open(tabSrc, '_blank'); + } + } + } + + // ---------------------- isOpenWithTab ---------------------- + + /** + * 是否在Tab中打开 + */ + window.isOpenWithTab = function () { + return !!window.parent.$.adminTab; + } + +}); diff --git a/xxl-job-admin/src/main/resources/static/js/common.1.js b/xxl-job-admin/src/main/resources/static/js/common.1.js deleted file mode 100644 index 7b736fbc..00000000 --- a/xxl-job-admin/src/main/resources/static/js/common.1.js +++ /dev/null @@ -1,164 +0,0 @@ -$(function(){ - - // logout - $("#logoutBtn").click(function(){ - layer.confirm( I18n.logout_confirm , { - icon: 3, - title: I18n.system_tips , - btn: [ I18n.system_ok, I18n.system_cancel ] - }, function(index){ - layer.close(index); - - $.post(base_url + "/logout", function(data, status) { - if (data.code == "200") { - layer.msg( I18n.logout_success ); - setTimeout(function(){ - window.location.href = base_url + "/"; - }, 500); - } else { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (data.msg || I18n.logout_fail), - icon: '2' - }); - } - }); - }); - - }); - - // slideToTop - var slideToTop = $("
"); - slideToTop.html(''); - slideToTop.css({ - position: 'fixed', - bottom: '20px', - right: '25px', - width: '40px', - height: '40px', - color: '#eee', - 'font-size': '', - 'line-height': '40px', - 'text-align': 'center', - 'background-color': '#222d32', - cursor: 'pointer', - 'border-radius': '5px', - 'z-index': '99999', - opacity: '.7', - 'display': 'none' - }); - slideToTop.on('mouseenter', function () { - $(this).css('opacity', '1'); - }); - slideToTop.on('mouseout', function () { - $(this).css('opacity', '.7'); - }); - $('.wrapper').append(slideToTop); - $(window).scroll(function () { - if ($(window).scrollTop() >= 150) { - if (!$(slideToTop).is(':visible')) { - $(slideToTop).fadeIn(500); - } - } else { - $(slideToTop).fadeOut(500); - } - }); - $(slideToTop).click(function () { - $("html,body").animate({ // firefox ie not support body, chrome support body. but found that new version chrome not support body too. - scrollTop: 0 - }, 100); - }); - - // left menu status v: js + server + cookie - $('.sidebar-toggle').click(function(){ - var xxljob_adminlte_settings = $.cookie('xxljob_adminlte_settings'); // on=open,off=close - if ('off' == xxljob_adminlte_settings) { - xxljob_adminlte_settings = 'on'; - } else { - xxljob_adminlte_settings = 'off'; - } - $.cookie('xxljob_adminlte_settings', xxljob_adminlte_settings, { expires: 7 }); //$.cookie('the_cookie', '', { expires: -1 }); - }); - - // left menu status v1: js + cookie - /* - var xxljob_adminlte_settings = $.cookie('xxljob_adminlte_settings'); - if (xxljob_adminlte_settings == 'off') { - $('body').addClass('sidebar-collapse'); - } - */ - - - // update pwd - $('#updatePwd').on('click', function(){ - $('#updatePwdModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - var updatePwdModalValidate = $("#updatePwdModal .form").validate({ - errorElement : 'span', - errorClass : 'help-block', - focusInvalid : true, - rules : { - oldPassword : { - required : true , - rangelength:[4,20] - }, - password : { - required : true , - rangelength:[4,20] - } - }, - messages : { - oldPassword : { - required : I18n.system_please_input +I18n.change_pwd_field_oldpwd, - rangelength : "密码长度限制为4~20" - }, - password : { - required : I18n.system_please_input +I18n.change_pwd_field_newpwd, - rangelength : "密码长度限制为4~20" - } - }, - highlight : function(element) { - $(element).closest('.form-group').addClass('has-error'); - }, - success : function(label) { - label.closest('.form-group').removeClass('has-error'); - label.remove(); - }, - errorPlacement : function(error, element) { - element.parent('div').append(error); - }, - submitHandler : function(form) { - $.post(base_url + "/user/updatePwd", $("#updatePwdModal .form").serialize(), function(data, status) { - if (data.code == 200) { - $('#updatePwdModal').modal('hide'); - - layer.msg( I18n.change_pwd_suc_to_logout ); - setTimeout(function(){ - $.post(base_url + "/logout", function(data, status) { - if (data.code == 200) { - window.location.href = base_url + "/"; - } else { - layer.open({ - icon: '2', - content: (data.msg|| I18n.logout_fail) - }); - } - }); - }, 500); - } else { - layer.open({ - icon: '2', - content: (data.msg|| I18n.change_pwd + I18n.system_fail ) - }); - } - }); - } - }); - $("#updatePwdModal").on('hide.bs.modal', function () { - $("#updatePwdModal .form")[0].reset(); - updatePwdModalValidate.resetForm(); - $("#updatePwdModal .form .form-group").removeClass("has-error"); - }); - -}); diff --git a/xxl-job-admin/src/main/resources/static/js/index.js b/xxl-job-admin/src/main/resources/static/js/index.js deleted file mode 100644 index 09111c58..00000000 --- a/xxl-job-admin/src/main/resources/static/js/index.js +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Created by xuxueli on 17/4/24. - */ -$(function () { - - // filter Time - var rangesConf = {}; - rangesConf[I18n.daterangepicker_ranges_today] = [moment().startOf('day'), moment().endOf('day')]; - rangesConf[I18n.daterangepicker_ranges_yesterday] = [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')]; - rangesConf[I18n.daterangepicker_ranges_this_month] = [moment().startOf('month'), moment().endOf('month')]; - rangesConf[I18n.daterangepicker_ranges_last_month] = [moment().subtract(1, 'months').startOf('month'), moment().subtract(1, 'months').endOf('month')]; - rangesConf[I18n.daterangepicker_ranges_recent_week] = [moment().subtract(1, 'weeks').startOf('day'), moment().endOf('day')]; - rangesConf[I18n.daterangepicker_ranges_recent_month] = [moment().subtract(1, 'months').startOf('day'), moment().endOf('day')]; - - $('#filterTime').daterangepicker({ - autoApply:false, - singleDatePicker:false, - showDropdowns:false, // 是否显示年月选择条件 - timePicker: true, // 是否显示小时和分钟选择条件 - timePickerIncrement: 10, // 时间的增量,单位为分钟 - timePicker24Hour : true, - opens : 'left', //日期选择框的弹出位置 - ranges: rangesConf, - locale : { - format: 'YYYY-MM-DD HH:mm:ss', - separator : ' - ', - customRangeLabel : I18n.daterangepicker_custom_name , - applyLabel : I18n.system_ok , - cancelLabel : I18n.system_cancel , - fromLabel : I18n.daterangepicker_custom_starttime , - toLabel : I18n.daterangepicker_custom_endtime , - daysOfWeek : I18n.daterangepicker_custom_daysofweek.split(',') , // '日', '一', '二', '三', '四', '五', '六' - monthNames : I18n.daterangepicker_custom_monthnames.split(',') , // '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月' - firstDay : 1 - }, - startDate: rangesConf[I18n.daterangepicker_ranges_recent_week][0] , - endDate: rangesConf[I18n.daterangepicker_ranges_recent_week][1] - }, function (start, end, label) { - freshChartDate(start, end); - }); - freshChartDate(rangesConf[I18n.daterangepicker_ranges_recent_week][0], rangesConf[I18n.daterangepicker_ranges_recent_week][1]); - - /** - * fresh Chart Date - * - * @param startDate - * @param endDate - */ - function freshChartDate(startDate, endDate) { - $.ajax({ - type : 'POST', - url : base_url + '/chartInfo', - data : { - 'startDate':startDate.format('YYYY-MM-DD HH:mm:ss'), - 'endDate':endDate.format('YYYY-MM-DD HH:mm:ss') - }, - dataType : "json", - success : function(data){ - if (data.code == 200) { - lineChartInit(data) - pieChartInit(data); - } else { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (data.msg || I18n.job_dashboard_report_loaddata_fail ), - icon: '2' - }); - } - } - }); - } - - /** - * line Chart Init - */ - function lineChartInit(data) { - var option = { - title: { - text: I18n.job_dashboard_date_report - }, - tooltip : { - trigger: 'axis', - axisPointer: { - type: 'cross', - label: { - backgroundColor: '#6a7985' - } - } - }, - legend: { - data:[I18n.joblog_status_suc, I18n.joblog_status_fail, I18n.joblog_status_running] - }, - toolbox: { - feature: { - /*saveAsImage: {}*/ - } - }, - grid: { - left: '3%', - right: '4%', - bottom: '3%', - containLabel: true - }, - xAxis : [ - { - type : 'category', - boundaryGap : false, - data : data.content.triggerDayList - } - ], - yAxis : [ - { - type : 'value' - } - ], - series : [ - { - name:I18n.joblog_status_suc, - type:'line', - stack: 'Total', - areaStyle: {normal: {}}, - data: data.content.triggerDayCountSucList - }, - { - name:I18n.joblog_status_fail, - type:'line', - stack: 'Total', - label: { - normal: { - show: true, - position: 'top' - } - }, - areaStyle: {normal: {}}, - data: data.content.triggerDayCountFailList - }, - { - name:I18n.joblog_status_running, - type:'line', - stack: 'Total', - areaStyle: {normal: {}}, - data: data.content.triggerDayCountRunningList - } - ], - color:['#00A65A', '#c23632', '#F39C12'] - }; - - var lineChart = echarts.init(document.getElementById('lineChart')); - lineChart.setOption(option); - } - - /** - * pie Chart Init - */ - function pieChartInit(data) { - var option = { - title : { - text: I18n.job_dashboard_rate_report , - /*subtext: 'subtext',*/ - x:'center' - }, - tooltip : { - trigger: 'item', - formatter: "{b} : {c} ({d}%)" - }, - legend: { - orient: 'vertical', - left: 'left', - data: [I18n.joblog_status_suc, I18n.joblog_status_fail, I18n.joblog_status_running ] - }, - series : [ - { - //name: '分布比例', - type: 'pie', - radius : '55%', - center: ['50%', '60%'], - data:[ - { - name:I18n.joblog_status_suc, - value:data.content.triggerCountSucTotal - }, - { - name:I18n.joblog_status_fail, - value:data.content.triggerCountFailTotal - }, - { - name:I18n.joblog_status_running, - value:data.content.triggerCountRunningTotal - } - ], - itemStyle: { - emphasis: { - shadowBlur: 10, - shadowOffsetX: 0, - shadowColor: 'rgba(0, 0, 0, 0.5)' - } - } - } - ], - color:['#00A65A', '#c23632', '#F39C12'] - }; - var pieChart = echarts.init(document.getElementById('pieChart')); - pieChart.setOption(option); - } - -}); diff --git a/xxl-job-admin/src/main/resources/static/js/jobcode.index.1.js b/xxl-job-admin/src/main/resources/static/js/jobcode.index.1.js deleted file mode 100644 index 668d6347..00000000 --- a/xxl-job-admin/src/main/resources/static/js/jobcode.index.1.js +++ /dev/null @@ -1,97 +0,0 @@ -$(function() { - - // init code editor - var codeEditor; - function initIde(glueSource) { - if (codeEditor == null) { - codeEditor = CodeMirror(document.getElementById("ideWindow"), { - mode : ideMode, - lineNumbers : true, - matchBrackets : true, - value: glueSource - }); - } else { - codeEditor.setValue(glueSource); - } - } - - initIde($("#version_now").val()); - - // code change - $(".source_version").click(function(){ - var sourceId = $(this).attr('version'); - var temp = $( "#" + sourceId ).val(); - - //codeEditor.setValue(''); - initIde(temp); - }); - - // code source save - $("#save").click(function() { - $('#saveModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - - $("#saveModal .ok").click(function() { - - var glueSource = codeEditor.getValue(); - var glueRemark = $("#glueRemark").val(); - - if (!glueRemark) { - layer.open({ - title: I18n.system_tips, - btn: [ I18n.system_ok], - content: I18n.system_please_input + I18n.jobinfo_glue_remark , - icon: '2' - }); - return; - } - if (glueRemark.length <4 || glueRemark.length > 100) { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: I18n.jobinfo_glue_remark_limit , - icon: '2' - }); - return; - } - - $.ajax({ - type : 'POST', - url : base_url + '/jobcode/save', - data : { - 'id' : id, - 'glueSource' : glueSource, - 'glueRemark' : glueRemark - }, - dataType : "json", - success : function(data){ - if (data.code == 200) { - layer.open({ - title: I18n.system_tips, - btn: [ I18n.system_ok ], - content: (I18n.system_save + I18n.system_success) , - icon: '1', - end: function(layero, index){ - //$(window).unbind('beforeunload'); - window.location.reload(); - } - }); - } else { - layer.open({ - title: I18n.system_tips, - btn: [ I18n.system_ok ], - content: (data.msg || (I18n.system_save + I18n.system_fail) ), - icon: '2' - }); - } - } - }); - - }); - - // before upload - /*$(window).bind('beforeunload',function(){ - return 'Glue尚未保存,确定离开Glue编辑器?'; - });*/ - -}); diff --git a/xxl-job-admin/src/main/resources/static/js/jobgroup.index.1.js b/xxl-job-admin/src/main/resources/static/js/jobgroup.index.1.js deleted file mode 100644 index 0e5b2356..00000000 --- a/xxl-job-admin/src/main/resources/static/js/jobgroup.index.1.js +++ /dev/null @@ -1,370 +0,0 @@ -$(function() { - - // init date tables - var jobGroupTable = $("#jobgroup_list").dataTable({ - "deferRender": true, - "processing" : true, - "serverSide": true, - "ajax": { - url: base_url + "/jobgroup/pageList", - type:"post", - data : function ( d ) { - var obj = {}; - obj.appname = $('#appname').val(); - obj.title = $('#title').val(); - obj.start = d.start; - obj.length = d.length; - return obj; - } - }, - "searching": false, - "ordering": false, - //"scrollX": true, // scroll x,close self-adaption - "columns": [ - { - "data": 'id', - "visible" : false - }, - { - "data": 'appname', - "visible" : true, - "width":'30%' - }, - { - "data": 'title', - "visible" : true, - "width":'30%' - }, - { - "data": 'addressType', - "width":'10%', - "visible" : true, - "render": function ( data, type, row ) { - if (row.addressType == 0) { - return I18n.jobgroup_field_addressType_0; - } else { - return I18n.jobgroup_field_addressType_1; - } - } - }, - { - "data": 'registryList', - "width":'15%', - "visible" : true, - "render": function ( data, type, row ) { - return row.registryList - ?'' - + I18n.system_show +' ( ' + row.registryList.length+ ' )' - :I18n.system_empty; - } - }, - { - "data": I18n.system_opt , - "width":'15%', - "render": function ( data, type, row ) { - return function(){ - // data - tableData['key'+row.id] = row; - - // opt - var html = '
\n' + - ' \n' + - ' \n' + - ' \n' + - '
'; - - return html; - }; - } - } - ], - "language" : { - "sProcessing" : I18n.dataTable_sProcessing , - "sLengthMenu" : I18n.dataTable_sLengthMenu , - "sZeroRecords" : I18n.dataTable_sZeroRecords , - "sInfo" : I18n.dataTable_sInfo , - "sInfoEmpty" : I18n.dataTable_sInfoEmpty , - "sInfoFiltered" : I18n.dataTable_sInfoFiltered , - "sInfoPostFix" : "", - "sSearch" : I18n.dataTable_sSearch , - "sUrl" : "", - "sEmptyTable" : I18n.dataTable_sEmptyTable , - "sLoadingRecords" : I18n.dataTable_sLoadingRecords , - "sInfoThousands" : ",", - "oPaginate" : { - "sFirst" : I18n.dataTable_sFirst , - "sPrevious" : I18n.dataTable_sPrevious , - "sNext" : I18n.dataTable_sNext , - "sLast" : I18n.dataTable_sLast - }, - "oAria" : { - "sSortAscending" : I18n.dataTable_sSortAscending , - "sSortDescending" : I18n.dataTable_sSortDescending - } - } - }); - - // table data - var tableData = {}; - - // search btn - $('#searchBtn').on('click', function(){ - jobGroupTable.fnDraw(); - }); - - // job registryinfo - $("#jobgroup_list").on('click', '.show_registryList',function() { - var id = $(this).attr("_id"); - var row = tableData['key'+id]; - - /*var html = '
'; - if (row.registryList) { - for (var index in row.registryList) { - html += (parseInt(index)+1) + '. ' + row.registryList[index] + '
'; - } - } - html += '
'; - - layer.open({ - title: I18n.jobinfo_opt_registryinfo , - btn: [ I18n.system_ok ], - content: html - });*/ - - var html = ''; - if (row.registryList) { - for (var index in row.registryList) { - html += ''; - html += ''; - } - } - html += '
' + (parseInt(index)+1) + '' + row.registryList[index] + '
'; - - $('#showRegistryListModal .data').html(html); - $('#showRegistryListModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - - - // opt_del - $("#jobgroup_list").on('click', '.opt_del',function() { - var id = $(this).parents('ul').attr("_id"); - - layer.confirm( (I18n.system_ok + I18n.jobgroup_del + '?') , { - icon: 3, - title: I18n.system_tips , - btn: [ I18n.system_ok, I18n.system_cancel ] - }, function(index){ - layer.close(index); - - $.ajax({ - type : 'POST', - url : base_url + '/jobgroup/remove', - data : {"id":id}, - dataType : "json", - success : function(data){ - if (data.code == 200) { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (I18n.jobgroup_del + I18n.system_success), - icon: '1', - end: function(layero, index){ - jobGroupTable.fnDraw(); - } - }); - } else { - layer.open({ - title: I18n.system_tips, - btn: [ I18n.system_ok ], - content: (data.msg || (I18n.jobgroup_del + I18n.system_fail)), - icon: '2' - }); - } - }, - }); - }); - }); - - - // jquery.validate “low letters start, limit contants、 letters、numbers and line-through.” - jQuery.validator.addMethod("myValid01", function(value, element) { - var length = value.length; - var valid = /^[a-z][a-zA-Z0-9-]*$/; - return this.optional(element) || valid.test(value); - }, I18n.jobgroup_field_appname_limit ); - - $('.add').on('click', function(){ - $('#addModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - var addModalValidate = $("#addModal .form").validate({ - errorElement : 'span', - errorClass : 'help-block', - focusInvalid : true, - rules : { - appname : { - required : true, - rangelength:[4,64], - myValid01 : true - }, - title : { - required : true, - rangelength:[4, 12] - } - }, - messages : { - appname : { - required : I18n.system_please_input+"AppName", - rangelength: I18n.jobgroup_field_appname_length , - myValid01: I18n.jobgroup_field_appname_limit - }, - title : { - required : I18n.system_please_input + I18n.jobgroup_field_title , - rangelength: I18n.jobgroup_field_title_length - } - }, - highlight : function(element) { - $(element).closest('.form-group').addClass('has-error'); - }, - success : function(label) { - label.closest('.form-group').removeClass('has-error'); - label.remove(); - }, - errorPlacement : function(error, element) { - element.parent('div').append(error); - }, - submitHandler : function(form) { - $.post(base_url + "/jobgroup/save", $("#addModal .form").serialize(), function(data, status) { - if (data.code == "200") { - $('#addModal').modal('hide'); - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: I18n.system_add_suc , - icon: '1', - end: function(layero, index){ - jobGroupTable.fnDraw(); - } - }); - } else { - layer.open({ - title: I18n.system_tips, - btn: [ I18n.system_ok ], - content: (data.msg || I18n.system_add_fail ), - icon: '2' - }); - } - }); - } - }); - $("#addModal").on('hide.bs.modal', function () { - $("#addModal .form")[0].reset(); - addModalValidate.resetForm(); - $("#addModal .form .form-group").removeClass("has-error"); - }); - - // addressType change - $("#addModal input[name=addressType], #updateModal input[name=addressType]").click(function(){ - var addressType = $(this).val(); - var $addressList = $(this).parents("form").find("textarea[name=addressList]"); - if (addressType == 0) { - $addressList.css("background-color", "#eee"); // 自动注册 - $addressList.attr("readonly","readonly"); - $addressList.val(""); - } else { - $addressList.css("background-color", "white"); - $addressList.removeAttr("readonly"); - } - }); - - // opt_edit - $("#jobgroup_list").on('click', '.opt_edit',function() { - var id = $(this).parents('ul').attr("_id"); - var row = tableData['key'+id]; - - $("#updateModal .form input[name='id']").val( row.id ); - $("#updateModal .form input[name='appname']").val( row.appname ); - $("#updateModal .form input[name='title']").val( row.title ); - - // 注册方式 - $("#updateModal .form input[name='addressType']").removeAttr('checked'); - $("#updateModal .form input[name='addressType'][value='"+ row.addressType +"']").click(); - // 机器地址 - $("#updateModal .form textarea[name='addressList']").val( row.addressList ); - - $('#updateModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - var updateModalValidate = $("#updateModal .form").validate({ - errorElement : 'span', - errorClass : 'help-block', - focusInvalid : true, - rules : { - appname : { - required : true, - rangelength:[4,64], - myValid01 : true - }, - title : { - required : true, - rangelength:[4, 12] - } - }, - messages : { - appname : { - required : I18n.system_please_input+"AppName", - rangelength: I18n.jobgroup_field_appname_length , - myValid01: I18n.jobgroup_field_appname_limit - }, - title : { - required : I18n.system_please_input + I18n.jobgroup_field_title , - rangelength: I18n.jobgroup_field_title_length - } - }, - highlight : function(element) { - $(element).closest('.form-group').addClass('has-error'); - }, - success : function(label) { - label.closest('.form-group').removeClass('has-error'); - label.remove(); - }, - errorPlacement : function(error, element) { - element.parent('div').append(error); - }, - submitHandler : function(form) { - $.post(base_url + "/jobgroup/update", $("#updateModal .form").serialize(), function(data, status) { - if (data.code == "200") { - $('#updateModal').modal('hide'); - - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: I18n.system_update_suc , - icon: '1', - end: function(layero, index){ - jobGroupTable.fnDraw(); - } - }); - } else { - layer.open({ - title: I18n.system_tips, - btn: [ I18n.system_ok ], - content: (data.msg || I18n.system_update_fail ), - icon: '2' - }); - } - }); - } - }); - $("#updateModal").on('hide.bs.modal', function () { - $("#updateModal .form")[0].reset(); - addModalValidate.resetForm(); - $("#updateModal .form .form-group").removeClass("has-error"); - }); - - -}); diff --git a/xxl-job-admin/src/main/resources/static/js/jobinfo.index.1.js b/xxl-job-admin/src/main/resources/static/js/jobinfo.index.1.js deleted file mode 100644 index b479e972..00000000 --- a/xxl-job-admin/src/main/resources/static/js/jobinfo.index.1.js +++ /dev/null @@ -1,739 +0,0 @@ -$(function() { - - // init date tables - var jobTable = $("#job_list").dataTable({ - "deferRender": true, - "processing" : true, - "serverSide": true, - "ajax": { - url: base_url + "/jobinfo/pageList", - type:"post", - data : function ( d ) { - var obj = {}; - obj.jobGroup = $('#jobGroup').val(); - obj.triggerStatus = $('#triggerStatus').val(); - obj.jobDesc = $('#jobDesc').val(); - obj.executorHandler = $('#executorHandler').val(); - obj.author = $('#author').val(); - obj.start = d.start; - obj.length = d.length; - return obj; - } - }, - "searching": false, - "ordering": false, - //"scrollX": true, // scroll x,close self-adaption - "columns": [ - { - "data": 'id', - "bSortable": false, - "visible" : true, - "width":'7%' - }, - { - "data": 'jobGroup', - "visible" : false, - "render": function ( data, type, row ) { - var groupMenu = $("#jobGroup").find("option"); - for ( var index in $("#jobGroup").find("option")) { - if ($(groupMenu[index]).attr('value') == data) { - return $(groupMenu[index]).html(); - } - } - return data; - } - }, - { - "data": 'jobDesc', - "visible" : true, - "width":'25%' - }, - { - "data": 'scheduleType', - "visible" : true, - "width":'13%', - "render": function ( data, type, row ) { - if (row.scheduleConf) { - return row.scheduleType + ':'+ row.scheduleConf; - } else { - return row.scheduleType; - } - } - }, - { - "data": 'glueType', - "width":'25%', - "visible" : true, - "render": function ( data, type, row ) { - var glueTypeTitle = findGlueTypeTitle(row.glueType); - if (row.executorHandler) { - return glueTypeTitle +":" + row.executorHandler; - } else { - return glueTypeTitle; - } - } - }, - { "data": 'executorParam', "visible" : false}, - { - "data": 'addTime', - "visible" : false, - "render": function ( data, type, row ) { - return data?moment(new Date(data)).format("YYYY-MM-DD HH:mm:ss"):""; - } - }, - { - "data": 'updateTime', - "visible" : false, - "render": function ( data, type, row ) { - return data?moment(new Date(data)).format("YYYY-MM-DD HH:mm:ss"):""; - } - }, - { "data": 'author', "visible" : true, "width":'10%'}, - { "data": 'alarmEmail', "visible" : false}, - { - "data": 'triggerStatus', - "width":'10%', - "visible" : true, - "render": function ( data, type, row ) { - // status - if (1 == data) { - return 'RUNNING'; - } else { - return 'STOP'; - } - return data; - } - }, - { - "data": I18n.system_opt , - "width":'10%', - "render": function ( data, type, row ) { - return function(){ - - // status - var start_stop_div = ""; - if (1 == row.triggerStatus ) { - start_stop_div = '
  • '+ I18n.jobinfo_opt_stop +'
  • \n'; - } else { - start_stop_div = '
  • '+ I18n.jobinfo_opt_start +'
  • \n'; - } - - // job_next_time_html - var job_next_time_html = ''; - if (row.scheduleType == 'CRON' || row.scheduleType == 'FIX_RATE') { - job_next_time_html = '
  • ' + I18n.jobinfo_opt_next_time + '
  • \n'; - } - - // log url - var logHref = base_url +'/joblog?jobId='+ row.id; - - // code url - var codeBtn = ""; - if ('BEAN' != row.glueType) { - var codeUrl = base_url +'/jobcode?jobId='+ row.id; - codeBtn = '
  • GLUE IDE
  • \n'; - codeBtn += '
  • \n'; - } - - // data - tableData['key'+row.id] = row; - - // opt - var html = '
    \n' + - ' \n' + - ' \n' + - ' \n' + - '
    '; - - return html; - }; - } - } - ], - "language" : { - "sProcessing" : I18n.dataTable_sProcessing , - "sLengthMenu" : I18n.dataTable_sLengthMenu , - "sZeroRecords" : I18n.dataTable_sZeroRecords , - "sInfo" : I18n.dataTable_sInfo , - "sInfoEmpty" : I18n.dataTable_sInfoEmpty , - "sInfoFiltered" : I18n.dataTable_sInfoFiltered , - "sInfoPostFix" : "", - "sSearch" : I18n.dataTable_sSearch , - "sUrl" : "", - "sEmptyTable" : I18n.dataTable_sEmptyTable , - "sLoadingRecords" : I18n.dataTable_sLoadingRecords , - "sInfoThousands" : ",", - "oPaginate" : { - "sFirst" : I18n.dataTable_sFirst , - "sPrevious" : I18n.dataTable_sPrevious , - "sNext" : I18n.dataTable_sNext , - "sLast" : I18n.dataTable_sLast - }, - "oAria" : { - "sSortAscending" : I18n.dataTable_sSortAscending , - "sSortDescending" : I18n.dataTable_sSortDescending - } - } - }); - - // table data - var tableData = {}; - - // search btn - $('#searchBtn').on('click', function(){ - jobTable.fnDraw(); - }); - - // jobGroup change - $('#jobGroup').on('change', function(){ - //reload - var jobGroup = $('#jobGroup').val(); - window.location.href = base_url + "/jobinfo?jobGroup=" + jobGroup; - }); - - // job operate - $("#job_list").on('click', '.job_operate',function() { - var typeName; - var url; - var needFresh = false; - - var type = $(this).attr("_type"); - if ("job_pause" == type) { - typeName = I18n.jobinfo_opt_stop ; - url = base_url + "/jobinfo/stop"; - needFresh = true; - } else if ("job_resume" == type) { - typeName = I18n.jobinfo_opt_start ; - url = base_url + "/jobinfo/start"; - needFresh = true; - } else if ("job_del" == type) { - typeName = I18n.system_opt_del ; - url = base_url + "/jobinfo/remove"; - needFresh = true; - } else { - return; - } - - var id = $(this).parents('ul').attr("_id"); - - layer.confirm( I18n.system_ok + typeName + '?', { - icon: 3, - title: I18n.system_tips , - btn: [ I18n.system_ok, I18n.system_cancel ] - }, function(index){ - layer.close(index); - - $.ajax({ - type : 'POST', - url : url, - data : { - "id" : id - }, - dataType : "json", - success : function(data){ - if (data.code == 200) { - layer.msg( typeName + I18n.system_success ); - if (needFresh) { - //window.location.reload(); - jobTable.fnDraw(false); - } - } else { - layer.msg( data.msg || typeName + I18n.system_fail ); - } - } - }); - }); - }); - - // job trigger - $("#job_list").on('click', '.job_trigger',function() { - var id = $(this).parents('ul').attr("_id"); - var row = tableData['key'+id]; - - $("#jobTriggerModal .form input[name='id']").val( row.id ); - $("#jobTriggerModal .form textarea[name='executorParam']").val( row.executorParam ); - - $('#jobTriggerModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - $("#jobTriggerModal .ok").on('click',function() { - $.ajax({ - type : 'POST', - url : base_url + "/jobinfo/trigger", - data : { - "id" : $("#jobTriggerModal .form input[name='id']").val(), - "executorParam" : $("#jobTriggerModal .textarea[name='executorParam']").val(), - "addressList" : $("#jobTriggerModal .textarea[name='addressList']").val() - }, - dataType : "json", - success : function(data){ - if (data.code == 200) { - $('#jobTriggerModal').modal('hide'); - - layer.msg( I18n.jobinfo_opt_run + I18n.system_success ); - } else { - layer.msg( data.msg || I18n.jobinfo_opt_run + I18n.system_fail ); - } - } - }); - }); - $("#jobTriggerModal").on('hide.bs.modal', function () { - $("#jobTriggerModal .form")[0].reset(); - }); - - - // job registryinfo - $("#job_list").on('click', '.job_registryinfo',function() { - var id = $(this).parents('ul').attr("_id"); - var row = tableData['key'+id]; - - var jobGroup = row.jobGroup; - - $.ajax({ - type : 'POST', - url : base_url + "/jobgroup/loadById", - data : { - "id" : jobGroup - }, - dataType : "json", - success : function(data){ - - var html = '
    '; - if (data.code == 200 && data.content.registryList) { - for (var index in data.content.registryList) { - html += (parseInt(index)+1) + '. ' + data.content.registryList[index] + '
    '; - } - } - html += '
    '; - - layer.open({ - title: I18n.jobinfo_opt_registryinfo , - btn: [ I18n.system_ok ], - content: html - }); - - } - }); - - }); - - // job_next_time - $("#job_list").on('click', '.job_next_time',function() { - var id = $(this).parents('ul').attr("_id"); - var row = tableData['key'+id]; - - $.ajax({ - type : 'POST', - url : base_url + "/jobinfo/nextTriggerTime", - data : { - "scheduleType" : row.scheduleType, - "scheduleConf" : row.scheduleConf - }, - dataType : "json", - success : function(data){ - - if (data.code != 200) { - layer.open({ - title: I18n.jobinfo_opt_next_time , - btn: [ I18n.system_ok ], - content: data.msg - }); - } else { - var html = '
    '; - if (data.code == 200 && data.content) { - for (var index in data.content) { - html += '' + data.content[index] + '
    '; - } - } - html += '
    '; - - layer.open({ - title: I18n.jobinfo_opt_next_time , - btn: [ I18n.system_ok ], - content: html - }); - } - - } - }); - - }); - - // add - $(".add").click(function(){ - - // init-cronGen - $("#addModal .form input[name='schedule_conf_CRON']").show().siblings().remove(); - $("#addModal .form input[name='schedule_conf_CRON']").cronGen({}); - - // 》init scheduleType - $("#updateModal .form select[name=scheduleType]").change(); - - // 》init glueType - $("#updateModal .form select[name=glueType]").change(); - - $('#addModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - var addModalValidate = $("#addModal .form").validate({ - errorElement : 'span', - errorClass : 'help-block', - focusInvalid : true, - rules : { - jobDesc : { - required : true, - maxlength: 50 - }, - author : { - required : true - }/*, - executorTimeout : { - digits:true - }, - executorFailRetryCount : { - digits:true - }*/ - }, - messages : { - jobDesc : { - required : I18n.system_please_input + I18n.jobinfo_field_jobdesc - }, - author : { - required : I18n.system_please_input + I18n.jobinfo_field_author - }/*, - executorTimeout : { - digits: I18n.system_please_input + I18n.system_digits - }, - executorFailRetryCount : { - digits: I18n.system_please_input + I18n.system_digits - }*/ - }, - highlight : function(element) { - $(element).closest('.form-group').addClass('has-error'); - }, - success : function(label) { - label.closest('.form-group').removeClass('has-error'); - label.remove(); - }, - errorPlacement : function(error, element) { - element.parent('div').append(error); - }, - submitHandler : function(form) { - - // process executorTimeout+executorFailRetryCount - var executorTimeout = $("#addModal .form input[name='executorTimeout']").val(); - if(!/^\d+$/.test(executorTimeout)) { - executorTimeout = 0; - } - $("#addModal .form input[name='executorTimeout']").val(executorTimeout); - var executorFailRetryCount = $("#addModal .form input[name='executorFailRetryCount']").val(); - if(!/^\d+$/.test(executorFailRetryCount)) { - executorFailRetryCount = 0; - } - $("#addModal .form input[name='executorFailRetryCount']").val(executorFailRetryCount); - - // process schedule_conf - var scheduleType = $("#addModal .form select[name='scheduleType']").val(); - var scheduleConf; - if (scheduleType == 'CRON') { - scheduleConf = $("#addModal .form input[name='cronGen_display']").val(); - } else if (scheduleType == 'FIX_RATE') { - scheduleConf = $("#addModal .form input[name='schedule_conf_FIX_RATE']").val(); - } else if (scheduleType == 'FIX_DELAY') { - scheduleConf = $("#addModal .form input[name='schedule_conf_FIX_DELAY']").val(); - } - $("#addModal .form input[name='scheduleConf']").val( scheduleConf ); - - $.post(base_url + "/jobinfo/add", $("#addModal .form").serialize(), function(data, status) { - if (data.code == "200") { - $('#addModal').modal('hide'); - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: I18n.system_add_suc , - icon: '1', - end: function(layero, index){ - jobTable.fnDraw(); - //window.location.reload(); - } - }); - } else { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (data.msg || I18n.system_add_fail), - icon: '2' - }); - } - }); - } - }); - $("#addModal").on('hide.bs.modal', function () { - addModalValidate.resetForm(); - $("#addModal .form")[0].reset(); - $("#addModal .form .form-group").removeClass("has-error"); - $(".remote_panel").show(); // remote - - $("#addModal .form input[name='executorHandler']").removeAttr("readonly"); - }); - - // scheduleType change - $(".scheduleType").change(function(){ - var scheduleType = $(this).val(); - $(this).parents("form").find(".schedule_conf").hide(); - $(this).parents("form").find(".schedule_conf_" + scheduleType).show(); - - }); - - // glueType change - $(".glueType").change(function(){ - // executorHandler - var $executorHandler = $(this).parents("form").find("input[name='executorHandler']"); - var glueType = $(this).val(); - if ('BEAN' != glueType) { - $executorHandler.val(""); - $executorHandler.attr("readonly","readonly"); - } else { - $executorHandler.removeAttr("readonly"); - } - }); - - $("#addModal .glueType").change(function(){ - // glueSource - var glueType = $(this).val(); - if ('GLUE_GROOVY'==glueType){ - $("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_java").val() ); - } else if ('GLUE_SHELL'==glueType){ - $("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_shell").val() ); - } else if ('GLUE_PYTHON'==glueType){ - $("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_python").val() ); - } else if ('GLUE_PHP'==glueType){ - $("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_php").val() ); - } else if ('GLUE_NODEJS'==glueType){ - $("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_nodejs").val() ); - } else if ('GLUE_POWERSHELL'==glueType){ - $("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_powershell").val() ); - } else { - $("#addModal .form textarea[name='glueSource']").val(""); - } - }); - - // update - $("#job_list").on('click', '.update',function() { - - var id = $(this).parents('ul').attr("_id"); - var row = tableData['key'+id]; - - // fill base - $("#updateModal .form input[name='id']").val( row.id ); - $('#updateModal .form select[name=jobGroup] option[value='+ row.jobGroup +']').prop('selected', true); - $("#updateModal .form input[name='jobDesc']").val( row.jobDesc ); - $("#updateModal .form input[name='author']").val( row.author ); - $("#updateModal .form input[name='alarmEmail']").val( row.alarmEmail ); - - // fill trigger - $('#updateModal .form select[name=scheduleType] option[value='+ row.scheduleType +']').prop('selected', true); - $("#updateModal .form input[name='scheduleConf']").val( row.scheduleConf ); - if (row.scheduleType == 'CRON') { - $("#updateModal .form input[name='schedule_conf_CRON']").val( row.scheduleConf ); - } else if (row.scheduleType == 'FIX_RATE') { - $("#updateModal .form input[name='schedule_conf_FIX_RATE']").val( row.scheduleConf ); - } else if (row.scheduleType == 'FIX_DELAY') { - $("#updateModal .form input[name='schedule_conf_FIX_DELAY']").val( row.scheduleConf ); - } - - // 》init scheduleType - $("#updateModal .form select[name=scheduleType]").change(); - - // fill job - $('#updateModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true); - $("#updateModal .form input[name='executorHandler']").val( row.executorHandler ); - $("#updateModal .form textarea[name='executorParam']").val( row.executorParam ); - - // 》init glueType - $("#updateModal .form select[name=glueType]").change(); - - // 》init-cronGen - $("#updateModal .form input[name='schedule_conf_CRON']").show().siblings().remove(); - $("#updateModal .form input[name='schedule_conf_CRON']").cronGen({}); - - // fill advanced - $('#updateModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true); - $("#updateModal .form input[name='childJobId']").val( row.childJobId ); - $('#updateModal .form select[name=misfireStrategy] option[value='+ row.misfireStrategy +']').prop('selected', true); - $('#updateModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true); - $("#updateModal .form input[name='executorTimeout']").val( row.executorTimeout ); - $("#updateModal .form input[name='executorFailRetryCount']").val( row.executorFailRetryCount ); - - // show - $('#updateModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - var updateModalValidate = $("#updateModal .form").validate({ - errorElement : 'span', - errorClass : 'help-block', - focusInvalid : true, - - rules : { - jobDesc : { - required : true, - maxlength: 50 - }, - author : { - required : true - } - }, - messages : { - jobDesc : { - required : I18n.system_please_input + I18n.jobinfo_field_jobdesc - }, - author : { - required : I18n.system_please_input + I18n.jobinfo_field_author - } - }, - highlight : function(element) { - $(element).closest('.form-group').addClass('has-error'); - }, - success : function(label) { - label.closest('.form-group').removeClass('has-error'); - label.remove(); - }, - errorPlacement : function(error, element) { - element.parent('div').append(error); - }, - submitHandler : function(form) { - - // process executorTimeout + executorFailRetryCount - var executorTimeout = $("#updateModal .form input[name='executorTimeout']").val(); - if(!/^\d+$/.test(executorTimeout)) { - executorTimeout = 0; - } - $("#updateModal .form input[name='executorTimeout']").val(executorTimeout); - var executorFailRetryCount = $("#updateModal .form input[name='executorFailRetryCount']").val(); - if(!/^\d+$/.test(executorFailRetryCount)) { - executorFailRetryCount = 0; - } - $("#updateModal .form input[name='executorFailRetryCount']").val(executorFailRetryCount); - - - // process schedule_conf - var scheduleType = $("#updateModal .form select[name='scheduleType']").val(); - var scheduleConf; - if (scheduleType == 'CRON') { - scheduleConf = $("#updateModal .form input[name='cronGen_display']").val(); - } else if (scheduleType == 'FIX_RATE') { - scheduleConf = $("#updateModal .form input[name='schedule_conf_FIX_RATE']").val(); - } else if (scheduleType == 'FIX_DELAY') { - scheduleConf = $("#updateModal .form input[name='schedule_conf_FIX_DELAY']").val(); - } - $("#updateModal .form input[name='scheduleConf']").val( scheduleConf ); - - // post - $.post(base_url + "/jobinfo/update", $("#updateModal .form").serialize(), function(data, status) { - if (data.code == "200") { - $('#updateModal').modal('hide'); - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: I18n.system_update_suc , - icon: '1', - end: function(layero, index){ - //window.location.reload(); - jobTable.fnDraw(); - } - }); - } else { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (data.msg || I18n.system_update_fail ), - icon: '2' - }); - } - }); - } - }); - $("#updateModal").on('hide.bs.modal', function () { - updateModalValidate.resetForm(); - $("#updateModal .form")[0].reset(); - $("#updateModal .form .form-group").removeClass("has-error"); - }); - - /** - * find title by name, GlueType - */ - function findGlueTypeTitle(glueType) { - var glueTypeTitle; - $("#addModal .form select[name=glueType] option").each(function () { - var name = $(this).val(); - var title = $(this).text(); - if (glueType == name) { - glueTypeTitle = title; - return false - } - }); - return glueTypeTitle; - } - - // job_copy - $("#job_list").on('click', '.job_copy',function() { - - var id = $(this).parents('ul').attr("_id"); - var row = tableData['key'+id]; - - // fill base - $('#addModal .form select[name=jobGroup] option[value='+ row.jobGroup +']').prop('selected', true); - $("#addModal .form input[name='jobDesc']").val( row.jobDesc ); - $("#addModal .form input[name='author']").val( row.author ); - $("#addModal .form input[name='alarmEmail']").val( row.alarmEmail ); - - // fill trigger - $('#addModal .form select[name=scheduleType] option[value='+ row.scheduleType +']').prop('selected', true); - $("#addModal .form input[name='scheduleConf']").val( row.scheduleConf ); - if (row.scheduleType == 'CRON') { - $("#addModal .form input[name='schedule_conf_CRON']").val( row.scheduleConf ); - } else if (row.scheduleType == 'FIX_RATE') { - $("#addModal .form input[name='schedule_conf_FIX_RATE']").val( row.scheduleConf ); - } else if (row.scheduleType == 'FIX_DELAY') { - $("#addModal .form input[name='schedule_conf_FIX_DELAY']").val( row.scheduleConf ); - } - - // 》init scheduleType - $("#addModal .form select[name=scheduleType]").change(); - - // fill job - $('#addModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true); - $("#addModal .form input[name='executorHandler']").val( row.executorHandler ); - $("#addModal .form textarea[name='executorParam']").val( row.executorParam ); - - // 》init glueType - $("#addModal .form select[name=glueType]").change(); - - // 》init-cronGen - $("#addModal .form input[name='schedule_conf_CRON']").show().siblings().remove(); - $("#addModal .form input[name='schedule_conf_CRON']").cronGen({}); - - // fill advanced - $('#addModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true); - $("#addModal .form input[name='childJobId']").val( row.childJobId ); - $('#addModal .form select[name=misfireStrategy] option[value='+ row.misfireStrategy +']').prop('selected', true); - $('#addModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true); - $("#addModal .form input[name='executorTimeout']").val( row.executorTimeout ); - $("#addModal .form input[name='executorFailRetryCount']").val( row.executorFailRetryCount ); - - // show - $('#addModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - -}); diff --git a/xxl-job-admin/src/main/resources/static/js/joblog.detail.1.js b/xxl-job-admin/src/main/resources/static/js/joblog.detail.1.js deleted file mode 100644 index 0638eee2..00000000 --- a/xxl-job-admin/src/main/resources/static/js/joblog.detail.1.js +++ /dev/null @@ -1,89 +0,0 @@ -$(function() { - - // trigger fail, end - if ( !(triggerCode == 200 || handleCode != 0) ) { - $('#logConsoleRunning').hide(); - $('#logConsole').append(''+ I18n.joblog_rolling_log_triggerfail +''); - return; - } - - // pull log - var fromLineNum = 1; // [from, to], start as 1 - var pullFailCount = 0; - function pullLog() { - // pullFailCount, max=20 - if (pullFailCount++ > 20) { - logRunStop(''+ I18n.joblog_rolling_log_failoften +''); - return; - } - - // load - console.log("pullLog, fromLineNum:" + fromLineNum); - - $.ajax({ - type : 'POST', - async: false, // sync, make log ordered - url : base_url + '/joblog/logDetailCat', - data : { - "logId":logId, - "fromLineNum":fromLineNum - }, - dataType : "json", - success : function(data){ - - if (data.code == 200) { - if (!data.content) { - console.log('pullLog fail'); - return; - } - if (fromLineNum != data.content.fromLineNum) { - console.log('pullLog fromLineNum not match'); - return; - } - if (fromLineNum > data.content.toLineNum ) { - console.log('pullLog already line-end'); - - // valid end - if (data.content.end) { - logRunStop('
    [Rolling Log Finish]'); - return; - } - - return; - } - - // append content - fromLineNum = data.content.toLineNum + 1; - $('#logConsole').append(data.content.logContent); - pullFailCount = 0; - - // scroll to bottom - scrollTo(0, document.body.scrollHeight); // $('#logConsolePre').scrollTop( document.body.scrollHeight + 300 ); - - } else { - console.log('pullLog fail:'+data.msg); - } - } - }); - } - - // pull first page - pullLog(); - - // handler already callback, end - if (handleCode > 0) { - logRunStop('
    [Load Log Finish]'); - return; - } - - // round until end - var logRun = setInterval(function () { - pullLog() - }, 3000); - function logRunStop(content){ - $('#logConsoleRunning').hide(); - logRun = window.clearInterval(logRun); - $('#logConsole').append(content); - } - -}); diff --git a/xxl-job-admin/src/main/resources/static/js/joblog.index.1.js b/xxl-job-admin/src/main/resources/static/js/joblog.index.1.js deleted file mode 100644 index e0fc3f20..00000000 --- a/xxl-job-admin/src/main/resources/static/js/joblog.index.1.js +++ /dev/null @@ -1,396 +0,0 @@ -$(function() { - - // jobGroup change, job list init and select - $("#jobGroup").on("change", function () { - var jobGroup = $(this).children('option:selected').val(); - $.ajax({ - type : 'POST', - async: false, // async, avoid js invoke pagelist before jobId data init - url : base_url + '/joblog/getJobsByGroup', - data : {"jobGroup":jobGroup}, - dataType : "json", - success : function(data){ - if (data.code == 200) { - $("#jobId").html( '' ); - $.each(data.content, function (n, value) { - $("#jobId").append(''); - }); - if ($("#jobId").attr("paramVal")){ - $("#jobId").find("option[value='" + $("#jobId").attr("paramVal") + "']").attr("selected",true); - } - } else { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (data.msg || I18n.system_api_error ), - icon: '2' - }); - } - }, - }); - }); - if ($("#jobGroup").attr("paramVal")){ - $("#jobGroup").find("option[value='" + $("#jobGroup").attr("paramVal") + "']").attr("selected",true); - $("#jobGroup").change(); - } - - // filter Time - var rangesConf = {}; - rangesConf[I18n.daterangepicker_ranges_recent_hour] = [moment().subtract(1, 'hours'), moment()]; - rangesConf[I18n.daterangepicker_ranges_today] = [moment().startOf('day'), moment().endOf('day')]; - rangesConf[I18n.daterangepicker_ranges_yesterday] = [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')]; - rangesConf[I18n.daterangepicker_ranges_this_month] = [moment().startOf('month'), moment().endOf('month')]; - rangesConf[I18n.daterangepicker_ranges_last_month] = [moment().subtract(1, 'months').startOf('month'), moment().subtract(1, 'months').endOf('month')]; - rangesConf[I18n.daterangepicker_ranges_recent_week] = [moment().subtract(1, 'weeks').startOf('day'), moment().endOf('day')]; - rangesConf[I18n.daterangepicker_ranges_recent_month] = [moment().subtract(1, 'months').startOf('day'), moment().endOf('day')]; - - $('#filterTime').daterangepicker({ - autoApply:false, - singleDatePicker:false, - showDropdowns:false, // 是否显示年月选择条件 - timePicker: true, // 是否显示小时和分钟选择条件 - timePickerIncrement: 10, // 时间的增量,单位为分钟 - timePicker24Hour : true, - opens : 'left', //日期选择框的弹出位置 - ranges: rangesConf, - locale : { - format: 'YYYY-MM-DD HH:mm:ss', - separator : ' - ', - customRangeLabel : I18n.daterangepicker_custom_name , - applyLabel : I18n.system_ok , - cancelLabel : I18n.system_cancel , - fromLabel : I18n.daterangepicker_custom_starttime , - toLabel : I18n.daterangepicker_custom_endtime , - daysOfWeek : I18n.daterangepicker_custom_daysofweek.split(',') , // '日', '一', '二', '三', '四', '五', '六' - monthNames : I18n.daterangepicker_custom_monthnames.split(',') , // '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月' - firstDay : 1 - }, - startDate: rangesConf[I18n.daterangepicker_ranges_today][0], - endDate: rangesConf[I18n.daterangepicker_ranges_today][1] - }); - - // init date tables - var logTable = $("#joblog_list").dataTable({ - "deferRender": true, - "processing" : true, - "serverSide": true, - "ajax": { - url: base_url + "/joblog/pageList" , - type:"post", - data : function ( d ) { - var obj = {}; - obj.jobGroup = $('#jobGroup').val(); - obj.jobId = $('#jobId').val(); - obj.logStatus = $('#logStatus').val(); - obj.filterTime = $('#filterTime').val(); - obj.start = d.start; - obj.length = d.length; - return obj; - } - }, - "searching": false, - "ordering": false, - //"scrollX": false, - "columns": [ - { - "data": 'jobId', - "visible" : true, - "width":'10%', - "render": function ( data, type, row ) { - - var jobhandler = ''; - if (row.executorHandler) { - jobhandler = "
    JobHandler:" + row.executorHandler; - } - - var temp = ''; - temp += I18n.joblog_field_executorAddress + ':' + (row.executorAddress?row.executorAddress:''); - temp += jobhandler; - temp += '
    '+ I18n.jobinfo_field_executorparam +':' + row.executorParam; - - return ''+ row.jobId +''+ temp +''; - } - }, - { "data": 'jobGroup', "visible" : false}, - { - "data": 'triggerTime', - "width":'20%', - "render": function ( data, type, row ) { - return data?moment(data).format("YYYY-MM-DD HH:mm:ss"):""; - } - }, - { - "data": 'triggerCode', - "width":'10%', - "render": function ( data, type, row ) { - var html = data; - if (data == 200) { - html = ''+ I18n.system_success +''; - } else if (data == 500) { - html = ''+ I18n.system_fail +''; - } else if (data == 0) { - html = ''; - } - return html; - } - }, - { - "data": 'triggerMsg', - "width":'10%', - "render": function ( data, type, row ) { - return data?''+ I18n.system_show +''+ data +'':I18n.system_empty; - } - }, - { - "data": 'handleTime', - "width":'20%', - "render": function ( data, type, row ) { - return data?moment(data).format("YYYY-MM-DD HH:mm:ss"):""; - } - }, - { - "data": 'handleCode', - "width":'10%', - "render": function ( data, type, row ) { - var html = data; - if (data == 200) { - html = ''+ I18n.joblog_handleCode_200 +''; - } else if (data == 500) { - html = ''+ I18n.joblog_handleCode_500 +''; - } else if (data == 502) { - html = ''+ I18n.joblog_handleCode_502 +''; - } else if (data == 0) { - html = ''; - } - return html; - } - }, - { - "data": 'handleMsg', - "width":'10%', - "render": function ( data, type, row ) { - return data?''+ I18n.system_show +''+ data +'':I18n.system_empty; - } - }, - { - "data": 'handleMsg' , - "bSortable": false, - "width":'10%', - "render": function ( data, type, row ) { - // better support expression or string, not function - return function () { - if (row.triggerCode == 200 || row.handleCode != 0){ - - /*var temp = ''+ I18n.joblog_rolling_log +''; - if(row.handleCode == 0){ - temp += '
    '+ I18n.joblog_kill_log +''; - }*/ - //return temp; - - var logKillDiv = ''; - if(row.handleCode == 0){ - logKillDiv = '
  • \n' + - '
  • '+ I18n.joblog_kill_log +'
  • \n'; - } - - var html = '
    \n' + - ' \n' + - ' \n' + - ' \n' + - '
    '; - - return html; - } - return null; - } - } - } - ], - "language" : { - "sProcessing" : I18n.dataTable_sProcessing , - "sLengthMenu" : I18n.dataTable_sLengthMenu , - "sZeroRecords" : I18n.dataTable_sZeroRecords , - "sInfo" : I18n.dataTable_sInfo , - "sInfoEmpty" : I18n.dataTable_sInfoEmpty , - "sInfoFiltered" : I18n.dataTable_sInfoFiltered , - "sInfoPostFix" : "", - "sSearch" : I18n.dataTable_sSearch , - "sUrl" : "", - "sEmptyTable" : I18n.dataTable_sEmptyTable , - "sLoadingRecords" : I18n.dataTable_sLoadingRecords , - "sInfoThousands" : ",", - "oPaginate" : { - "sFirst" : I18n.dataTable_sFirst , - "sPrevious" : I18n.dataTable_sPrevious , - "sNext" : I18n.dataTable_sNext , - "sLast" : I18n.dataTable_sLast - }, - "oAria" : { - "sSortAscending" : I18n.dataTable_sSortAscending , - "sSortDescending" : I18n.dataTable_sSortDescending - } - } - }); - logTable.on('xhr.dt',function(e, settings, json, xhr) { - if (json.code && json.code != 200) { - layer.msg( json.msg || I18n.system_api_error ); - } - }); - - // logTips alert - $('#joblog_list').on('click', '.logTips', function(){ - var msg = $(this).find('span').html(); - ComAlertTec.show(msg); - }); - - // search Btn - $('#searchBtn').on('click', function(){ - logTable.fnDraw(); - }); - - // logDetail look - $('#joblog_list').on('click', '.logDetail', function(){ - var _id = $(this).attr('_id'); - - window.open(base_url + '/joblog/logDetailPage?id=' + _id); - return; - }); - - /** - * log Kill - */ - $('#joblog_list').on('click', '.logKill', function(){ - var _id = $(this).attr('_id'); - - layer.confirm( (I18n.system_ok + I18n.joblog_kill_log + '?'), { - icon: 3, - title: I18n.system_tips , - btn: [ I18n.system_ok, I18n.system_cancel ] - }, function(index){ - layer.close(index); - - $.ajax({ - type : 'POST', - url : base_url + '/joblog/logKill', - data : {"id":_id}, - dataType : "json", - success : function(data){ - if (data.code == 200) { - layer.open({ - title: I18n.system_tips, - btn: [ I18n.system_ok ], - content: I18n.system_opt_suc , - icon: '1', - end: function(layero, index){ - logTable.fnDraw(); - } - }); - } else { - layer.open({ - title: I18n.system_tips, - btn: [ I18n.system_ok ], - content: (data.msg || I18n.system_opt_fail ), - icon: '2' - }); - } - }, - }); - }); - - }); - - /** - * clear Log - */ - $('#clearLog').on('click', function(){ - - var jobGroup = $('#jobGroup').val(); - var jobId = $('#jobId').val(); - - var jobGroupText = $("#jobGroup").find("option:selected").text(); - var jobIdText = $("#jobId").find("option:selected").text(); - - $('#clearLogModal input[name=jobGroup]').val(jobGroup); - $('#clearLogModal input[name=jobId]').val(jobId); - - $('#clearLogModal .jobGroupText').val(jobGroupText); - $('#clearLogModal .jobIdText').val(jobIdText); - - $('#clearLogModal').modal('show'); - - }); - $("#clearLogModal .ok").on('click', function(){ - $.post(base_url + "/joblog/clearLog", $("#clearLogModal .form").serialize(), function(data, status) { - if (data.code == "200") { - $('#clearLogModal').modal('hide'); - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (I18n.joblog_clean_log + I18n.system_success) , - icon: '1', - end: function(layero, index){ - logTable.fnDraw(); - } - }); - } else { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (data.msg || (I18n.joblog_clean_log + I18n.system_fail) ), - icon: '2' - }); - } - }); - }); - $("#clearLogModal").on('hide.bs.modal', function () { - $("#clearLogModal .form")[0].reset(); - }); - -}); - - -// Com Alert by Tec theme -var ComAlertTec = { - html:function(){ - var html = - ''; - return html; - }, - show:function(msg, callback){ - // dom init - if ($('#ComAlertTec').length == 0){ - $('body').append(ComAlertTec.html()); - } - - // init com alert - $('#ComAlertTec .alert').html(msg); - $('#ComAlertTec').modal('show'); - - $('#ComAlertTec .ok').click(function(){ - $('#ComAlertTec').modal('hide'); - if(typeof callback == 'function') { - callback(); - } - }); - } -}; diff --git a/xxl-job-admin/src/main/resources/static/js/login.1.js b/xxl-job-admin/src/main/resources/static/js/login.1.js deleted file mode 100644 index ef409615..00000000 --- a/xxl-job-admin/src/main/resources/static/js/login.1.js +++ /dev/null @@ -1,66 +0,0 @@ -$(function(){ - - // input iCheck - $('input').iCheck({ - checkboxClass: 'icheckbox_square-blue', - radioClass: 'iradio_square-blue', - increaseArea: '20%' // optional - }); - - // login Form Valid - var loginFormValid = $("#loginForm").validate({ - errorElement : 'span', - errorClass : 'help-block', - focusInvalid : true, - rules : { - userName : { - required : true , - minlength: 4, - maxlength: 20 - }, - password : { - required : true , - minlength: 4, - maxlength: 20 - } - }, - messages : { - userName : { - required : I18n.login_username_empty, - minlength : I18n.login_username_lt_4 - }, - password : { - required : I18n.login_password_empty , - minlength : I18n.login_password_lt_4 - /*,maxlength:"登录密码不应超过20位"*/ - } - }, - highlight : function(element) { - $(element).closest('.form-group').addClass('has-error'); - }, - success : function(label) { - label.closest('.form-group').removeClass('has-error'); - label.remove(); - }, - errorPlacement : function(error, element) { - element.parent('div').append(error); - }, - submitHandler : function(form) { - $.post(base_url + "/login", $("#loginForm").serialize(), function(data, status) { - if (data.code == "200") { - layer.msg( I18n.login_success ); - setTimeout(function(){ - window.location.href = base_url + "/"; - }, 500); - } else { - layer.open({ - title: I18n.system_tips, - btn: [ I18n.system_ok ], - content: (data.msg || I18n.login_fail ), - icon: '2' - }); - } - }); - } - }); -}); \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/static/js/user.index.1.js b/xxl-job-admin/src/main/resources/static/js/user.index.1.js deleted file mode 100644 index 48d3f302..00000000 --- a/xxl-job-admin/src/main/resources/static/js/user.index.1.js +++ /dev/null @@ -1,328 +0,0 @@ -$(function() { - - // init date tables - var userListTable = $("#user_list").dataTable({ - "deferRender": true, - "processing" : true, - "serverSide": true, - "ajax": { - url: base_url + "/user/pageList", - type:"post", - data : function ( d ) { - var obj = {}; - obj.username = $('#username').val(); - obj.role = $('#role').val(); - obj.start = d.start; - obj.length = d.length; - return obj; - } - }, - "searching": false, - "ordering": false, - //"scrollX": true, // scroll x,close self-adaption - "columns": [ - { - "data": 'id', - "visible" : false, - "width":'10%' - }, - { - "data": 'username', - "visible" : true, - "width":'20%' - }, - { - "data": 'password', - "visible" : false, - "width":'20%', - "render": function ( data, type, row ) { - return '*********'; - } - }, - { - "data": 'role', - "visible" : true, - "width":'10%', - "render": function ( data, type, row ) { - if (data == 1) { - return I18n.user_role_admin - } else { - return I18n.user_role_normal - } - } - }, - { - "data": 'permission', - "width":'10%', - "visible" : false - }, - { - "data": I18n.system_opt , - "width":'15%', - "render": function ( data, type, row ) { - return function(){ - // html - tableData['key'+row.id] = row; - var html = '

    '+ - ' '+ - ' '+ - '

    '; - - return html; - }; - } - } - ], - "language" : { - "sProcessing" : I18n.dataTable_sProcessing , - "sLengthMenu" : I18n.dataTable_sLengthMenu , - "sZeroRecords" : I18n.dataTable_sZeroRecords , - "sInfo" : I18n.dataTable_sInfo , - "sInfoEmpty" : I18n.dataTable_sInfoEmpty , - "sInfoFiltered" : I18n.dataTable_sInfoFiltered , - "sInfoPostFix" : "", - "sSearch" : I18n.dataTable_sSearch , - "sUrl" : "", - "sEmptyTable" : I18n.dataTable_sEmptyTable , - "sLoadingRecords" : I18n.dataTable_sLoadingRecords , - "sInfoThousands" : ",", - "oPaginate" : { - "sFirst" : I18n.dataTable_sFirst , - "sPrevious" : I18n.dataTable_sPrevious , - "sNext" : I18n.dataTable_sNext , - "sLast" : I18n.dataTable_sLast - }, - "oAria" : { - "sSortAscending" : I18n.dataTable_sSortAscending , - "sSortDescending" : I18n.dataTable_sSortDescending - } - } - }); - - // table data - var tableData = {}; - - // search btn - $('#searchBtn').on('click', function(){ - userListTable.fnDraw(); - }); - - // job operate - $("#user_list").on('click', '.delete',function() { - var id = $(this).parent('p').attr("id"); - - layer.confirm( I18n.system_ok + I18n.system_opt_del + '?', { - icon: 3, - title: I18n.system_tips , - btn: [ I18n.system_ok, I18n.system_cancel ] - }, function(index){ - layer.close(index); - - $.ajax({ - type : 'POST', - url : base_url + "/user/remove", - data : { - "id" : id - }, - dataType : "json", - success : function(data){ - if (data.code == 200) { - layer.msg( I18n.system_success ); - userListTable.fnDraw(false); - } else { - layer.msg( data.msg || I18n.system_opt_del + I18n.system_fail ); - } - } - }); - }); - }); - - // add role - $("#addModal .form input[name=role]").change(function () { - var role = $(this).val(); - if (role == 1) { - $("#addModal .form input[name=permission]").parents('.form-group').hide(); - } else { - $("#addModal .form input[name=permission]").parents('.form-group').show(); - } - $("#addModal .form input[name='permission']").prop("checked",false); - }); - - jQuery.validator.addMethod("myValid01", function(value, element) { - var length = value.length; - var valid = /^[a-z][a-z0-9]*$/; - return this.optional(element) || valid.test(value); - }, I18n.user_username_valid ); - - // add - $(".add").click(function(){ - $('#addModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - var addModalValidate = $("#addModal .form").validate({ - errorElement : 'span', - errorClass : 'help-block', - focusInvalid : true, - rules : { - username : { - required : true, - rangelength:[4, 20], - myValid01: true - }, - password : { - required : true, - rangelength:[4, 20] - } - }, - messages : { - username : { - required : I18n.system_please_input + I18n.user_username, - rangelength: I18n.system_lengh_limit + "[4-20]" - }, - password : { - required : I18n.system_please_input + I18n.user_password, - rangelength: I18n.system_lengh_limit + "[4-20]" - } - }, - highlight : function(element) { - $(element).closest('.form-group').addClass('has-error'); - }, - success : function(label) { - label.closest('.form-group').removeClass('has-error'); - label.remove(); - }, - errorPlacement : function(error, element) { - element.parent('div').append(error); - }, - submitHandler : function(form) { - - var permissionArr = []; - $("#addModal .form input[name=permission]:checked").each(function(){ - permissionArr.push($(this).val()); - }); - - var paramData = { - "username": $("#addModal .form input[name=username]").val(), - "password": $("#addModal .form input[name=password]").val(), - "role": $("#addModal .form input[name=role]:checked").val(), - "permission": permissionArr.join(',') - }; - - $.post(base_url + "/user/add", paramData, function(data, status) { - if (data.code == "200") { - $('#addModal').modal('hide'); - - layer.msg( I18n.system_add_suc ); - userListTable.fnDraw(); - } else { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (data.msg || I18n.system_add_fail), - icon: '2' - }); - } - }); - } - }); - $("#addModal").on('hide.bs.modal', function () { - $("#addModal .form")[0].reset(); - addModalValidate.resetForm(); - $("#addModal .form .form-group").removeClass("has-error"); - $(".remote_panel").show(); // remote - - $("#addModal .form input[name=permission]").parents('.form-group').show(); - }); - - // update role - $("#updateModal .form input[name=role]").change(function () { - var role = $(this).val(); - if (role == 1) { - $("#updateModal .form input[name=permission]").parents('.form-group').hide(); - } else { - $("#updateModal .form input[name=permission]").parents('.form-group').show(); - } - $("#updateModal .form input[name='permission']").prop("checked",false); - }); - - // update - $("#user_list").on('click', '.update',function() { - - var id = $(this).parent('p').attr("id"); - var row = tableData['key'+id]; - - // base data - $("#updateModal .form input[name='id']").val( row.id ); - $("#updateModal .form input[name='username']").val( row.username ); - $("#updateModal .form input[name='password']").val( '' ); - $("#updateModal .form input[name='role'][value='"+ row.role +"']").click(); - var permissionArr = []; - if (row.permission) { - permissionArr = row.permission.split(","); - } - $("#updateModal .form input[name='permission']").each(function () { - if($.inArray($(this).val(), permissionArr) > -1) { - $(this).prop("checked",true); - } else { - $(this).prop("checked",false); - } - }); - - // show - $('#updateModal').modal({backdrop: false, keyboard: false}).modal('show'); - }); - var updateModalValidate = $("#updateModal .form").validate({ - errorElement : 'span', - errorClass : 'help-block', - focusInvalid : true, - highlight : function(element) { - $(element).closest('.form-group').addClass('has-error'); - }, - success : function(label) { - label.closest('.form-group').removeClass('has-error'); - label.remove(); - }, - errorPlacement : function(error, element) { - element.parent('div').append(error); - }, - submitHandler : function(form) { - - var permissionArr =[]; - $("#updateModal .form input[name=permission]:checked").each(function(){ - permissionArr.push($(this).val()); - }); - - var paramData = { - "id": $("#updateModal .form input[name=id]").val(), - "username": $("#updateModal .form input[name=username]").val(), - "password": $("#updateModal .form input[name=password]").val(), - "role": $("#updateModal .form input[name=role]:checked").val(), - "permission": permissionArr.join(',') - }; - - $.post(base_url + "/user/update", paramData, function(data, status) { - if (data.code == "200") { - $('#updateModal').modal('hide'); - - layer.msg( I18n.system_update_suc ); - userListTable.fnDraw(); - } else { - layer.open({ - title: I18n.system_tips , - btn: [ I18n.system_ok ], - content: (data.msg || I18n.system_update_fail), - icon: '2' - }); - } - }); - } - }); - $("#updateModal").on('hide.bs.modal', function () { - $("#updateModal .form")[0].reset(); - updateModalValidate.resetForm(); - $("#updateModal .form .form-group").removeClass("has-error"); - $(".remote_panel").show(); // remote - - $("#updateModal .form input[name=permission]").parents('.form-group').show(); - }); - -}); diff --git a/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/bootstrap-table.min.css b/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/bootstrap-table.min.css new file mode 100644 index 00000000..77566111 --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/bootstrap-table.min.css @@ -0,0 +1,10 @@ +/** + * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) + * + * @version v1.24.1 + * @homepage https://bootstrap-table.com + * @author wenzhixin (http://wenzhixin.net.cn/) + * @license MIT + */ + +@charset "UTF-8";.bootstrap-table .fixed-table-toolbar::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-toolbar .bs-bars,.bootstrap-table .fixed-table-toolbar .columns,.bootstrap-table .fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group>.btn{border-radius:0}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu{text-align:left;max-height:300px;overflow:auto;-ms-overflow-style:scrollbar;z-index:1001}.bootstrap-table .fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.4286}.bootstrap-table .fixed-table-toolbar .columns-left{margin-right:5px}.bootstrap-table .fixed-table-toolbar .columns-right{margin-left:5px}.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu{right:0;left:auto}.bootstrap-table .fixed-table-container{position:relative;clear:both}.bootstrap-table .fixed-table-container .table{width:100%;margin-bottom:0!important}.bootstrap-table .fixed-table-container .table td,.bootstrap-table .fixed-table-container .table th{vertical-align:middle;box-sizing:border-box}.bootstrap-table .fixed-table-container .table tfoot th,.bootstrap-table .fixed-table-container .table thead th{vertical-align:bottom;padding:0;margin:0}.bootstrap-table .fixed-table-container .table tfoot th:focus,.bootstrap-table .fixed-table-container .table thead th:focus{outline:0 solid transparent}.bootstrap-table .fixed-table-container .table tfoot th.detail,.bootstrap-table .fixed-table-container .table thead th.detail{width:30px}.bootstrap-table .fixed-table-container .table tfoot th .th-inner,.bootstrap-table .fixed-table-container .table thead th .th-inner{padding:.75rem;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bootstrap-table .fixed-table-container .table tfoot th .sortable,.bootstrap-table .fixed-table-container .table thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px!important}.bootstrap-table .fixed-table-container .table tfoot th .sortable.sortable-center,.bootstrap-table .fixed-table-container .table thead th .sortable.sortable-center{padding-left:20px!important;padding-right:20px!important}.bootstrap-table .fixed-table-container .table tfoot th .both,.bootstrap-table .fixed-table-container .table thead th .both{background-image:url('data:image/svg+xml;utf8,');background-size:16px 16px;background-position:center right 2px}.bootstrap-table .fixed-table-container .table tfoot th .asc,.bootstrap-table .fixed-table-container .table thead th .asc{background-image:url('data:image/svg+xml;utf8,')}.bootstrap-table .fixed-table-container .table tfoot th .desc,.bootstrap-table .fixed-table-container .table thead th .desc{background-image:url('data:image/svg+xml;utf8,')}.bootstrap-table .fixed-table-container .table tbody tr.selected td{background-color:rgba(0,0,0,.075)}.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td{text-align:center}.bootstrap-table .fixed-table-container .table tbody tr .card-view{display:flex}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title{font-weight:700;display:inline-block;min-width:30%;width:auto!important;text-align:left!important}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value{width:100%!important;text-align:left!important}.bootstrap-table .fixed-table-container .table .bs-checkbox{text-align:center}.bootstrap-table .fixed-table-container .table .bs-checkbox label{margin-bottom:0}.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox],.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio]{margin:0 auto!important}.bootstrap-table .fixed-table-container .table.table-sm .th-inner{padding:.25rem}.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer){border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height.has-card-view{border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border{border-left:1px solid #dee2e6;border-right:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table thead th{border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th{border-bottom:1px solid #32383e}.bootstrap-table .fixed-table-container .fixed-table-header{overflow:hidden}.bootstrap-table .fixed-table-container .fixed-table-body{overflow:auto;height:100%}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading{align-items:center;background:#fff;display:flex;justify-content:center;position:absolute;bottom:0;width:100%;max-width:100%;z-index:1000;transition:visibility 0s,opacity .15s ease-in-out;opacity:0;visibility:hidden}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open{visibility:visible;opacity:1}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap{align-items:baseline;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text{margin-right:6px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap{align-items:center;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before{content:"";animation-duration:1.5s;animation-iteration-count:infinite;animation-name:loading;background:#212529;border-radius:50%;display:block;height:5px;margin:0 4px;opacity:0;width:5px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot{animation-delay:.3s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after{animation-delay:.6s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark{background:#212529}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before{background:#fff}.bootstrap-table .fixed-table-container .fixed-table-footer{overflow:hidden}.bootstrap-table .fixed-table-pagination::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-pagination>.pagination,.bootstrap-table .fixed-table-pagination>.pagination-detail{margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-pagination>.pagination-detail .pagination-info{line-height:34px;margin-right:5px}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list{display:inline-block}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group{position:relative;display:inline-block;vertical-align:middle}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group .dropdown-menu{margin-bottom:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination{margin:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a{color:#c8c8c8}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::before{content:"⬅"}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::after{content:"➡"}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.disabled a{pointer-events:none;cursor:default}.bootstrap-table.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100%!important;background:#fff;height:100vh;overflow-y:scroll}.bootstrap-table.bootstrap4 .pagination-lg .page-link,.bootstrap-table.bootstrap5 .pagination-lg .page-link{padding:.5rem 1rem}.bootstrap-table.bootstrap5 .float-left{float:left}.bootstrap-table.bootstrap5 .float-right{float:right}div.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}@keyframes loading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/bootstrap-table.min.js b/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/bootstrap-table.min.js new file mode 100644 index 00000000..0e4d37b7 --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/bootstrap-table.min.js @@ -0,0 +1,10 @@ +/** + * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) + * + * @version v1.24.1 + * @homepage https://bootstrap-table.com + * @author wenzhixin (http://wenzhixin.net.cn/) + * @license MIT + */ + +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).BootstrapTable=e(t.jQuery)}(this,(function(t){"use strict";function e(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=Array(e);n=t.length?{done:!0}:{done:!1,value:t[i++]}},e:function(t){throw t},f:r}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,a=!0,s=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return a=t.done,t},e:function(t){s=!0,o=t},f:function(){try{a||null==n.return||n.return()}finally{if(s)throw o}}}}function o(t,e,n){return(e=u(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function a(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function s(t){for(var e=1;e0&&t[0]<4?1:+(t[0]+t[1])),!e&&i&&(!(t=i.match(/Edge\/(\d+)/))||t[1]>=74)&&(t=i.match(/Chrome\/(\d+)/))&&(e=+t[1]),nt=e}function jt(){if(ot)return rt;ot=1;var t=Et(),e=P(),n=b().String;return rt=!!Object.getOwnPropertySymbols&&!e((function(){var e=Symbol("symbol detection");return!n(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&t&&t<41}))}function Nt(){if(st)return at;st=1;var t=jt();return at=t&&!Symbol.sham&&"symbol"==typeof Symbol.iterator}function Ft(){if(ct)return lt;ct=1;var t=At(),e=Pt(),n=$t(),i=Nt(),r=Object;return lt=i?function(t){return"symbol"==typeof t}:function(i){var o=t("Symbol");return e(o)&&n(o.prototype,r(i))}}function Dt(){if(ht)return ut;ht=1;var t=String;return ut=function(e){try{return t(e)}catch(t){return"Object"}}}function Lt(){if(dt)return ft;dt=1;var t=Pt(),e=Dt(),n=TypeError;return ft=function(i){if(t(i))return i;throw new n(e(i)+" is not a function")}}function _t(){if(gt)return pt;gt=1;var t=Lt(),e=kt();return pt=function(n,i){var r=n[i];return e(r)?void 0:t(r)}}function Vt(){if(bt)return vt;bt=1;var t=$(),e=Pt(),n=It(),i=TypeError;return vt=function(r,o){var a,s;if("string"===o&&e(a=r.toString)&&!n(s=t(a,r)))return s;if(e(a=r.valueOf)&&!n(s=t(a,r)))return s;if("string"!==o&&e(a=r.toString)&&!n(s=t(a,r)))return s;throw new i("Can't convert object to primitive value")}}var Bt,Ht,Mt,Ut,zt,qt,Wt,Gt,Kt,Jt,Yt,Qt,Xt,Zt,te,ee,ne,ie,re,oe,ae,se,le,ce,ue={exports:{}};function he(){return Ht?Bt:(Ht=1,Bt=!1)}function fe(){if(Ut)return Mt;Ut=1;var t=b(),e=Object.defineProperty;return Mt=function(n,i){try{e(t,n,{value:i,configurable:!0,writable:!0})}catch(e){t[n]=i}return i}}function de(){if(zt)return ue.exports;zt=1;var t=he(),e=b(),n=fe(),i="__core-js_shared__",r=ue.exports=e[i]||n(i,{});return(r.versions||(r.versions=[])).push({version:"3.39.0",mode:t?"pure":"global",copyright:"© 2014-2024 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE",source:"https://github.com/zloirock/core-js"}),ue.exports}function pe(){if(Wt)return qt;Wt=1;var t=de();return qt=function(e,n){return t[e]||(t[e]=n||{})}}function ge(){if(Kt)return Gt;Kt=1;var t=Tt(),e=Object;return Gt=function(n){return e(t(n))}}function ve(){if(Yt)return Jt;Yt=1;var t=St(),e=ge(),n=t({}.hasOwnProperty);return Jt=Object.hasOwn||function(t,i){return n(e(t),i)}}function be(){if(Xt)return Qt;Xt=1;var t=St(),e=0,n=Math.random(),i=t(1..toString);return Qt=function(t){return"Symbol("+(void 0===t?"":t)+")_"+i(++e+n,36)}}function me(){if(te)return Zt;te=1;var t=b(),e=pe(),n=ve(),i=be(),r=jt(),o=Nt(),a=t.Symbol,s=e("wks"),l=o?a.for||a:a&&a.withoutSetter||i;return Zt=function(t){return n(s,t)||(s[t]=r&&n(a,t)?a[t]:l("Symbol."+t)),s[t]}}function ye(){if(ne)return ee;ne=1;var t=$(),e=It(),n=Ft(),i=_t(),r=Vt(),o=me(),a=TypeError,s=o("toPrimitive");return ee=function(o,l){if(!e(o)||n(o))return o;var c,u=i(o,s);if(u){if(void 0===l&&(l="default"),c=t(u,o,l),!e(c)||n(c))return c;throw new a("Can't convert object to primitive value")}return void 0===l&&(l="number"),r(o,l)}}function we(){if(re)return ie;re=1;var t=ye(),e=Ft();return ie=function(n){var i=t(n,"string");return e(i)?i:i+""}}function Se(){if(ae)return oe;ae=1;var t=b(),e=It(),n=t.document,i=e(n)&&e(n.createElement);return oe=function(t){return i?n.createElement(t):{}}}function xe(){if(le)return se;le=1;var t=I(),e=P(),n=Se();return se=!t&&!e((function(){return 7!==Object.defineProperty(n("div"),"a",{get:function(){return 7}}).a}))}function Oe(){if(ce)return C;ce=1;var t=I(),e=$(),n=yt(),i=wt(),r=Ct(),o=we(),a=ve(),s=xe(),l=Object.getOwnPropertyDescriptor;return C.f=t?l:function(t,c){if(t=r(t),c=o(c),s)try{return l(t,c)}catch(t){}if(a(t,c))return i(!e(n.f,t,c),t[c])},C}var ke,Te,Ce,Pe,Ie,Ae,$e,Re={};function Ee(){if(Te)return ke;Te=1;var t=I(),e=P();return ke=t&&e((function(){return 42!==Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))}function je(){if(Pe)return Ce;Pe=1;var t=It(),e=String,n=TypeError;return Ce=function(i){if(t(i))return i;throw new n(e(i)+" is not an object")}}function Ne(){if(Ie)return Re;Ie=1;var t=I(),e=xe(),n=Ee(),i=je(),r=we(),o=TypeError,a=Object.defineProperty,s=Object.getOwnPropertyDescriptor,l="enumerable",c="configurable",u="writable";return Re.f=t?n?function(t,e,n){if(i(t),e=r(e),i(n),"function"==typeof t&&"prototype"===e&&"value"in n&&u in n&&!n[u]){var o=s(t,e);o&&o[u]&&(t[e]=n.value,n={configurable:c in n?n[c]:o[c],enumerable:l in n?n[l]:o[l],writable:!1})}return a(t,e,n)}:a:function(t,n,s){if(i(t),n=r(n),i(s),e)try{return a(t,n,s)}catch(t){}if("get"in s||"set"in s)throw new o("Accessors not supported");return"value"in s&&(t[n]=s.value),t},Re}function Fe(){if($e)return Ae;$e=1;var t=I(),e=Ne(),n=wt();return Ae=t?function(t,i,r){return e.f(t,i,n(1,r))}:function(t,e,n){return t[e]=n,t}}var De,Le,_e,Ve,Be,He,Me,Ue,ze,qe,We,Ge,Ke,Je,Ye,Qe={exports:{}};function Xe(){if(Le)return De;Le=1;var t=I(),e=ve(),n=Function.prototype,i=t&&Object.getOwnPropertyDescriptor,r=e(n,"name"),o=r&&"something"===function(){}.name,a=r&&(!t||t&&i(n,"name").configurable);return De={EXISTS:r,PROPER:o,CONFIGURABLE:a}}function Ze(){if(Ve)return _e;Ve=1;var t=St(),e=Pt(),n=de(),i=t(Function.toString);return e(n.inspectSource)||(n.inspectSource=function(t){return i(t)}),_e=n.inspectSource}function tn(){if(Ue)return Me;Ue=1;var t=pe(),e=be(),n=t("keys");return Me=function(t){return n[t]||(n[t]=e(t))}}function en(){return qe?ze:(qe=1,ze={})}function nn(){if(Ge)return We;Ge=1;var t,e,n,i=function(){if(He)return Be;He=1;var t=b(),e=Pt(),n=t.WeakMap;return Be=e(n)&&/native code/.test(String(n))}(),r=b(),o=It(),a=Fe(),s=ve(),l=de(),c=tn(),u=en(),h="Object already initialized",f=r.TypeError,d=r.WeakMap;if(i||l.state){var p=l.state||(l.state=new d);p.get=p.get,p.has=p.has,p.set=p.set,t=function(t,e){if(p.has(t))throw new f(h);return e.facade=t,p.set(t,e),e},e=function(t){return p.get(t)||{}},n=function(t){return p.has(t)}}else{var g=c("state");u[g]=!0,t=function(t,e){if(s(t,g))throw new f(h);return e.facade=t,a(t,g,e),e},e=function(t){return s(t,g)?t[g]:{}},n=function(t){return s(t,g)}}return We={set:t,get:e,has:n,enforce:function(i){return n(i)?e(i):t(i,{})},getterFor:function(t){return function(n){var i;if(!o(n)||(i=e(n)).type!==t)throw new f("Incompatible receiver, "+t+" required");return i}}}}function rn(){if(Ke)return Qe.exports;Ke=1;var t=St(),e=P(),n=Pt(),i=ve(),r=I(),o=Xe().CONFIGURABLE,a=Ze(),s=nn(),l=s.enforce,c=s.get,u=String,h=Object.defineProperty,f=t("".slice),d=t("".replace),p=t([].join),g=r&&!e((function(){return 8!==h((function(){}),"length",{value:8}).length})),v=String(String).split("String"),b=Qe.exports=function(t,e,n){"Symbol("===f(u(e),0,7)&&(e="["+d(u(e),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),n&&n.getter&&(e="get "+e),n&&n.setter&&(e="set "+e),(!i(t,"name")||o&&t.name!==e)&&(r?h(t,"name",{value:e,configurable:!0}):t.name=e),g&&n&&i(n,"arity")&&t.length!==n.arity&&h(t,"length",{value:n.arity});try{n&&i(n,"constructor")&&n.constructor?r&&h(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}var a=l(t);return i(a,"source")||(a.source=p(v,"string"==typeof e?e:"")),t};return Function.prototype.toString=b((function(){return n(this)&&c(this).source||a(this)}),"toString"),Qe.exports}function on(){if(Ye)return Je;Ye=1;var t=Pt(),e=Ne(),n=rn(),i=fe();return Je=function(r,o,a,s){s||(s={});var l=s.enumerable,c=void 0!==s.name?s.name:o;if(t(a)&&n(a,c,s),s.global)l?r[o]=a:i(o,a);else{try{s.unsafe?r[o]&&(l=!0):delete r[o]}catch(t){}l?r[o]=a:e.f(r,o,{value:a,enumerable:!1,configurable:!s.nonConfigurable,writable:!s.nonWritable})}return r}}var an,sn,ln,cn,un,hn,fn,dn,pn,gn,vn,bn,mn,yn,wn,Sn,xn,On={};function kn(){if(cn)return ln;cn=1;var t=function(){if(sn)return an;sn=1;var t=Math.ceil,e=Math.floor;return an=Math.trunc||function(n){var i=+n;return(i>0?e:t)(i)}}();return ln=function(e){var n=+e;return n!=n||0===n?0:t(n)}}function Tn(){if(hn)return un;hn=1;var t=kn(),e=Math.max,n=Math.min;return un=function(i,r){var o=t(i);return o<0?e(o+r,0):n(o,r)}}function Cn(){if(dn)return fn;dn=1;var t=kn(),e=Math.min;return fn=function(n){var i=t(n);return i>0?e(i,9007199254740991):0}}function Pn(){if(gn)return pn;gn=1;var t=Cn();return pn=function(e){return t(e.length)}}function In(){if(bn)return vn;bn=1;var t=Ct(),e=Tn(),n=Pn(),i=function(i){return function(r,o,a){var s=t(r),l=n(s);if(0===l)return!i&&-1;var c,u=e(a,l);if(i&&o!=o){for(;l>u;)if((c=s[u++])!=c)return!0}else for(;l>u;u++)if((i||u in s)&&s[u]===o)return i||u||0;return!i&&-1}};return vn={includes:i(!0),indexOf:i(!1)}}function An(){if(yn)return mn;yn=1;var t=St(),e=ve(),n=Ct(),i=In().indexOf,r=en(),o=t([].push);return mn=function(t,a){var s,l=n(t),c=0,u=[];for(s in l)!e(r,s)&&e(l,s)&&o(u,s);for(;a.length>c;)e(l,s=a[c++])&&(~i(u,s)||o(u,s));return u}}function $n(){return Sn?wn:(Sn=1,wn=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"])}function Rn(){if(xn)return On;xn=1;var t=An(),e=$n().concat("length","prototype");return On.f=Object.getOwnPropertyNames||function(n){return t(n,e)},On}var En,jn,Nn,Fn,Dn,Ln,_n,Vn,Bn,Hn,Mn,Un,zn,qn,Wn,Gn,Kn,Jn,Yn,Qn,Xn,Zn,ti,ei,ni,ii,ri,oi,ai={};function si(){return En||(En=1,ai.f=Object.getOwnPropertySymbols),ai}function li(){if(Nn)return jn;Nn=1;var t=At(),e=St(),n=Rn(),i=si(),r=je(),o=e([].concat);return jn=t("Reflect","ownKeys")||function(t){var e=n.f(r(t)),a=i.f;return a?o(e,a(t)):e}}function ci(){if(Dn)return Fn;Dn=1;var t=ve(),e=li(),n=Oe(),i=Ne();return Fn=function(r,o,a){for(var s=e(o),l=i.f,c=n.f,u=0;u9007199254740991)throw t("Maximum allowed index exceeded");return e}}function pi(){if(Wn)return qn;Wn=1;var t=I(),e=Ne(),n=wt();return qn=function(i,r,o){t?e.f(i,r,n(0,o)):i[r]=o}}function gi(){if(Kn)return Gn;Kn=1;var t={};return t[me()("toStringTag")]="z",Gn="[object z]"===String(t)}function vi(){if(Yn)return Jn;Yn=1;var t=gi(),e=Pt(),n=xt(),i=me()("toStringTag"),r=Object,o="Arguments"===n(function(){return arguments}());return Jn=t?n:function(t){var a,s,l;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(s=function(t,e){try{return t[e]}catch(t){}}(a=r(t),i))?s:o?n(a):"Object"===(l=n(a))&&e(a.callee)?"Arguments":l}}function bi(){if(Xn)return Qn;Xn=1;var t=St(),e=P(),n=Pt(),i=vi(),r=At(),o=Ze(),a=function(){},s=r("Reflect","construct"),l=/^\s*(?:class|function)\b/,c=t(l.exec),u=!l.test(a),h=function(t){if(!n(t))return!1;try{return s(a,[],t),!0}catch(t){return!1}},f=function(t){if(!n(t))return!1;switch(i(t)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return u||!!c(l,o(t))}catch(t){return!0}};return f.sham=!0,Qn=!s||e((function(){var t;return h(h.call)||!h(Object)||!h((function(){t=!0}))||t}))?f:h}function mi(){if(ti)return Zn;ti=1;var t=fi(),e=bi(),n=It(),i=me()("species"),r=Array;return Zn=function(o){var a;return t(o)&&(a=o.constructor,(e(a)&&(a===r||t(a.prototype))||n(a)&&null===(a=a[i]))&&(a=void 0)),void 0===a?r:a}}function yi(){if(ni)return ei;ni=1;var t=mi();return ei=function(e,n){return new(t(e))(0===n?0:n)}}function wi(){if(ri)return ii;ri=1;var t=P(),e=me(),n=Et(),i=e("species");return ii=function(e){return n>=51||!t((function(){var t=[];return(t.constructor={})[i]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}}!function(){if(oi)return v;oi=1;var t=hi(),e=P(),n=fi(),i=It(),r=ge(),o=Pn(),a=di(),s=pi(),l=yi(),c=wi(),u=me(),h=Et(),f=u("isConcatSpreadable"),d=h>=51||!e((function(){var t=[];return t[f]=!1,t.concat()[0]!==t})),p=function(t){if(!i(t))return!1;var e=t[f];return void 0!==e?!!e:n(t)};t({target:"Array",proto:!0,arity:1,forced:!d||!c("concat")},{concat:function(t){var e,n,i,c,u,h=r(this),f=l(h,0),d=0;for(e=-1,i=arguments.length;ek;k++)if((d||k in S)&&(y=O(m=S[k],k,w),e))if(s)C[k]=y;else if(y)switch(e){case 3:return!0;case 5:return m;case 6:return k;case 2:a(C,m)}else switch(e){case 4:return!1;case 7:a(C,m)}return h?-1:c||u?u:C}};return Ti={forEach:s(0),map:s(1),filter:s(2),some:s(3),every:s(4),find:s(5),findIndex:s(6),filterReject:s(7)}}!function(){if(Pi)return Ii;Pi=1;var t=hi(),e=Ri().filter;t({target:"Array",proto:!0,forced:!wi()("filter")},{filter:function(t){return e(this,t,arguments.length>1?arguments[1]:void 0)}})}();var Ei,ji,Ni,Fi,Di,Li,_i,Vi,Bi,Hi,Mi={},Ui={};function zi(){if(ji)return Ei;ji=1;var t=An(),e=$n();return Ei=Object.keys||function(n){return t(n,e)}}function qi(){if(Di)return Fi;Di=1;var t=At();return Fi=t("document","documentElement")}function Wi(){if(_i)return Li;_i=1;var t,e=je(),n=function(){if(Ni)return Ui;Ni=1;var t=I(),e=Ee(),n=Ne(),i=je(),r=Ct(),o=zi();return Ui.f=t&&!e?Object.defineProperties:function(t,e){i(t);for(var a,s=r(e),l=o(e),c=l.length,u=0;c>u;)n.f(t,a=l[u++],s[a]);return t},Ui}(),i=$n(),r=en(),o=qi(),a=Se(),s=tn(),l="prototype",c="script",u=s("IE_PROTO"),h=function(){},f=function(t){return"<"+c+">"+t+""},d=function(t){t.write(f("")),t.close();var e=t.parentWindow.Object;return t=null,e},p=function(){try{t=new ActiveXObject("htmlfile")}catch(t){}var e,n,r;p="undefined"!=typeof document?document.domain&&t?d(t):(n=a("iframe"),r="java"+c+":",n.style.display="none",o.appendChild(n),n.src=String(r),(e=n.contentWindow.document).open(),e.write(f("document.F=Object")),e.close(),e.F):d(t);for(var s=i.length;s--;)delete p[l][i[s]];return p()};return r[u]=!0,Li=Object.create||function(t,i){var r;return null!==t?(h[l]=e(t),r=new h,h[l]=null,r[u]=t):r=p(),void 0===i?r:n.f(r,i)}}function Gi(){if(Bi)return Vi;Bi=1;var t=me(),e=Wi(),n=Ne().f,i=t("unscopables"),r=Array.prototype;return void 0===r[i]&&n(r,i,{configurable:!0,value:e(null)}),Vi=function(t){r[i][t]=!0}}!function(){if(Hi)return Mi;Hi=1;var t=hi(),e=Ri().find,n=Gi(),i="find",r=!0;i in[]&&Array(1)[i]((function(){r=!1})),t({target:"Array",proto:!0,forced:r},{find:function(t){return e(this,t,arguments.length>1?arguments[1]:void 0)}}),n(i)}();var Ki,Ji={};!function(){if(Ki)return Ji;Ki=1;var t=hi(),e=Ri().findIndex,n=Gi(),i="findIndex",r=!0;i in[]&&Array(1)[i]((function(){r=!1})),t({target:"Array",proto:!0,forced:r},{findIndex:function(t){return e(this,t,arguments.length>1?arguments[1]:void 0)}}),n(i)}();var Yi,Qi={};!function(){if(Yi)return Qi;Yi=1;var t=hi(),e=In().includes,n=P(),i=Gi();t({target:"Array",proto:!0,forced:n((function(){return!Array(1).includes()}))},{includes:function(t){return e(this,t,arguments.length>1?arguments[1]:void 0)}}),i("includes")}();var Xi,Zi,tr,er,nr,ir,rr,or,ar,sr,lr,cr,ur,hr,fr,dr,pr,gr,vr,br,mr,yr,wr,Sr,xr,Or,kr,Tr,Cr,Pr={};function Ir(){if(Zi)return Xi;Zi=1;var t=P();return Xi=function(e,n){var i=[][e];return!!i&&t((function(){i.call(null,n||function(){return 1},1)}))}}function Ar(){return nr?er:(nr=1,er={})}function $r(){if(rr)return ir;rr=1;var t=P();return ir=!t((function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}))}function Rr(){if(ar)return or;ar=1;var t=ve(),e=Pt(),n=ge(),i=tn(),r=$r(),o=i("IE_PROTO"),a=Object,s=a.prototype;return or=r?a.getPrototypeOf:function(i){var r=n(i);if(t(r,o))return r[o];var l=r.constructor;return e(l)&&r instanceof l?l.prototype:r instanceof a?s:null}}function Er(){if(lr)return sr;lr=1;var t,e,n,i=P(),r=Pt(),o=It(),a=Wi(),s=Rr(),l=on(),c=me(),u=he(),h=c("iterator"),f=!1;return[].keys&&("next"in(n=[].keys())?(e=s(s(n)))!==Object.prototype&&(t=e):f=!0),!o(t)||i((function(){var e={};return t[h].call(e)!==e}))?t={}:u&&(t=a(t)),r(t[h])||l(t,h,(function(){return this})),sr={IteratorPrototype:t,BUGGY_SAFARI_ITERATORS:f}}function jr(){if(ur)return cr;ur=1;var t=Ne().f,e=ve(),n=me()("toStringTag");return cr=function(i,r,o){i&&!o&&(i=i.prototype),i&&!e(i,n)&&t(i,n,{configurable:!0,value:r})}}function Nr(){if(fr)return hr;fr=1;var t=Er().IteratorPrototype,e=Wi(),n=wt(),i=jr(),r=Ar(),o=function(){return this};return hr=function(a,s,l,c){var u=s+" Iterator";return a.prototype=e(t,{next:n(+!c,l)}),i(a,u,!1,!0),r[u]=o,a}}function Fr(){if(vr)return gr;vr=1;var t=It();return gr=function(e){return t(e)||null===e}}function Dr(){if(mr)return br;mr=1;var t=Fr(),e=String,n=TypeError;return br=function(i){if(t(i))return i;throw new n("Can't set "+e(i)+" as a prototype")}}function Lr(){if(wr)return yr;wr=1;var t=function(){if(pr)return dr;pr=1;var t=St(),e=Lt();return dr=function(n,i,r){try{return t(e(Object.getOwnPropertyDescriptor(n,i)[r]))}catch(t){}}}(),e=It(),n=Tt(),i=Dr();return yr=Object.setPrototypeOf||("__proto__"in{}?function(){var r,o=!1,a={};try{(r=t(Object.prototype,"__proto__","set"))(a,[]),o=a instanceof Array}catch(t){}return function(t,a){return n(t),i(a),e(t)?(o?r(t,a):t.__proto__=a,t):t}}():void 0)}function _r(){if(xr)return Sr;xr=1;var t=hi(),e=$(),n=he(),i=Xe(),r=Pt(),o=Nr(),a=Rr(),s=Lr(),l=jr(),c=Fe(),u=on(),h=me(),f=Ar(),d=Er(),p=i.PROPER,g=i.CONFIGURABLE,v=d.IteratorPrototype,b=d.BUGGY_SAFARI_ITERATORS,m=h("iterator"),y="keys",w="values",S="entries",x=function(){return this};return Sr=function(i,h,d,O,k,T,C){o(d,h,O);var P,I,A,$=function(t){if(t===k&&F)return F;if(!b&&t&&t in j)return j[t];switch(t){case y:case w:case S:return function(){return new d(this,t)}}return function(){return new d(this)}},R=h+" Iterator",E=!1,j=i.prototype,N=j[m]||j["@@iterator"]||k&&j[k],F=!b&&N||$(k),D="Array"===h&&j.entries||N;if(D&&(P=a(D.call(new i)))!==Object.prototype&&P.next&&(n||a(P)===v||(s?s(P,v):r(P[m])||u(P,m,x)),l(P,R,!0,!0),n&&(f[R]=x)),p&&k===w&&N&&N.name!==w&&(!n&&g?c(j,"name",w):(E=!0,F=function(){return e(N,this)})),k)if(I={values:$(w),keys:T?F:$(y),entries:$(S)},C)for(A in I)(b||E||!(A in j))&&u(j,A,I[A]);else t({target:h,proto:!0,forced:b||E},I);return n&&!C||j[m]===F||u(j,m,F,{name:k}),f[h]=F,I}}function Vr(){return kr?Or:(kr=1,Or=function(t,e){return{value:t,done:e}})}function Br(){if(Cr)return Tr;Cr=1;var t=Ct(),e=Gi(),n=Ar(),i=nn(),r=Ne().f,o=_r(),a=Vr(),s=he(),l=I(),c="Array Iterator",u=i.set,h=i.getterFor(c);Tr=o(Array,"Array",(function(e,n){u(this,{type:c,target:t(e),index:0,kind:n})}),(function(){var t=h(this),e=t.target,n=t.index++;if(!e||n>=e.length)return t.target=null,a(void 0,!0);switch(t.kind){case"keys":return a(n,!1);case"values":return a(e[n],!1)}return a([n,e[n]],!1)}),"values");var f=n.Arguments=n.Array;if(e("keys"),e("values"),e("entries"),!s&&l&&"values"!==f.name)try{r(f,"name",{value:"values"})}catch(t){}return Tr}!function(){if(tr)return Pr;tr=1;var t=hi(),e=Ai(),n=In().indexOf,i=Ir(),r=e([].indexOf),o=!!r&&1/r([1],1,-0)<0;t({target:"Array",proto:!0,forced:o||!i("indexOf")},{indexOf:function(t){var e=arguments.length>1?arguments[1]:void 0;return o?r(this,t,e)||0:n(this,t,e)}})}(),Br();var Hr,Mr={};!function(){if(Hr)return Mr;Hr=1;var t=hi(),e=St(),n=Ot(),i=Ct(),r=Ir(),o=e([].join);t({target:"Array",proto:!0,forced:n!==Object||!r("join",",")},{join:function(t){return o(i(this),void 0===t?",":t)}})}();var Ur,zr={};!function(){if(Ur)return zr;Ur=1;var t=hi(),e=Ri().map;t({target:"Array",proto:!0,forced:!wi()("map")},{map:function(t){return e(this,t,arguments.length>1?arguments[1]:void 0)}})}();var qr,Wr={};!function(){if(qr)return Wr;qr=1;var t=hi(),e=St(),n=fi(),i=e([].reverse),r=[1,2];t({target:"Array",proto:!0,forced:String(r)===String(r.reverse())},{reverse:function(){return n(this)&&(this.length=this.length),i(this)}})}();var Gr,Kr,Jr,Yr={};function Qr(){if(Kr)return Gr;Kr=1;var t=St();return Gr=t([].slice)}!function(){if(Jr)return Yr;Jr=1;var t=hi(),e=fi(),n=bi(),i=It(),r=Tn(),o=Pn(),a=Ct(),s=pi(),l=me(),c=wi(),u=Qr(),h=c("slice"),f=l("species"),d=Array,p=Math.max;t({target:"Array",proto:!0,forced:!h},{slice:function(t,l){var c,h,g,v=a(this),b=o(v),m=r(t,b),y=r(void 0===l?b:l,b);if(e(v)&&(c=v.constructor,(n(c)&&(c===d||e(c.prototype))||i(c)&&null===(c=c[f]))&&(c=void 0),c===d||void 0===c))return u(v,m,y);for(h=new(void 0===c?d:c)(p(y-m,0)),g=0;m0;)i[s]=i[--s];s!==l++&&(i[s]=a)}else for(var c=e(o/2),u=n(t(i,0,c),r),h=n(t(i,c),r),f=u.length,d=h.length,p=0,g=0;p3)){if(h)return!0;if(d)return d<603;var t,e,n,i,r="";for(t=65;t<76;t++){switch(e=String.fromCharCode(t),t){case 66:case 69:case 70:case 72:n=3;break;case 68:case 71:n=4;break;default:n=2}for(i=0;i<47;i++)p.push({k:e+i,v:n})}for(p.sort((function(t,e){return e.v-t.v})),i=0;ia(n)?1:-1}}(t)),s=r(u),c=0;cw-p+d;v--)c(y,v-1)}else if(d>p)for(v=w-p;v>S;v--)m=v+d-1,(b=v+p-1)in y?y[m]=y[b]:c(y,m);for(v=0;v2)if(c=m(c),43===(e=T(c,0))||45===e){if(88===(n=T(c,2))||120===n)return NaN}else if(48===e){switch(T(c,1)){case 66:case 98:i=2,r=49;break;case 79:case 111:i=8,r=55;break;default:return+c}for(a=(o=k(c,2)).length,s=0;sr)return NaN;return parseInt(o,i)}return+c},A=a(y,!w(" 0o1")||!w("0b1")||w("+0x1")),$=function(t){var e,n=arguments.length<1?0:w(function(t){var e=h(t,"number");return"bigint"==typeof e?e:C(e)}(t));return c(x,e=this)&&f((function(){v(e)}))?l(Object(n),this,$):n};$.prototype=x,A&&!e&&(x.constructor=$),t({global:!0,constructor:!0,wrap:!0,forced:A},{Number:$});var R=function(t,e){for(var i,r=n?d(e):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,isFinite,isInteger,isNaN,isSafeInteger,parseFloat,parseInt,fromString,range".split(","),o=0;r.length>o;o++)s(e,i=r[o])&&!s(t,i)&&g(t,i,p(e,i))};e&&S&&R(r[y],S),(A||e)&&R(r[y],w)}();var Vo,Bo,Ho,Mo={};!function(){if(Ho)return Mo;Ho=1;var t=hi(),e=function(){if(Bo)return Vo;Bo=1;var t=I(),e=St(),n=$(),i=P(),r=zi(),o=si(),a=yt(),s=ge(),l=Ot(),c=Object.assign,u=Object.defineProperty,h=e([].concat);return Vo=!c||i((function(){if(t&&1!==c({b:1},c(u({},"a",{enumerable:!0,get:function(){u(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},n={},i=Symbol("assign detection"),o="abcdefghijklmnopqrst";return e[i]=7,o.split("").forEach((function(t){n[t]=t})),7!==c({},e)[i]||r(c({},n)).join("")!==o}))?function(e,i){for(var c=s(e),u=arguments.length,f=1,d=o.f,p=a.f;u>f;)for(var g,v=l(arguments[f++]),b=d?h(r(v),d(v)):r(v),m=b.length,y=0;m>y;)g=b[y++],t&&!n(p,v,g)||(c[g]=v[g]);return c}:c,Vo}();t({target:"Object",stat:!0,arity:2,forced:Object.assign!==e},{assign:e})}();var Uo,zo,qo,Wo={};!function(){if(qo)return Wo;qo=1;var t=hi(),e=function(){if(zo)return Uo;zo=1;var t=I(),e=P(),n=St(),i=Rr(),r=zi(),o=Ct(),a=n(yt().f),s=n([].push),l=t&&e((function(){var t=Object.create(null);return t[2]=2,!a(t,2)})),c=function(e){return function(n){for(var c,u=o(n),h=r(u),f=l&&null===i(u),d=h.length,p=0,g=[];d>p;)c=h[p++],t&&!(f?c in u:a(u,c))||s(g,e?[c,u[c]]:u[c]);return g}};return Uo={entries:c(!0),values:c(!1)}}().entries;t({target:"Object",stat:!0},{entries:function(t){return e(t)}})}();var Go,Ko={};!function(){if(Go)return Ko;Go=1;var t=hi(),e=ge(),n=zi();t({target:"Object",stat:!0,forced:P()((function(){n(1)}))},{keys:function(t){return n(e(t))}})}();var Jo,Yo,Qo,Xo={};!function(){if(Qo)return Xo;Qo=1;var t=gi(),e=on(),n=function(){if(Yo)return Jo;Yo=1;var t=gi(),e=vi();return Jo=t?{}.toString:function(){return"[object "+e(this)+"]"}}();t||e(Object.prototype,"toString",n,{unsafe:!0})}();var Zo,ta,ea,na={};!function(){if(ea)return na;ea=1;var t=hi(),e=function(){if(ta)return Zo;ta=1;var t=b(),e=P(),n=St(),i=po(),r=_o().trim,o=Lo(),a=n("".charAt),s=t.parseFloat,l=t.Symbol,c=l&&l.iterator,u=1/s(o+"-0")!=-1/0||c&&!e((function(){s(Object(c))}));return Zo=u?function(t){var e=r(i(t)),n=s(e);return 0===n&&"-"===a(e,0)?-0:n}:s}();t({global:!0,forced:parseFloat!==e},{parseFloat:e})}();var ia,ra,oa,aa={};!function(){if(oa)return aa;oa=1;var t=hi(),e=function(){if(ra)return ia;ra=1;var t=b(),e=P(),n=St(),i=po(),r=_o().trim,o=Lo(),a=t.parseInt,s=t.Symbol,l=s&&s.iterator,c=/^[+-]?0x/i,u=n(c.exec),h=8!==a(o+"08")||22!==a(o+"0x16")||l&&!e((function(){a(Object(l))}));return ia=h?function(t,e){var n=r(i(t));return a(n,e>>>0||(u(c,n)?16:10))}:a}();t({global:!0,forced:parseInt!==e},{parseInt:e})}();var sa,la,ca,ua,ha,fa,da,pa,ga,va,ba,ma,ya,wa,Sa,xa,Oa,ka,Ta,Ca={};function Pa(){if(la)return sa;la=1;var t=It(),e=xt(),n=me()("match");return sa=function(i){var r;return t(i)&&(void 0!==(r=i[n])?!!r:"RegExp"===e(i))}}function Ia(){if(ua)return ca;ua=1;var t=je();return ca=function(){var e=t(this),n="";return e.hasIndices&&(n+="d"),e.global&&(n+="g"),e.ignoreCase&&(n+="i"),e.multiline&&(n+="m"),e.dotAll&&(n+="s"),e.unicode&&(n+="u"),e.unicodeSets&&(n+="v"),e.sticky&&(n+="y"),n}}function Aa(){if(fa)return ha;fa=1;var t=$(),e=ve(),n=$t(),i=Ia(),r=RegExp.prototype;return ha=function(o){var a=o.flags;return void 0!==a||"flags"in r||e(o,"flags")||!n(r,o)?a:t(i,o)}}function $a(){if(pa)return da;pa=1;var t=P(),e=b().RegExp,n=t((function(){var t=e("a","y");return t.lastIndex=2,null!==t.exec("abcd")})),i=n||t((function(){return!e("a","y").sticky})),r=n||t((function(){var t=e("^r","gy");return t.lastIndex=2,null!==t.exec("str")}));return da={BROKEN_CARET:r,MISSED_STICKY:i,UNSUPPORTED_Y:n}}function Ra(){if(va)return ga;va=1;var t=Ne().f;return ga=function(e,n,i){i in e||t(e,i,{configurable:!0,get:function(){return n[i]},set:function(t){n[i]=t}})}}function Ea(){if(ma)return ba;ma=1;var t=rn(),e=Ne();return ba=function(n,i,r){return r.get&&t(r.get,i,{getter:!0}),r.set&&t(r.set,i,{setter:!0}),e.f(n,i,r)}}function ja(){if(wa)return ya;wa=1;var t=At(),e=Ea(),n=me(),i=I(),r=n("species");return ya=function(n){var o=t(n);i&&o&&!o[r]&&e(o,r,{configurable:!0,get:function(){return this}})}}function Na(){if(xa)return Sa;xa=1;var t=P(),e=b().RegExp;return Sa=t((function(){var t=e(".","s");return!(t.dotAll&&t.test("\n")&&"s"===t.flags)}))}function Fa(){if(ka)return Oa;ka=1;var t=P(),e=b().RegExp;return Oa=t((function(){var t=e("(?b)","g");return"b"!==t.exec("b").groups.a||"bc"!=="b".replace(t,"$c")}))}!function(){if(Ta)return Ca;Ta=1;var t=I(),e=b(),n=St(),i=ui(),r=Fo(),o=Fe(),a=Wi(),s=Rn().f,l=$t(),c=Pa(),u=po(),h=Aa(),f=$a(),d=Ra(),p=on(),g=P(),v=ve(),m=nn().enforce,y=ja(),w=me(),S=Na(),x=Fa(),O=w("match"),k=e.RegExp,T=k.prototype,C=e.SyntaxError,A=n(T.exec),$=n("".charAt),R=n("".replace),E=n("".indexOf),j=n("".slice),N=/^\?<[^\s\d!#%&*+<=>@^][^\s!#%&*+<=>@^]*>/,F=/a/g,D=/a/g,L=new k(F)!==F,_=f.MISSED_STICKY,V=f.UNSUPPORTED_Y,B=t&&(!L||_||S||x||g((function(){return D[O]=!1,k(F)!==F||k(D)===D||"/a/i"!==String(k(F,"i"))})));if(i("RegExp",B)){for(var H=function(t,e){var n,i,s,f,d,p,g=l(T,this),b=c(t),y=void 0===e,w=[],O=t;if(!g&&b&&y&&t.constructor===H)return t;if((b||l(T,t))&&(t=t.source,y&&(e=h(O))),t=void 0===t?"":u(t),e=void 0===e?"":u(e),O=t,S&&"dotAll"in F&&(i=!!e&&E(e,"s")>-1)&&(e=R(e,/s/g,"")),n=e,_&&"sticky"in F&&(s=!!e&&E(e,"y")>-1)&&V&&(e=R(e,/y/g,"")),x&&(f=function(t){for(var e,n=t.length,i=0,r="",o=[],s=a(null),l=!1,c=!1,u=0,h="";i<=n;i++){if("\\"===(e=$(t,i)))e+=$(t,++i);else if("]"===e)l=!1;else if(!l)switch(!0){case"["===e:l=!0;break;case"("===e:if(r+=e,"?:"===j(t,i+1,i+3))continue;A(N,j(t,i+1))&&(i+=2,c=!0),u++;continue;case">"===e&&c:if(""===h||v(s,h))throw new C("Invalid capture group name");s[h]=!0,o[o.length]=[h,u],c=!1,h="";continue}c?h+=e:r+=e}return[r,o]}(t),t=f[0],w=f[1]),d=r(k(t,e),g?this:T,H),(i||s||w.length)&&(p=m(d),i&&(p.dotAll=!0,p.raw=H(function(t){for(var e,n=t.length,i=0,r="",o=!1;i<=n;i++)"\\"!==(e=$(t,i))?o||"."!==e?("["===e?o=!0:"]"===e&&(o=!1),r+=e):r+="[\\s\\S]":r+=e+$(t,++i);return r}(t),n)),s&&(p.sticky=!0),w.length&&(p.groups=w)),t!==O)try{o(d,"source",""===O?"(?:)":O)}catch(t){}return d},M=s(k),U=0;M.length>U;)d(H,k,M[U++]);T.constructor=H,H.prototype=T,p(e,"RegExp",H,{constructor:!0})}y("RegExp")}();var Da,La,_a,Va={};function Ba(){if(La)return Da;La=1;var t,e,n=$(),i=St(),r=po(),o=Ia(),a=$a(),s=pe(),l=Wi(),c=nn().get,u=Na(),h=Fa(),f=s("native-string-replace",String.prototype.replace),d=RegExp.prototype.exec,p=d,g=i("".charAt),v=i("".indexOf),b=i("".replace),m=i("".slice),y=(e=/b*/g,n(d,t=/a/,"a"),n(d,e,"a"),0!==t.lastIndex||0!==e.lastIndex),w=a.BROKEN_CARET,S=void 0!==/()??/.exec("")[1];return(y||S||w||u||h)&&(p=function(t){var e,i,a,s,u,h,x,O=this,k=c(O),T=r(t),C=k.raw;if(C)return C.lastIndex=O.lastIndex,e=n(p,C,T),O.lastIndex=C.lastIndex,e;var P=k.groups,I=w&&O.sticky,A=n(o,O),$=O.source,R=0,E=T;if(I&&(A=b(A,"y",""),-1===v(A,"g")&&(A+="g"),E=m(T,O.lastIndex),O.lastIndex>0&&(!O.multiline||O.multiline&&"\n"!==g(T,O.lastIndex-1))&&($="(?: "+$+")",E=" "+E,R++),i=new RegExp("^(?:"+$+")",A)),S&&(i=new RegExp("^"+$+"$(?!\\s)",A)),y&&(a=O.lastIndex),s=n(d,I?i:O,E),I?s?(s.input=m(s.input,R),s[0]=m(s[0],R),s.index=O.lastIndex,O.lastIndex+=s[0].length):O.lastIndex=0:y&&s&&(O.lastIndex=O.global?s.index+s[0].length:a),S&&s&&s.length>1&&n(f,s[0],i,(function(){for(u=1;u1?arguments[1]:void 0)}})}();var Xa,Za,ts,es,ns,is,rs,os,as,ss,ls,cs,us,hs={};function fs(){if(es)return ts;es=1,Ha();var t=$(),e=on(),n=Ba(),i=P(),r=me(),o=Fe(),a=r("species"),s=RegExp.prototype;return ts=function(l,c,u,h){var f=r(l),d=!i((function(){var t={};return t[f]=function(){return 7},7!==""[l](t)})),p=d&&!i((function(){var t=!1,e=/a/;return"split"===l&&((e={}).constructor={},e.constructor[a]=function(){return e},e.flags="",e[f]=/./[f]),e.exec=function(){return t=!0,null},e[f](""),!t}));if(!d||!p||u){var g=/./[f],v=c(f,""[l],(function(e,i,r,o,a){var l=i.exec;return l===n||l===s.exec?d&&!a?{done:!0,value:t(g,i,r,o)}:{done:!0,value:t(e,r,i,o)}:{done:!1}}));e(String.prototype,l,v[0]),e(s,f,v[1])}h&&o(s[f],"sham",!0)}}function ds(){if(is)return ns;is=1;var t=St(),e=kn(),n=po(),i=Tt(),r=t("".charAt),o=t("".charCodeAt),a=t("".slice),s=function(t){return function(s,l){var c,u,h=n(i(s)),f=e(l),d=h.length;return f<0||f>=d?t?"":void 0:(c=o(h,f))<55296||c>56319||f+1===d||(u=o(h,f+1))<56320||u>57343?t?r(h,f):c:t?a(h,f,f+2):u-56320+(c-55296<<10)+65536}};return ns={codeAt:s(!1),charAt:s(!0)}}function ps(){if(os)return rs;os=1;var t=ds().charAt;return rs=function(e,n,i){return n+(i?t(e,n).length:1)}}function gs(){if(ss)return as;ss=1;var t=St(),e=ge(),n=Math.floor,i=t("".charAt),r=t("".replace),o=t("".slice),a=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,s=/\$([$&'`]|\d{1,2})/g;return as=function(t,l,c,u,h,f){var d=c+t.length,p=u.length,g=s;return void 0!==h&&(h=e(h),g=a),r(f,g,(function(e,r){var a;switch(i(r,0)){case"$":return"$";case"&":return t;case"`":return o(l,0,c);case"'":return o(l,d);case"<":a=h[o(r,1,-1)];break;default:var s=+r;if(0===s)return e;if(s>p){var f=n(s/10);return 0===f?e:f<=p?void 0===u[f-1]?i(r,1):u[f-1]+i(r,1):e}a=u[s-1]}return void 0===a?"":a}))}}function vs(){if(cs)return ls;cs=1;var t=$(),e=je(),n=Pt(),i=xt(),r=Ba(),o=TypeError;return ls=function(a,s){var l=a.exec;if(n(l)){var c=t(l,a,s);return null!==c&&e(c),c}if("RegExp"===i(a))return t(r,a,s);throw new o("RegExp#exec called on incompatible receiver")}}!function(){if(us)return hs;us=1;var t=function(){if(Za)return Xa;Za=1;var t=A(),e=Function.prototype,n=e.apply,i=e.call;return Xa="object"==typeof Reflect&&Reflect.apply||(t?i.bind(n):function(){return i.apply(n,arguments)}),Xa}(),e=$(),n=St(),i=fs(),r=P(),o=je(),a=Pt(),s=kt(),l=kn(),c=Cn(),u=po(),h=Tt(),f=ps(),d=_t(),p=gs(),g=vs(),v=me()("replace"),b=Math.max,m=Math.min,y=n([].concat),w=n([].push),S=n("".indexOf),x=n("".slice),O="$0"==="a".replace(/./,"$0"),k=!!/./[v]&&""===/./[v]("a","$0");i("replace",(function(n,i,r){var O=k?"$":"$0";return[function(t,n){var r=h(this),o=s(t)?void 0:d(t,v);return o?e(o,t,r,n):e(i,u(r),t,n)},function(e,n){var s=o(this),h=u(e);if("string"==typeof n&&-1===S(n,O)&&-1===S(n,"$<")){var d=r(i,s,h,n);if(d.done)return d.value}var v=a(n);v||(n=u(n));var k,T=s.global;T&&(k=s.unicode,s.lastIndex=0);for(var C,P=[];null!==(C=g(s,h))&&(w(P,C),T);){""===u(C[0])&&(s.lastIndex=f(h,c(s.lastIndex),k))}for(var I,A="",$=0,R=0;R=$&&(A+=x(h,$,N)+E,$=N+j.length)}return A+x(h,$)}]}),!!r((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")}))||!O||k)}();var bs,ms,ys,ws={};function Ss(){return ms?bs:(ms=1,bs=Object.is||function(t,e){return t===e?0!==t||1/t==1/e:t!=t&&e!=e})}!function(){if(ys)return ws;ys=1;var t=$(),e=fs(),n=je(),i=kt(),r=Tt(),o=Ss(),a=po(),s=_t(),l=vs();e("search",(function(e,c,u){return[function(n){var o=r(this),l=i(n)?void 0:s(n,e);return l?t(l,n,o):new RegExp(n)[e](a(o))},function(t){var e=n(this),i=a(t),r=u(c,e,i);if(r.done)return r.value;var s=e.lastIndex;o(s,0)||(e.lastIndex=0);var h=l(e,i);return o(e.lastIndex,s)||(e.lastIndex=s),null===h?-1:h.index}]}))}();var xs,Os,ks,Ts,Cs,Ps={};function Is(){if(Os)return xs;Os=1;var t=bi(),e=Dt(),n=TypeError;return xs=function(i){if(t(i))return i;throw new n(e(i)+" is not a constructor")}}function As(){if(Ts)return ks;Ts=1;var t=je(),e=Is(),n=kt(),i=me()("species");return ks=function(r,o){var a,s=t(r).constructor;return void 0===s||n(a=t(s)[i])?o:e(a)}}!function(){if(Cs)return Ps;Cs=1;var t=$(),e=St(),n=fs(),i=je(),r=kt(),o=Tt(),a=As(),s=ps(),l=Cn(),c=po(),u=_t(),h=vs(),f=$a(),d=P(),p=f.UNSUPPORTED_Y,g=Math.min,v=e([].push),b=e("".slice),m=!d((function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2!==n.length||"a"!==n[0]||"b"!==n[1]})),y="c"==="abbc".split(/(b)*/)[1]||4!=="test".split(/(?:)/,-1).length||2!=="ab".split(/(?:ab)*/).length||4!==".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length;n("split",(function(e,n,f){var d="0".split(void 0,0).length?function(e,i){return void 0===e&&0===i?[]:t(n,this,e,i)}:n;return[function(n,i){var a=o(this),s=r(n)?void 0:u(n,e);return s?t(s,n,a,i):t(d,c(a),n,i)},function(t,e){var r=i(this),o=c(t);if(!y){var u=f(d,r,o,e,d!==n);if(u.done)return u.value}var m=a(r,RegExp),w=r.unicode,S=(r.ignoreCase?"i":"")+(r.multiline?"m":"")+(r.unicode?"u":"")+(p?"g":"y"),x=new m(p?"^(?:"+r.source+")":r,S),O=void 0===e?4294967295:e>>>0;if(0===O)return[];if(0===o.length)return null===h(x,o)?[o]:[];for(var k=0,T=0,C=[];T1?arguments[1]:void 0)},_s}(),r=Fe(),o=function(t){if(t&&t.forEach!==i)try{r(t,"forEach",i)}catch(e){t.forEach=i}};for(var a in e)e[a]&&o(t[a]&&t[a].prototype);o(n)}();var zs,qs={};!function(){if(zs)return qs;zs=1;var t=b(),e=Ms(),n=Us(),i=Br(),r=Fe(),o=jr(),a=me()("iterator"),s=i.values,l=function(t,n){if(t){if(t[a]!==s)try{r(t,a,s)}catch(e){t[a]=s}if(o(t,n,!0),e[n])for(var l in i)if(t[l]!==i[l])try{r(t,l,i[l])}catch(e){t[l]=i[l]}}};for(var c in e)l(t[c]&&t[c].prototype,c);l(n,"DOMTokenList")}();var Ws,Gs={};!function(){if(Ws)return Gs;Ws=1;var t=hi(),e=P(),n=ge(),i=Rr(),r=$r();t({target:"Object",stat:!0,forced:e((function(){i(1)})),sham:!r},{getPrototypeOf:function(t){return i(n(t))}})}();var Ks,Js={};!function(){if(Ks)return Js;Ks=1;var t,e=hi(),n=Ai(),i=Oe().f,r=Cn(),o=po(),a=Ya(),s=Tt(),l=Qa(),c=he(),u=n("".slice),h=Math.min,f=l("endsWith");e({target:"String",proto:!0,forced:!!(c||f||(t=i(String.prototype,"endsWith"),!t||t.writable))&&!f},{endsWith:function(t){var e=o(s(this));a(t);var n=arguments.length>1?arguments[1]:void 0,i=e.length,l=void 0===n?i:h(r(n),i),c=o(t);return u(e,l-c.length,l)===c}})}();var Ys,Qs={};!function(){if(Ys)return Qs;Ys=1;var t=ds().charAt,e=po(),n=nn(),i=_r(),r=Vr(),o="String Iterator",a=n.set,s=n.getterFor(o);i(String,"String",(function(t){a(this,{type:o,string:e(t),index:0})}),(function(){var e,n=s(this),i=n.string,o=n.index;return o>=i.length?r(void 0,!0):(e=t(i,o),n.index+=e.length,r(e,!1))}))}();var Xs,Zs={};!function(){if(Xs)return Zs;Xs=1;var t=$(),e=fs(),n=je(),i=kt(),r=Cn(),o=po(),a=Tt(),s=_t(),l=ps(),c=vs();e("match",(function(e,u,h){return[function(n){var r=a(this),l=i(n)?void 0:s(n,e);return l?t(l,n,r):new RegExp(n)[e](o(r))},function(t){var e=n(this),i=o(t),a=h(u,e,i);if(a.done)return a.value;if(!e.global)return c(e,i);var s=e.unicode;e.lastIndex=0;for(var f,d=[],p=0;null!==(f=c(e,i));){var g=o(f[0]);d[p]=g,""===g&&(e.lastIndex=l(i,r(e.lastIndex),s)),p++}return 0===p?null:d}]}))}();var tl,el={};!function(){if(tl)return el;tl=1;var t,e=hi(),n=Ai(),i=Oe().f,r=Cn(),o=po(),a=Ya(),s=Tt(),l=Qa(),c=he(),u=n("".slice),h=Math.min,f=l("startsWith");e({target:"String",proto:!0,forced:!!(c||f||(t=i(String.prototype,"startsWith"),!t||t.writable))&&!f},{startsWith:function(t){var e=o(s(this));a(t);var n=r(h(arguments.length>1?arguments[1]:void 0,e.length)),i=o(t);return u(e,n,n+i.length)===i}})}();var nl,il,rl,ol,al,sl,ll,cl,ul,hl,fl,dl,pl,gl,vl,bl,ml,yl,wl={};function Sl(){if(rl)return il;rl=1;var t=b(),e=I(),n=Object.getOwnPropertyDescriptor;return il=function(i){if(!e)return t[i];var r=n(t,i);return r&&r.value}}function xl(){if(ll)return sl;ll=1;var t=on();return sl=function(e,n,i){for(var r in n)t(e,r,n[r],i);return e}}function Ol(){if(ul)return cl;ul=1;var t=$t(),e=TypeError;return cl=function(n,i){if(t(i,n))return n;throw new e("Incorrect invocation")}}function kl(){if(fl)return hl;fl=1;var t=vi(),e=_t(),n=kt(),i=Ar(),r=me()("iterator");return hl=function(o){if(!n(o))return e(o,r)||e(o,"@@iterator")||i[t(o)]}}function Tl(){if(pl)return dl;pl=1;var t=$(),e=Lt(),n=je(),i=Dt(),r=kl(),o=TypeError;return dl=function(a,s){var l=arguments.length<2?r(a):s;if(e(l))return n(t(l,a));throw new o(i(a)+" is not iterable")},dl}function Cl(){if(vl)return gl;vl=1;var t=TypeError;return gl=function(e,n){if(el;){if(e=+arguments[l++],n(e,1114111)!==e)throw new i(e+" is not a valid code point");o[l]=e<65536?r(e):r(55296+((e-=65536)>>10),e%1024+56320)}return a(o,"")}})}();var t=hi(),e=b(),n=Sl(),i=At(),r=$(),o=St(),a=I(),s=function(){if(al)return ol;al=1;var t=P(),e=me(),n=I(),i=he(),r=e("iterator");return ol=!t((function(){var t=new URL("b?a=1&b=2&c=3","https://a"),e=t.searchParams,o=new URLSearchParams("a=1&a=2&b=3"),a="";return t.pathname="c%20d",e.forEach((function(t,n){e.delete("b"),a+=n+t})),o.delete("a",2),o.delete("b",void 0),i&&(!t.toJSON||!o.has("a",1)||o.has("a",2)||!o.has("a",void 0)||o.has("b"))||!e.size&&(i||!n)||!e.sort||"https://a/c%20d?a=1&c=3"!==t.href||"3"!==e.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!e[r]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("https://тест").host||"#%D0%B1"!==new URL("https://a#б").hash||"a1c3"!==a||"x"!==new URL("https://x",void 0).host}))}(),l=on(),c=Ea(),u=xl(),h=jr(),f=Nr(),d=nn(),p=Ol(),g=Pt(),v=ve(),m=$i(),y=vi(),w=je(),S=It(),x=po(),O=Wi(),k=wt(),T=Tl(),C=kl(),A=Vr(),R=Cl(),E=me(),j=go(),N=E("iterator"),F="URLSearchParams",D=F+"Iterator",L=d.set,_=d.getterFor(F),V=d.getterFor(D),B=n("fetch"),H=n("Request"),M=n("Headers"),U=H&&H.prototype,z=M&&M.prototype,q=e.TypeError,W=e.encodeURIComponent,G=String.fromCharCode,K=i("String","fromCodePoint"),J=parseInt,Y=o("".charAt),Q=o([].join),X=o([].push),Z=o("".replace),tt=o([].shift),et=o([].splice),nt=o("".split),it=o("".slice),rt=o(/./.exec),ot=/\+/g,at=/^[0-9a-f]+$/i,st=function(t,e){var n=it(t,e,e+2);return rt(at,n)?J(n,16):NaN},lt=function(t){for(var e=0,n=128;n>0&&t&n;n>>=1)e++;return e},ct=function(t){var e=null;switch(t.length){case 1:e=t[0];break;case 2:e=(31&t[0])<<6|63&t[1];break;case 3:e=(15&t[0])<<12|(63&t[1])<<6|63&t[2];break;case 4:e=(7&t[0])<<18|(63&t[1])<<12|(63&t[2])<<6|63&t[3]}return e>1114111?null:e},ut=function(t){for(var e=(t=Z(t,ot," ")).length,n="",i=0;ie){n+="%",i++;continue}var o=st(t,i+1);if(o!=o){n+=r,i++;continue}i+=2;var a=lt(o);if(0===a)r=G(o);else{if(1===a||a>4){n+="�",i++;continue}for(var s=[o],l=1;le||"%"!==Y(t,i));){var c=st(t,i+1);if(c!=c){i+=3;break}if(c>191||c<128)break;X(s,c),i+=2,l++}if(s.length!==a){n+="�";continue}var u=ct(s);null===u?n+="�":r=K(u)}}n+=r,i++}return n},ht=/[!'()~]|%20/g,ft={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},dt=function(t){return ft[t]},pt=function(t){return Z(W(t),ht,dt)},gt=f((function(t,e){L(this,{type:D,target:_(t).entries,index:0,kind:e})}),F,(function(){var t=V(this),e=t.target,n=t.index++;if(!e||n>=e.length)return t.target=null,A(void 0,!0);var i=e[n];switch(t.kind){case"keys":return A(i.key,!1);case"values":return A(i.value,!1)}return A([i.key,i.value],!1)}),!0),vt=function(t){this.entries=[],this.url=null,void 0!==t&&(S(t)?this.parseObject(t):this.parseQuery("string"==typeof t?"?"===Y(t,0)?it(t,1):t:x(t)))};vt.prototype={type:F,bindURL:function(t){this.url=t,this.update()},parseObject:function(t){var e,n,i,o,a,s,l,c=this.entries,u=C(t);if(u)for(n=(e=T(t,u)).next;!(i=r(n,e)).done;){if(a=(o=T(w(i.value))).next,(s=r(a,o)).done||(l=r(a,o)).done||!r(a,o).done)throw new q("Expected sequence with length 2");X(c,{key:x(s.value),value:x(l.value)})}else for(var h in t)v(t,h)&&X(c,{key:h,value:x(t[h])})},parseQuery:function(t){if(t)for(var e,n,i=this.entries,r=nt(t,"&"),o=0;o0?arguments[0]:void 0));a||(this.size=t.entries.length)},mt=bt.prototype;if(u(mt,{append:function(t,e){var n=_(this);R(arguments.length,2),X(n.entries,{key:x(t),value:x(e)}),a||this.length++,n.updateURL()},delete:function(t){for(var e=_(this),n=R(arguments.length,1),i=e.entries,r=x(t),o=n<2?void 0:arguments[1],s=void 0===o?o:x(o),l=0;le.key?1:-1})),t.updateURL()},forEach:function(t){for(var e,n=_(this).entries,i=m(t,arguments.length>1?arguments[1]:void 0),r=0;r1?Ot(arguments[1]):{})}}),g(H)){var kt=function(t){return p(this,U),new H(t,arguments.length>1?Ot(arguments[1]):{})};U.constructor=kt,kt.prototype=U,t({global:!0,constructor:!0,dontCallGetSet:!0,forced:!0},{Request:kt})}}return bl={URLSearchParams:bt,getState:_}}yl||(yl=1,Pl());var Il={getBootstrapVersion:function(){var e,n,i=5;if("undefined"!=typeof window&&null!==(e=window.bootstrap)&&void 0!==e&&null!==(e=e.Tooltip)&&void 0!==e&&e.VERSION){var r=window.bootstrap.Tooltip.VERSION;void 0!==r&&(i=parseInt(r,10))}else if(void 0!==t&&null!==(n=t.fn)&&void 0!==n&&null!==(n=n.dropdown)&&void 0!==n&&null!==(n=n.Constructor)&&void 0!==n&&n.VERSION){var o=t.fn.dropdown.Constructor.VERSION;void 0!==o&&(i=parseInt(o,10))}return i},getIconsPrefix:function(t){return{bootstrap3:"glyphicon",bootstrap4:"fa",bootstrap5:"bi","bootstrap-table":"icon",bulma:"fa",foundation:"fa",materialize:"material-icons",semantic:"fa"}[t]||"fa"},getIcons:function(t,e){return t[e]||{}},assignIcons:function(t,e,n){for(var i=0,r=Object.keys(t);i1?e-1:0),i=1;i0&&void 0!==arguments[0]?arguments[0]:{};return 0===Object.entries(t).length&&t.constructor===Object},isNumeric:function(t){return!isNaN(parseFloat(t))&&isFinite(t)},getFieldTitle:function(t,e){var n,i=r(t);try{for(i.s();!(n=i.n()).done;){var o=n.value;if(o.field===e)return o.title}}catch(t){i.e(t)}finally{i.f()}return""},setFieldIndex:function(t){var e,n=0,i=[],o=r(t[0]);try{for(o.s();!(e=o.n()).done;){n+=+e.value.colspan||1}}catch(t){o.e(t)}finally{o.f()}for(var a=0;a1){for(var h=0,f=function(t){var e=o.filter((function(e){return e.fieldIndex===t})),n=e[e.length-1];if(e.length>1)for(var i=0;i0}}}catch(t){l.e(t)}finally{l.f()}}}catch(t){a.e(t)}finally{a.f()}if(!(t.length<2)){var p,g=r(e);try{var v=function(){var t=p.value,e=o.filter((function(e){return e.fieldIndex===t.fieldIndex}));if(e.length>1){var n,i=r(e);try{for(i.s();!(n=i.n()).done;){n.value.visible=t.visible}}catch(t){i.e(t)}finally{i.f()}}};for(g.s();!(p=g.n()).done;)v()}catch(t){g.e(t)}finally{g.f()}}},getScrollBarWidth:function(){if(void 0===this.cachedWidth){var e=t("
    ").addClass("fixed-table-scroll-inner"),n=t("
    ").addClass("fixed-table-scroll-outer");n.append(e),t("body").append(n);var i=e[0].offsetWidth;n.css("overflow","scroll");var r=e[0].offsetWidth;i===r&&(r=n[0].clientWidth),n.remove(),this.cachedWidth=i-r}return this.cachedWidth},calculateObjectValue:function(t,e,n,i){var o=e;if("string"==typeof e){var a=e.split(".");if(a.length>1){o=window;var s,l=r(a);try{for(l.s();!(s=l.n()).done;){o=o[s.value]}}catch(t){l.e(t)}finally{l.f()}}else o=window[e]}return null!==o&&"object"===h(o)?o:"function"==typeof o?o.apply(t,n||[]):!o&&"string"==typeof e&&n&&this.sprintf.apply(this,[e].concat(c(n)))?this.sprintf.apply(this,[e].concat(c(n))):i},compareObjects:function(t,e,n){var i=Object.keys(t),r=Object.keys(e);if(n&&i.length!==r.length)return!1;for(var o=0,a=i;o/g,">").replace(/"/g,""").replace(/'/g,"'"):t},unescapeHTML:function(t){return"string"==typeof t&&t?t.toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'"):t},removeHTML:function(t){return t?t.toString().replace(/(<([^>]+)>)/gi,"").replace(/&[#A-Za-z0-9]+;/gi,"").trim():t},getRealDataAttr:function(t){for(var e=0,n=Object.entries(t);e3&&void 0!==arguments[3]?arguments[3]:void 0,o=t;if(void 0!==i&&(n=i),"string"!=typeof e||t.hasOwnProperty(e))return n?this.escapeHTML(t[e]):t[e];var a,s=r(e.split("."));try{for(s.s();!(a=s.n()).done;){var l=a.value;o=o&&o[l]}}catch(t){s.e(t)}finally{s.f()}return n?this.escapeHTML(o):o},isIEBrowser:function(){return navigator.userAgent.includes("MSIE ")||/Trident.*rv:11\./.test(navigator.userAgent)},findIndex:function(t,e){var n,i=r(t);try{for(i.s();!(n=i.n()).done;){var o=n.value;if(JSON.stringify(o)===JSON.stringify(e))return t.indexOf(o)}}catch(t){i.e(t)}finally{i.f()}return-1},trToData:function(e,n){var i=this,r=[],o=[];return n.each((function(n,a){var s=t(a),l={};l._id=s.attr("id"),l._class=s.attr("class"),l._data=i.getRealDataAttr(s.data()),l._style=s.attr("style"),s.find(">td,>th").each((function(r,a){for(var s=t(a),c=+s.attr("colspan")||1,u=+s.attr("rowspan")||1,h=r;o[n]&&o[n][h];h++);for(var f=h;fe?n:0;if(i.sortEmptyLast){if(""===t)return 1;if(""===e)return-1}return t===e?0:("string"!=typeof t&&(t=t.toString()),-1===t.localeCompare(e)?-1*n:n)},getEventName:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=e||"".concat(+new Date).concat(~~(1e6*Math.random())),"".concat(t,"-").concat(e)},hasDetailViewIcon:function(t){return t.detailView&&t.detailViewIcon&&!t.cardView},getDetailViewIndexOffset:function(t){return this.hasDetailViewIcon(t)&&"right"!==t.detailViewAlign?1:0},checkAutoMergeCells:function(t){var e,n=r(t);try{for(n.s();!(e=n.n()).done;)for(var i=e.value,o=0,a=Object.keys(i);o0){var i=e.substring(0,n).trim(),r=e.substring(n+1).trim();t.style.setProperty(i,r)}}));else if(Array.isArray(e)){var n,i=r(e);try{for(i.s();!(n=i.n()).done;){var o=n.value;this.parseStyle(t,o)}}catch(t){i.e(t)}finally{i.f()}}else if("object"===h(e))for(var a=0,s=Object.entries(e);a',icon:'',inputGroup:'
    %s%s
    ',pageDropdown:['"],pageDropdownItem:'
    ',pagination:['
      ',"
    "],paginationItem:'
  • %s
  • ',searchButton:'',searchClearButton:'',searchInput:'',toolbarDropdown:['"],toolbarDropdownItem:'',toolbarDropdownSeparator:'
  • '}},4:{classes:{buttonActive:"active",buttons:"secondary",buttonsDropdown:"btn-group",buttonsGroup:"btn-group",buttonsPrefix:"btn",dropdownActive:"active",dropup:"dropup",input:"form-control",inputGroup:"btn-group",inputPrefix:"form-control-",paginationActive:"active",paginationDropdown:"btn-group dropdown",pull:"float",select:"form-control"},html:{dropdownCaret:'',icon:'',inputGroup:'
    %s
    %s
    ',pageDropdown:['"],pageDropdownItem:'%s',pagination:['
      ',"
    "],paginationItem:'
  • %s
  • ',searchButton:'',searchClearButton:'',searchInput:'',toolbarDropdown:['"],toolbarDropdownItem:'',toolbarDropdownSeparator:''}},5:{classes:{buttonActive:"active",buttons:"secondary",buttonsDropdown:"btn-group",buttonsGroup:"btn-group",buttonsPrefix:"btn",dropdownActive:"active",dropup:"dropup",input:"form-control",inputGroup:"btn-group",inputPrefix:"form-control-",paginationActive:"active",paginationDropdown:"btn-group dropdown",pull:"float",select:"form-select"},html:{dataToggle:"data-bs-toggle",dropdownCaret:'',icon:'',inputGroup:'
    %s%s
    ',pageDropdown:['"],pageDropdownItem:'%s',pagination:['
      ',"
    "],paginationItem:'
  • %s
  • ',searchButton:'',searchClearButton:'',searchInput:'',toolbarDropdown:['"],toolbarDropdownItem:'',toolbarDropdownSeparator:''}}}[Al],Rl={ajax:void 0,ajaxOptions:{},buttons:{},buttonsAlign:"right",buttonsAttributeTitle:"title",buttonsClass:$l.classes.buttons,buttonsOrder:["paginationSwitch","refresh","toggle","fullscreen","columns"],buttonsPrefix:$l.classes.buttonsPrefix,buttonsToolbar:void 0,cache:!0,cardView:!1,checkboxHeader:!0,classes:"table table-bordered table-hover",clickToSelect:!1,columns:[[]],contentType:"application/json",customSearch:void 0,customSort:void 0,data:[],dataField:"rows",dataType:"json",detailFilter:function(t,e){return!0},detailFormatter:function(t,e){return""},detailView:!1,detailViewAlign:"left",detailViewByClick:!1,detailViewIcon:!0,escape:!1,escapeTitle:!0,filterOptions:{filterAlgorithm:"and"},fixedScroll:!1,footerField:"footer",footerStyle:function(t){return{}},headerStyle:function(t){return{}},height:void 0,icons:{},iconSize:void 0,iconsPrefix:void 0,idField:void 0,ignoreClickToSelectOn:function(t){var e=t.tagName;return["A","BUTTON"].includes(e)},loadingFontSize:"auto",loadingTemplate:function(t){return'\n '.concat(t,'\n \n \n ')},locale:void 0,maintainMetaData:!1,method:"get",minimumCountColumns:1,multipleSelectRow:!1,pageList:[10,25,50,100],pageNumber:1,pageSize:10,pagination:!1,paginationDetailHAlign:"left",paginationHAlign:"right",paginationLoadMore:!1,paginationLoop:!0,paginationNextText:"›",paginationPagesBySide:1,paginationParts:["pageInfo","pageSize","pageList"],paginationPreText:"‹",paginationSuccessivelySize:5,paginationUseIntermediate:!1,paginationVAlign:"bottom",queryParams:function(t){return t},queryParamsType:"limit",regexSearch:!1,rememberOrder:!1,responseHandler:function(t){return t},rowAttributes:function(t,e){return{}},rowStyle:function(t,e){return{}},search:!1,searchable:!1,searchAccentNeutralise:!1,searchAlign:"right",searchHighlight:!1,searchOnEnterKey:!1,searchSelector:!1,searchText:"",searchTimeOut:500,selectItemName:"btSelectItem",serverSort:!0,showButtonIcons:!0,showButtonText:!1,showColumns:!1,showColumnsSearch:!1,showColumnsToggleAll:!1,showExtendedPagination:!1,showFooter:!1,showFullscreen:!1,showHeader:!0,showPaginationSwitch:!1,showRefresh:!1,showSearchButton:!1,showSearchClearButton:!1,showToggle:!1,sidePagination:"client",silentSort:!0,singleSelect:!1,smartDisplay:!0,sortable:!0,sortClass:void 0,sortEmptyLast:!1,sortName:void 0,sortOrder:void 0,sortReset:!1,sortResetPage:!1,sortStable:!1,strictSearch:!1,theadClasses:"",toolbar:void 0,toolbarAlign:"left",totalField:"total",totalNotFiltered:0,totalNotFilteredField:"totalNotFiltered",totalRows:0,trimOnSearch:!0,undefinedText:"-",uniqueId:void 0,url:void 0,virtualScroll:!1,virtualScrollItemHeight:void 0,visibleSearch:!1,onAll:function(t,e){return!1},onCheck:function(t){return!1},onCheckAll:function(t){return!1},onCheckSome:function(t){return!1},onClickCell:function(t,e,n,i){return!1},onClickRow:function(t,e){return!1},onCollapseRow:function(t,e){return!1},onColumnSwitch:function(t,e){return!1},onColumnSwitchAll:function(t){return!1},onDblClickCell:function(t,e,n,i){return!1},onDblClickRow:function(t,e){return!1},onExpandRow:function(t,e,n){return!1},onLoadError:function(t){return!1},onLoadSuccess:function(t){return!1},onPageChange:function(t,e){return!1},onPostBody:function(){return!1},onPostFooter:function(){return!1},onPostHeader:function(){return!1},onPreBody:function(t){return!1},onRefresh:function(t){return!1},onRefreshOptions:function(t){return!1},onResetView:function(){return!1},onScrollBody:function(){return!1},onSearch:function(t){return!1},onSort:function(t,e){return!1},onToggle:function(t){return!1},onTogglePagination:function(t){return!1},onUncheck:function(t){return!1},onUncheckAll:function(t){return!1},onUncheckSome:function(t){return!1},onVirtualScroll:function(t,e){return!1}},El={formatLoadingMessage:function(){return"Loading, please wait"},formatRecordsPerPage:function(t){return"".concat(t," rows per page")},formatShowingRows:function(t,e,n,i){return void 0!==i&&i>0&&i>n?"Showing ".concat(t," to ").concat(e," of ").concat(n," rows (filtered from ").concat(i," total rows)"):"Showing ".concat(t," to ").concat(e," of ").concat(n," rows")},formatSRPaginationPreText:function(){return"previous page"},formatSRPaginationPageText:function(t){return"to page ".concat(t)},formatSRPaginationNextText:function(){return"next page"},formatDetailPagination:function(t){return"Showing ".concat(t," rows")},formatSearch:function(){return"Search"},formatClearSearch:function(){return"Clear Search"},formatNoMatches:function(){return"No matching records found"},formatPaginationSwitch:function(){return"Hide/Show pagination"},formatPaginationSwitchDown:function(){return"Show pagination"},formatPaginationSwitchUp:function(){return"Hide pagination"},formatRefresh:function(){return"Refresh"},formatToggleOn:function(){return"Show card view"},formatToggleOff:function(){return"Hide card view"},formatColumns:function(){return"Columns"},formatColumnsToggleAll:function(){return"Toggle all"},formatFullscreen:function(){return"Fullscreen"},formatAllRows:function(){return"All"}},jl={align:void 0,cardVisible:!0,cellStyle:void 0,checkbox:!1,checkboxEnabled:!0,class:void 0,clickToSelect:!0,colspan:void 0,detailFormatter:void 0,escape:void 0,events:void 0,falign:void 0,field:void 0,footerFormatter:void 0,footerStyle:void 0,formatter:void 0,halign:void 0,order:"asc",radio:!1,rowspan:void 0,searchable:!0,searchFormatter:!0,searchHighlightFormatter:!1,showSelectTitle:!1,sortable:!1,sorter:void 0,sortName:void 0,switchable:!0,switchableLabel:void 0,title:void 0,titleTooltip:void 0,valign:void 0,visible:!0,width:void 0,widthUnit:"px"};Object.assign(Rl,El);var Nl={COLUMN_DEFAULTS:jl,CONSTANTS:$l,DEFAULTS:Rl,EVENTS:{"all.bs.table":"onAll","check-all.bs.table":"onCheckAll","check-some.bs.table":"onCheckSome","check.bs.table":"onCheck","click-cell.bs.table":"onClickCell","click-row.bs.table":"onClickRow","collapse-row.bs.table":"onCollapseRow","column-switch-all.bs.table":"onColumnSwitchAll","column-switch.bs.table":"onColumnSwitch","dbl-click-cell.bs.table":"onDblClickCell","dbl-click-row.bs.table":"onDblClickRow","expand-row.bs.table":"onExpandRow","load-error.bs.table":"onLoadError","load-success.bs.table":"onLoadSuccess","page-change.bs.table":"onPageChange","post-body.bs.table":"onPostBody","post-footer.bs.table":"onPostFooter","post-header.bs.table":"onPostHeader","pre-body.bs.table":"onPreBody","refresh-options.bs.table":"onRefreshOptions","refresh.bs.table":"onRefresh","reset-view.bs.table":"onResetView","scroll-body.bs.table":"onScrollBody","search.bs.table":"onSearch","sort.bs.table":"onSort","toggle-pagination.bs.table":"onTogglePagination","toggle.bs.table":"onToggle","uncheck-all.bs.table":"onUncheckAll","uncheck-some.bs.table":"onUncheckSome","uncheck.bs.table":"onUncheck","virtual-scroll.bs.table":"onVirtualScroll"},ICONS:{glyphicon:{clearSearch:"glyphicon-trash",columns:"glyphicon-th icon-th",detailClose:"glyphicon-minus icon-minus",detailOpen:"glyphicon-plus icon-plus",fullscreen:"glyphicon-fullscreen",paginationSwitchDown:"glyphicon-collapse-down icon-chevron-down",paginationSwitchUp:"glyphicon-collapse-up icon-chevron-up",refresh:"glyphicon-refresh icon-refresh",search:"glyphicon-search",toggleOff:"glyphicon-list-alt icon-list-alt",toggleOn:"glyphicon-list-alt icon-list-alt"},fa:{clearSearch:"fa-trash",columns:"fa-th-list",detailClose:"fa-minus",detailOpen:"fa-plus",fullscreen:"fa-arrows-alt",paginationSwitchDown:"fa-caret-square-down",paginationSwitchUp:"fa-caret-square-up",refresh:"fa-sync",search:"fa-search",toggleOff:"fa-toggle-off",toggleOn:"fa-toggle-on"},bi:{clearSearch:"bi-trash",columns:"bi-list-ul",detailClose:"bi-dash",detailOpen:"bi-plus",fullscreen:"bi-arrows-move",paginationSwitchDown:"bi-caret-down-square",paginationSwitchUp:"bi-caret-up-square",refresh:"bi-arrow-clockwise",search:"bi-search",toggleOff:"bi-toggle-off",toggleOn:"bi-toggle-on"},icon:{clearSearch:"icon-trash-2",columns:"icon-list",detailClose:"icon-minus",detailOpen:"icon-plus",fullscreen:"icon-maximize",paginationSwitchDown:"icon-arrow-up-circle",paginationSwitchUp:"icon-arrow-down-circle",refresh:"icon-refresh-cw",search:"icon-search",toggleOff:"icon-toggle-right",toggleOn:"icon-toggle-right"},"material-icons":{clearSearch:"delete",columns:"view_list",detailClose:"remove",detailOpen:"add",fullscreen:"fullscreen",paginationSwitchDown:"grid_on",paginationSwitchUp:"grid_off",refresh:"refresh",search:"search",sort:"sort",toggleOff:"tablet",toggleOn:"tablet_android"}},LOCALES:{en:El,"en-US":El},METHODS:["getOptions","refreshOptions","getData","getFooterData","getSelections","load","append","prepend","remove","removeAll","insertRow","updateRow","getRowByUniqueId","updateByUniqueId","removeByUniqueId","updateCell","updateCellByUniqueId","showRow","hideRow","getHiddenRows","showColumn","hideColumn","getVisibleColumns","getHiddenColumns","showAllColumns","hideAllColumns","mergeCells","checkAll","uncheckAll","checkInvert","check","uncheck","checkBy","uncheckBy","refresh","destroy","resetView","showLoading","hideLoading","togglePagination","toggleFullscreen","toggleView","resetSearch","filterBy","sortBy","sortReset","scrollTo","getScrollPosition","selectPage","prevPage","nextPage","toggleDetailView","expandRow","collapseRow","expandRowByUniqueId","collapseRowByUniqueId","expandAllRows","collapseAllRows","updateColumnTitle","updateFormatText"],THEME:"bootstrap".concat(Al),VERSION:"1.24.1"},Fl=function(){return i((function t(e){var i=this;n(this,t),this.rows=e.rows,this.scrollEl=e.scrollEl,this.contentEl=e.contentEl,this.callback=e.callback,this.itemHeight=e.itemHeight,this.cache={},this.scrollTop=this.scrollEl.scrollTop,this.initDOM(this.rows,e.fixedScroll),this.scrollEl.scrollTop=this.scrollTop,this.lastCluster=0;var r=function(){i.lastCluster!==(i.lastCluster=i.getNum())&&(i.initDOM(i.rows),i.callback(i.startIndex,i.endIndex))};this.scrollEl.addEventListener("scroll",r,!1),this.destroy=function(){i.contentEl.innerHtml="",i.scrollEl.removeEventListener("scroll",r,!1)}}),[{key:"initDOM",value:function(t,e){void 0===this.clusterHeight?(this.cache.scrollTop=this.scrollEl.scrollTop,this.cache.data=this.contentEl.innerHTML=t[0]+t[0]+t[0],this.getRowsHeight(t)):0===this.blockHeight&&this.getRowsHeight(t);var n=this.initData(t,this.getNum(e)),i=n.rows.join(""),r=this.checkChanges("data",i),o=this.checkChanges("top",n.topOffset),a=this.checkChanges("bottom",n.bottomOffset),s=[];r&&o?(n.topOffset&&s.push(this.getExtra("top",n.topOffset)),s.push(i),n.bottomOffset&&s.push(this.getExtra("bottom",n.bottomOffset)),this.startIndex=n.start,this.endIndex=n.end,this.contentEl.innerHTML=s.join(""),e&&(this.contentEl.scrollTop=this.cache.scrollTop)):a&&(this.contentEl.lastChild.style.height="".concat(n.bottomOffset,"px"))}},{key:"getRowsHeight",value:function(){if(void 0===this.itemHeight||0===this.itemHeight){var t=this.contentEl.children,e=t[Math.floor(t.length/2)];this.itemHeight=e.offsetHeight}this.blockHeight=50*this.itemHeight,this.clusterRows=200,this.clusterHeight=4*this.blockHeight}},{key:"getNum",value:function(t){return this.scrollTop=t?this.cache.scrollTop:this.scrollEl.scrollTop,Math.floor(this.scrollTop/(this.clusterHeight-this.blockHeight))||0}},{key:"initData",value:function(t,e){if(t.length<50)return{topOffset:0,bottomOffset:0,rowsAbove:0,rows:t};var n=Math.max((this.clusterRows-50)*e,0),i=n+this.clusterRows,r=Math.max(n*this.itemHeight,0),o=Math.max((t.length-i)*this.itemHeight,0),a=[],s=n;r<1&&s++;for(var l=n;l
    ':"",n=["bottom","both"].includes(this.options.paginationVAlign)?'
    ':"",i=Il.calculateObjectValue(this.options,this.options.loadingTemplate,[this.options.formatLoadingMessage()]);this.$container=t('\n
    \n
    \n ').concat(e,'\n
    \n
    \n
    \n
    \n ').concat(i,'\n
    \n
    \n \n
    \n ').concat(n,"\n
    \n ")),this.$container.insertAfter(this.$el),this.$tableContainer=this.$container.find(".fixed-table-container"),this.$tableHeader=this.$container.find(".fixed-table-header"),this.$tableBody=this.$container.find(".fixed-table-body"),this.$tableLoading=this.$container.find(".fixed-table-loading"),this.$tableFooter=this.$el.find("tfoot"),this.options.buttonsToolbar?this.$toolbar=t("body").find(this.options.buttonsToolbar):this.$toolbar=this.$container.find(".fixed-table-toolbar"),this.$pagination=this.$container.find(".fixed-table-pagination"),this.$tableBody.append(this.$el),this.$container.after('
    '),this.$el.addClass(this.options.classes),this.$tableLoading.addClass(this.options.classes),this.options.height&&(this.$tableContainer.addClass("fixed-height"),this.options.showFooter&&this.$tableContainer.addClass("has-footer"),this.options.classes.split(" ").includes("table-bordered")&&(this.$tableBody.append('
    '),this.$tableBorder=this.$tableBody.find(".fixed-table-border"),this.$tableLoading.addClass("fixed-table-border")),this.$tableFooter=this.$container.find(".fixed-table-footer"))}},{key:"initTable",value:function(){var n=this,i=[];if(this.$header=this.$el.find(">thead"),this.$header.length?this.options.theadClasses&&this.$header.addClass(this.options.theadClasses):this.$header=t('')).appendTo(this.$el),this._headerTrClasses=[],this._headerTrStyles=[],this.$header.find("tr").each((function(e,r){var o=t(r),a=[];o.find("th").each((function(e,n){var i=t(n);void 0!==i.data("field")&&i.data("field","".concat(i.data("field")));var r=Object.assign({},i.data());for(var o in r)t.fn.bootstrapTable.columnDefaults.hasOwnProperty(o)&&delete r[o];a.push(Il.extend({},{_data:Il.getRealDataAttr(r),title:i.html(),class:i.attr("class"),titleTooltip:i.attr("title"),rowspan:i.attr("rowspan")?+i.attr("rowspan"):void 0,colspan:i.attr("colspan")?+i.attr("colspan"):void 0},i.data()))})),i.push(a),o.attr("class")&&n._headerTrClasses.push(o.attr("class")),o.attr("style")&&n._headerTrStyles.push(o.attr("style"))})),Array.isArray(this.options.columns[0])||(this.options.columns=[this.options.columns]),this.options.columns=Il.extend(!0,[],i,this.options.columns),this.columns=[],this.fieldsColumnsIndex=[],!1!==this.optionsColumnsChanged&&Il.setFieldIndex(this.options.columns),this.options.columns.forEach((function(t,i){t.forEach((function(t,r){var o=Il.extend({},e.COLUMN_DEFAULTS,t,{passed:t});void 0!==o.fieldIndex&&(n.columns[o.fieldIndex]=o,n.fieldsColumnsIndex[o.field]=o.fieldIndex),n.options.columns[i][r]=o}))})),!this.options.data.length){var r=Il.trToData(this.columns,this.$el.find(">tbody>tr"));r.length&&(this.options.data=r,this.fromHtml=!0)}this.options.pagination&&"server"!==this.options.sidePagination||(this.footerData=Il.trToData(this.columns,this.$el.find(">tfoot>tr"))),this.footerData&&this.$el.find("tfoot").html(""),!this.options.showFooter||this.options.cardView?this.$tableFooter.hide():this.$tableFooter.show()}},{key:"initHeader",value:function(){var e=this,n={},i=[];this.header={fields:[],styles:[],classes:[],formatters:[],detailFormatters:[],events:[],sorters:[],sortNames:[],cellStyles:[],searchables:[]},Il.updateFieldGroup(this.options.columns,this.columns),this.options.columns.forEach((function(t,r){var o=[];o.push(""));var a="";if(0===r&&Il.hasDetailViewIcon(e.options)){var s=e.options.columns.length>1?' rowspan="'.concat(e.options.columns.length,'"'):"";a='\n
    \n ')}a&&"right"!==e.options.detailViewAlign&&o.push(a),t.forEach((function(t,i){var a=Il.sprintf(' class="%s"',t.class),s=t.widthUnit,c=parseFloat(t.width),u=t.halign?t.halign:t.align,f=Il.sprintf("text-align: %s; ",u),d=Il.sprintf("text-align: %s; ",t.align),p=Il.sprintf("vertical-align: %s; ",t.valign);if(p+=Il.sprintf("width: %s; ",!t.checkbox&&!t.radio||c?c?c+s:void 0:t.showSelectTitle?void 0:"36px"),void 0!==t.fieldIndex||t.visible){var g=Il.calculateObjectValue(null,e.options.headerStyle,[t]),v=[],b=[],m="";if(g&&g.css)for(var y=0,w=Object.entries(g.css);y0)for(var k=0,T=Object.entries(t._data);k0?" data-not-first-th":"",b.length>0?b.join(" "):"",">"),o.push(Il.sprintf('
    ',e.options.sortable&&t.sortable?"sortable".concat("center"===u?" sortable-center":""," both"):""));var A=e.options.escape&&e.options.escapeTitle?Il.escapeHTML(t.title):t.title,$=A;t.checkbox&&(A="",!e.options.singleSelect&&e.options.checkboxHeader&&(A=''),e.header.stateField=t.field),t.radio&&(A="",e.header.stateField=t.field),!A&&t.showSelectTitle&&(A+=$),o.push(A),o.push("
    "),o.push('
    '),o.push("
    "),o.push("")}})),a&&"right"===e.options.detailViewAlign&&o.push(a),o.push(""),o.length>3&&i.push(o.join(""))})),this.$header.html(i.join("")),this.$header.find("th[data-field]").each((function(e,i){t(i).data(n[t(i).data("field")])})),this.$container.off("click",".th-inner").on("click",".th-inner",(function(n){var i=t(n.currentTarget);if(e.options.detailView&&!i.parent().hasClass("bs-checkbox")&&i.closest(".bootstrap-table")[0]!==e.$container[0])return!1;e.options.sortable&&i.parent().data().sortable&&e.onSort(n)}));var r=Il.getEventName("resize.bootstrap-table",this.$el.attr("id"));t(window).off(r),!this.options.showHeader||this.options.cardView?(this.$header.hide(),this.$tableHeader.hide(),this.$tableLoading.css("top",0)):(this.$header.show(),this.$tableHeader.show(),this.$tableLoading.css("top",this.$header.outerHeight()+1),this.getCaret(),t(window).on(r,(function(){return e.resetView()}))),this.$selectAll=this.$header.find('[name="btSelectAll"]'),this.$selectAll.off("click").on("click",(function(n){n.stopPropagation();var i=t(n.currentTarget).prop("checked");e[i?"checkAll":"uncheckAll"](),e.updateSelected()}))}},{key:"initData",value:function(t,e){"append"===e?this.options.data=this.options.data.concat(t):"prepend"===e?this.options.data=[].concat(t).concat(this.options.data):(t=t||Il.deepCopy(this.options.data),this.options.data=Array.isArray(t)?t:t[this.options.dataField]),this.data=c(this.options.data),this.options.sortReset&&(this.unsortedData=c(this.data)),"server"!==this.options.sidePagination&&this.initSort()}},{key:"initSort",value:function(){var t=this,e=this.options.sortName,n="desc"===this.options.sortOrder?-1:1,i=this.header.fields.indexOf(this.options.sortName),r=0;-1!==i?(this.options.sortStable&&this.data.forEach((function(t,e){t.hasOwnProperty("_position")||(t._position=e)})),this.options.customSort?Il.calculateObjectValue(this.options,this.options.customSort,[this.options.sortName,this.options.sortOrder,this.data]):this.data.sort((function(r,o){t.header.sortNames[i]&&(e=t.header.sortNames[i]);var a=Il.getItemField(r,e,t.options.escape),s=Il.getItemField(o,e,t.options.escape),l=Il.calculateObjectValue(t.header,t.header.sorters[i],[a,s,r,o]);return void 0!==l?t.options.sortStable&&0===l?n*(r._position-o._position):n*l:Il.sort(a,s,n,t.options,r._position,o._position)})),void 0!==this.options.sortClass&&(clearTimeout(r),r=setTimeout((function(){t.$el.removeClass(t.options.sortClass);var e=t.$header.find('[data-field="'.concat(t.options.sortName,'"]')).index();t.$el.find("tr td:nth-child(".concat(e+1,")")).addClass(t.options.sortClass)}),250))):this.options.sortReset&&(this.data=c(this.unsortedData))}},{key:"sortReset",value:function(){this.options.sortName=void 0,this.options.sortOrder=void 0,this._sort()}},{key:"sortBy",value:function(t){this.options.sortName=t.field,this.options.sortOrder=t.hasOwnProperty("sortOrder")?t.sortOrder:"asc",this._sort()}},{key:"onSort",value:function(e){var n=e.type,i=e.currentTarget,r="keypress"===n?t(i):t(i).parent(),o=this.$header.find("th").eq(r.index());if(this.$header.add(this.$header_).find("span.order").remove(),this.options.sortName===r.data("field")){var a=this.options.sortOrder,s=this.columns[this.fieldsColumnsIndex[r.data("field")]].sortOrder||this.columns[this.fieldsColumnsIndex[r.data("field")]].order;void 0===a?this.options.sortOrder="asc":"asc"===a?this.options.sortOrder=this.options.sortReset?"asc"===s?"desc":void 0:"desc":"desc"===this.options.sortOrder&&(this.options.sortOrder=this.options.sortReset?"desc"===s?"asc":void 0:"asc"),void 0===this.options.sortOrder&&(this.options.sortName=void 0)}else this.options.sortName=r.data("field"),this.options.rememberOrder?this.options.sortOrder="asc"===r.data("order")?"desc":"asc":this.options.sortOrder=this.columns[this.fieldsColumnsIndex[r.data("field")]].sortOrder||this.columns[this.fieldsColumnsIndex[r.data("field")]].order;r.add(o).data("order",this.options.sortOrder),this.getCaret(),this._sort()}},{key:"_sort",value:function(){if("server"===this.options.sidePagination&&this.options.serverSort)return this.options.pageNumber=1,this.trigger("sort",this.options.sortName,this.options.sortOrder),void this.initServer(this.options.silentSort);this.options.pagination&&this.options.sortResetPage&&(this.options.pageNumber=1,this.initPagination()),this.trigger("sort",this.options.sortName,this.options.sortOrder),this.initSort(),this.initBody()}},{key:"initToolbar",value:function(){var e,n=this,i=this.options,o=[],a=0,s=0;this.$toolbar.find(".bs-bars").children().length&&t("body").append(t(i.toolbar)),this.$toolbar.html(""),"string"!=typeof i.toolbar&&"object"!==h(i.toolbar)||t(Il.sprintf('
    ',this.constants.classes.pull,i.toolbarAlign)).appendTo(this.$toolbar).append(t(i.toolbar)),o=['
    ')],"string"==typeof i.buttonsOrder&&(i.buttonsOrder=i.buttonsOrder.replace(/\[|\]| |'/g,"").split(",")),this.buttons=Object.assign(this.buttons,{paginationSwitch:{text:i.pagination?i.formatPaginationSwitchUp():i.formatPaginationSwitchDown(),icon:i.pagination?i.icons.paginationSwitchDown:i.icons.paginationSwitchUp,render:!1,event:this.togglePagination,attributes:{"aria-label":i.formatPaginationSwitch(),title:i.formatPaginationSwitch()}},refresh:{text:i.formatRefresh(),icon:i.icons.refresh,render:!1,event:this.refresh,attributes:{"aria-label":i.formatRefresh(),title:i.formatRefresh()}},toggle:{text:i.formatToggleOn(),icon:i.icons.toggleOff,render:!1,event:this.toggleView,attributes:{"aria-label":i.formatToggleOn(),title:i.formatToggleOn()}},fullscreen:{text:i.formatFullscreen(),icon:i.icons.fullscreen,render:!1,event:this.toggleFullscreen,attributes:{"aria-label":i.formatFullscreen(),title:i.formatFullscreen()}},columns:{render:!1,html:function(){var t=[];if(t.push('
    \n \n ").concat(n.constants.html.toolbarDropdown[0])),i.showColumnsSearch&&(t.push(Il.sprintf(n.constants.html.toolbarDropdownItem,Il.sprintf('',n.constants.classes.input,i.formatSearch()))),t.push(n.constants.html.toolbarDropdownSeparator)),i.showColumnsToggleAll){var e=n.getVisibleColumns().length===n.columns.filter((function(t){return!n.isSelectionColumn(t)})).length;t.push(Il.sprintf(n.constants.html.toolbarDropdownItem,Il.sprintf(' %s',e?'checked="checked"':"",i.formatColumnsToggleAll()))),t.push(n.constants.html.toolbarDropdownSeparator)}var r=0;return n.columns.forEach((function(t){t.visible&&r++})),n.columns.forEach((function(e,o){if(!n.isSelectionColumn(e)&&(!i.cardView||e.cardVisible)){var a=e.visible?' checked="checked"':"",l=r<=i.minimumCountColumns&&a?' disabled="disabled"':"";e.switchable&&(t.push(Il.sprintf(n.constants.html.toolbarDropdownItem,Il.sprintf(' %s',e.field,o,a,l,e.switchableLabel||e.title))),s++)}})),t.push(n.constants.html.toolbarDropdown[1],"
    "),t.join("")}}});for(var c={},u=0,f=Object.entries(this.buttons);u"}c[p]=v;var k="show".concat(p.charAt(0).toUpperCase()).concat(p.substring(1)),T=i[k];!(!g.hasOwnProperty("render")||g.hasOwnProperty("render")&&g.render)||void 0!==T&&!0!==T||(i[k]=!0),i.buttonsOrder.includes(p)||i.buttonsOrder.push(p)}var C,P=r(i.buttonsOrder);try{for(P.s();!(C=P.n()).done;){var I=C.value;i["show".concat(I.charAt(0).toUpperCase()).concat(I.substring(1))]&&o.push(c[I])}}catch(t){P.e(t)}finally{P.f()}o.push("
    "),(this.showToolbar||o.length>2)&&this.$toolbar.append(o.join(""));for(var A=function(){var t=l(R[$],2),e=t[0],i=t[1];if(i.hasOwnProperty("event")){if("function"==typeof i.event||"string"==typeof i.event){var r="string"==typeof i.event?window[i.event]:i.event;return n.$toolbar.find('button[name="'.concat(e,'"]')).off("click").on("click",(function(){return r.call(n)})),1}for(var o=function(){var t=l(s[a],2),i=t[0],r=t[1],o="string"==typeof r?window[r]:r;n.$toolbar.find('button[name="'.concat(e,'"]')).off(i).on(i,(function(){return o.call(n)}))},a=0,s=Object.entries(i.event);a'),B=V;if(i.showSearchButton||i.showSearchClearButton){var H=(i.showSearchButton?L:"")+(i.showSearchClearButton?_:"");B=i.search?Il.sprintf(this.constants.html.inputGroup,V,H):H}o.push(Il.sprintf('\n
    \n %s\n
    \n '),B)),this.$toolbar.append(o.join(""));var M=Il.getSearchInput(this);i.showSearchButton?(this.$toolbar.find(".search button[name=search]").off("click").on("click",(function(){clearTimeout(a),a=setTimeout((function(){n.onSearch({currentTarget:M})}),i.searchTimeOut)})),i.searchOnEnterKey&&D(M)):D(M),i.showSearchClearButton&&this.$toolbar.find(".search button[name=clearSearch]").click((function(){n.resetSearch()}))}else"string"==typeof i.searchSelector&&D(Il.getSearchInput(this))}},{key:"onSearch",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.currentTarget,i=e.firedByInitSearchText,r=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(void 0!==n&&t(n).length&&r){var o=t(n).val().trim();if(this.options.trimOnSearch&&t(n).val()!==o&&t(n).val(o),this.searchText===o)return;var a=Il.getSearchInput(this),s=n instanceof jQuery?n:t(n);(s.is(a)||s.hasClass("search-input"))&&(this.searchText=o,this.options.searchText=o)}i||(this.options.pageNumber=1),this.initSearch(),i?"client"===this.options.sidePagination&&this.updatePagination():this.updatePagination(),this.trigger("search",this.searchText)}},{key:"initSearch",value:function(){var e=this;if(this.filterOptions=this.filterOptions||this.options.filterOptions,"server"!==this.options.sidePagination){if(this.options.customSearch)return this.data=Il.calculateObjectValue(this.options,this.options.customSearch,[this.options.data,this.searchText,this.filterColumns]),this.options.sortReset&&(this.unsortedData=c(this.data)),void this.initSort();var n=this.searchText&&(this.fromHtml?Il.escapeHTML(this.searchText):this.searchText),i=n?n.toLowerCase():"",r=Il.isEmptyObject(this.filterColumns)?null:this.filterColumns;this.options.searchAccentNeutralise&&(i=Il.normalizeAccent(i)),"function"==typeof this.filterOptions.filterAlgorithm?this.data=this.options.data.filter((function(t){return e.filterOptions.filterAlgorithm.apply(null,[t,r])})):"string"==typeof this.filterOptions.filterAlgorithm&&(this.data=r?this.options.data.filter((function(t){var n=e.filterOptions.filterAlgorithm;if("and"===n){for(var i in r)if(Array.isArray(r[i])&&!r[i].includes(t[i])||!Array.isArray(r[i])&&t[i]!==r[i])return!1}else if("or"===n){var o=!1;for(var a in r)(Array.isArray(r[a])&&r[a].includes(t[a])||!Array.isArray(r[a])&&t[a]===r[a])&&(o=!0);return o}return!0})):c(this.options.data));var o=this.getVisibleFields();this.data=i?this.data.filter((function(r,a){for(var s=0;s").html(u).text())),"string"==typeof u||"number"==typeof u)if(e.options.strictSearch){if("".concat(u).toLowerCase()===i)return!0}else if(e.options.regexSearch){if(Il.regexCompare(u,n))return!0}else{var d=/(?:(<=|=>|=<|>=|>|<)(?:\s+)?(-?\d+)?|(-?\d+)?(\s+)?(<=|=>|=<|>=|>|<))/gm.exec(e.searchText),p=!1;if(d){var g=d[1]||"".concat(d[5],"l"),v=d[2]||d[3],b=parseInt(u,10),m=parseInt(v,10);switch(g){case">":case"m;break;case"<":case">l":p=b=l":case"=>l":p=b<=m;break;case">=":case"=>":case"<=l":case"==m}}if(p||"".concat(u).toLowerCase().includes(i))return!0}}return!1})):this.data,this.options.sortReset&&(this.unsortedData=c(this.data)),this.initSort()}}},{key:"initPagination",value:function(){var t=this,e=this.options;if(e.pagination){this.$pagination.show();var n,i,r,o,a,s,l,c=[],u=!1,h=this.getData({includeHiddenRows:!1}),f=e.pageList;if("string"==typeof f&&(f=f.replace(/\[|\]| /g,"").toLowerCase().split(",")),f=f.map((function(t){return"string"==typeof t?t.toLowerCase()===e.formatAllRows().toLowerCase()||["all","unlimited"].includes(t.toLowerCase())?e.formatAllRows():+t:t})),this.paginationParts=e.paginationParts,"string"==typeof this.paginationParts&&(this.paginationParts=this.paginationParts.replace(/\[|\]| |'/g,"").split(",")),"server"!==e.sidePagination&&(e.totalRows=h.length),this.totalPages=0,e.totalRows&&(e.pageSize===e.formatAllRows()&&(e.pageSize=e.totalRows,u=!0),this.totalPages=1+~~((e.totalRows-1)/e.pageSize),e.totalPages=this.totalPages),this.totalPages>0&&e.pageNumber>this.totalPages&&(e.pageNumber=this.totalPages),this.pageFrom=(e.pageNumber-1)*e.pageSize+1,this.pageTo=e.pageNumber*e.pageSize,this.pageTo>e.totalRows&&(this.pageTo=e.totalRows),this.options.pagination&&"server"!==this.options.sidePagination&&(this.options.totalNotFiltered=this.options.data.length),this.options.showExtendedPagination||(this.options.totalNotFiltered=void 0),(this.paginationParts.includes("pageInfo")||this.paginationParts.includes("pageInfoShort")||this.paginationParts.includes("pageSize"))&&c.push('
    ')),this.paginationParts.includes("pageInfo")||this.paginationParts.includes("pageInfoShort")){var d=this.options.totalRows;"client"===this.options.sidePagination&&this.options.paginationLoadMore&&!this._paginationLoaded&&this.totalPages>1&&(d+=" +");var p=this.paginationParts.includes("pageInfoShort")?e.formatDetailPagination(d):e.formatShowingRows(this.pageFrom,this.pageTo,d,e.totalNotFiltered);c.push('\n '.concat(p,"\n "))}if(this.paginationParts.includes("pageSize")){c.push('
    ');var g=['
    \n \n ").concat(this.constants.html.pageDropdown[0])];f.forEach((function(n,i){var r;(!e.smartDisplay||0===i||f[i-1]")),c.push(e.formatRecordsPerPage(g.join("")))}if((this.paginationParts.includes("pageInfo")||this.paginationParts.includes("pageInfoShort")||this.paginationParts.includes("pageSize"))&&c.push("
    "),this.paginationParts.includes("pageList")){c.push('
    '),Il.sprintf(this.constants.html.pagination[0],Il.sprintf(" pagination-%s",e.iconSize)),Il.sprintf(this.constants.html.paginationItem," page-pre",e.formatSRPaginationPreText(),e.paginationPreText)),this.totalPagesthis.totalPages-i&&(i=i-(e.paginationSuccessivelySize-(this.totalPages-i))+1),i<1&&(i=1),r>this.totalPages&&(r=this.totalPages);var v=Math.round(e.paginationPagesBySide/2),b=function(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return Il.sprintf(t.constants.html.paginationItem,i+(n===e.pageNumber?" ".concat(t.constants.classes.paginationActive):""),e.formatSRPaginationPageText(n),n)};if(i>1){var m=e.paginationPagesBySide;for(m>=i&&(m=i-1),n=1;n<=m;n++)c.push(b(n));i-1===m+1?(n=i-1,c.push(b(n))):i-1>m&&(i-2*e.paginationPagesBySide>e.paginationPagesBySide&&e.paginationUseIntermediate?(n=Math.round((i-v)/2+v),c.push(b(n," page-intermediate"))):c.push(Il.sprintf(this.constants.html.paginationItem," page-first-separator disabled","","...")))}for(n=i;n<=r;n++)c.push(b(n));if(this.totalPages>r){var y=this.totalPages-(e.paginationPagesBySide-1);for(r>=y&&(y=r+1),r+1===y-1?(n=r+1,c.push(b(n))):y>r+1&&(this.totalPages-r>2*e.paginationPagesBySide&&e.paginationUseIntermediate?(n=Math.round((this.totalPages-v-r)/2+r),c.push(b(n," page-intermediate"))):c.push(Il.sprintf(this.constants.html.paginationItem," page-last-separator disabled","","..."))),n=y;n<=this.totalPages;n++)c.push(b(n))}c.push(Il.sprintf(this.constants.html.paginationItem," page-next",e.formatSRPaginationNextText(),e.paginationNextText)),c.push(this.constants.html.pagination[1],"
    ")}this.$pagination.html(c.join(""));var w=["bottom","both"].includes(e.paginationVAlign)?" ".concat(this.constants.classes.dropup):"";this.$pagination.last().find(".page-list > div").addClass(w),e.onlyInfoPagination||(o=this.$pagination.find(".page-list a"),a=this.$pagination.find(".page-pre"),s=this.$pagination.find(".page-next"),l=this.$pagination.find(".page-item").not(".page-next, .page-pre, .page-last-separator, .page-first-separator"),this.totalPages<=1&&this.$pagination.find("div.pagination").hide(),e.smartDisplay&&(f.length<2||e.totalRows<=f[0])&&this.$pagination.find("div.page-list").hide(),this.$pagination[this.getData().length?"show":"hide"](),e.paginationLoop||(1===e.pageNumber&&a.addClass("disabled"),e.pageNumber===this.totalPages&&s.addClass("disabled")),u&&(e.pageSize=e.formatAllRows()),o.off("click").on("click",(function(e){return t.onPageListChange(e)})),a.off("click").on("click",(function(e){return t.onPagePre(e)})),s.off("click").on("click",(function(e){return t.onPageNext(e)})),l.off("click").on("click",(function(e){return t.onPageNumber(e)})))}else this.$pagination.hide()}},{key:"updatePagination",value:function(e){e&&t(e.currentTarget).hasClass("disabled")||(this.options.maintainMetaData||this.resetRows(),this.initPagination(),this.trigger("page-change",this.options.pageNumber,this.options.pageSize),"server"===this.options.sidePagination||"client"===this.options.sidePagination&&this.options.paginationLoadMore&&!this._paginationLoaded&&this.options.pageNumber===this.totalPages?this.initServer():this.initBody())}},{key:"onPageListChange",value:function(e){e.preventDefault();var n=t(e.currentTarget);return n.parent().addClass(this.constants.classes.dropdownActive).siblings().removeClass(this.constants.classes.dropdownActive),this.options.pageSize=n.text().toUpperCase()===this.options.formatAllRows().toUpperCase()?this.options.formatAllRows():+n.text(),this.$toolbar.find(".page-size").text(this.options.pageSize),this.updatePagination(e),!1}},{key:"onPagePre",value:function(e){if(!t(e.target).hasClass("disabled"))return e.preventDefault(),this.options.pageNumber-1==0?this.options.pageNumber=this.options.totalPages:this.options.pageNumber--,this.updatePagination(e),!1}},{key:"onPageNext",value:function(e){if(!t(e.target).hasClass("disabled"))return e.preventDefault(),this.options.pageNumber+1>this.options.totalPages?this.options.pageNumber=1:this.options.pageNumber++,this.updatePagination(e),!1}},{key:"onPageNumber",value:function(e){if(e.preventDefault(),this.options.pageNumber!==+t(e.currentTarget).text())return this.options.pageNumber=+t(e.currentTarget).text(),this.updatePagination(e),!1}},{key:"initRow",value:function(e,n,i,r){var o=this;if(!(Il.findIndex(this.hiddenRows,e)>-1)){var a=Il.calculateObjectValue(this.options,this.options.rowStyle,[e,n],{}),u=Il.calculateObjectValue(this.options,this.options.rowAttributes,[e,n],{}),f={};if(e._data&&!Il.isEmptyObject(e._data))for(var d=0,p=Object.entries(e._data);dtbody"),this.$body.length||(this.$body=t("").appendTo(this.$el)),this.options.pagination&&"server"!==this.options.sidePagination||(this.pageFrom=1,this.pageTo=r.length);var o=[],a=t(document.createDocumentFragment()),s=!1,l=[];this.autoMergeCells=Il.checkAutoMergeCells(r.slice(this.pageFrom-1,this.pageTo));for(var c=this.pageFrom-1;c tr[data-uniqueid="%s"][data-has-detail-view]',p)).next();g.is("tr.detail-view")&&(l.push(c),n&&p===n||d.push(g[0]))}this.options.virtualScroll?o.push(t("
    ").html(d).html()):a.append(d)}}this.$el.removeAttr("role"),s?this.options.virtualScroll?(this.virtualScroll&&this.virtualScroll.destroy(),this.virtualScroll=new Fl({rows:o,fixedScroll:e,scrollEl:this.$tableBody[0],contentEl:this.$body[0],itemHeight:this.options.virtualScrollItemHeight,callback:function(t,e){i.fitHeader(),i.initBodyEvent(),i.trigger("virtual-scroll",t,e)}})):this.$body.html(a):(this.$body.html(''.concat(Il.sprintf('%s',this.getVisibleFields().length+Il.getDetailViewIndexOffset(this.options),this.options.formatNoMatches()),"")),this.$el.attr("role","presentation")),l.forEach((function(t){i.expandRow(t)})),e||this.scrollTo(0),this.initBodyEvent(),this.initFooter(),this.resetView(),this.updateSelected(),"server"!==this.options.sidePagination&&(this.options.totalRows=r.length),this.trigger("post-body",r)}},{key:"initBodyEvent",value:function(){var e=this;this.$body.find("> tr[data-index] > td").off("click dblclick").on("click dblclick",(function(n){var i=t(n.currentTarget);if(!(i.find(".detail-icon").length||i.index()-Il.getDetailViewIndexOffset(e.options)<0)){var r=i.parent(),o=t(n.target).parents(".card-views").children(),a=t(n.target).parents(".card-view"),s=r.data("index"),l=e.data[s],c=e.options.cardView?o.index(a):i[0].cellIndex,u=e.getVisibleFields()[c-Il.getDetailViewIndexOffset(e.options)],h=e.columns[e.fieldsColumnsIndex[u]],f=Il.getItemField(l,u,e.options.escape,h.escape);if(e.trigger("click"===n.type?"click-cell":"dbl-click-cell",u,f,l,i),e.trigger("click"===n.type?"click-row":"dbl-click-row",l,r,u),"click"===n.type&&e.options.clickToSelect&&h.clickToSelect&&!Il.calculateObjectValue(e.options,e.options.ignoreClickToSelectOn,[n.target])){var d=r.find(Il.sprintf('[name="%s"]',e.options.selectItemName));d.length&&d[0].click()}"click"===n.type&&e.options.detailViewByClick&&e.toggleDetailView(s,e.header.detailFormatters[e.fieldsColumnsIndex[u]])}})).off("mousedown").on("mousedown",(function(t){e.multipleSelectRowCtrlKey=t.ctrlKey||t.metaKey,e.multipleSelectRowShiftKey=t.shiftKey})),this.$body.find("> tr[data-index] > td > .detail-icon").off("click").on("click",(function(n){return n.preventDefault(),e.toggleDetailView(t(n.currentTarget).parent().parent().data("index")),!1})),this.$selectItem=this.$body.find(Il.sprintf('[name="%s"]',this.options.selectItemName)),this.$selectItem.off("click").on("click",(function(n){n.stopImmediatePropagation();var i=t(n.currentTarget);e._toggleCheck(i.prop("checked"),i.data("index"))})),this.header.events.forEach((function(n,i){var r=n;if(r){if("string"==typeof r&&(r=Il.calculateObjectValue(null,r)),!r)throw new Error("Unknown event in the scope: ".concat(n));var o=e.header.fields[i],a=e.getVisibleFields().indexOf(o);if(-1!==a){a+=Il.getDetailViewIndexOffset(e.options);var s=function(n){if(!r.hasOwnProperty(n))return 1;var i=r[n];e.$body.find(">tr:not(.no-records-found)").each((function(r,s){var l=t(s),c=l.find(e.options.cardView?".card-views>.card-view":">td").eq(a),u=n.indexOf(" "),h=n.substring(0,u),f=n.substring(u+1);c.find(f).off(h).on(h,(function(t){var n=l.data("index"),r=e.data[n],a=r[o];i.apply(e,[t,a,r,n])}))}))};for(var l in r)s(l)}}}))}},{key:"initServer",value:function(e,n){var i=this,o={},a=this.header.fields.indexOf(this.options.sortName),s={searchText:this.searchText,sortName:this.options.sortName,sortOrder:this.options.sortOrder};if(this.header.sortNames[a]&&(s.sortName=this.header.sortNames[a]),this.options.pagination&&"server"===this.options.sidePagination&&(s.pageSize=this.options.pageSize===this.options.formatAllRows()?this.options.totalRows:this.options.pageSize,s.pageNumber=this.options.pageNumber),this.options.url||this.options.ajax){if("limit"===this.options.queryParamsType&&(s={search:s.searchText,sort:s.sortName,order:s.sortOrder},this.options.pagination&&"server"===this.options.sidePagination&&(s.offset=this.options.pageSize===this.options.formatAllRows()?0:this.options.pageSize*(this.options.pageNumber-1),s.limit=this.options.pageSize,0!==s.limit&&this.options.pageSize!==this.options.formatAllRows()||delete s.limit)),this.options.search&&"server"===this.options.sidePagination&&this.options.searchable&&this.columns.filter((function(t){return t.searchable})).length){s.searchable=[];var l,c=r(this.columns);try{for(c.s();!(l=c.n()).done;){var u=l.value;!u.checkbox&&u.searchable&&(this.options.visibleSearch&&u.visible||!this.options.visibleSearch)&&s.searchable.push(u.field)}}catch(t){c.e(t)}finally{c.f()}}if(Il.isEmptyObject(this.filterColumnsPartial)||(s.filter=JSON.stringify(this.filterColumnsPartial,null)),Il.extend(s,n||{}),!1!==(o=Il.calculateObjectValue(this.options,this.options.queryParams,[s],o))){e||this.showLoading();var h=Il.extend({},Il.calculateObjectValue(null,this.options.ajaxOptions),{type:this.options.method,url:this.options.url,data:"application/json"===this.options.contentType&&"post"===this.options.method?JSON.stringify(o):o,cache:this.options.cache,contentType:this.options.contentType,dataType:this.options.dataType,success:function(t,n,r){var o=Il.calculateObjectValue(i.options,i.options.responseHandler,[t,r],t);"client"===i.options.sidePagination&&i.options.paginationLoadMore&&(i._paginationLoaded=i.data.length===o.length),i.load(o),i.trigger("load-success",o,r&&r.status,r),e||i.hideLoading(),"server"===i.options.sidePagination&&i.options.pageNumber>1&&o[i.options.totalField]>0&&!o[i.options.dataField].length&&i.updatePagination()},error:function(t){if(t&&0===t.status&&i._xhrAbort)i._xhrAbort=!1;else{var n=[];"server"===i.options.sidePagination&&((n={})[i.options.totalField]=0,n[i.options.dataField]=[]),i.load(n),i.trigger("load-error",t&&t.status,t),e||i.hideLoading()}}});return this.options.ajax?Il.calculateObjectValue(this,this.options.ajax,[h],null):(this._xhr&&4!==this._xhr.readyState&&(this._xhrAbort=!0,this._xhr.abort()),this._xhr=t.ajax(h)),o}}}},{key:"initSearchText",value:function(){if(this.options.search&&(this.searchText="",""!==this.options.searchText)){var t=Il.getSearchInput(this);t.val(this.options.searchText),this.onSearch({currentTarget:t,firedByInitSearchText:!0})}}},{key:"getCaret",value:function(){var e=this;this.$header.find("th").each((function(n,i){t(i).find(".sortable").removeClass("desc asc").addClass(t(i).data("field")===e.options.sortName?e.options.sortOrder:"both")}))}},{key:"updateSelected",value:function(){var e=this.$selectItem.filter(":enabled").length&&this.$selectItem.filter(":enabled").length===this.$selectItem.filter(":enabled").filter(":checked").length;this.$selectAll.add(this.$selectAll_).prop("checked",e),this.$selectItem.each((function(e,n){t(n).closest("tr")[t(n).prop("checked")?"addClass":"removeClass"]("selected")}))}},{key:"updateRows",value:function(){var e=this;this.$selectItem.each((function(n,i){e.data[t(i).data("index")][e.header.stateField]=t(i).prop("checked")}))}},{key:"resetRows",value:function(){var t,e=r(this.data);try{for(e.s();!(t=e.n()).done;){var n=t.value;this.$selectAll.prop("checked",!1),this.$selectItem.prop("checked",!1),this.header.stateField&&(n[this.header.stateField]=!1)}}catch(t){e.e(t)}finally{e.f()}this.initHiddenRows()}},{key:"trigger",value:function(n){for(var i,r,o="".concat(n,".bs.table"),a=arguments.length,s=new Array(a>1?a-1:0),l=1;ln.clientHeight+this.$header.outerHeight()?Il.getScrollBarWidth():0;this.$el.css("margin-top",-this.$header.outerHeight());var r=this.$tableHeader.find(":focus");if(r.length>0){var o=r.parents("th");if(o.length>0){var a=o.attr("data-field");if(void 0!==a){var s=this.$header.find("[data-field='".concat(a,"']"));s.length>0&&s.find(":input").addClass("focus-temp")}}}this.$header_=this.$header.clone(!0,!0),this.$selectAll_=this.$header_.find('[name="btSelectAll"]'),this.$tableHeader.css("margin-right",i).find("table").css("width",this.$el.outerWidth()).html("").attr("class",this.$el.attr("class")).append(this.$header_),this.$tableLoading.css("width",this.$el.outerWidth());var l=t(".focus-temp:visible:eq(0)");l.length>0&&(l.focus(),this.$header.find(".focus-temp").removeClass("focus-temp")),this.$header.find("th[data-field]").each((function(n,i){e.$header_.find(Il.sprintf('th[data-field="%s"]',t(i).data("field"))).data(t(i).data())}));for(var c=this.getVisibleFields(),u=this.$header_.find("th"),h=this.$body.find(">tr:not(.no-records-found,.virtual-scroll-top)").eq(0);h.length&&h.find('>td[colspan]:not([colspan="1"])').length;)h=h.next();var f=h.find("> *").length;h.find("> *").each((function(n,i){var r=t(i);if(Il.hasDetailViewIcon(e.options)&&(0===n&&"right"!==e.options.detailViewAlign||n===f-1&&"right"===e.options.detailViewAlign)){var o=u.filter(".detail"),a=o.innerWidth()-o.find(".fht-cell").width();o.find(".fht-cell").width(r.innerWidth()-a)}else{var s=n-Il.getDetailViewIndexOffset(e.options),l=e.$header_.find(Il.sprintf('th[data-field="%s"]',c[s]));l.length>1&&(l=t(u[r[0].cellIndex]));var h=l.innerWidth()-l.find(".fht-cell").width();l.find(".fht-cell").width(r.innerWidth()-h)}})),this.horizontalScroll(),this.trigger("post-header")}}},{key:"initFooter",value:function(){if(this.options.showFooter&&!this.options.cardView){var t=this.getData(),e=[],n="";Il.hasDetailViewIcon(this.options)&&(n=Il.h("th",{class:"detail"},[Il.h("div",{class:"th-inner"}),Il.h("div",{class:"fht-cell"})])),n&&"right"!==this.options.detailViewAlign&&e.push(n);var i,o=r(this.columns);try{for(o.s();!(i=o.n()).done;){var a=i.value,l=this.footerData&&this.footerData.length>0;if(a.visible&&(!l||a.field in this.footerData[0])){if(this.options.cardView&&!a.cardVisible)return;var u=Il.calculateObjectValue(null,a.footerStyle||this.options.footerStyle,[a]),h=u&&u.css||{},f=l&&this.footerData[0]["_".concat(a.field,"_colspan")]||0,d=l&&this.footerData[0][a.field]||"";d=Il.calculateObjectValue(a,a.footerFormatter,[t,d],d),e.push(Il.h("th",{class:[a.class,u&&u.classes],style:s({"text-align":a.falign?a.falign:a.align,"vertical-align":a.valign},h),colspan:f||void 0},[Il.h("div",{class:"th-inner"},c(Il.htmlToNodes(d))),Il.h("div",{class:"fht-cell"})]))}}}catch(t){o.e(t)}finally{o.f()}n&&"right"===this.options.detailViewAlign&&e.push(n),this.options.height||this.$tableFooter.length||(this.$el.append(""),this.$tableFooter=this.$el.find("tfoot")),this.$tableFooter.find("tr").length||this.$tableFooter.html("
    "),this.$tableFooter.find("tr").html(e),this.trigger("post-footer",this.$tableFooter)}}},{key:"fitFooter",value:function(){var e=this;if(this.$el.is(":hidden"))setTimeout((function(){return e.fitFooter()}),100);else{var n=this.$tableBody.get(0),i=this.hasScrollBar&&n.scrollHeight>n.clientHeight+this.$header.outerHeight()?Il.getScrollBarWidth():0;this.$tableFooter.css("margin-right",i).find("table").css("width",this.$el.outerWidth()).attr("class",this.$el.attr("class"));var r=this.$tableFooter.find("th"),o=this.$body.find(">tr:first-child:not(.no-records-found)");for(r.find(".fht-cell").width("auto");o.length&&o.find('>td[colspan]:not([colspan="1"])').length;)o=o.next();var a=o.find("> *").length;o.find("> *").each((function(n,i){var o=t(i);if(Il.hasDetailViewIcon(e.options)&&(0===n&&"left"===e.options.detailViewAlign||n===a-1&&"right"===e.options.detailViewAlign)){var s=r.filter(".detail"),l=s.innerWidth()-s.find(".fht-cell").width();s.find(".fht-cell").width(o.innerWidth()-l)}else{var c=r.eq(n),u=c.innerWidth()-c.find(".fht-cell").width();c.find(".fht-cell").width(o.innerWidth()-u)}})),this.horizontalScroll()}}},{key:"horizontalScroll",value:function(){var t=this;this.$tableBody.off("scroll").on("scroll",(function(){var e=t.$tableBody.scrollLeft();t.options.showHeader&&t.options.height&&t.$tableHeader.scrollLeft(e),t.options.showFooter&&!t.options.cardView&&t.$tableFooter.scrollLeft(e),t.trigger("scroll-body",t.$tableBody)}))}},{key:"getVisibleFields",value:function(){var t,e=[],n=r(this.header.fields);try{for(n.s();!(t=n.n()).done;){var i=t.value,o=this.columns[this.fieldsColumnsIndex[i]];o&&o.visible&&(!this.options.cardView||o.cardVisible)&&e.push(i)}}catch(t){n.e(t)}finally{n.f()}return e}},{key:"initHiddenRows",value:function(){this.hiddenRows=[]}},{key:"getOptions",value:function(){var t=Il.extend({},this.options);return delete t.data,Il.extend(!0,{},t)}},{key:"refreshOptions",value:function(t){Il.compareObjects(this.options,t,!0)||(this.optionsColumnsChanged=!!t.columns,this.options=Il.extend(this.options,t),this.trigger("refresh-options",this.options),this.destroy(),this.init())}},{key:"getData",value:function(t){var e=this,n=this.options.data;if(!(this.searchText||this.options.customSearch||void 0!==this.options.sortName||this.enableCustomSort)&&Il.isEmptyObject(this.filterColumns)&&"function"!=typeof this.options.filterOptions.filterAlgorithm&&Il.isEmptyObject(this.filterColumnsPartial)||t&&t.unfiltered||(n=this.data),t&&!t.includeHiddenRows){var i=this.getHiddenRows();n=n.filter((function(t){return-1===Il.findIndex(i,t)}))}return t&&t.useCurrentPage&&(n=n.slice(this.pageFrom-1,this.pageTo)),t&&t.formatted?n.map((function(t){for(var n={},i=0,r=Object.entries(t);i=0;n--){var i=this.options.data[n],r=Il.getItemField(i,t.field,this.options.escape,i.escape);void 0===r&&"$index"!==t.field||(!i.hasOwnProperty(t.field)&&"$index"===t.field&&t.values.includes(n)||t.values.includes(r))&&(e++,this.options.data.splice(n,1))}e&&("server"===this.options.sidePagination&&(this.options.totalRows-=e,this.data=c(this.options.data)),this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0))}},{key:"removeAll",value:function(){this.options.data.length>0&&(this.data.splice(0,this.data.length),this.options.data.splice(0,this.options.data.length),this.initSearch(),this.initPagination(),this.initBody(!0))}},{key:"insertRow",value:function(t){if(t.hasOwnProperty("index")&&t.hasOwnProperty("row")){var e=this.data[t.index],n=this.options.data.indexOf(e);-1!==n?(this.data.splice(t.index,0,t.row),this.options.data.splice(n,0,t.row),this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0)):this.append([t.row])}}},{key:"updateRow",value:function(t){var e,n=r(Array.isArray(t)?t:[t]);try{for(n.s();!(e=n.n()).done;){var i=e.value;if(i.hasOwnProperty("index")&&i.hasOwnProperty("row")){var o=this.data[i.index],a=this.options.data.indexOf(o);i.hasOwnProperty("replace")&&i.replace?(this.data[i.index]=i.row,this.options.data[a]=i.row):(Il.extend(this.data[i.index],i.row),Il.extend(this.options.data[a],i.row))}}}catch(t){n.e(t)}finally{n.f()}this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0)}},{key:"getRowByUniqueId",value:function(t){var e,n,i=this.options.uniqueId,r=t,o=null;for(e=this.options.data.length-1;e>=0;e--){n=this.options.data[e];var a=Il.getItemField(n,i,this.options.escape,n.escape);if(void 0!==a&&("string"==typeof a?r=t.toString():"number"==typeof a&&(Number(a)===a&&a%1==0?r=parseInt(t,10):a===Number(a)&&0!==a&&(r=parseFloat(t))),a===r)){o=n;break}}return o}},{key:"updateByUniqueId",value:function(t){var e,n=null,i=r(Array.isArray(t)?t:[t]);try{for(i.s();!(e=i.n()).done;){var o=e.value;if(o.hasOwnProperty("id")&&o.hasOwnProperty("row")){var a=this.options.data.indexOf(this.getRowByUniqueId(o.id));-1!==a&&(o.hasOwnProperty("replace")&&o.replace?this.options.data[a]=o.row:Il.extend(this.options.data[a],o.row),n=o.id)}}}catch(t){i.e(t)}finally{i.f()}this.initSearch(),this.initPagination(),this.initSort(),this.initBody(!0,n)}},{key:"removeByUniqueId",value:function(t){var e=this.options.data.length,n=this.getRowByUniqueId(t);n&&this.options.data.splice(this.options.data.indexOf(n),1),e!==this.options.data.length&&("server"===this.options.sidePagination&&(this.options.totalRows-=1,this.data=c(this.options.data)),this.initSearch(),this.initPagination(),this.initBody(!0))}},{key:"_updateCellOnly",value:function(e,n){var i=this.initRow(this.data[n],n),r=this.getVisibleFields().indexOf(e);-1!==r&&(r+=Il.getDetailViewIndexOffset(this.options),this.$body.find(">tr[data-index=".concat(n,"]")).find(">td:eq(".concat(r,")")).replaceWith(t(i).find(">td:eq(".concat(r,")"))),this.initBodyEvent(),this.initFooter(),this.resetView(),this.updateSelected())}},{key:"updateCell",value:function(t){if(t.hasOwnProperty("index")&&t.hasOwnProperty("field")&&t.hasOwnProperty("value")){var e=this.data[t.index],n=this.options.data.indexOf(e);this.data[t.index][t.field]=t.value,this.options.data[n][t.field]=t.value,!1!==t.reinit?(this.initSort(),this.initBody(!0)):this._updateCellOnly(t.field,t.index)}}},{key:"updateCellByUniqueId",value:function(t){var e=this;(Array.isArray(t)?t:[t]).forEach((function(t){var n=t.id,i=t.field,r=t.value,o=e.options.data.indexOf(e.getRowByUniqueId(n));-1!==o&&(e.options.data[o][i]=r)})),!1!==t.reinit?(this.initSort(),this.initBody(!0)):this._updateCellOnly(t.field,this.options.data.indexOf(this.getRowByUniqueId(t.id)))}},{key:"showRow",value:function(t){this._toggleRow(t,!0)}},{key:"hideRow",value:function(t){this._toggleRow(t,!1)}},{key:"_toggleRow",value:function(t,e){var n;if(t.hasOwnProperty("index")?n=this.getData()[t.index]:t.hasOwnProperty("uniqueId")&&(n=this.getRowByUniqueId(t.uniqueId)),n){var i=Il.findIndex(this.hiddenRows,n);e||-1!==i?e&&i>-1&&this.hiddenRows.splice(i,1):this.hiddenRows.push(n),this.initBody(!0),this.initPagination()}}},{key:"getHiddenRows",value:function(t){if(t)return this.initHiddenRows(),this.initBody(!0),void this.initPagination();var e,n=[],i=r(this.getData());try{for(i.s();!(e=i.n()).done;){var o=e.value;this.hiddenRows.includes(o)&&n.push(o)}}catch(t){i.e(t)}finally{i.f()}return this.hiddenRows=n,n}},{key:"showColumn",value:function(t){var e=this;(Array.isArray(t)?t:[t]).forEach((function(t){e._toggleColumn(e.fieldsColumnsIndex[t],!0,!0)}))}},{key:"hideColumn",value:function(t){var e=this;(Array.isArray(t)?t:[t]).forEach((function(t){e._toggleColumn(e.fieldsColumnsIndex[t],!1,!0)}))}},{key:"_toggleColumn",value:function(t,e,n){if(void 0!==t&&this.columns[t].visible!==e&&(this.columns[t].visible=e,this.initHeader(),this.initSearch(),this.initPagination(),this.initBody(),this.options.showColumns)){var i=this.$toolbar.find('.keep-open input:not(".toggle-all")').prop("disabled",!1);n&&i.filter(Il.sprintf('[value="%s"]',t)).prop("checked",e),i.filter(":checked").length<=this.options.minimumCountColumns&&i.filter(":checked").prop("disabled",!0)}}},{key:"getVisibleColumns",value:function(){var t=this;return this.columns.filter((function(e){return e.visible&&!t.isSelectionColumn(e)}))}},{key:"getHiddenColumns",value:function(){return this.columns.filter((function(t){return!t.visible}))}},{key:"isSelectionColumn",value:function(t){return t.radio||t.checkbox}},{key:"showAllColumns",value:function(){this._toggleAllColumns(!0)}},{key:"hideAllColumns",value:function(){this._toggleAllColumns(!1)}},{key:"_toggleAllColumns",value:function(e){var n,i=this,o=r(this.columns.slice().reverse());try{for(o.s();!(n=o.n()).done;){var a=n.value;if(a.switchable){if(!e&&this.options.showColumns&&this.getVisibleColumns().filter((function(t){return t.switchable})).length===this.options.minimumCountColumns)continue;a.visible=e}}}catch(t){o.e(t)}finally{o.f()}if(this.initHeader(),this.initSearch(),this.initPagination(),this.initBody(),this.options.showColumns){var s=this.$toolbar.find('.keep-open input[type="checkbox"]:not(".toggle-all")').prop("disabled",!1);e?s.prop("checked",e):s.get().reverse().forEach((function(n){s.filter(":checked").length>i.options.minimumCountColumns&&t(n).prop("checked",e)})),s.filter(":checked").length<=this.options.minimumCountColumns&&s.filter(":checked").prop("disabled",!0)}}},{key:"mergeCells",value:function(t){var e,n,i=t.index,r=this.getVisibleFields().indexOf(t.field),o=+t.rowspan||1,a=+t.colspan||1,s=this.$body.find(">tr[data-index]");r+=Il.getDetailViewIndexOffset(this.options);var l=s.eq(i).find(">td").eq(r);if(!(i<0||r<0||i>=this.data.length)){for(e=i;etd").eq(n).hide();l.attr("rowspan",o).attr("colspan",a).show()}}},{key:"checkAll",value:function(){this._toggleCheckAll(!0)}},{key:"uncheckAll",value:function(){this._toggleCheckAll(!1)}},{key:"_toggleCheckAll",value:function(t){var e=this.getSelections();this.$selectAll.add(this.$selectAll_).prop("checked",t),this.$selectItem.filter(":enabled").prop("checked",t),this.updateRows(),this.updateSelected();var n=this.getSelections();t?this.trigger("check-all",n,e):this.trigger("uncheck-all",n,e)}},{key:"checkInvert",value:function(){var e=this.$selectItem.filter(":enabled"),n=e.filter(":checked");e.each((function(e,n){t(n).prop("checked",!t(n).prop("checked"))})),this.updateRows(),this.updateSelected(),this.trigger("uncheck-some",n),n=this.getSelections(),this.trigger("check-some",n)}},{key:"check",value:function(t){this._toggleCheck(!0,t)}},{key:"uncheck",value:function(t){this._toggleCheck(!1,t)}},{key:"_toggleCheck",value:function(t,e){var n=this.$selectItem.filter('[data-index="'.concat(e,'"]')),i=this.data[e];if(n.is(":radio")||this.options.singleSelect||this.options.multipleSelectRow&&!this.multipleSelectRowCtrlKey&&!this.multipleSelectRowShiftKey){var o,a=r(this.options.data);try{for(a.s();!(o=a.n()).done;){o.value[this.header.stateField]=!1}}catch(t){a.e(t)}finally{a.f()}this.$selectItem.filter(":checked").not(n).prop("checked",!1)}if(i[this.header.stateField]=t,this.options.multipleSelectRow){if(this.multipleSelectRowShiftKey&&this.multipleSelectRowLastSelectedIndex>=0)for(var s=l(this.multipleSelectRowLastSelectedIndexn.clientWidth}if(!this.options.cardView&&this.options.showHeader&&this.options.height?(this.$tableHeader.show(),this.resetHeader(),e+=this.$header.outerHeight(!0)+1):(this.$tableHeader.hide(),this.trigger("post-header")),!this.options.cardView&&this.options.showFooter&&(this.$tableFooter.show(),this.fitFooter(),this.options.height&&(e+=this.$tableFooter.outerHeight(!0))),this.$container.hasClass("fullscreen"))this.$tableContainer.css("height",""),this.$tableContainer.css("width","");else if(this.options.height){this.$tableBorder&&(this.$tableBorder.css("width",""),this.$tableBorder.css("height",""));var i=this.$toolbar.outerHeight(!0),r=this.$pagination.outerHeight(!0),o=this.options.height-i-r,a=this.$tableBody.find(">table"),s=a.outerHeight();if(this.$tableContainer.css("height","".concat(o,"px")),this.$tableBorder&&a.is(":visible")){var l=o-s-2;this.hasScrollBar&&(l-=Il.getScrollBarWidth()),this.$tableBorder.css("width","".concat(a.outerWidth(),"px")),this.$tableBorder.css("height","".concat(l,"px"))}}this.options.cardView?(this.$el.css("margin-top","0"),this.$tableContainer.css("padding-bottom","0"),this.$tableFooter.hide()):(this.getCaret(),this.$tableContainer.css("padding-bottom","".concat(e,"px"))),this.trigger("reset-view")}},{key:"showLoading",value:function(){this.$tableLoading.toggleClass("open",!0);var t=this.options.loadingFontSize;"auto"===this.options.loadingFontSize&&(t=.04*this.$tableLoading.width(),t=Math.max(12,t),t=Math.min(32,t),t="".concat(t,"px")),this.$tableLoading.find(".loading-text").css("font-size",t)}},{key:"hideLoading",value:function(){this.$tableLoading.toggleClass("open",!1)}},{key:"togglePagination",value:function(){this.options.pagination=!this.options.pagination;var t=this.options.showButtonIcons?this.options.pagination?this.options.icons.paginationSwitchDown:this.options.icons.paginationSwitchUp:"",e=this.options.showButtonText?this.options.pagination?this.options.formatPaginationSwitchUp():this.options.formatPaginationSwitchDown():"";this.$toolbar.find('button[name="paginationSwitch"]').html("".concat(Il.sprintf(this.constants.html.icon,this.options.iconsPrefix,t)," ").concat(e)),this.updatePagination(),this.trigger("toggle-pagination",this.options.pagination)}},{key:"toggleFullscreen",value:function(){this.$el.closest(".bootstrap-table").toggleClass("fullscreen"),this.resetView()}},{key:"toggleView",value:function(){this.options.cardView=!this.options.cardView,this.initHeader();var t=this.options.showButtonIcons?this.options.cardView?this.options.icons.toggleOn:this.options.icons.toggleOff:"",e=this.options.cardView?this.options.formatToggleOff():this.options.formatToggleOn();this.$toolbar.find('button[name="toggle"]').html("".concat(Il.sprintf(this.constants.html.icon,this.options.iconsPrefix,t)," ").concat(this.options.showButtonText?e:"")).attr("aria-label",e).attr(this.options.buttonsAttributeTitle,e),this.initBody(),this.trigger("toggle",this.options.cardView)}},{key:"resetSearch",value:function(t){var e=Il.getSearchInput(this),n=t||"";e.val(n),this.searchText=n,this.onSearch({currentTarget:e},!1)}},{key:"filterBy",value:function(t,e){this.filterOptions=Il.isEmptyObject(e)?this.options.filterOptions:Il.extend({},this.options.filterOptions,e),this.filterColumns=Il.isEmptyObject(t)?{}:t,this.options.pageNumber=1,this.initSearch(),this.updatePagination()}},{key:"scrollTo",value:function(e){var n={unit:"px",value:0};"object"===h(e)?n=Object.assign(n,e):"string"==typeof e&&"bottom"===e?n.value=this.$tableBody[0].scrollHeight:"string"!=typeof e&&"number"!=typeof e||(n.value=e);var i=n.value;"rows"===n.unit&&(i=0,this.$body.find("> tr:lt(".concat(n.value,")")).each((function(e,n){i+=t(n).outerHeight(!0)}))),this.$tableBody.scrollTop(i)}},{key:"getScrollPosition",value:function(){return this.$tableBody.scrollTop()}},{key:"selectPage",value:function(t){t>0&&t<=this.options.totalPages&&(this.options.pageNumber=t,this.updatePagination())}},{key:"prevPage",value:function(){this.options.pageNumber>1&&(this.options.pageNumber--,this.updatePagination())}},{key:"nextPage",value:function(){this.options.pageNumber tr[data-index="%s"]',t)).next().is("tr.detail-view")?this.collapseRow(t):this.expandRow(t,e),this.resetView()}},{key:"expandRow",value:function(t,e){var n=this.data[t],i=this.$body.find(Il.sprintf('> tr[data-index="%s"][data-has-detail-view]',t));if(this.options.detailViewIcon&&i.find("a.detail-icon").html(Il.sprintf(this.constants.html.icon,this.options.iconsPrefix,this.options.icons.detailClose)),!i.next().is("tr.detail-view")){i.after(Il.sprintf('',i.children("td").length));var r=i.next().find("td"),o=e||this.options.detailFormatter,a=Il.calculateObjectValue(this.options,o,[t,n,r],"");1===r.length&&r.append(a),this.trigger("expand-row",t,n,r)}}},{key:"expandRowByUniqueId",value:function(t){var e=this.getRowByUniqueId(t);e&&this.expandRow(this.data.indexOf(e))}},{key:"collapseRow",value:function(t){var e=this.data[t],n=this.$body.find(Il.sprintf('> tr[data-index="%s"][data-has-detail-view]',t));n.next().is("tr.detail-view")&&(this.options.detailViewIcon&&n.find("a.detail-icon").html(Il.sprintf(this.constants.html.icon,this.options.iconsPrefix,this.options.icons.detailOpen)),this.trigger("collapse-row",t,e,n.next()),n.next().remove())}},{key:"collapseRowByUniqueId",value:function(t){var e=this.getRowByUniqueId(t);e&&this.collapseRow(this.data.indexOf(e))}},{key:"expandAllRows",value:function(){for(var e=this.$body.find("> tr[data-index][data-has-detail-view]"),n=0;n tr[data-index][data-has-detail-view]"),n=0;n1?n-1:0),r=1;r (http://wenzhixin.net.cn/) + * @license MIT + */ + +!function(r,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],t):t((r="undefined"!=typeof globalThis?globalThis:r||self).jQuery)}(this,(function(r){"use strict";function t(r,t){(null==t||t>r.length)&&(t=r.length);for(var n=0,e=Array(t);n=r.length?{done:!0}:{done:!1,value:r[o++]}},e:function(r){throw r},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var u,f=!0,c=!1;return{s:function(){e=e.call(r)},n:function(){var r=e.next();return f=r.done,r},e:function(r){c=!0,u=r},f:function(){try{f||null==e.return||e.return()}finally{if(c)throw u}}}}function i(){return i="undefined"!=typeof Reflect&&Reflect.get?Reflect.get.bind():function(r,t,n){var e=function(r,t){for(;!{}.hasOwnProperty.call(r,t)&&null!==(r=u(r)););return r}(r,t);if(e){var o=Object.getOwnPropertyDescriptor(e,t);return o.get?o.get.call(arguments.length<3?r:n):o.value}},i.apply(null,arguments)}function u(r){return u=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(r){return r.__proto__||Object.getPrototypeOf(r)},u(r)}function f(){try{var r=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(r){}return(f=function(){return!!r})()}function c(r,t){return c=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(r,t){return r.__proto__=t,r},c(r,t)}function a(r,t,n,e){var o=i(u(1&e?r.prototype:r),t,n);return 2&e&&"function"==typeof o?function(r){return o.apply(n,r)}:o}function l(r){var t=function(r,t){if("object"!=typeof r||!r)return r;var n=r[Symbol.toPrimitive];if(void 0!==n){var e=n.call(r,t);if("object"!=typeof e)return e;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(r)}(r,"string");return"symbol"==typeof t?t:t+""}var s,v,p="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},y={};function b(){if(v)return s;v=1;var r=function(r){return r&&r.Math===Math&&r};return s=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof p&&p)||r("object"==typeof s&&s)||function(){return this}()||Function("return this")()}var d,h,g,m,w,j,O,S,P={};function E(){return h?d:(h=1,d=function(r){try{return!!r()}catch(r){return!0}})}function T(){if(m)return g;m=1;var r=E();return g=!r((function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]}))}function F(){if(j)return w;j=1;var r=E();return w=!r((function(){var r=function(){}.bind();return"function"!=typeof r||r.hasOwnProperty("prototype")}))}function A(){if(S)return O;S=1;var r=F(),t=Function.prototype.call;return O=r?t.bind(t):function(){return t.apply(t,arguments)},O}var _,I,k,x,R,C,M,B,z,D,L,N,q,G,U,W,$,H,K,Q,V,X,Y,J,Z,rr,tr,nr,er,or,ir,ur,fr,cr,ar,lr,sr,vr,pr,yr,br,dr={};function hr(){if(_)return dr;_=1;var r={}.propertyIsEnumerable,t=Object.getOwnPropertyDescriptor,n=t&&!r.call({1:2},1);return dr.f=n?function(r){var n=t(this,r);return!!n&&n.enumerable}:r,dr}function gr(){return k?I:(k=1,I=function(r,t){return{enumerable:!(1&r),configurable:!(2&r),writable:!(4&r),value:t}})}function mr(){if(R)return x;R=1;var r=F(),t=Function.prototype,n=t.call,e=r&&t.bind.bind(n,n);return x=r?e:function(r){return function(){return n.apply(r,arguments)}},x}function wr(){if(M)return C;M=1;var r=mr(),t=r({}.toString),n=r("".slice);return C=function(r){return n(t(r),8,-1)}}function jr(){if(z)return B;z=1;var r=mr(),t=E(),n=wr(),e=Object,o=r("".split);return B=t((function(){return!e("z").propertyIsEnumerable(0)}))?function(r){return"String"===n(r)?o(r,""):e(r)}:e}function Or(){return L?D:(L=1,D=function(r){return null==r})}function Sr(){if(q)return N;q=1;var r=Or(),t=TypeError;return N=function(n){if(r(n))throw new t("Can't call method on "+n);return n}}function Pr(){if(U)return G;U=1;var r=jr(),t=Sr();return G=function(n){return r(t(n))}}function Er(){if($)return W;$=1;var r="object"==typeof document&&document.all;return W=void 0===r&&void 0!==r?function(t){return"function"==typeof t||t===r}:function(r){return"function"==typeof r}}function Tr(){if(K)return H;K=1;var r=Er();return H=function(t){return"object"==typeof t?null!==t:r(t)}}function Fr(){if(V)return Q;V=1;var r=b(),t=Er();return Q=function(n,e){return arguments.length<2?(o=r[n],t(o)?o:void 0):r[n]&&r[n][e];var o},Q}function Ar(){if(tr)return rr;tr=1;var r,t,n=b(),e=function(){if(Z)return J;Z=1;var r=b().navigator,t=r&&r.userAgent;return J=t?String(t):""}(),o=n.process,i=n.Deno,u=o&&o.versions||i&&i.version,f=u&&u.v8;return f&&(t=(r=f.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!t&&e&&(!(r=e.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=e.match(/Chrome\/(\d+)/))&&(t=+r[1]),rr=t}function _r(){if(er)return nr;er=1;var r=Ar(),t=E(),n=b().String;return nr=!!Object.getOwnPropertySymbols&&!t((function(){var t=Symbol("symbol detection");return!n(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&r&&r<41}))}function Ir(){if(ir)return or;ir=1;var r=_r();return or=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator}function kr(){if(fr)return ur;fr=1;var r=Fr(),t=Er(),n=function(){if(Y)return X;Y=1;var r=mr();return X=r({}.isPrototypeOf)}(),e=Ir(),o=Object;return ur=e?function(r){return"symbol"==typeof r}:function(e){var i=r("Symbol");return t(i)&&n(i.prototype,o(e))}}function xr(){if(ar)return cr;ar=1;var r=String;return cr=function(t){try{return r(t)}catch(r){return"Object"}}}function Rr(){if(sr)return lr;sr=1;var r=Er(),t=xr(),n=TypeError;return lr=function(e){if(r(e))return e;throw new n(t(e)+" is not a function")}}function Cr(){if(pr)return vr;pr=1;var r=Rr(),t=Or();return vr=function(n,e){var o=n[e];return t(o)?void 0:r(o)}}function Mr(){if(br)return yr;br=1;var r=A(),t=Er(),n=Tr(),e=TypeError;return yr=function(o,i){var u,f;if("string"===i&&t(u=o.toString)&&!n(f=r(u,o)))return f;if(t(u=o.valueOf)&&!n(f=r(u,o)))return f;if("string"!==i&&t(u=o.toString)&&!n(f=r(u,o)))return f;throw new e("Can't convert object to primitive value")}}var Br,zr,Dr,Lr,Nr,qr,Gr,Ur,Wr,$r,Hr,Kr,Qr,Vr,Xr,Yr,Jr,Zr,rt,tt,nt,et,ot,it,ut={exports:{}};function ft(){if(Lr)return Dr;Lr=1;var r=b(),t=Object.defineProperty;return Dr=function(n,e){try{t(r,n,{value:e,configurable:!0,writable:!0})}catch(t){r[n]=e}return e}}function ct(){if(Nr)return ut.exports;Nr=1;var r=zr?Br:(zr=1,Br=!1),t=b(),n=ft(),e="__core-js_shared__",o=ut.exports=t[e]||n(e,{});return(o.versions||(o.versions=[])).push({version:"3.39.0",mode:r?"pure":"global",copyright:"© 2014-2024 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE",source:"https://github.com/zloirock/core-js"}),ut.exports}function at(){if(Gr)return qr;Gr=1;var r=ct();return qr=function(t,n){return r[t]||(r[t]=n||{})}}function lt(){if(Wr)return Ur;Wr=1;var r=Sr(),t=Object;return Ur=function(n){return t(r(n))}}function st(){if(Hr)return $r;Hr=1;var r=mr(),t=lt(),n=r({}.hasOwnProperty);return $r=Object.hasOwn||function(r,e){return n(t(r),e)}}function vt(){if(Qr)return Kr;Qr=1;var r=mr(),t=0,n=Math.random(),e=r(1..toString);return Kr=function(r){return"Symbol("+(void 0===r?"":r)+")_"+e(++t+n,36)}}function pt(){if(Xr)return Vr;Xr=1;var r=b(),t=at(),n=st(),e=vt(),o=_r(),i=Ir(),u=r.Symbol,f=t("wks"),c=i?u.for||u:u&&u.withoutSetter||e;return Vr=function(r){return n(f,r)||(f[r]=o&&n(u,r)?u[r]:c("Symbol."+r)),f[r]}}function yt(){if(Jr)return Yr;Jr=1;var r=A(),t=Tr(),n=kr(),e=Cr(),o=Mr(),i=pt(),u=TypeError,f=i("toPrimitive");return Yr=function(i,c){if(!t(i)||n(i))return i;var a,l=e(i,f);if(l){if(void 0===c&&(c="default"),a=r(l,i,c),!t(a)||n(a))return a;throw new u("Can't convert object to primitive value")}return void 0===c&&(c="number"),o(i,c)}}function bt(){if(rt)return Zr;rt=1;var r=yt(),t=kr();return Zr=function(n){var e=r(n,"string");return t(e)?e:e+""}}function dt(){if(ot)return et;ot=1;var r=T(),t=E(),n=function(){if(nt)return tt;nt=1;var r=b(),t=Tr(),n=r.document,e=t(n)&&t(n.createElement);return tt=function(r){return e?n.createElement(r):{}}}();return et=!r&&!t((function(){return 7!==Object.defineProperty(n("div"),"a",{get:function(){return 7}}).a}))}function ht(){if(it)return P;it=1;var r=T(),t=A(),n=hr(),e=gr(),o=Pr(),i=bt(),u=st(),f=dt(),c=Object.getOwnPropertyDescriptor;return P.f=r?c:function(r,a){if(r=o(r),a=i(a),f)try{return c(r,a)}catch(r){}if(u(r,a))return e(!t(n.f,r,a),r[a])},P}var gt,mt,wt,jt,Ot,St,Pt,Et={};function Tt(){if(jt)return wt;jt=1;var r=Tr(),t=String,n=TypeError;return wt=function(e){if(r(e))return e;throw new n(t(e)+" is not an object")}}function Ft(){if(Ot)return Et;Ot=1;var r=T(),t=dt(),n=function(){if(mt)return gt;mt=1;var r=T(),t=E();return gt=r&&t((function(){return 42!==Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))}(),e=Tt(),o=bt(),i=TypeError,u=Object.defineProperty,f=Object.getOwnPropertyDescriptor,c="enumerable",a="configurable",l="writable";return Et.f=r?n?function(r,t,n){if(e(r),t=o(t),e(n),"function"==typeof r&&"prototype"===t&&"value"in n&&l in n&&!n[l]){var i=f(r,t);i&&i[l]&&(r[t]=n.value,n={configurable:a in n?n[a]:i[a],enumerable:c in n?n[c]:i[c],writable:!1})}return u(r,t,n)}:u:function(r,n,f){if(e(r),n=o(n),e(f),t)try{return u(r,n,f)}catch(r){}if("get"in f||"set"in f)throw new i("Accessors not supported");return"value"in f&&(r[n]=f.value),r},Et}function At(){if(Pt)return St;Pt=1;var r=T(),t=Ft(),n=gr();return St=r?function(r,e,o){return t.f(r,e,n(1,o))}:function(r,t,n){return r[t]=n,r}}var _t,It,kt,xt,Rt,Ct,Mt,Bt,zt,Dt,Lt,Nt,qt,Gt,Ut,Wt={exports:{}};function $t(){if(xt)return kt;xt=1;var r=mr(),t=Er(),n=ct(),e=r(Function.toString);return t(n.inspectSource)||(n.inspectSource=function(r){return e(r)}),kt=n.inspectSource}function Ht(){if(Bt)return Mt;Bt=1;var r=at(),t=vt(),n=r("keys");return Mt=function(r){return n[r]||(n[r]=t(r))}}function Kt(){return Dt?zt:(Dt=1,zt={})}function Qt(){if(Nt)return Lt;Nt=1;var r,t,n,e=function(){if(Ct)return Rt;Ct=1;var r=b(),t=Er(),n=r.WeakMap;return Rt=t(n)&&/native code/.test(String(n))}(),o=b(),i=Tr(),u=At(),f=st(),c=ct(),a=Ht(),l=Kt(),s="Object already initialized",v=o.TypeError,p=o.WeakMap;if(e||c.state){var y=c.state||(c.state=new p);y.get=y.get,y.has=y.has,y.set=y.set,r=function(r,t){if(y.has(r))throw new v(s);return t.facade=r,y.set(r,t),t},t=function(r){return y.get(r)||{}},n=function(r){return y.has(r)}}else{var d=a("state");l[d]=!0,r=function(r,t){if(f(r,d))throw new v(s);return t.facade=r,u(r,d,t),t},t=function(r){return f(r,d)?r[d]:{}},n=function(r){return f(r,d)}}return Lt={set:r,get:t,has:n,enforce:function(e){return n(e)?t(e):r(e,{})},getterFor:function(r){return function(n){var e;if(!i(n)||(e=t(n)).type!==r)throw new v("Incompatible receiver, "+r+" required");return e}}}}function Vt(){if(qt)return Wt.exports;qt=1;var r=mr(),t=E(),n=Er(),e=st(),o=T(),i=function(){if(It)return _t;It=1;var r=T(),t=st(),n=Function.prototype,e=r&&Object.getOwnPropertyDescriptor,o=t(n,"name"),i=o&&"something"===function(){}.name,u=o&&(!r||r&&e(n,"name").configurable);return _t={EXISTS:o,PROPER:i,CONFIGURABLE:u}}().CONFIGURABLE,u=$t(),f=Qt(),c=f.enforce,a=f.get,l=String,s=Object.defineProperty,v=r("".slice),p=r("".replace),y=r([].join),b=o&&!t((function(){return 8!==s((function(){}),"length",{value:8}).length})),d=String(String).split("String"),h=Wt.exports=function(r,t,n){"Symbol("===v(l(t),0,7)&&(t="["+p(l(t),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),n&&n.getter&&(t="get "+t),n&&n.setter&&(t="set "+t),(!e(r,"name")||i&&r.name!==t)&&(o?s(r,"name",{value:t,configurable:!0}):r.name=t),b&&n&&e(n,"arity")&&r.length!==n.arity&&s(r,"length",{value:n.arity});try{n&&e(n,"constructor")&&n.constructor?o&&s(r,"prototype",{writable:!1}):r.prototype&&(r.prototype=void 0)}catch(r){}var u=c(r);return e(u,"source")||(u.source=y(d,"string"==typeof t?t:"")),r};return Function.prototype.toString=h((function(){return n(this)&&a(this).source||u(this)}),"toString"),Wt.exports}function Xt(){if(Ut)return Gt;Ut=1;var r=Er(),t=Ft(),n=Vt(),e=ft();return Gt=function(o,i,u,f){f||(f={});var c=f.enumerable,a=void 0!==f.name?f.name:i;if(r(u)&&n(u,a,f),f.global)c?o[i]=u:e(i,u);else{try{f.unsafe?o[i]&&(c=!0):delete o[i]}catch(r){}c?o[i]=u:t.f(o,i,{value:u,enumerable:!1,configurable:!f.nonConfigurable,writable:!f.nonWritable})}return o}}var Yt,Jt,Zt,rn,tn,nn,en,on,un,fn,cn,an,ln,sn,vn,pn,yn,bn={};function dn(){if(rn)return Zt;rn=1;var r=function(){if(Jt)return Yt;Jt=1;var r=Math.ceil,t=Math.floor;return Yt=Math.trunc||function(n){var e=+n;return(e>0?t:r)(e)}}();return Zt=function(t){var n=+t;return n!=n||0===n?0:r(n)}}function hn(){if(nn)return tn;nn=1;var r=dn(),t=Math.max,n=Math.min;return tn=function(e,o){var i=r(e);return i<0?t(i+o,0):n(i,o)}}function gn(){if(on)return en;on=1;var r=dn(),t=Math.min;return en=function(n){var e=r(n);return e>0?t(e,9007199254740991):0}}function mn(){if(fn)return un;fn=1;var r=gn();return un=function(t){return r(t.length)}}function wn(){if(sn)return ln;sn=1;var r=mr(),t=st(),n=Pr(),e=function(){if(an)return cn;an=1;var r=Pr(),t=hn(),n=mn(),e=function(e){return function(o,i,u){var f=r(o),c=n(f);if(0===c)return!e&&-1;var a,l=t(u,c);if(e&&i!=i){for(;c>l;)if((a=f[l++])!=a)return!0}else for(;c>l;l++)if((e||l in f)&&f[l]===i)return e||l||0;return!e&&-1}};return cn={includes:e(!0),indexOf:e(!1)}}().indexOf,o=Kt(),i=r([].push);return ln=function(r,u){var f,c=n(r),a=0,l=[];for(f in c)!t(o,f)&&t(c,f)&&i(l,f);for(;u.length>a;)t(c,f=u[a++])&&(~e(l,f)||i(l,f));return l}}function jn(){return pn?vn:(pn=1,vn=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"])}var On,Sn,Pn,En,Tn,Fn,An,_n,In,kn,xn,Rn,Cn,Mn,Bn,zn,Dn,Ln,Nn,qn,Gn,Un,Wn,$n,Hn,Kn,Qn,Vn,Xn,Yn,Jn={};function Zn(){return On||(On=1,Jn.f=Object.getOwnPropertySymbols),Jn}function re(){if(Pn)return Sn;Pn=1;var r=Fr(),t=mr(),n=function(){if(yn)return bn;yn=1;var r=wn(),t=jn().concat("length","prototype");return bn.f=Object.getOwnPropertyNames||function(n){return r(n,t)},bn}(),e=Zn(),o=Tt(),i=t([].concat);return Sn=r("Reflect","ownKeys")||function(r){var t=n.f(o(r)),u=e.f;return u?i(t,u(r)):t}}function te(){if(Tn)return En;Tn=1;var r=st(),t=re(),n=ht(),e=Ft();return En=function(o,i,u){for(var f=t(i),c=e.f,a=n.f,l=0;l=51||!r((function(){var r=[];return(r.constructor={})[e]=function(){return{foo:1}},1!==r[t](Boolean).foo}))}}!function(){if(Yn)return y;Yn=1;var r=ne(),t=function(){if(Qn)return Kn;Qn=1;var r=ee(),t=mr(),n=jr(),e=lt(),o=mn(),i=ae(),u=t([].push),f=function(t){var f=1===t,c=2===t,a=3===t,l=4===t,s=6===t,v=7===t,p=5===t||s;return function(y,b,d,h){for(var g,m,w=e(y),j=n(w),O=o(j),S=r(b,d),P=0,E=h||i,T=f?E(y,O):c||v?E(y,0):void 0;O>P;P++)if((p||P in j)&&(m=S(g=j[P],P,w),t))if(f)T[P]=m;else if(m)switch(t){case 3:return!0;case 5:return g;case 6:return P;case 2:u(T,g)}else switch(t){case 4:return!1;case 7:u(T,g)}return s?-1:a||l?l:T}};return Kn={forEach:f(0),map:f(1),filter:f(2),some:f(3),every:f(4),find:f(5),findIndex:f(6),filterReject:f(7)}}().filter;r({target:"Array",proto:!0,forced:!le()("filter")},{filter:function(r){return t(this,r,arguments.length>1?arguments[1]:void 0)}})}();var se,ve,pe,ye={};function be(){if(ve)return se;ve=1;var r=E();return se=function(t,n){var e=[][t];return!!e&&r((function(){e.call(null,n||function(){return 1},1)}))}}!function(){if(pe)return ye;pe=1;var r=ne(),t=mr(),n=jr(),e=Pr(),o=be(),i=t([].join);r({target:"Array",proto:!0,forced:n!==Object||!o("join",",")},{join:function(r){return i(e(this),void 0===r?",":r)}})}();var de,he,ge,me,we,je={};function Oe(){if(he)return de;he=1;var r=wn(),t=jn();return de=Object.keys||function(n){return r(n,t)}}!function(){if(we)return je;we=1;var r=ne(),t=function(){if(me)return ge;me=1;var r=T(),t=mr(),n=A(),e=E(),o=Oe(),i=Zn(),u=hr(),f=lt(),c=jr(),a=Object.assign,l=Object.defineProperty,s=t([].concat);return ge=!a||e((function(){if(r&&1!==a({b:1},a(l({},"a",{enumerable:!0,get:function(){l(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var t={},n={},e=Symbol("assign detection"),i="abcdefghijklmnopqrst";return t[e]=7,i.split("").forEach((function(r){n[r]=r})),7!==a({},t)[e]||o(a({},n)).join("")!==i}))?function(t,e){for(var a=f(t),l=arguments.length,v=1,p=i.f,y=u.f;l>v;)for(var b,d=c(arguments[v++]),h=p?s(o(d),p(d)):o(d),g=h.length,m=0;g>m;)b=h[m++],r&&!n(y,d,b)||(a[b]=d[b]);return a}:a,ge}();r({target:"Object",stat:!0,arity:2,forced:Object.assign!==t},{assign:t})}();var Se,Pe,Ee,Te={};!function(){if(Ee)return Te;Ee=1;var r=ie(),t=Xt(),n=function(){if(Pe)return Se;Pe=1;var r=ie(),t=ue();return Se=r?{}.toString:function(){return"[object "+t(this)+"]"}}();r||t(Object.prototype,"toString",n,{unsafe:!0})}();var Fe=r.fn.bootstrapTable.utils;Object.assign(r.fn.bootstrapTable.defaults,{treeEnable:!1,treeShowField:null,idField:"id",parentIdField:"pid",rootParentId:null}),r.BootstrapTable=function(t){function i(){return function(r,t){if(!(r instanceof t))throw new TypeError("Cannot call a class as a function")}(this,i),n(this,i,arguments)}return function(r,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");r.prototype=Object.create(t&&t.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),Object.defineProperty(r,"prototype",{writable:!1}),t&&c(r,t)}(i,t),e(i,[{key:"init",value:function(){this._rowStyle=this.options.rowStyle;for(var r=arguments.length,t=new Array(r),n=0;n (http://wenzhixin.net.cn/) + * @license MIT + */ + +!function(r,n){"object"==typeof exports&&"undefined"!=typeof module?n(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],n):n((r="undefined"!=typeof globalThis?globalThis:r||self).jQuery)}(this,(function(r){"use strict";var n,t,e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},o={};function u(){if(t)return n;t=1;var r=function(r){return r&&r.Math===Math&&r};return n=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof e&&e)||r("object"==typeof n&&n)||function(){return this}()||Function("return this")()}var i,f,c,a,l,s,v,p,g={};function y(){return f?i:(f=1,i=function(r){try{return!!r()}catch(r){return!0}})}function b(){if(a)return c;a=1;var r=y();return c=!r((function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]}))}function h(){if(s)return l;s=1;var r=y();return l=!r((function(){var r=function(){}.bind();return"function"!=typeof r||r.hasOwnProperty("prototype")}))}function m(){if(p)return v;p=1;var r=h(),n=Function.prototype.call;return v=r?n.bind(n):function(){return n.apply(n,arguments)},v}var d,w,S,O,j,P,T,E,x,C,A,F,R,M,k,I,L,N,z,D,G,H,U,_,q,B,W,$,J,K,Q,V,X,Y,Z,rr,nr,tr,er,or,ur,ir={};function fr(){if(d)return ir;d=1;var r={}.propertyIsEnumerable,n=Object.getOwnPropertyDescriptor,t=n&&!r.call({1:2},1);return ir.f=t?function(r){var t=n(this,r);return!!t&&t.enumerable}:r,ir}function cr(){return S?w:(S=1,w=function(r,n){return{enumerable:!(1&r),configurable:!(2&r),writable:!(4&r),value:n}})}function ar(){if(j)return O;j=1;var r=h(),n=Function.prototype,t=n.call,e=r&&n.bind.bind(t,t);return O=r?e:function(r){return function(){return t.apply(r,arguments)}},O}function lr(){if(T)return P;T=1;var r=ar(),n=r({}.toString),t=r("".slice);return P=function(r){return t(n(r),8,-1)}}function sr(){if(x)return E;x=1;var r=ar(),n=y(),t=lr(),e=Object,o=r("".split);return E=n((function(){return!e("z").propertyIsEnumerable(0)}))?function(r){return"String"===t(r)?o(r,""):e(r)}:e}function vr(){return A?C:(A=1,C=function(r){return null==r})}function pr(){if(R)return F;R=1;var r=vr(),n=TypeError;return F=function(t){if(r(t))throw new n("Can't call method on "+t);return t}}function gr(){if(k)return M;k=1;var r=sr(),n=pr();return M=function(t){return r(n(t))}}function yr(){if(L)return I;L=1;var r="object"==typeof document&&document.all;return I=void 0===r&&void 0!==r?function(n){return"function"==typeof n||n===r}:function(r){return"function"==typeof r}}function br(){if(z)return N;z=1;var r=yr();return N=function(n){return"object"==typeof n?null!==n:r(n)}}function hr(){if(G)return D;G=1;var r=u(),n=yr();return D=function(t,e){return arguments.length<2?(o=r[t],n(o)?o:void 0):r[t]&&r[t][e];var o},D}function mr(){if(W)return B;W=1;var r,n,t=u(),e=function(){if(q)return _;q=1;var r=u().navigator,n=r&&r.userAgent;return _=n?String(n):""}(),o=t.process,i=t.Deno,f=o&&o.versions||i&&i.version,c=f&&f.v8;return c&&(n=(r=c.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!n&&e&&(!(r=e.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=e.match(/Chrome\/(\d+)/))&&(n=+r[1]),B=n}function dr(){if(J)return $;J=1;var r=mr(),n=y(),t=u().String;return $=!!Object.getOwnPropertySymbols&&!n((function(){var n=Symbol("symbol detection");return!t(n)||!(Object(n)instanceof Symbol)||!Symbol.sham&&r&&r<41}))}function wr(){if(Q)return K;Q=1;var r=dr();return K=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator}function Sr(){if(X)return V;X=1;var r=hr(),n=yr(),t=function(){if(U)return H;U=1;var r=ar();return H=r({}.isPrototypeOf)}(),e=wr(),o=Object;return V=e?function(r){return"symbol"==typeof r}:function(e){var u=r("Symbol");return n(u)&&t(u.prototype,o(e))}}function Or(){if(Z)return Y;Z=1;var r=String;return Y=function(n){try{return r(n)}catch(r){return"Object"}}}function jr(){if(nr)return rr;nr=1;var r=yr(),n=Or(),t=TypeError;return rr=function(e){if(r(e))return e;throw new t(n(e)+" is not a function")}}function Pr(){if(er)return tr;er=1;var r=jr(),n=vr();return tr=function(t,e){var o=t[e];return n(o)?void 0:r(o)}}function Tr(){if(ur)return or;ur=1;var r=m(),n=yr(),t=br(),e=TypeError;return or=function(o,u){var i,f;if("string"===u&&n(i=o.toString)&&!t(f=r(i,o)))return f;if(n(i=o.valueOf)&&!t(f=r(i,o)))return f;if("string"!==u&&n(i=o.toString)&&!t(f=r(i,o)))return f;throw new e("Can't convert object to primitive value")}}var Er,xr,Cr,Ar,Fr,Rr,Mr,kr,Ir,Lr,Nr,zr,Dr,Gr,Hr,Ur,_r,qr,Br,Wr,$r,Jr,Kr,Qr,Vr={exports:{}};function Xr(){if(Ar)return Cr;Ar=1;var r=u(),n=Object.defineProperty;return Cr=function(t,e){try{n(r,t,{value:e,configurable:!0,writable:!0})}catch(n){r[t]=e}return e}}function Yr(){if(Fr)return Vr.exports;Fr=1;var r=xr?Er:(xr=1,Er=!1),n=u(),t=Xr(),e="__core-js_shared__",o=Vr.exports=n[e]||t(e,{});return(o.versions||(o.versions=[])).push({version:"3.39.0",mode:r?"pure":"global",copyright:"© 2014-2024 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE",source:"https://github.com/zloirock/core-js"}),Vr.exports}function Zr(){if(Mr)return Rr;Mr=1;var r=Yr();return Rr=function(n,t){return r[n]||(r[n]=t||{})}}function rn(){if(Ir)return kr;Ir=1;var r=pr(),n=Object;return kr=function(t){return n(r(t))}}function nn(){if(Nr)return Lr;Nr=1;var r=ar(),n=rn(),t=r({}.hasOwnProperty);return Lr=Object.hasOwn||function(r,e){return t(n(r),e)}}function tn(){if(Dr)return zr;Dr=1;var r=ar(),n=0,t=Math.random(),e=r(1..toString);return zr=function(r){return"Symbol("+(void 0===r?"":r)+")_"+e(++n+t,36)}}function en(){if(Hr)return Gr;Hr=1;var r=u(),n=Zr(),t=nn(),e=tn(),o=dr(),i=wr(),f=r.Symbol,c=n("wks"),a=i?f.for||f:f&&f.withoutSetter||e;return Gr=function(r){return t(c,r)||(c[r]=o&&t(f,r)?f[r]:a("Symbol."+r)),c[r]}}function on(){if(_r)return Ur;_r=1;var r=m(),n=br(),t=Sr(),e=Pr(),o=Tr(),u=en(),i=TypeError,f=u("toPrimitive");return Ur=function(u,c){if(!n(u)||t(u))return u;var a,l=e(u,f);if(l){if(void 0===c&&(c="default"),a=r(l,u,c),!n(a)||t(a))return a;throw new i("Can't convert object to primitive value")}return void 0===c&&(c="number"),o(u,c)}}function un(){if(Br)return qr;Br=1;var r=on(),n=Sr();return qr=function(t){var e=r(t,"string");return n(e)?e:e+""}}function fn(){if(Kr)return Jr;Kr=1;var r=b(),n=y(),t=function(){if($r)return Wr;$r=1;var r=u(),n=br(),t=r.document,e=n(t)&&n(t.createElement);return Wr=function(r){return e?t.createElement(r):{}}}();return Jr=!r&&!n((function(){return 7!==Object.defineProperty(t("div"),"a",{get:function(){return 7}}).a}))}function cn(){if(Qr)return g;Qr=1;var r=b(),n=m(),t=fr(),e=cr(),o=gr(),u=un(),i=nn(),f=fn(),c=Object.getOwnPropertyDescriptor;return g.f=r?c:function(r,a){if(r=o(r),a=u(a),f)try{return c(r,a)}catch(r){}if(i(r,a))return e(!n(t.f,r,a),r[a])},g}var an,ln,sn,vn,pn,gn,yn,bn={};function hn(){if(vn)return sn;vn=1;var r=br(),n=String,t=TypeError;return sn=function(e){if(r(e))return e;throw new t(n(e)+" is not an object")}}function mn(){if(pn)return bn;pn=1;var r=b(),n=fn(),t=function(){if(ln)return an;ln=1;var r=b(),n=y();return an=r&&n((function(){return 42!==Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))}(),e=hn(),o=un(),u=TypeError,i=Object.defineProperty,f=Object.getOwnPropertyDescriptor,c="enumerable",a="configurable",l="writable";return bn.f=r?t?function(r,n,t){if(e(r),n=o(n),e(t),"function"==typeof r&&"prototype"===n&&"value"in t&&l in t&&!t[l]){var u=f(r,n);u&&u[l]&&(r[n]=t.value,t={configurable:a in t?t[a]:u[a],enumerable:c in t?t[c]:u[c],writable:!1})}return i(r,n,t)}:i:function(r,t,f){if(e(r),t=o(t),e(f),n)try{return i(r,t,f)}catch(r){}if("get"in f||"set"in f)throw new u("Accessors not supported");return"value"in f&&(r[t]=f.value),r},bn}function dn(){if(yn)return gn;yn=1;var r=b(),n=mn(),t=cr();return gn=r?function(r,e,o){return n.f(r,e,t(1,o))}:function(r,n,t){return r[n]=t,r}}var wn,Sn,On,jn,Pn,Tn,En,xn,Cn,An,Fn,Rn,Mn,kn,In,Ln={exports:{}};function Nn(){if(jn)return On;jn=1;var r=ar(),n=yr(),t=Yr(),e=r(Function.toString);return n(t.inspectSource)||(t.inspectSource=function(r){return e(r)}),On=t.inspectSource}function zn(){if(xn)return En;xn=1;var r=Zr(),n=tn(),t=r("keys");return En=function(r){return t[r]||(t[r]=n(r))}}function Dn(){return An?Cn:(An=1,Cn={})}function Gn(){if(Rn)return Fn;Rn=1;var r,n,t,e=function(){if(Tn)return Pn;Tn=1;var r=u(),n=yr(),t=r.WeakMap;return Pn=n(t)&&/native code/.test(String(t))}(),o=u(),i=br(),f=dn(),c=nn(),a=Yr(),l=zn(),s=Dn(),v="Object already initialized",p=o.TypeError,g=o.WeakMap;if(e||a.state){var y=a.state||(a.state=new g);y.get=y.get,y.has=y.has,y.set=y.set,r=function(r,n){if(y.has(r))throw new p(v);return n.facade=r,y.set(r,n),n},n=function(r){return y.get(r)||{}},t=function(r){return y.has(r)}}else{var b=l("state");s[b]=!0,r=function(r,n){if(c(r,b))throw new p(v);return n.facade=r,f(r,b,n),n},n=function(r){return c(r,b)?r[b]:{}},t=function(r){return c(r,b)}}return Fn={set:r,get:n,has:t,enforce:function(e){return t(e)?n(e):r(e,{})},getterFor:function(r){return function(t){var e;if(!i(t)||(e=n(t)).type!==r)throw new p("Incompatible receiver, "+r+" required");return e}}}}function Hn(){if(Mn)return Ln.exports;Mn=1;var r=ar(),n=y(),t=yr(),e=nn(),o=b(),u=function(){if(Sn)return wn;Sn=1;var r=b(),n=nn(),t=Function.prototype,e=r&&Object.getOwnPropertyDescriptor,o=n(t,"name"),u=o&&"something"===function(){}.name,i=o&&(!r||r&&e(t,"name").configurable);return wn={EXISTS:o,PROPER:u,CONFIGURABLE:i}}().CONFIGURABLE,i=Nn(),f=Gn(),c=f.enforce,a=f.get,l=String,s=Object.defineProperty,v=r("".slice),p=r("".replace),g=r([].join),h=o&&!n((function(){return 8!==s((function(){}),"length",{value:8}).length})),m=String(String).split("String"),d=Ln.exports=function(r,n,t){"Symbol("===v(l(n),0,7)&&(n="["+p(l(n),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),t&&t.getter&&(n="get "+n),t&&t.setter&&(n="set "+n),(!e(r,"name")||u&&r.name!==n)&&(o?s(r,"name",{value:n,configurable:!0}):r.name=n),h&&t&&e(t,"arity")&&r.length!==t.arity&&s(r,"length",{value:t.arity});try{t&&e(t,"constructor")&&t.constructor?o&&s(r,"prototype",{writable:!1}):r.prototype&&(r.prototype=void 0)}catch(r){}var i=c(r);return e(i,"source")||(i.source=g(m,"string"==typeof n?n:"")),r};return Function.prototype.toString=d((function(){return t(this)&&a(this).source||i(this)}),"toString"),Ln.exports}function Un(){if(In)return kn;In=1;var r=yr(),n=mn(),t=Hn(),e=Xr();return kn=function(o,u,i,f){f||(f={});var c=f.enumerable,a=void 0!==f.name?f.name:u;if(r(i)&&t(i,a,f),f.global)c?o[u]=i:e(u,i);else{try{f.unsafe?o[u]&&(c=!0):delete o[u]}catch(r){}c?o[u]=i:n.f(o,u,{value:i,enumerable:!1,configurable:!f.nonConfigurable,writable:!f.nonWritable})}return o}}var _n,qn,Bn,Wn,$n,Jn,Kn,Qn,Vn,Xn,Yn,Zn,rt,nt,tt,et,ot,ut={};function it(){if(Wn)return Bn;Wn=1;var r=function(){if(qn)return _n;qn=1;var r=Math.ceil,n=Math.floor;return _n=Math.trunc||function(t){var e=+t;return(e>0?n:r)(e)}}();return Bn=function(n){var t=+n;return t!=t||0===t?0:r(t)}}function ft(){if(Jn)return $n;Jn=1;var r=it(),n=Math.max,t=Math.min;return $n=function(e,o){var u=r(e);return u<0?n(u+o,0):t(u,o)}}function ct(){if(Qn)return Kn;Qn=1;var r=it(),n=Math.min;return Kn=function(t){var e=r(t);return e>0?n(e,9007199254740991):0}}function at(){if(Xn)return Vn;Xn=1;var r=ct();return Vn=function(n){return r(n.length)}}function lt(){if(nt)return rt;nt=1;var r=ar(),n=nn(),t=gr(),e=function(){if(Zn)return Yn;Zn=1;var r=gr(),n=ft(),t=at(),e=function(e){return function(o,u,i){var f=r(o),c=t(f);if(0===c)return!e&&-1;var a,l=n(i,c);if(e&&u!=u){for(;c>l;)if((a=f[l++])!=a)return!0}else for(;c>l;l++)if((e||l in f)&&f[l]===u)return e||l||0;return!e&&-1}};return Yn={includes:e(!0),indexOf:e(!1)}}().indexOf,o=Dn(),u=r([].push);return rt=function(r,i){var f,c=t(r),a=0,l=[];for(f in c)!n(o,f)&&n(c,f)&&u(l,f);for(;i.length>a;)n(c,f=i[a++])&&(~e(l,f)||u(l,f));return l}}function st(){return et?tt:(et=1,tt=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"])}var vt,pt,gt,yt,bt,ht,mt,dt,wt,St,Ot,jt,Pt,Tt,Et,xt,Ct,At,Ft,Rt,Mt,kt,It,Lt,Nt,zt,Dt,Gt,Ht={};function Ut(){return vt||(vt=1,Ht.f=Object.getOwnPropertySymbols),Ht}function _t(){if(gt)return pt;gt=1;var r=hr(),n=ar(),t=function(){if(ot)return ut;ot=1;var r=lt(),n=st().concat("length","prototype");return ut.f=Object.getOwnPropertyNames||function(t){return r(t,n)},ut}(),e=Ut(),o=hn(),u=n([].concat);return pt=r("Reflect","ownKeys")||function(r){var n=t.f(o(r)),i=e.f;return i?u(n,i(r)):n}}function qt(){if(bt)return yt;bt=1;var r=nn(),n=_t(),t=cn(),e=mn();return yt=function(o,u,i){for(var f=n(u),c=e.f,a=t.f,l=0;l9007199254740991)throw r("Maximum allowed index exceeded");return n}}function Jt(){if(Et)return Tt;Et=1;var r=b(),n=mn(),t=cr();return Tt=function(e,o,u){r?n.f(e,o,t(0,u)):e[o]=u}}function Kt(){if(Ft)return At;Ft=1;var r=function(){if(Ct)return xt;Ct=1;var r={};return r[en()("toStringTag")]="z",xt="[object z]"===String(r)}(),n=yr(),t=lr(),e=en()("toStringTag"),o=Object,u="Arguments"===t(function(){return arguments}());return At=r?t:function(r){var i,f,c;return void 0===r?"Undefined":null===r?"Null":"string"==typeof(f=function(r,n){try{return r[n]}catch(r){}}(i=o(r),e))?f:u?t(i):"Object"===(c=t(i))&&n(i.callee)?"Arguments":c}}function Qt(){if(Mt)return Rt;Mt=1;var r=ar(),n=y(),t=yr(),e=Kt(),o=hr(),u=Nn(),i=function(){},f=o("Reflect","construct"),c=/^\s*(?:class|function)\b/,a=r(c.exec),l=!c.test(i),s=function(r){if(!t(r))return!1;try{return f(i,[],r),!0}catch(r){return!1}},v=function(r){if(!t(r))return!1;switch(e(r)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return l||!!a(c,u(r))}catch(r){return!0}};return v.sham=!0,Rt=!f||n((function(){var r;return s(s.call)||!s(Object)||!s((function(){r=!0}))||r}))?v:s}function Vt(){if(It)return kt;It=1;var r=Wt(),n=Qt(),t=br(),e=en()("species"),o=Array;return kt=function(u){var i;return r(u)&&(i=u.constructor,(n(i)&&(i===o||r(i.prototype))||t(i)&&null===(i=i[e]))&&(i=void 0)),void 0===i?o:i}}function Xt(){if(Nt)return Lt;Nt=1;var r=Vt();return Lt=function(n,t){return new(r(n))(0===t?0:t)}}function Yt(){if(Dt)return zt;Dt=1;var r=y(),n=en(),t=mr(),e=n("species");return zt=function(n){return t>=51||!r((function(){var r=[];return(r.constructor={})[e]=function(){return{foo:1}},1!==r[n](Boolean).foo}))}}!function(){if(Gt)return o;Gt=1;var r=Bt(),n=y(),t=Wt(),e=br(),u=rn(),i=at(),f=$t(),c=Jt(),a=Xt(),l=Yt(),s=en(),v=mr(),p=s("isConcatSpreadable"),g=v>=51||!n((function(){var r=[];return r[p]=!1,r.concat()[0]!==r})),b=function(r){if(!e(r))return!1;var n=r[p];return void 0!==n?!!n:t(r)};r({target:"Array",proto:!0,arity:1,forced:!g||!l("concat")},{concat:function(r){var n,t,e,o,l,s=u(this),v=a(s,0),p=0;for(n=-1,e=arguments.length;nv;)for(var y,b=c(arguments[v++]),h=p?s(o(b),p(b)):o(b),m=h.length,d=0;m>d;)y=h[d++],r&&!t(g,b,y)||(a[y]=b[y]);return a}:a,ne}();r({target:"Object",stat:!0,arity:2,forced:Object.assign!==n},{assign:n})}(),r.fn.bootstrapTable.locales["en-US"]=r.fn.bootstrapTable.locales.en={formatCopyRows:function(){return"Copy Rows"},formatPrint:function(){return"Print"},formatLoadingMessage:function(){return"Loading, please wait"},formatRecordsPerPage:function(r){return"".concat(r," rows per page")},formatShowingRows:function(r,n,t,e){return void 0!==e&&e>0&&e>t?"Showing ".concat(r," to ").concat(n," of ").concat(t," rows (filtered from ").concat(e," total rows)"):"Showing ".concat(r," to ").concat(n," of ").concat(t," rows")},formatSRPaginationPreText:function(){return"previous page"},formatSRPaginationPageText:function(r){return"to page ".concat(r)},formatSRPaginationNextText:function(){return"next page"},formatDetailPagination:function(r){return"Showing ".concat(r," rows")},formatClearSearch:function(){return"Clear Search"},formatSearch:function(){return"Search"},formatNoMatches:function(){return"No matching records found"},formatPaginationSwitch:function(){return"Hide/Show pagination"},formatPaginationSwitchDown:function(){return"Show pagination"},formatPaginationSwitchUp:function(){return"Hide pagination"},formatRefresh:function(){return"Refresh"},formatToggleOn:function(){return"Show card view"},formatToggleOff:function(){return"Hide card view"},formatColumns:function(){return"Columns"},formatColumnsToggleAll:function(){return"Toggle all"},formatFullscreen:function(){return"Fullscreen"},formatAllRows:function(){return"All"},formatAutoRefresh:function(){return"Auto Refresh"},formatExport:function(){return"Export data"},formatJumpTo:function(){return"GO"},formatAdvancedSearch:function(){return"Advanced search"},formatAdvancedCloseButton:function(){return"Close"},formatFilterControlSwitch:function(){return"Hide/Show controls"},formatFilterControlSwitchHide:function(){return"Hide controls"},formatFilterControlSwitchShow:function(){return"Show controls"}},Object.assign(r.fn.bootstrapTable.defaults,r.fn.bootstrapTable.locales["en-US"])})); diff --git a/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js b/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js new file mode 100644 index 00000000..ae4ff00e --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js @@ -0,0 +1,10 @@ +/** + * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) + * + * @version v1.24.1 + * @homepage https://bootstrap-table.com + * @author wenzhixin (http://wenzhixin.net.cn/) + * @license MIT + */ + +!function(r,n){"object"==typeof exports&&"undefined"!=typeof module?n(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],n):n((r="undefined"!=typeof globalThis?globalThis:r||self).jQuery)}(this,(function(r){"use strict";var n,t,e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},o={};function u(){if(t)return n;t=1;var r=function(r){return r&&r.Math===Math&&r};return n=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof e&&e)||r("object"==typeof n&&n)||function(){return this}()||Function("return this")()}var i,f,c,a,l,s,v,p,y={};function b(){return f?i:(f=1,i=function(r){try{return!!r()}catch(r){return!0}})}function g(){if(a)return c;a=1;var r=b();return c=!r((function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]}))}function h(){if(s)return l;s=1;var r=b();return l=!r((function(){var r=function(){}.bind();return"function"!=typeof r||r.hasOwnProperty("prototype")}))}function m(){if(p)return v;p=1;var r=h(),n=Function.prototype.call;return v=r?n.bind(n):function(){return n.apply(n,arguments)},v}var d,w,S,O,j,P,T,E,x,C,A,F,R,M,z,N,k,I,L,D,_,q,G,B,U,W,$,H,J,K,Q,V,X,Y,Z,rr,nr,tr,er,or,ur,ir={};function fr(){if(d)return ir;d=1;var r={}.propertyIsEnumerable,n=Object.getOwnPropertyDescriptor,t=n&&!r.call({1:2},1);return ir.f=t?function(r){var t=n(this,r);return!!t&&t.enumerable}:r,ir}function cr(){return S?w:(S=1,w=function(r,n){return{enumerable:!(1&r),configurable:!(2&r),writable:!(4&r),value:n}})}function ar(){if(j)return O;j=1;var r=h(),n=Function.prototype,t=n.call,e=r&&n.bind.bind(t,t);return O=r?e:function(r){return function(){return t.apply(r,arguments)}},O}function lr(){if(T)return P;T=1;var r=ar(),n=r({}.toString),t=r("".slice);return P=function(r){return t(n(r),8,-1)}}function sr(){if(x)return E;x=1;var r=ar(),n=b(),t=lr(),e=Object,o=r("".split);return E=n((function(){return!e("z").propertyIsEnumerable(0)}))?function(r){return"String"===t(r)?o(r,""):e(r)}:e}function vr(){return A?C:(A=1,C=function(r){return null==r})}function pr(){if(R)return F;R=1;var r=vr(),n=TypeError;return F=function(t){if(r(t))throw new n("Can't call method on "+t);return t}}function yr(){if(z)return M;z=1;var r=sr(),n=pr();return M=function(t){return r(n(t))}}function br(){if(k)return N;k=1;var r="object"==typeof document&&document.all;return N=void 0===r&&void 0!==r?function(n){return"function"==typeof n||n===r}:function(r){return"function"==typeof r}}function gr(){if(L)return I;L=1;var r=br();return I=function(n){return"object"==typeof n?null!==n:r(n)}}function hr(){if(_)return D;_=1;var r=u(),n=br();return D=function(t,e){return arguments.length<2?(o=r[t],n(o)?o:void 0):r[t]&&r[t][e];var o},D}function mr(){if($)return W;$=1;var r,n,t=u(),e=function(){if(U)return B;U=1;var r=u().navigator,n=r&&r.userAgent;return B=n?String(n):""}(),o=t.process,i=t.Deno,f=o&&o.versions||i&&i.version,c=f&&f.v8;return c&&(n=(r=c.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!n&&e&&(!(r=e.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=e.match(/Chrome\/(\d+)/))&&(n=+r[1]),W=n}function dr(){if(J)return H;J=1;var r=mr(),n=b(),t=u().String;return H=!!Object.getOwnPropertySymbols&&!n((function(){var n=Symbol("symbol detection");return!t(n)||!(Object(n)instanceof Symbol)||!Symbol.sham&&r&&r<41}))}function wr(){if(Q)return K;Q=1;var r=dr();return K=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator}function Sr(){if(X)return V;X=1;var r=hr(),n=br(),t=function(){if(G)return q;G=1;var r=ar();return q=r({}.isPrototypeOf)}(),e=wr(),o=Object;return V=e?function(r){return"symbol"==typeof r}:function(e){var u=r("Symbol");return n(u)&&t(u.prototype,o(e))}}function Or(){if(Z)return Y;Z=1;var r=String;return Y=function(n){try{return r(n)}catch(r){return"Object"}}}function jr(){if(nr)return rr;nr=1;var r=br(),n=Or(),t=TypeError;return rr=function(e){if(r(e))return e;throw new t(n(e)+" is not a function")}}function Pr(){if(er)return tr;er=1;var r=jr(),n=vr();return tr=function(t,e){var o=t[e];return n(o)?void 0:r(o)}}function Tr(){if(ur)return or;ur=1;var r=m(),n=br(),t=gr(),e=TypeError;return or=function(o,u){var i,f;if("string"===u&&n(i=o.toString)&&!t(f=r(i,o)))return f;if(n(i=o.valueOf)&&!t(f=r(i,o)))return f;if("string"!==u&&n(i=o.toString)&&!t(f=r(i,o)))return f;throw new e("Can't convert object to primitive value")}}var Er,xr,Cr,Ar,Fr,Rr,Mr,zr,Nr,kr,Ir,Lr,Dr,_r,qr,Gr,Br,Ur,Wr,$r,Hr,Jr,Kr,Qr,Vr={exports:{}};function Xr(){if(Ar)return Cr;Ar=1;var r=u(),n=Object.defineProperty;return Cr=function(t,e){try{n(r,t,{value:e,configurable:!0,writable:!0})}catch(n){r[t]=e}return e}}function Yr(){if(Fr)return Vr.exports;Fr=1;var r=xr?Er:(xr=1,Er=!1),n=u(),t=Xr(),e="__core-js_shared__",o=Vr.exports=n[e]||t(e,{});return(o.versions||(o.versions=[])).push({version:"3.39.0",mode:r?"pure":"global",copyright:"© 2014-2024 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE",source:"https://github.com/zloirock/core-js"}),Vr.exports}function Zr(){if(Mr)return Rr;Mr=1;var r=Yr();return Rr=function(n,t){return r[n]||(r[n]=t||{})}}function rn(){if(Nr)return zr;Nr=1;var r=pr(),n=Object;return zr=function(t){return n(r(t))}}function nn(){if(Ir)return kr;Ir=1;var r=ar(),n=rn(),t=r({}.hasOwnProperty);return kr=Object.hasOwn||function(r,e){return t(n(r),e)}}function tn(){if(Dr)return Lr;Dr=1;var r=ar(),n=0,t=Math.random(),e=r(1..toString);return Lr=function(r){return"Symbol("+(void 0===r?"":r)+")_"+e(++n+t,36)}}function en(){if(qr)return _r;qr=1;var r=u(),n=Zr(),t=nn(),e=tn(),o=dr(),i=wr(),f=r.Symbol,c=n("wks"),a=i?f.for||f:f&&f.withoutSetter||e;return _r=function(r){return t(c,r)||(c[r]=o&&t(f,r)?f[r]:a("Symbol."+r)),c[r]}}function on(){if(Br)return Gr;Br=1;var r=m(),n=gr(),t=Sr(),e=Pr(),o=Tr(),u=en(),i=TypeError,f=u("toPrimitive");return Gr=function(u,c){if(!n(u)||t(u))return u;var a,l=e(u,f);if(l){if(void 0===c&&(c="default"),a=r(l,u,c),!n(a)||t(a))return a;throw new i("Can't convert object to primitive value")}return void 0===c&&(c="number"),o(u,c)}}function un(){if(Wr)return Ur;Wr=1;var r=on(),n=Sr();return Ur=function(t){var e=r(t,"string");return n(e)?e:e+""}}function fn(){if(Kr)return Jr;Kr=1;var r=g(),n=b(),t=function(){if(Hr)return $r;Hr=1;var r=u(),n=gr(),t=r.document,e=n(t)&&n(t.createElement);return $r=function(r){return e?t.createElement(r):{}}}();return Jr=!r&&!n((function(){return 7!==Object.defineProperty(t("div"),"a",{get:function(){return 7}}).a}))}function cn(){if(Qr)return y;Qr=1;var r=g(),n=m(),t=fr(),e=cr(),o=yr(),u=un(),i=nn(),f=fn(),c=Object.getOwnPropertyDescriptor;return y.f=r?c:function(r,a){if(r=o(r),a=u(a),f)try{return c(r,a)}catch(r){}if(i(r,a))return e(!n(t.f,r,a),r[a])},y}var an,ln,sn,vn,pn,yn,bn,gn={};function hn(){if(vn)return sn;vn=1;var r=gr(),n=String,t=TypeError;return sn=function(e){if(r(e))return e;throw new t(n(e)+" is not an object")}}function mn(){if(pn)return gn;pn=1;var r=g(),n=fn(),t=function(){if(ln)return an;ln=1;var r=g(),n=b();return an=r&&n((function(){return 42!==Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))}(),e=hn(),o=un(),u=TypeError,i=Object.defineProperty,f=Object.getOwnPropertyDescriptor,c="enumerable",a="configurable",l="writable";return gn.f=r?t?function(r,n,t){if(e(r),n=o(n),e(t),"function"==typeof r&&"prototype"===n&&"value"in t&&l in t&&!t[l]){var u=f(r,n);u&&u[l]&&(r[n]=t.value,t={configurable:a in t?t[a]:u[a],enumerable:c in t?t[c]:u[c],writable:!1})}return i(r,n,t)}:i:function(r,t,f){if(e(r),t=o(t),e(f),n)try{return i(r,t,f)}catch(r){}if("get"in f||"set"in f)throw new u("Accessors not supported");return"value"in f&&(r[t]=f.value),r},gn}function dn(){if(bn)return yn;bn=1;var r=g(),n=mn(),t=cr();return yn=r?function(r,e,o){return n.f(r,e,t(1,o))}:function(r,n,t){return r[n]=t,r}}var wn,Sn,On,jn,Pn,Tn,En,xn,Cn,An,Fn,Rn,Mn,zn,Nn,kn={exports:{}};function In(){if(jn)return On;jn=1;var r=ar(),n=br(),t=Yr(),e=r(Function.toString);return n(t.inspectSource)||(t.inspectSource=function(r){return e(r)}),On=t.inspectSource}function Ln(){if(xn)return En;xn=1;var r=Zr(),n=tn(),t=r("keys");return En=function(r){return t[r]||(t[r]=n(r))}}function Dn(){return An?Cn:(An=1,Cn={})}function _n(){if(Rn)return Fn;Rn=1;var r,n,t,e=function(){if(Tn)return Pn;Tn=1;var r=u(),n=br(),t=r.WeakMap;return Pn=n(t)&&/native code/.test(String(t))}(),o=u(),i=gr(),f=dn(),c=nn(),a=Yr(),l=Ln(),s=Dn(),v="Object already initialized",p=o.TypeError,y=o.WeakMap;if(e||a.state){var b=a.state||(a.state=new y);b.get=b.get,b.has=b.has,b.set=b.set,r=function(r,n){if(b.has(r))throw new p(v);return n.facade=r,b.set(r,n),n},n=function(r){return b.get(r)||{}},t=function(r){return b.has(r)}}else{var g=l("state");s[g]=!0,r=function(r,n){if(c(r,g))throw new p(v);return n.facade=r,f(r,g,n),n},n=function(r){return c(r,g)?r[g]:{}},t=function(r){return c(r,g)}}return Fn={set:r,get:n,has:t,enforce:function(e){return t(e)?n(e):r(e,{})},getterFor:function(r){return function(t){var e;if(!i(t)||(e=n(t)).type!==r)throw new p("Incompatible receiver, "+r+" required");return e}}}}function qn(){if(Mn)return kn.exports;Mn=1;var r=ar(),n=b(),t=br(),e=nn(),o=g(),u=function(){if(Sn)return wn;Sn=1;var r=g(),n=nn(),t=Function.prototype,e=r&&Object.getOwnPropertyDescriptor,o=n(t,"name"),u=o&&"something"===function(){}.name,i=o&&(!r||r&&e(t,"name").configurable);return wn={EXISTS:o,PROPER:u,CONFIGURABLE:i}}().CONFIGURABLE,i=In(),f=_n(),c=f.enforce,a=f.get,l=String,s=Object.defineProperty,v=r("".slice),p=r("".replace),y=r([].join),h=o&&!n((function(){return 8!==s((function(){}),"length",{value:8}).length})),m=String(String).split("String"),d=kn.exports=function(r,n,t){"Symbol("===v(l(n),0,7)&&(n="["+p(l(n),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),t&&t.getter&&(n="get "+n),t&&t.setter&&(n="set "+n),(!e(r,"name")||u&&r.name!==n)&&(o?s(r,"name",{value:n,configurable:!0}):r.name=n),h&&t&&e(t,"arity")&&r.length!==t.arity&&s(r,"length",{value:t.arity});try{t&&e(t,"constructor")&&t.constructor?o&&s(r,"prototype",{writable:!1}):r.prototype&&(r.prototype=void 0)}catch(r){}var i=c(r);return e(i,"source")||(i.source=y(m,"string"==typeof n?n:"")),r};return Function.prototype.toString=d((function(){return t(this)&&a(this).source||i(this)}),"toString"),kn.exports}function Gn(){if(Nn)return zn;Nn=1;var r=br(),n=mn(),t=qn(),e=Xr();return zn=function(o,u,i,f){f||(f={});var c=f.enumerable,a=void 0!==f.name?f.name:u;if(r(i)&&t(i,a,f),f.global)c?o[u]=i:e(u,i);else{try{f.unsafe?o[u]&&(c=!0):delete o[u]}catch(r){}c?o[u]=i:n.f(o,u,{value:i,enumerable:!1,configurable:!f.nonConfigurable,writable:!f.nonWritable})}return o}}var Bn,Un,Wn,$n,Hn,Jn,Kn,Qn,Vn,Xn,Yn,Zn,rt,nt,tt,et,ot,ut={};function it(){if($n)return Wn;$n=1;var r=function(){if(Un)return Bn;Un=1;var r=Math.ceil,n=Math.floor;return Bn=Math.trunc||function(t){var e=+t;return(e>0?n:r)(e)}}();return Wn=function(n){var t=+n;return t!=t||0===t?0:r(t)}}function ft(){if(Jn)return Hn;Jn=1;var r=it(),n=Math.max,t=Math.min;return Hn=function(e,o){var u=r(e);return u<0?n(u+o,0):t(u,o)}}function ct(){if(Qn)return Kn;Qn=1;var r=it(),n=Math.min;return Kn=function(t){var e=r(t);return e>0?n(e,9007199254740991):0}}function at(){if(Xn)return Vn;Xn=1;var r=ct();return Vn=function(n){return r(n.length)}}function lt(){if(nt)return rt;nt=1;var r=ar(),n=nn(),t=yr(),e=function(){if(Zn)return Yn;Zn=1;var r=yr(),n=ft(),t=at(),e=function(e){return function(o,u,i){var f=r(o),c=t(f);if(0===c)return!e&&-1;var a,l=n(i,c);if(e&&u!=u){for(;c>l;)if((a=f[l++])!=a)return!0}else for(;c>l;l++)if((e||l in f)&&f[l]===u)return e||l||0;return!e&&-1}};return Yn={includes:e(!0),indexOf:e(!1)}}().indexOf,o=Dn(),u=r([].push);return rt=function(r,i){var f,c=t(r),a=0,l=[];for(f in c)!n(o,f)&&n(c,f)&&u(l,f);for(;i.length>a;)n(c,f=i[a++])&&(~e(l,f)||u(l,f));return l}}function st(){return et?tt:(et=1,tt=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"])}var vt,pt,yt,bt,gt,ht,mt,dt,wt,St,Ot,jt,Pt,Tt,Et,xt,Ct,At,Ft,Rt,Mt,zt,Nt,kt,It,Lt,Dt,_t,qt={};function Gt(){return vt||(vt=1,qt.f=Object.getOwnPropertySymbols),qt}function Bt(){if(yt)return pt;yt=1;var r=hr(),n=ar(),t=function(){if(ot)return ut;ot=1;var r=lt(),n=st().concat("length","prototype");return ut.f=Object.getOwnPropertyNames||function(t){return r(t,n)},ut}(),e=Gt(),o=hn(),u=n([].concat);return pt=r("Reflect","ownKeys")||function(r){var n=t.f(o(r)),i=e.f;return i?u(n,i(r)):n}}function Ut(){if(gt)return bt;gt=1;var r=nn(),n=Bt(),t=cn(),e=mn();return bt=function(o,u,i){for(var f=n(u),c=e.f,a=t.f,l=0;l9007199254740991)throw r("Maximum allowed index exceeded");return n}}function Jt(){if(Et)return Tt;Et=1;var r=g(),n=mn(),t=cr();return Tt=function(e,o,u){r?n.f(e,o,t(0,u)):e[o]=u}}function Kt(){if(Ft)return At;Ft=1;var r=function(){if(Ct)return xt;Ct=1;var r={};return r[en()("toStringTag")]="z",xt="[object z]"===String(r)}(),n=br(),t=lr(),e=en()("toStringTag"),o=Object,u="Arguments"===t(function(){return arguments}());return At=r?t:function(r){var i,f,c;return void 0===r?"Undefined":null===r?"Null":"string"==typeof(f=function(r,n){try{return r[n]}catch(r){}}(i=o(r),e))?f:u?t(i):"Object"===(c=t(i))&&n(i.callee)?"Arguments":c}}function Qt(){if(Mt)return Rt;Mt=1;var r=ar(),n=b(),t=br(),e=Kt(),o=hr(),u=In(),i=function(){},f=o("Reflect","construct"),c=/^\s*(?:class|function)\b/,a=r(c.exec),l=!c.test(i),s=function(r){if(!t(r))return!1;try{return f(i,[],r),!0}catch(r){return!1}},v=function(r){if(!t(r))return!1;switch(e(r)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return l||!!a(c,u(r))}catch(r){return!0}};return v.sham=!0,Rt=!f||n((function(){var r;return s(s.call)||!s(Object)||!s((function(){r=!0}))||r}))?v:s}function Vt(){if(Nt)return zt;Nt=1;var r=$t(),n=Qt(),t=gr(),e=en()("species"),o=Array;return zt=function(u){var i;return r(u)&&(i=u.constructor,(n(i)&&(i===o||r(i.prototype))||t(i)&&null===(i=i[e]))&&(i=void 0)),void 0===i?o:i}}function Xt(){if(It)return kt;It=1;var r=Vt();return kt=function(n,t){return new(r(n))(0===t?0:t)}}function Yt(){if(Dt)return Lt;Dt=1;var r=b(),n=en(),t=mr(),e=n("species");return Lt=function(n){return t>=51||!r((function(){var r=[];return(r.constructor={})[e]=function(){return{foo:1}},1!==r[n](Boolean).foo}))}}!function(){if(_t)return o;_t=1;var r=Wt(),n=b(),t=$t(),e=gr(),u=rn(),i=at(),f=Ht(),c=Jt(),a=Xt(),l=Yt(),s=en(),v=mr(),p=s("isConcatSpreadable"),y=v>=51||!n((function(){var r=[];return r[p]=!1,r.concat()[0]!==r})),g=function(r){if(!e(r))return!1;var n=r[p];return void 0!==n?!!n:t(r)};r({target:"Array",proto:!0,arity:1,forced:!y||!l("concat")},{concat:function(r){var n,t,e,o,l,s=u(this),v=a(s,0),p=0;for(n=-1,e=arguments.length;nv;)for(var b,g=c(arguments[v++]),h=p?s(o(g),p(g)):o(g),m=h.length,d=0;m>d;)b=h[d++],r&&!t(y,g,b)||(a[b]=g[b]);return a}:a,ne}();r({target:"Object",stat:!0,arity:2,forced:Object.assign!==n},{assign:n})}(),r.fn.bootstrapTable.locales["zh-CN"]=r.fn.bootstrapTable.locales.zh={formatCopyRows:function(){return"复制行"},formatPrint:function(){return"打印"},formatLoadingMessage:function(){return"正在努力地加载数据中,请稍候"},formatRecordsPerPage:function(r){return"每页显示 ".concat(r," 条记录")},formatShowingRows:function(r,n,t,e){return void 0!==e&&e>0&&e>t?"显示第 ".concat(r," 到第 ").concat(n," 条记录,总共 ").concat(t," 条记录(从 ").concat(e," 总记录中过滤)"):"显示第 ".concat(r," 到第 ").concat(n," 条记录,总共 ").concat(t," 条记录")},formatSRPaginationPreText:function(){return"上一页"},formatSRPaginationPageText:function(r){return"第".concat(r,"页")},formatSRPaginationNextText:function(){return"下一页"},formatDetailPagination:function(r){return"总共 ".concat(r," 条记录")},formatClearSearch:function(){return"清空过滤"},formatSearch:function(){return"搜索"},formatNoMatches:function(){return"没有找到匹配的记录"},formatPaginationSwitch:function(){return"隐藏/显示分页"},formatPaginationSwitchDown:function(){return"显示分页"},formatPaginationSwitchUp:function(){return"隐藏分页"},formatRefresh:function(){return"刷新"},formatToggleOn:function(){return"显示卡片视图"},formatToggleOff:function(){return"隐藏卡片视图"},formatColumns:function(){return"列"},formatColumnsToggleAll:function(){return"切换所有"},formatFullscreen:function(){return"全屏"},formatAllRows:function(){return"所有"},formatAutoRefresh:function(){return"自动刷新"},formatExport:function(){return"导出数据"},formatJumpTo:function(){return"跳转"},formatAdvancedSearch:function(){return"高级搜索"},formatAdvancedCloseButton:function(){return"关闭"},formatFilterControlSwitch:function(){return"隐藏/显示过滤控制"},formatFilterControlSwitchHide:function(){return"隐藏过滤控制"},formatFilterControlSwitchShow:function(){return"显示过滤控制"}},Object.assign(r.fn.bootstrapTable.defaults,r.fn.bootstrapTable.locales["zh-CN"])})); diff --git a/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/locale/bootstrap-table-zh-TW.min.js b/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/locale/bootstrap-table-zh-TW.min.js new file mode 100644 index 00000000..c7f6497d --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/bootstrap-table/locale/bootstrap-table-zh-TW.min.js @@ -0,0 +1,10 @@ +/** + * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) + * + * @version v1.24.1 + * @homepage https://bootstrap-table.com + * @author wenzhixin (http://wenzhixin.net.cn/) + * @license MIT + */ + +!function(r,n){"object"==typeof exports&&"undefined"!=typeof module?n(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],n):n((r="undefined"!=typeof globalThis?globalThis:r||self).jQuery)}(this,(function(r){"use strict";var n,t,e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},o={};function u(){if(t)return n;t=1;var r=function(r){return r&&r.Math===Math&&r};return n=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof e&&e)||r("object"==typeof n&&n)||function(){return this}()||Function("return this")()}var i,f,c,a,l,s,v,p,y={};function b(){return f?i:(f=1,i=function(r){try{return!!r()}catch(r){return!0}})}function g(){if(a)return c;a=1;var r=b();return c=!r((function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]}))}function m(){if(s)return l;s=1;var r=b();return l=!r((function(){var r=function(){}.bind();return"function"!=typeof r||r.hasOwnProperty("prototype")}))}function h(){if(p)return v;p=1;var r=m(),n=Function.prototype.call;return v=r?n.bind(n):function(){return n.apply(n,arguments)},v}var d,w,S,O,j,P,T,E,x,A,C,F,R,M,k,z,I,L,N,D,_,q,G,W,B,U,$,H,J,K,Q,V,X,Y,Z,rr,nr,tr,er,or,ur,ir={};function fr(){if(d)return ir;d=1;var r={}.propertyIsEnumerable,n=Object.getOwnPropertyDescriptor,t=n&&!r.call({1:2},1);return ir.f=t?function(r){var t=n(this,r);return!!t&&t.enumerable}:r,ir}function cr(){return S?w:(S=1,w=function(r,n){return{enumerable:!(1&r),configurable:!(2&r),writable:!(4&r),value:n}})}function ar(){if(j)return O;j=1;var r=m(),n=Function.prototype,t=n.call,e=r&&n.bind.bind(t,t);return O=r?e:function(r){return function(){return t.apply(r,arguments)}},O}function lr(){if(T)return P;T=1;var r=ar(),n=r({}.toString),t=r("".slice);return P=function(r){return t(n(r),8,-1)}}function sr(){if(x)return E;x=1;var r=ar(),n=b(),t=lr(),e=Object,o=r("".split);return E=n((function(){return!e("z").propertyIsEnumerable(0)}))?function(r){return"String"===t(r)?o(r,""):e(r)}:e}function vr(){return C?A:(C=1,A=function(r){return null==r})}function pr(){if(R)return F;R=1;var r=vr(),n=TypeError;return F=function(t){if(r(t))throw new n("Can't call method on "+t);return t}}function yr(){if(k)return M;k=1;var r=sr(),n=pr();return M=function(t){return r(n(t))}}function br(){if(I)return z;I=1;var r="object"==typeof document&&document.all;return z=void 0===r&&void 0!==r?function(n){return"function"==typeof n||n===r}:function(r){return"function"==typeof r}}function gr(){if(N)return L;N=1;var r=br();return L=function(n){return"object"==typeof n?null!==n:r(n)}}function mr(){if(_)return D;_=1;var r=u(),n=br();return D=function(t,e){return arguments.length<2?(o=r[t],n(o)?o:void 0):r[t]&&r[t][e];var o},D}function hr(){if($)return U;$=1;var r,n,t=u(),e=function(){if(B)return W;B=1;var r=u().navigator,n=r&&r.userAgent;return W=n?String(n):""}(),o=t.process,i=t.Deno,f=o&&o.versions||i&&i.version,c=f&&f.v8;return c&&(n=(r=c.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!n&&e&&(!(r=e.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=e.match(/Chrome\/(\d+)/))&&(n=+r[1]),U=n}function dr(){if(J)return H;J=1;var r=hr(),n=b(),t=u().String;return H=!!Object.getOwnPropertySymbols&&!n((function(){var n=Symbol("symbol detection");return!t(n)||!(Object(n)instanceof Symbol)||!Symbol.sham&&r&&r<41}))}function wr(){if(Q)return K;Q=1;var r=dr();return K=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator}function Sr(){if(X)return V;X=1;var r=mr(),n=br(),t=function(){if(G)return q;G=1;var r=ar();return q=r({}.isPrototypeOf)}(),e=wr(),o=Object;return V=e?function(r){return"symbol"==typeof r}:function(e){var u=r("Symbol");return n(u)&&t(u.prototype,o(e))}}function Or(){if(Z)return Y;Z=1;var r=String;return Y=function(n){try{return r(n)}catch(r){return"Object"}}}function jr(){if(nr)return rr;nr=1;var r=br(),n=Or(),t=TypeError;return rr=function(e){if(r(e))return e;throw new t(n(e)+" is not a function")}}function Pr(){if(er)return tr;er=1;var r=jr(),n=vr();return tr=function(t,e){var o=t[e];return n(o)?void 0:r(o)}}function Tr(){if(ur)return or;ur=1;var r=h(),n=br(),t=gr(),e=TypeError;return or=function(o,u){var i,f;if("string"===u&&n(i=o.toString)&&!t(f=r(i,o)))return f;if(n(i=o.valueOf)&&!t(f=r(i,o)))return f;if("string"!==u&&n(i=o.toString)&&!t(f=r(i,o)))return f;throw new e("Can't convert object to primitive value")}}var Er,xr,Ar,Cr,Fr,Rr,Mr,kr,zr,Ir,Lr,Nr,Dr,_r,qr,Gr,Wr,Br,Ur,$r,Hr,Jr,Kr,Qr,Vr={exports:{}};function Xr(){if(Cr)return Ar;Cr=1;var r=u(),n=Object.defineProperty;return Ar=function(t,e){try{n(r,t,{value:e,configurable:!0,writable:!0})}catch(n){r[t]=e}return e}}function Yr(){if(Fr)return Vr.exports;Fr=1;var r=xr?Er:(xr=1,Er=!1),n=u(),t=Xr(),e="__core-js_shared__",o=Vr.exports=n[e]||t(e,{});return(o.versions||(o.versions=[])).push({version:"3.39.0",mode:r?"pure":"global",copyright:"© 2014-2024 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE",source:"https://github.com/zloirock/core-js"}),Vr.exports}function Zr(){if(Mr)return Rr;Mr=1;var r=Yr();return Rr=function(n,t){return r[n]||(r[n]=t||{})}}function rn(){if(zr)return kr;zr=1;var r=pr(),n=Object;return kr=function(t){return n(r(t))}}function nn(){if(Lr)return Ir;Lr=1;var r=ar(),n=rn(),t=r({}.hasOwnProperty);return Ir=Object.hasOwn||function(r,e){return t(n(r),e)}}function tn(){if(Dr)return Nr;Dr=1;var r=ar(),n=0,t=Math.random(),e=r(1..toString);return Nr=function(r){return"Symbol("+(void 0===r?"":r)+")_"+e(++n+t,36)}}function en(){if(qr)return _r;qr=1;var r=u(),n=Zr(),t=nn(),e=tn(),o=dr(),i=wr(),f=r.Symbol,c=n("wks"),a=i?f.for||f:f&&f.withoutSetter||e;return _r=function(r){return t(c,r)||(c[r]=o&&t(f,r)?f[r]:a("Symbol."+r)),c[r]}}function on(){if(Wr)return Gr;Wr=1;var r=h(),n=gr(),t=Sr(),e=Pr(),o=Tr(),u=en(),i=TypeError,f=u("toPrimitive");return Gr=function(u,c){if(!n(u)||t(u))return u;var a,l=e(u,f);if(l){if(void 0===c&&(c="default"),a=r(l,u,c),!n(a)||t(a))return a;throw new i("Can't convert object to primitive value")}return void 0===c&&(c="number"),o(u,c)}}function un(){if(Ur)return Br;Ur=1;var r=on(),n=Sr();return Br=function(t){var e=r(t,"string");return n(e)?e:e+""}}function fn(){if(Kr)return Jr;Kr=1;var r=g(),n=b(),t=function(){if(Hr)return $r;Hr=1;var r=u(),n=gr(),t=r.document,e=n(t)&&n(t.createElement);return $r=function(r){return e?t.createElement(r):{}}}();return Jr=!r&&!n((function(){return 7!==Object.defineProperty(t("div"),"a",{get:function(){return 7}}).a}))}function cn(){if(Qr)return y;Qr=1;var r=g(),n=h(),t=fr(),e=cr(),o=yr(),u=un(),i=nn(),f=fn(),c=Object.getOwnPropertyDescriptor;return y.f=r?c:function(r,a){if(r=o(r),a=u(a),f)try{return c(r,a)}catch(r){}if(i(r,a))return e(!n(t.f,r,a),r[a])},y}var an,ln,sn,vn,pn,yn,bn,gn={};function mn(){if(vn)return sn;vn=1;var r=gr(),n=String,t=TypeError;return sn=function(e){if(r(e))return e;throw new t(n(e)+" is not an object")}}function hn(){if(pn)return gn;pn=1;var r=g(),n=fn(),t=function(){if(ln)return an;ln=1;var r=g(),n=b();return an=r&&n((function(){return 42!==Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))}(),e=mn(),o=un(),u=TypeError,i=Object.defineProperty,f=Object.getOwnPropertyDescriptor,c="enumerable",a="configurable",l="writable";return gn.f=r?t?function(r,n,t){if(e(r),n=o(n),e(t),"function"==typeof r&&"prototype"===n&&"value"in t&&l in t&&!t[l]){var u=f(r,n);u&&u[l]&&(r[n]=t.value,t={configurable:a in t?t[a]:u[a],enumerable:c in t?t[c]:u[c],writable:!1})}return i(r,n,t)}:i:function(r,t,f){if(e(r),t=o(t),e(f),n)try{return i(r,t,f)}catch(r){}if("get"in f||"set"in f)throw new u("Accessors not supported");return"value"in f&&(r[t]=f.value),r},gn}function dn(){if(bn)return yn;bn=1;var r=g(),n=hn(),t=cr();return yn=r?function(r,e,o){return n.f(r,e,t(1,o))}:function(r,n,t){return r[n]=t,r}}var wn,Sn,On,jn,Pn,Tn,En,xn,An,Cn,Fn,Rn,Mn,kn,zn,In={exports:{}};function Ln(){if(jn)return On;jn=1;var r=ar(),n=br(),t=Yr(),e=r(Function.toString);return n(t.inspectSource)||(t.inspectSource=function(r){return e(r)}),On=t.inspectSource}function Nn(){if(xn)return En;xn=1;var r=Zr(),n=tn(),t=r("keys");return En=function(r){return t[r]||(t[r]=n(r))}}function Dn(){return Cn?An:(Cn=1,An={})}function _n(){if(Rn)return Fn;Rn=1;var r,n,t,e=function(){if(Tn)return Pn;Tn=1;var r=u(),n=br(),t=r.WeakMap;return Pn=n(t)&&/native code/.test(String(t))}(),o=u(),i=gr(),f=dn(),c=nn(),a=Yr(),l=Nn(),s=Dn(),v="Object already initialized",p=o.TypeError,y=o.WeakMap;if(e||a.state){var b=a.state||(a.state=new y);b.get=b.get,b.has=b.has,b.set=b.set,r=function(r,n){if(b.has(r))throw new p(v);return n.facade=r,b.set(r,n),n},n=function(r){return b.get(r)||{}},t=function(r){return b.has(r)}}else{var g=l("state");s[g]=!0,r=function(r,n){if(c(r,g))throw new p(v);return n.facade=r,f(r,g,n),n},n=function(r){return c(r,g)?r[g]:{}},t=function(r){return c(r,g)}}return Fn={set:r,get:n,has:t,enforce:function(e){return t(e)?n(e):r(e,{})},getterFor:function(r){return function(t){var e;if(!i(t)||(e=n(t)).type!==r)throw new p("Incompatible receiver, "+r+" required");return e}}}}function qn(){if(Mn)return In.exports;Mn=1;var r=ar(),n=b(),t=br(),e=nn(),o=g(),u=function(){if(Sn)return wn;Sn=1;var r=g(),n=nn(),t=Function.prototype,e=r&&Object.getOwnPropertyDescriptor,o=n(t,"name"),u=o&&"something"===function(){}.name,i=o&&(!r||r&&e(t,"name").configurable);return wn={EXISTS:o,PROPER:u,CONFIGURABLE:i}}().CONFIGURABLE,i=Ln(),f=_n(),c=f.enforce,a=f.get,l=String,s=Object.defineProperty,v=r("".slice),p=r("".replace),y=r([].join),m=o&&!n((function(){return 8!==s((function(){}),"length",{value:8}).length})),h=String(String).split("String"),d=In.exports=function(r,n,t){"Symbol("===v(l(n),0,7)&&(n="["+p(l(n),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),t&&t.getter&&(n="get "+n),t&&t.setter&&(n="set "+n),(!e(r,"name")||u&&r.name!==n)&&(o?s(r,"name",{value:n,configurable:!0}):r.name=n),m&&t&&e(t,"arity")&&r.length!==t.arity&&s(r,"length",{value:t.arity});try{t&&e(t,"constructor")&&t.constructor?o&&s(r,"prototype",{writable:!1}):r.prototype&&(r.prototype=void 0)}catch(r){}var i=c(r);return e(i,"source")||(i.source=y(h,"string"==typeof n?n:"")),r};return Function.prototype.toString=d((function(){return t(this)&&a(this).source||i(this)}),"toString"),In.exports}function Gn(){if(zn)return kn;zn=1;var r=br(),n=hn(),t=qn(),e=Xr();return kn=function(o,u,i,f){f||(f={});var c=f.enumerable,a=void 0!==f.name?f.name:u;if(r(i)&&t(i,a,f),f.global)c?o[u]=i:e(u,i);else{try{f.unsafe?o[u]&&(c=!0):delete o[u]}catch(r){}c?o[u]=i:n.f(o,u,{value:i,enumerable:!1,configurable:!f.nonConfigurable,writable:!f.nonWritable})}return o}}var Wn,Bn,Un,$n,Hn,Jn,Kn,Qn,Vn,Xn,Yn,Zn,rt,nt,tt,et,ot,ut={};function it(){if($n)return Un;$n=1;var r=function(){if(Bn)return Wn;Bn=1;var r=Math.ceil,n=Math.floor;return Wn=Math.trunc||function(t){var e=+t;return(e>0?n:r)(e)}}();return Un=function(n){var t=+n;return t!=t||0===t?0:r(t)}}function ft(){if(Jn)return Hn;Jn=1;var r=it(),n=Math.max,t=Math.min;return Hn=function(e,o){var u=r(e);return u<0?n(u+o,0):t(u,o)}}function ct(){if(Qn)return Kn;Qn=1;var r=it(),n=Math.min;return Kn=function(t){var e=r(t);return e>0?n(e,9007199254740991):0}}function at(){if(Xn)return Vn;Xn=1;var r=ct();return Vn=function(n){return r(n.length)}}function lt(){if(nt)return rt;nt=1;var r=ar(),n=nn(),t=yr(),e=function(){if(Zn)return Yn;Zn=1;var r=yr(),n=ft(),t=at(),e=function(e){return function(o,u,i){var f=r(o),c=t(f);if(0===c)return!e&&-1;var a,l=n(i,c);if(e&&u!=u){for(;c>l;)if((a=f[l++])!=a)return!0}else for(;c>l;l++)if((e||l in f)&&f[l]===u)return e||l||0;return!e&&-1}};return Yn={includes:e(!0),indexOf:e(!1)}}().indexOf,o=Dn(),u=r([].push);return rt=function(r,i){var f,c=t(r),a=0,l=[];for(f in c)!n(o,f)&&n(c,f)&&u(l,f);for(;i.length>a;)n(c,f=i[a++])&&(~e(l,f)||u(l,f));return l}}function st(){return et?tt:(et=1,tt=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"])}var vt,pt,yt,bt,gt,mt,ht,dt,wt,St,Ot,jt,Pt,Tt,Et,xt,At,Ct,Ft,Rt,Mt,kt,zt,It,Lt,Nt,Dt,_t,qt={};function Gt(){return vt||(vt=1,qt.f=Object.getOwnPropertySymbols),qt}function Wt(){if(yt)return pt;yt=1;var r=mr(),n=ar(),t=function(){if(ot)return ut;ot=1;var r=lt(),n=st().concat("length","prototype");return ut.f=Object.getOwnPropertyNames||function(t){return r(t,n)},ut}(),e=Gt(),o=mn(),u=n([].concat);return pt=r("Reflect","ownKeys")||function(r){var n=t.f(o(r)),i=e.f;return i?u(n,i(r)):n}}function Bt(){if(gt)return bt;gt=1;var r=nn(),n=Wt(),t=cn(),e=hn();return bt=function(o,u,i){for(var f=n(u),c=e.f,a=t.f,l=0;l9007199254740991)throw r("Maximum allowed index exceeded");return n}}function Jt(){if(Et)return Tt;Et=1;var r=g(),n=hn(),t=cr();return Tt=function(e,o,u){r?n.f(e,o,t(0,u)):e[o]=u}}function Kt(){if(Ft)return Ct;Ft=1;var r=function(){if(At)return xt;At=1;var r={};return r[en()("toStringTag")]="z",xt="[object z]"===String(r)}(),n=br(),t=lr(),e=en()("toStringTag"),o=Object,u="Arguments"===t(function(){return arguments}());return Ct=r?t:function(r){var i,f,c;return void 0===r?"Undefined":null===r?"Null":"string"==typeof(f=function(r,n){try{return r[n]}catch(r){}}(i=o(r),e))?f:u?t(i):"Object"===(c=t(i))&&n(i.callee)?"Arguments":c}}function Qt(){if(Mt)return Rt;Mt=1;var r=ar(),n=b(),t=br(),e=Kt(),o=mr(),u=Ln(),i=function(){},f=o("Reflect","construct"),c=/^\s*(?:class|function)\b/,a=r(c.exec),l=!c.test(i),s=function(r){if(!t(r))return!1;try{return f(i,[],r),!0}catch(r){return!1}},v=function(r){if(!t(r))return!1;switch(e(r)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return l||!!a(c,u(r))}catch(r){return!0}};return v.sham=!0,Rt=!f||n((function(){var r;return s(s.call)||!s(Object)||!s((function(){r=!0}))||r}))?v:s}function Vt(){if(zt)return kt;zt=1;var r=$t(),n=Qt(),t=gr(),e=en()("species"),o=Array;return kt=function(u){var i;return r(u)&&(i=u.constructor,(n(i)&&(i===o||r(i.prototype))||t(i)&&null===(i=i[e]))&&(i=void 0)),void 0===i?o:i}}function Xt(){if(Lt)return It;Lt=1;var r=Vt();return It=function(n,t){return new(r(n))(0===t?0:t)}}function Yt(){if(Dt)return Nt;Dt=1;var r=b(),n=en(),t=hr(),e=n("species");return Nt=function(n){return t>=51||!r((function(){var r=[];return(r.constructor={})[e]=function(){return{foo:1}},1!==r[n](Boolean).foo}))}}!function(){if(_t)return o;_t=1;var r=Ut(),n=b(),t=$t(),e=gr(),u=rn(),i=at(),f=Ht(),c=Jt(),a=Xt(),l=Yt(),s=en(),v=hr(),p=s("isConcatSpreadable"),y=v>=51||!n((function(){var r=[];return r[p]=!1,r.concat()[0]!==r})),g=function(r){if(!e(r))return!1;var n=r[p];return void 0!==n?!!n:t(r)};r({target:"Array",proto:!0,arity:1,forced:!y||!l("concat")},{concat:function(r){var n,t,e,o,l,s=u(this),v=a(s,0),p=0;for(n=-1,e=arguments.length;nv;)for(var b,g=c(arguments[v++]),m=p?s(o(g),p(g)):o(g),h=m.length,d=0;h>d;)b=m[d++],r&&!t(y,g,b)||(a[b]=g[b]);return a}:a,ne}();r({target:"Object",stat:!0,arity:2,forced:Object.assign!==n},{assign:n})}(),r.fn.bootstrapTable.locales["zh-TW"]={formatCopyRows:function(){return"複製行"},formatPrint:function(){return"列印"},formatLoadingMessage:function(){return"正在努力地載入資料,請稍候"},formatRecordsPerPage:function(r){return"每頁顯示 ".concat(r," 項記錄")},formatShowingRows:function(r,n,t,e){return void 0!==e&&e>0&&e>t?"顯示第 ".concat(r," 到第 ").concat(n," 項記錄,總共 ").concat(t," 項記錄(從 ").concat(e," 總記錄中過濾)"):"顯示第 ".concat(r," 到第 ").concat(n," 項記錄,總共 ").concat(t," 項記錄")},formatSRPaginationPreText:function(){return"上一頁"},formatSRPaginationPageText:function(r){return"第".concat(r,"頁")},formatSRPaginationNextText:function(){return"下一頁"},formatDetailPagination:function(r){return"總共 ".concat(r," 項記錄")},formatClearSearch:function(){return"清空過濾"},formatSearch:function(){return"搜尋"},formatNoMatches:function(){return"沒有找到符合的結果"},formatPaginationSwitch:function(){return"隱藏/顯示分頁"},formatPaginationSwitchDown:function(){return"顯示分頁"},formatPaginationSwitchUp:function(){return"隱藏分頁"},formatRefresh:function(){return"重新整理"},formatToggleOn:function(){return"顯示卡片視圖"},formatToggleOff:function(){return"隱藏卡片視圖"},formatColumns:function(){return"列"},formatColumnsToggleAll:function(){return"切換所有"},formatFullscreen:function(){return"全屏"},formatAllRows:function(){return"所有"},formatAutoRefresh:function(){return"自動刷新"},formatExport:function(){return"導出數據"},formatJumpTo:function(){return"跳轉"},formatAdvancedSearch:function(){return"高級搜尋"},formatAdvancedCloseButton:function(){return"關閉"},formatFilterControlSwitch:function(){return"隱藏/顯示過濾控制"},formatFilterControlSwitchHide:function(){return"隱藏過濾控制"},formatFilterControlSwitchShow:function(){return"顯示過濾控制"}},Object.assign(r.fn.bootstrapTable.defaults,r.fn.bootstrapTable.locales["zh-TW"])})); diff --git a/xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen.js b/xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen.js index 22393722..2a7de4fd 100755 --- a/xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen.js +++ b/xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen.js @@ -258,24 +258,27 @@ var weekly3 = $("
    ",{"class":"line"}); $("",{type : "radio", value : "3", name : "week"}).appendTo(weekly3); - $(weekly3).append("周期 从星期"); + $(weekly3).append("周期 每周第"); $("",{type : "text", id : "weekStart_0", value : "1", style:"width:35px; height:20px; text-align: center; margin: 0 3px;"}).appendTo(weekly3); - $(weekly3).append("-"); + $(weekly3).append("天-第"); $("",{type : "text", id : "weekEnd_0", value : "2", style:"width:35px; height:20px; text-align: center; margin: 0 3px;"}).appendTo(weekly3); + $(weekly3).append("天"); $(weekly3).appendTo(weeklyTab); var weekly4 = $("
    ",{"class":"line"}); $("",{type : "radio", value : "4", name : "week"}).appendTo(weekly4); - $(weekly4).append("第"); + $(weekly4).append("从第"); $("",{type : "text", id : "weekStart_1", value : "1", style:"width:35px; height:20px; text-align: center; margin: 0 3px;"}).appendTo(weekly4); - $(weekly4).append("周的星期"); + $(weekly4).append("天开始,间隔"); $("",{type : "text", id : "weekEnd_1", value : "1", style:"width:35px; height:20px; text-align: center; margin: 0 3px;"}).appendTo(weekly4); + $(weekly4).append("天执行一次"); $(weekly4).appendTo(weeklyTab); var weekly5 = $("
    ",{"class":"line"}); $("",{type : "radio", value : "5", name : "week"}).appendTo(weekly5); - $(weekly5).append("本月最后一个星期"); + $(weekly5).append("本月最后一周的第"); $("",{type : "text", id : "weekStart_2", value : "1", style:"width:35px; height:20px; text-align: center; margin: 0 3px;"}).appendTo(weekly5); + $(weekly5).append("天"); $(weekly5).appendTo(weeklyTab); var weekly6 = $("
    ",{"class":"line"}); @@ -283,7 +286,7 @@ $(weekly6).append("指定"); $(weekly6).appendTo(weeklyTab); - $(weeklyTab).append('
    1234567
    '); + $(weeklyTab).append('
    周日周一周二周三周四周五周六
    '); $("",{type : "hidden", id : "weekHidden"}).appendTo(weeklyTab); $(weeklyTab).appendTo(tabContent); @@ -647,7 +650,7 @@ dataType : "json", success : function(data){ if (data.code === 200) { - $('#runTime').val(data.content.join("\n")); + $('#runTime').val(data.data.join("\n")); } else { $('#runTime').val(data.msg); } diff --git a/xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen_en.js b/xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen_en.js index cbf84ee3..3c7fa394 100755 --- a/xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen_en.js +++ b/xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen_en.js @@ -12,7 +12,7 @@ var cronContainer = $("
    ", { id: "CronContainer", style: "display:none;width:300px;height:300px;" }); var mainDiv = $("
    ", { id: "CronGenMainDiv", style: "width:410px;height:420px;" }); var topMenu = $("
      ", { "class": "nav nav-tabs", id: "CronGenTabs" }); - $('
    • ', { 'class': 'active' }).html($('')).appendTo(topMenu); + $('
    • ', { 'class': 'active' }).html($('Second')).appendTo(topMenu); $('
    • ').html($('Minute')).appendTo(topMenu); $('
    • ').html($('Hour')).appendTo(topMenu); $('
    • ').html($('Day')).appendTo(topMenu); @@ -268,7 +268,7 @@ $("",{type : "radio", value : "4", name : "week"}).appendTo(weekly4); $(weekly4).append("The"); $("",{type : "text", id : "weekStart_1", value : "1", style:"width:35px; height:20px; text-align: center; margin: 0 3px;"}).appendTo(weekly4); - $(weekly4).append("th week, and day "); + $(weekly4).append("th week, once every "); $("",{type : "text", id : "weekEnd_1", value : "1", style:"width:35px; height:20px; text-align: center; margin: 0 3px;"}).appendTo(weekly4); $(weekly4).appendTo(weeklyTab); @@ -283,7 +283,7 @@ $(weekly6).append("specify"); $(weekly6).appendTo(weeklyTab); - $(weeklyTab).append('
      1234567
      '); + $(weeklyTab).append('
      SUNMONTUEWEDTHUFRISAT
      '); $("",{type : "hidden", id : "weekHidden"}).appendTo(weeklyTab); $(weeklyTab).appendTo(tabContent); @@ -647,7 +647,7 @@ dataType : "json", success : function(data){ if (data.code === 200) { - $('#runTime').val(data.content.join("\n")); + $('#runTime').val(data.data.join("\n")); } else { $('#runTime').val(data.msg); } diff --git a/xxl-job-admin/src/main/resources/static/plugins/fullscreen/jquery.fullscreen.js b/xxl-job-admin/src/main/resources/static/plugins/fullscreen/jquery.fullscreen.js new file mode 100644 index 00000000..66b44318 --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/fullscreen/jquery.fullscreen.js @@ -0,0 +1,180 @@ +/** + * modify base jQuery FullScreen, support IE + */ +(function(jQuery) { + + /** + * Sets or gets the fullscreen state. + * + * @param {boolean=} state + * True to enable fullscreen mode, false to disable it. If not + * specified then the current fullscreen state is returned. + * @return {boolean|Element|jQuery|null} + * When querying the fullscreen state then the current fullscreen + * element (or true if browser doesn't support it) is returned + * when browser is currently in full screen mode. False is returned + * if browser is not in full screen mode. Null is returned if + * browser doesn't support fullscreen mode at all. When setting + * the fullscreen state then the current jQuery selection is + * returned for chaining. + * @this {jQuery} + */ + function fullScreen(state) + { + var e, func, doc; + + // Do nothing when nothing was selected + if (!this.length) return this; + + // We only use the first selected element because it doesn't make sense + // to fullscreen multiple elements. + e = (/** @type {Element} */ this[0]); + + // Find the real element and the document (Depends on whether the + // document itself or a HTML element was selected) + if (e.ownerDocument) + { + doc = e.ownerDocument; + } + else + { + doc = e; + e = doc.documentElement; + } + + // When no state was specified then return the current state. + if (state == null) + { + // When fullscreen mode is not supported then return null + if (!((/** @type {?Function} */ doc["exitFullscreen"]) + || (/** @type {?Function} */ doc["webkitExitFullscreen"]) + || (/** @type {?Function} */ doc["webkitCancelFullScreen"]) + || (/** @type {?Function} */ doc["msExitFullscreen"]) + || (/** @type {?Function} */ doc["mozCancelFullScreen"]))) + { + return null; + } + + // Check fullscreen state + state = !!doc["fullscreenElement"] + || !!doc["msFullscreenElement"] + || !!doc["webkitIsFullScreen"] + || !!doc["mozFullScreen"]; + if (!state) return state; + + // Return current fullscreen element or "true" if browser doesn't + // support this + return (/** @type {?Element} */ doc["fullscreenElement"]) + || (/** @type {?Element} */ doc["webkitFullscreenElement"]) + || (/** @type {?Element} */ doc["webkitCurrentFullScreenElement"]) + || (/** @type {?Element} */ doc["msFullscreenElement"]) + || (/** @type {?Element} */ doc["mozFullScreenElement"]) + || state; + } + + // When state was specified then enter or exit fullscreen mode. + if (state) + { + // Enter fullscreen + func = (/** @type {?Function} */ e["requestFullscreen"]) + || (/** @type {?Function} */ e["webkitRequestFullscreen"]) + || (/** @type {?Function} */ e["webkitRequestFullScreen"]) + || (/** @type {?Function} */ e["msRequestFullscreen"]) + || (/** @type {?Function} */ e["mozRequestFullScreen"]); + if (func) + { + func.call(e); + } + return this; + } + else + { + // Exit fullscreen + func = (/** @type {?Function} */ doc["exitFullscreen"]) + || (/** @type {?Function} */ doc["webkitExitFullscreen"]) + || (/** @type {?Function} */ doc["webkitCancelFullScreen"]) + || (/** @type {?Function} */ doc["msExitFullscreen"]) + || (/** @type {?Function} */ doc["mozCancelFullScreen"]); + if (func) func.call(doc); + return this; + } + } + + /** + * Toggles the fullscreen mode. + * + * @return {!jQuery} + * The jQuery selection for chaining. + * @this {jQuery} + */ + function toggleFullScreen() + { + return (/** @type {!jQuery} */ fullScreen.call(this, + !fullScreen.call(this))); + } + + /** + * Handles the browser-specific fullscreenchange event and triggers + * a jquery event for it. + * + * @param {?Event} event + * The fullscreenchange event. + */ + function fullScreenChangeHandler(event) + { + jQuery(document).trigger(new jQuery.Event("fullscreenchange")); + } + + /** + * Handles the browser-specific fullscreenerror event and triggers + * a jquery event for it. + * + * @param {?Event} event + * The fullscreenerror event. + */ + function fullScreenErrorHandler(event) + { + jQuery(document).trigger(new jQuery.Event("fullscreenerror")); + } + + /** + * Installs the fullscreenchange event handler. + */ + function installFullScreenHandlers() + { + var e, change, error; + + // Determine event name + e = document; + if (e["webkitCancelFullScreen"]) + { + change = "webkitfullscreenchange"; + error = "webkitfullscreenerror"; + } + else if (e["msExitFullscreen"]) + { + change = "MSFullscreenChange"; + error = "MSFullscreenError"; + } + else if (e["mozCancelFullScreen"]) + { + change = "mozfullscreenchange"; + error = "mozfullscreenerror"; + } + else + { + change = "fullscreenchange"; + error = "fullscreenerror"; + } + + // Install the event handlers + jQuery(document).bind(change, fullScreenChangeHandler); + jQuery(document).bind(error, fullScreenErrorHandler); + } + + jQuery.fn["fullScreen"] = fullScreen; + jQuery.fn["toggleFullScreen"] = toggleFullScreen; + installFullScreenHandlers(); + + })(jQuery); + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/img/collapse.png b/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/img/collapse.png new file mode 100644 index 00000000..76577a57 Binary files /dev/null and b/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/img/collapse.png differ diff --git a/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/img/expand.png b/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/img/expand.png new file mode 100644 index 00000000..cfb42a45 Binary files /dev/null and b/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/img/expand.png differ diff --git a/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/jquery.treegrid.css b/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/jquery.treegrid.css new file mode 100644 index 00000000..e8c4ca12 --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/jquery.treegrid.css @@ -0,0 +1,6 @@ +.treegrid-indent {width:16px; height: 16px; display: inline-block; position: relative;} + +.treegrid-expander {width:16px; height: 16px; display: inline-block; position: relative; cursor: pointer;} + +.treegrid-expander-expanded{background-image: url(./img/collapse.png); } +.treegrid-expander-collapsed{background-image: url(./img/expand.png);} diff --git a/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/jquery.treegrid.min.js b/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/jquery.treegrid.min.js new file mode 100644 index 00000000..7b7566f3 --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/jquery-treegrid/jquery.treegrid.min.js @@ -0,0 +1,2 @@ +/*! jquery-treegrid 0.3.0 */ +!function(a){var b={initTree:function(b){var c=a.extend({},this.treegrid.defaults,b);return this.each(function(){var b=a(this);b.treegrid("setTreeContainer",a(this)),b.treegrid("setSettings",c),c.getRootNodes.apply(this,[a(this)]).treegrid("initNode",c),b.treegrid("getRootNodes").treegrid("render")})},initNode:function(b){return this.each(function(){var c=a(this);c.treegrid("setTreeContainer",b.getTreeGridContainer.apply(this)),c.treegrid("getChildNodes").treegrid("initNode",b),c.treegrid("initExpander").treegrid("initIndent").treegrid("initEvents").treegrid("initState").treegrid("initChangeEvent").treegrid("initSettingsEvents")})},initChangeEvent:function(){var b=a(this);return b.on("change",function(){var b=a(this);b.treegrid("render"),b.treegrid("getSetting","saveState")&&b.treegrid("saveState")}),b},initEvents:function(){var b=a(this);return b.on("collapse",function(){var b=a(this);b.removeClass("treegrid-expanded"),b.addClass("treegrid-collapsed")}),b.on("expand",function(){var b=a(this);b.removeClass("treegrid-collapsed"),b.addClass("treegrid-expanded")}),b},initSettingsEvents:function(){var b=a(this);return b.on("change",function(){var b=a(this);"function"==typeof b.treegrid("getSetting","onChange")&&b.treegrid("getSetting","onChange").apply(b)}),b.on("collapse",function(){var b=a(this);"function"==typeof b.treegrid("getSetting","onCollapse")&&b.treegrid("getSetting","onCollapse").apply(b)}),b.on("expand",function(){var b=a(this);"function"==typeof b.treegrid("getSetting","onExpand")&&b.treegrid("getSetting","onExpand").apply(b)}),b},initExpander:function(){var b=a(this),c=b.find("td").get(b.treegrid("getSetting","treeColumn")),d=b.treegrid("getSetting","expanderTemplate"),e=b.treegrid("getSetting","getExpander").apply(this);return e&&e.remove(),a(d).prependTo(c).click(function(){a(a(this).closest("tr")).treegrid("toggle")}),b},initIndent:function(){var b=a(this);b.find(".treegrid-indent").remove();for(var c=b.treegrid("getSetting","indentTemplate"),d=b.find(".treegrid-expander"),e=b.treegrid("getDepth"),f=0;e>f;f++)a(c).insertBefore(d);return b},initState:function(){var b=a(this);return b.treegrid(b.treegrid("getSetting","saveState")&&!b.treegrid("isFirstInit")?"restoreState":"expanded"===b.treegrid("getSetting","initialState")?"expand":"collapse"),b},isFirstInit:function(){var b=a(this).treegrid("getTreeContainer");return void 0===b.data("first_init")&&b.data("first_init",void 0===a.cookie(b.treegrid("getSetting","saveStateName"))),b.data("first_init")},saveState:function(){var b=a(this);if("cookie"===b.treegrid("getSetting","saveStateMethod")){var c=a.cookie(b.treegrid("getSetting","saveStateName"))||"",d=""===c?[]:c.split(","),e=b.treegrid("getNodeId");b.treegrid("isExpanded")?-1===a.inArray(e,d)&&d.push(e):b.treegrid("isCollapsed")&&-1!==a.inArray(e,d)&&d.splice(a.inArray(e,d),1),a.cookie(b.treegrid("getSetting","saveStateName"),d.join(","))}return b},restoreState:function(){var b=a(this);if("cookie"===b.treegrid("getSetting","saveStateMethod")){var c=a.cookie(b.treegrid("getSetting","saveStateName")).split(",");b.treegrid(-1!==a.inArray(b.treegrid("getNodeId"),c)?"expand":"collapse")}return b},getSetting:function(b){return a(this).treegrid("getTreeContainer")?a(this).treegrid("getTreeContainer").data("settings")[b]:null},setSettings:function(b){a(this).treegrid("getTreeContainer").data("settings",b)},getTreeContainer:function(){return a(this).data("treegrid")},setTreeContainer:function(b){return a(this).data("treegrid",b)},getRootNodes:function(){return a(this).treegrid("getSetting","getRootNodes").apply(this,[a(this).treegrid("getTreeContainer")])},getAllNodes:function(){return a(this).treegrid("getSetting","getAllNodes").apply(this,[a(this).treegrid("getTreeContainer")])},isNode:function(){return null!==a(this).treegrid("getNodeId")},getNodeId:function(){return null===a(this).treegrid("getSetting","getNodeId")?null:a(this).treegrid("getSetting","getNodeId").apply(this)},getParentNodeId:function(){return a(this).treegrid("getSetting","getParentNodeId").apply(this)},getParentNode:function(){return null===a(this).treegrid("getParentNodeId")?null:a(this).treegrid("getSetting","getNodeById").apply(this,[a(this).treegrid("getParentNodeId"),a(this).treegrid("getTreeContainer")])},getChildNodes:function(){return a(this).treegrid("getSetting","getChildNodes").apply(this,[a(this).treegrid("getNodeId"),a(this).treegrid("getTreeContainer")])},getDepth:function(){return null===a(this).treegrid("getParentNode")?0:a(this).treegrid("getParentNode").treegrid("getDepth")+1},isRoot:function(){return 0===a(this).treegrid("getDepth")},isLeaf:function(){return 0===a(this).treegrid("getChildNodes").length},isLast:function(){if(a(this).treegrid("isNode")){var b=a(this).treegrid("getParentNode");if(null===b){if(a(this).treegrid("getNodeId")===a(this).treegrid("getRootNodes").last().treegrid("getNodeId"))return!0}else if(a(this).treegrid("getNodeId")===b.treegrid("getChildNodes").last().treegrid("getNodeId"))return!0}return!1},isFirst:function(){if(a(this).treegrid("isNode")){var b=a(this).treegrid("getParentNode");if(null===b){if(a(this).treegrid("getNodeId")===a(this).treegrid("getRootNodes").first().treegrid("getNodeId"))return!0}else if(a(this).treegrid("getNodeId")===b.treegrid("getChildNodes").first().treegrid("getNodeId"))return!0}return!1},isExpanded:function(){return a(this).hasClass("treegrid-expanded")},isCollapsed:function(){return a(this).hasClass("treegrid-collapsed")},isOneOfParentsCollapsed:function(){var b=a(this);return b.treegrid("isRoot")?!1:b.treegrid("getParentNode").treegrid("isCollapsed")?!0:b.treegrid("getParentNode").treegrid("isOneOfParentsCollapsed")},expand:function(){return this.treegrid("isLeaf")||this.treegrid("isExpanded")?this:(this.trigger("expand"),this.trigger("change"),this)},expandAll:function(){var b=a(this);return b.treegrid("getRootNodes").treegrid("expandRecursive"),b},expandRecursive:function(){return a(this).each(function(){var b=a(this);b.treegrid("expand"),b.treegrid("isLeaf")||b.treegrid("getChildNodes").treegrid("expandRecursive")})},collapse:function(){return a(this).each(function(){var b=a(this);b.treegrid("isLeaf")||b.treegrid("isCollapsed")||(b.trigger("collapse"),b.trigger("change"))})},collapseAll:function(){var b=a(this);return b.treegrid("getRootNodes").treegrid("collapseRecursive"),b},collapseRecursive:function(){return a(this).each(function(){var b=a(this);b.treegrid("collapse"),b.treegrid("isLeaf")||b.treegrid("getChildNodes").treegrid("collapseRecursive")})},toggle:function(){var b=a(this);return b.treegrid(b.treegrid("isExpanded")?"collapse":"expand"),b},render:function(){return a(this).each(function(){var b=a(this);b.treegrid("isOneOfParentsCollapsed")?b.hide():b.show(),b.treegrid("isLeaf")||(b.treegrid("renderExpander"),b.treegrid("getChildNodes").treegrid("render"))})},renderExpander:function(){return a(this).each(function(){var b=a(this),c=b.treegrid("getSetting","getExpander").apply(this);c?b.treegrid("isCollapsed")?(c.removeClass(b.treegrid("getSetting","expanderExpandedClass")),c.addClass(b.treegrid("getSetting","expanderCollapsedClass"))):(c.removeClass(b.treegrid("getSetting","expanderCollapsedClass")),c.addClass(b.treegrid("getSetting","expanderExpandedClass"))):(b.treegrid("initExpander"),b.treegrid("renderExpander"))})}};a.fn.treegrid=function(c){return b[c]?b[c].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof c&&c?void a.error("Method with name "+c+" does not exists for jQuery.treegrid"):b.initTree.apply(this,arguments)},a.fn.treegrid.defaults={initialState:"expanded",saveState:!1,saveStateMethod:"cookie",saveStateName:"tree-grid-state",expanderTemplate:'',indentTemplate:'',expanderExpandedClass:"treegrid-expander-expanded",expanderCollapsedClass:"treegrid-expander-collapsed",treeColumn:0,getExpander:function(){return a(this).find(".treegrid-expander")},getNodeId:function(){var b=/treegrid-([A-Za-z0-9_-]+)/;return b.test(a(this).attr("class"))?b.exec(a(this).attr("class"))[1]:null},getParentNodeId:function(){var b=/treegrid-parent-([A-Za-z0-9_-]+)/;return b.test(a(this).attr("class"))?b.exec(a(this).attr("class"))[1]:null},getNodeById:function(a,b){var c="treegrid-"+a;return b.find("tr."+c)},getChildNodes:function(a,b){var c="treegrid-parent-"+a;return b.find("tr."+c)},getTreeGridContainer:function(){return a(this).closest("table")},getRootNodes:function(b){var c=a.grep(b.find("tr"),function(b){var c=a(b).attr("class"),d=/treegrid-([A-Za-z0-9_-]+)/,e=/treegrid-parent-([A-Za-z0-9_-]+)/;return d.test(c)&&!e.test(c)});return a(c)},getAllNodes:function(b){var c=a.grep(b.find("tr"),function(b){var c=a(b).attr("class"),d=/treegrid-([A-Za-z0-9_-]+)/;return d.test(c)});return a(c)},onCollapse:null,onExpand:null,onChange:null}}(jQuery); \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/static/plugins/jquery/jquery.cookie.js b/xxl-job-admin/src/main/resources/static/plugins/jquery/jquery.cookie.js deleted file mode 100644 index c7f3a59b..00000000 --- a/xxl-job-admin/src/main/resources/static/plugins/jquery/jquery.cookie.js +++ /dev/null @@ -1,117 +0,0 @@ -/*! - * jQuery Cookie Plugin v1.4.1 - * https://github.com/carhartl/jquery-cookie - * - * Copyright 2013 Klaus Hartl - * Released under the MIT license - */ -(function (factory) { - if (typeof define === 'function' && define.amd) { - // AMD - define(['jquery'], factory); - } else if (typeof exports === 'object') { - // CommonJS - factory(require('jquery')); - } else { - // Browser globals - factory(jQuery); - } -}(function ($) { - - var pluses = /\+/g; - - function encode(s) { - return config.raw ? s : encodeURIComponent(s); - } - - function decode(s) { - return config.raw ? s : decodeURIComponent(s); - } - - function stringifyCookieValue(value) { - return encode(config.json ? JSON.stringify(value) : String(value)); - } - - function parseCookieValue(s) { - if (s.indexOf('"') === 0) { - // This is a quoted cookie as according to RFC2068, unescape... - s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); - } - - try { - // Replace server-side written pluses with spaces. - // If we can't decode the cookie, ignore it, it's unusable. - // If we can't parse the cookie, ignore it, it's unusable. - s = decodeURIComponent(s.replace(pluses, ' ')); - return config.json ? JSON.parse(s) : s; - } catch(e) {} - } - - function read(s, converter) { - var value = config.raw ? s : parseCookieValue(s); - return $.isFunction(converter) ? converter(value) : value; - } - - var config = $.cookie = function (key, value, options) { - - // Write - - if (value !== undefined && !$.isFunction(value)) { - options = $.extend({}, config.defaults, options); - - if (typeof options.expires === 'number') { - var days = options.expires, t = options.expires = new Date(); - t.setTime(+t + days * 864e+5); - } - - return (document.cookie = [ - encode(key), '=', stringifyCookieValue(value), - options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE - options.path ? '; path=' + options.path : '', - options.domain ? '; domain=' + options.domain : '', - options.secure ? '; secure' : '' - ].join('')); - } - - // Read - - var result = key ? undefined : {}; - - // To prevent the for loop in the first place assign an empty array - // in case there are no cookies at all. Also prevents odd result when - // calling $.cookie(). - var cookies = document.cookie ? document.cookie.split('; ') : []; - - for (var i = 0, l = cookies.length; i < l; i++) { - var parts = cookies[i].split('='); - var name = decode(parts.shift()); - var cookie = parts.join('='); - - if (key && key === name) { - // If second argument (value) is a function it's a converter... - result = read(cookie, value); - break; - } - - // Prevent storing a cookie that we couldn't decode. - if (!key && (cookie = read(cookie)) !== undefined) { - result[name] = cookie; - } - } - - return result; - }; - - config.defaults = {}; - - $.removeCookie = function (key, options) { - if ($.cookie(key) === undefined) { - return false; - } - - // Must not alter options, thus extending a fresh object... - $.cookie(key, '', $.extend({}, options, { expires: -1 })); - return !$.cookie(key); - }; - -})); diff --git a/xxl-job-admin/src/main/resources/static/plugins/nprogress/nprogress.css b/xxl-job-admin/src/main/resources/static/plugins/nprogress/nprogress.css new file mode 100644 index 00000000..6752d7f4 --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/nprogress/nprogress.css @@ -0,0 +1,74 @@ +/* Make clicks pass-through */ +#nprogress { + pointer-events: none; +} + +#nprogress .bar { + background: #29d; + + position: fixed; + z-index: 1031; + top: 0; + left: 0; + + width: 100%; + height: 2px; +} + +/* Fancy blur effect */ +#nprogress .peg { + display: block; + position: absolute; + right: 0px; + width: 100px; + height: 100%; + box-shadow: 0 0 10px #29d, 0 0 5px #29d; + opacity: 1.0; + + -webkit-transform: rotate(3deg) translate(0px, -4px); + -ms-transform: rotate(3deg) translate(0px, -4px); + transform: rotate(3deg) translate(0px, -4px); +} + +/* Remove these to get rid of the spinner */ +#nprogress .spinner { + display: block; + position: fixed; + z-index: 1031; + top: 15px; + right: 15px; +} + +#nprogress .spinner-icon { + width: 18px; + height: 18px; + box-sizing: border-box; + + border: solid 2px transparent; + border-top-color: #29d; + border-left-color: #29d; + border-radius: 50%; + + -webkit-animation: nprogress-spinner 400ms linear infinite; + animation: nprogress-spinner 400ms linear infinite; +} + +.nprogress-custom-parent { + overflow: hidden; + position: relative; +} + +.nprogress-custom-parent #nprogress .spinner, +.nprogress-custom-parent #nprogress .bar { + position: absolute; +} + +@-webkit-keyframes nprogress-spinner { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } +} +@keyframes nprogress-spinner { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + diff --git a/xxl-job-admin/src/main/resources/static/plugins/nprogress/nprogress.js b/xxl-job-admin/src/main/resources/static/plugins/nprogress/nprogress.js new file mode 100644 index 00000000..b23b3006 --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/nprogress/nprogress.js @@ -0,0 +1,476 @@ +/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT */ + +;(function(root, factory) { + + if (typeof define === 'function' && define.amd) { + define(factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.NProgress = factory(); + } + +})(this, function() { + var NProgress = {}; + + NProgress.version = '0.2.0'; + + var Settings = NProgress.settings = { + minimum: 0.08, + easing: 'ease', + positionUsing: '', + speed: 200, + trickle: true, + trickleRate: 0.02, + trickleSpeed: 800, + showSpinner: true, + barSelector: '[role="bar"]', + spinnerSelector: '[role="spinner"]', + parent: 'body', + template: '
      ' + }; + + /** + * Updates configuration. + * + * NProgress.configure({ + * minimum: 0.1 + * }); + */ + NProgress.configure = function(options) { + var key, value; + for (key in options) { + value = options[key]; + if (value !== undefined && options.hasOwnProperty(key)) Settings[key] = value; + } + + return this; + }; + + /** + * Last number. + */ + + NProgress.status = null; + + /** + * Sets the progress bar status, where `n` is a number from `0.0` to `1.0`. + * + * NProgress.set(0.4); + * NProgress.set(1.0); + */ + + NProgress.set = function(n) { + var started = NProgress.isStarted(); + + n = clamp(n, Settings.minimum, 1); + NProgress.status = (n === 1 ? null : n); + + var progress = NProgress.render(!started), + bar = progress.querySelector(Settings.barSelector), + speed = Settings.speed, + ease = Settings.easing; + + progress.offsetWidth; /* Repaint */ + + queue(function(next) { + // Set positionUsing if it hasn't already been set + if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS(); + + // Add transition + css(bar, barPositionCSS(n, speed, ease)); + + if (n === 1) { + // Fade out + css(progress, { + transition: 'none', + opacity: 1 + }); + progress.offsetWidth; /* Repaint */ + + setTimeout(function() { + css(progress, { + transition: 'all ' + speed + 'ms linear', + opacity: 0 + }); + setTimeout(function() { + NProgress.remove(); + next(); + }, speed); + }, speed); + } else { + setTimeout(next, speed); + } + }); + + return this; + }; + + NProgress.isStarted = function() { + return typeof NProgress.status === 'number'; + }; + + /** + * Shows the progress bar. + * This is the same as setting the status to 0%, except that it doesn't go backwards. + * + * NProgress.start(); + * + */ + NProgress.start = function() { + if (!NProgress.status) NProgress.set(0); + + var work = function() { + setTimeout(function() { + if (!NProgress.status) return; + NProgress.trickle(); + work(); + }, Settings.trickleSpeed); + }; + + if (Settings.trickle) work(); + + return this; + }; + + /** + * Hides the progress bar. + * This is the *sort of* the same as setting the status to 100%, with the + * difference being `done()` makes some placebo effect of some realistic motion. + * + * NProgress.done(); + * + * If `true` is passed, it will show the progress bar even if its hidden. + * + * NProgress.done(true); + */ + + NProgress.done = function(force) { + if (!force && !NProgress.status) return this; + + return NProgress.inc(0.3 + 0.5 * Math.random()).set(1); + }; + + /** + * Increments by a random amount. + */ + + NProgress.inc = function(amount) { + var n = NProgress.status; + + if (!n) { + return NProgress.start(); + } else { + if (typeof amount !== 'number') { + amount = (1 - n) * clamp(Math.random() * n, 0.1, 0.95); + } + + n = clamp(n + amount, 0, 0.994); + return NProgress.set(n); + } + }; + + NProgress.trickle = function() { + return NProgress.inc(Math.random() * Settings.trickleRate); + }; + + /** + * Waits for all supplied jQuery promises and + * increases the progress as the promises resolve. + * + * @param $promise jQUery Promise + */ + (function() { + var initial = 0, current = 0; + + NProgress.promise = function($promise) { + if (!$promise || $promise.state() === "resolved") { + return this; + } + + if (current === 0) { + NProgress.start(); + } + + initial++; + current++; + + $promise.always(function() { + current--; + if (current === 0) { + initial = 0; + NProgress.done(); + } else { + NProgress.set((initial - current) / initial); + } + }); + + return this; + }; + + })(); + + /** + * (Internal) renders the progress bar markup based on the `template` + * setting. + */ + + NProgress.render = function(fromStart) { + if (NProgress.isRendered()) return document.getElementById('nprogress'); + + addClass(document.documentElement, 'nprogress-busy'); + + var progress = document.createElement('div'); + progress.id = 'nprogress'; + progress.innerHTML = Settings.template; + + var bar = progress.querySelector(Settings.barSelector), + perc = fromStart ? '-100' : toBarPerc(NProgress.status || 0), + parent = document.querySelector(Settings.parent), + spinner; + + css(bar, { + transition: 'all 0 linear', + transform: 'translate3d(' + perc + '%,0,0)' + }); + + if (!Settings.showSpinner) { + spinner = progress.querySelector(Settings.spinnerSelector); + spinner && removeElement(spinner); + } + + if (parent != document.body) { + addClass(parent, 'nprogress-custom-parent'); + } + + parent.appendChild(progress); + return progress; + }; + + /** + * Removes the element. Opposite of render(). + */ + + NProgress.remove = function() { + removeClass(document.documentElement, 'nprogress-busy'); + removeClass(document.querySelector(Settings.parent), 'nprogress-custom-parent'); + var progress = document.getElementById('nprogress'); + progress && removeElement(progress); + }; + + /** + * Checks if the progress bar is rendered. + */ + + NProgress.isRendered = function() { + return !!document.getElementById('nprogress'); + }; + + /** + * Determine which positioning CSS rule to use. + */ + + NProgress.getPositioningCSS = function() { + // Sniff on document.body.style + var bodyStyle = document.body.style; + + // Sniff prefixes + var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' : + ('MozTransform' in bodyStyle) ? 'Moz' : + ('msTransform' in bodyStyle) ? 'ms' : + ('OTransform' in bodyStyle) ? 'O' : ''; + + if (vendorPrefix + 'Perspective' in bodyStyle) { + // Modern browsers with 3D support, e.g. Webkit, IE10 + return 'translate3d'; + } else if (vendorPrefix + 'Transform' in bodyStyle) { + // Browsers without 3D support, e.g. IE9 + return 'translate'; + } else { + // Browsers without translate() support, e.g. IE7-8 + return 'margin'; + } + }; + + /** + * Helpers + */ + + function clamp(n, min, max) { + if (n < min) return min; + if (n > max) return max; + return n; + } + + /** + * (Internal) converts a percentage (`0..1`) to a bar translateX + * percentage (`-100%..0%`). + */ + + function toBarPerc(n) { + return (-1 + n) * 100; + } + + + /** + * (Internal) returns the correct CSS for changing the bar's + * position given an n percentage, and speed and ease from Settings + */ + + function barPositionCSS(n, speed, ease) { + var barCSS; + + if (Settings.positionUsing === 'translate3d') { + barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' }; + } else if (Settings.positionUsing === 'translate') { + barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' }; + } else { + barCSS = { 'margin-left': toBarPerc(n)+'%' }; + } + + barCSS.transition = 'all '+speed+'ms '+ease; + + return barCSS; + } + + /** + * (Internal) Queues a function to be executed. + */ + + var queue = (function() { + var pending = []; + + function next() { + var fn = pending.shift(); + if (fn) { + fn(next); + } + } + + return function(fn) { + pending.push(fn); + if (pending.length == 1) next(); + }; + })(); + + /** + * (Internal) Applies css properties to an element, similar to the jQuery + * css method. + * + * While this helper does assist with vendor prefixed property names, it + * does not perform any manipulation of values prior to setting styles. + */ + + var css = (function() { + var cssPrefixes = [ 'Webkit', 'O', 'Moz', 'ms' ], + cssProps = {}; + + function camelCase(string) { + return string.replace(/^-ms-/, 'ms-').replace(/-([\da-z])/gi, function(match, letter) { + return letter.toUpperCase(); + }); + } + + function getVendorProp(name) { + var style = document.body.style; + if (name in style) return name; + + var i = cssPrefixes.length, + capName = name.charAt(0).toUpperCase() + name.slice(1), + vendorName; + while (i--) { + vendorName = cssPrefixes[i] + capName; + if (vendorName in style) return vendorName; + } + + return name; + } + + function getStyleProp(name) { + name = camelCase(name); + return cssProps[name] || (cssProps[name] = getVendorProp(name)); + } + + function applyCss(element, prop, value) { + prop = getStyleProp(prop); + element.style[prop] = value; + } + + return function(element, properties) { + var args = arguments, + prop, + value; + + if (args.length == 2) { + for (prop in properties) { + value = properties[prop]; + if (value !== undefined && properties.hasOwnProperty(prop)) applyCss(element, prop, value); + } + } else { + applyCss(element, args[1], args[2]); + } + } + })(); + + /** + * (Internal) Determines if an element or space separated list of class names contains a class name. + */ + + function hasClass(element, name) { + var list = typeof element == 'string' ? element : classList(element); + return list.indexOf(' ' + name + ' ') >= 0; + } + + /** + * (Internal) Adds a class to an element. + */ + + function addClass(element, name) { + var oldList = classList(element), + newList = oldList + name; + + if (hasClass(oldList, name)) return; + + // Trim the opening space. + element.className = newList.substring(1); + } + + /** + * (Internal) Removes a class from an element. + */ + + function removeClass(element, name) { + var oldList = classList(element), + newList; + + if (!hasClass(element, name)) return; + + // Replace the class name. + newList = oldList.replace(' ' + name + ' ', ' '); + + // Trim the opening and closing spaces. + element.className = newList.substring(1, newList.length - 1); + } + + /** + * (Internal) Gets a space separated list of the class names on the element. + * The list is wrapped with a single space on each end to facilitate finding + * matches within the list. + */ + + function classList(element) { + return (' ' + (element.className || '') + ' ').replace(/\s+/gi, ' '); + } + + /** + * (Internal) Removes an element from the DOM. + */ + + function removeElement(element) { + element && element.parentNode && element.parentNode.removeChild(element); + } + + return NProgress; +}); + diff --git a/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/line_conn.png b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/line_conn.png new file mode 100644 index 00000000..b211da2f Binary files /dev/null and b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/line_conn.png differ diff --git a/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/loading.gif b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/loading.gif new file mode 100644 index 00000000..e8c28929 Binary files /dev/null and b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/loading.gif differ diff --git a/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/metro.gif b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/metro.gif new file mode 100644 index 00000000..664b969a Binary files /dev/null and b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/metro.gif differ diff --git a/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/metro.png b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/metro.png new file mode 100644 index 00000000..e9e58a3a Binary files /dev/null and b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/img/metro.png differ diff --git a/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/metroStyle.css b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/metroStyle.css new file mode 100644 index 00000000..af81f423 --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/zTree/css/metroStyle/metroStyle.css @@ -0,0 +1,96 @@ +/*------------------------------------- +zTree Style + +version: 3.4 +author: Hunter.z +email: hunter.z@263.net +website: http://code.google.com/p/jquerytree/ + +-------------------------------------*/ + +.ztree * {padding:0; margin:0; font-size:12px; font-family: Verdana, Arial, Helvetica, AppleGothic, sans-serif} +.ztree {margin:0; padding:5px; color:#333} +.ztree li{padding:0; margin:0; list-style:none; line-height:17px; text-align:left; white-space:nowrap; outline:0} +.ztree li ul{ margin:0; padding:0 0 0 18px} +.ztree li ul.line{ background:url(./img/line_conn.png) 0 0 repeat-y;} + +.ztree li a {padding-right:3px; margin:0; cursor:pointer; height:21px; color:#333; background-color: transparent; text-decoration:none; vertical-align:top; display: inline-block} +.ztree li a:hover {text-decoration:underline} +.ztree li a.curSelectedNode {padding-top:0px; background-color:#e5e5e5; color:black; height:21px; opacity:0.8;} +.ztree li a.curSelectedNode_Edit {padding-top:0px; background-color:#e5e5e5; color:black; height:21px; border:1px #666 solid; opacity:0.8;} +.ztree li a.tmpTargetNode_inner {padding-top:0px; background-color:#aaa; color:white; height:21px; border:1px #666 solid; + opacity:0.8; filter:alpha(opacity=80)} +.ztree li a.tmpTargetNode_prev {} +.ztree li a.tmpTargetNode_next {} +.ztree li a input.rename {height:14px; width:80px; padding:0; margin:0; + font-size:12px; border:1px #585956 solid; *border:0px} +.ztree li span {line-height:21px; margin-right:2px} +.ztree li span.button {line-height:0; margin:0; padding: 0; width:21px; height:21px; display: inline-block; vertical-align:middle; + border:0 none; cursor: pointer;outline:none; + background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; + background-image:url("./img/metro.png"); *background-image:url("./img/metro.gif")} + +.ztree li span.button.chk {width:13px; height:13px; margin:0 2px; cursor: auto} +.ztree li span.button.chk.checkbox_false_full {background-position: -5px -5px;} +.ztree li span.button.chk.checkbox_false_full_focus {background-position: -5px -26px;} +.ztree li span.button.chk.checkbox_false_part {background-position: -5px -48px;} +.ztree li span.button.chk.checkbox_false_part_focus {background-position: -5px -68px;} +.ztree li span.button.chk.checkbox_false_disable {background-position: -5px -89px;} +.ztree li span.button.chk.checkbox_true_full {background-position: -26px -5px;} +.ztree li span.button.chk.checkbox_true_full_focus {background-position: -26px -26px;} +.ztree li span.button.chk.checkbox_true_part {background-position: -26px -48px;} +.ztree li span.button.chk.checkbox_true_part_focus {background-position: -26px -68px;} +.ztree li span.button.chk.checkbox_true_disable {background-position: -26px -89px;} +.ztree li span.button.chk.radio_false_full {background-position: -47px -5px;} +.ztree li span.button.chk.radio_false_full_focus {background-position: -47px -26px;} +.ztree li span.button.chk.radio_false_part {background-position: -47px -47px;} +.ztree li span.button.chk.radio_false_part_focus {background-position: -47px -68px;} +.ztree li span.button.chk.radio_false_disable {background-position: -47px -89px;} +.ztree li span.button.chk.radio_true_full {background-position: -68px -5px;} +.ztree li span.button.chk.radio_true_full_focus {background-position: -68px -26px;} +.ztree li span.button.chk.radio_true_part {background-position: -68px -47px;} +.ztree li span.button.chk.radio_true_part_focus {background-position: -68px -68px;} +.ztree li span.button.chk.radio_true_disable {background-position: -68px -89px;} + +.ztree li span.button.switch {width:21px; height:21px} +.ztree li span.button.root_open{background-position:-105px -63px} +.ztree li span.button.root_close{background-position:-126px -63px} +.ztree li span.button.roots_open{background-position: -105px 0;} +.ztree li span.button.roots_close{background-position: -126px 0;} +.ztree li span.button.center_open{background-position: -105px -21px;} +.ztree li span.button.center_close{background-position: -126px -21px;} +.ztree li span.button.bottom_open{background-position: -105px -42px;} +.ztree li span.button.bottom_close{background-position: -126px -42px;} +.ztree li span.button.noline_open{background-position: -105px -84px;} +.ztree li span.button.noline_close{background-position: -126px -84px;} +.ztree li span.button.root_docu{ background:none;} +.ztree li span.button.roots_docu{background-position: -84px 0;} +.ztree li span.button.center_docu{background-position: -84px -21px;} +.ztree li span.button.bottom_docu{background-position: -84px -42px;} +.ztree li span.button.noline_docu{ background:none;} + +.ztree li span.button.ico_open{margin-right:2px; background-position: -147px -21px; vertical-align:top; *vertical-align:middle} +.ztree li span.button.ico_close{margin-right:2px; margin-right:2px; background-position: -147px 0; vertical-align:top; *vertical-align:middle} +.ztree li span.button.ico_docu{margin-right:2px; background-position: -147px -42px; vertical-align:top; *vertical-align:middle} +.ztree li span.button.edit {margin-left:2px; margin-right: -1px; background-position: -189px -21px; vertical-align:top; *vertical-align:middle} +.ztree li span.button.edit:hover { + background-position: -168px -21px; +} +.ztree li span.button.remove {margin-left:2px; margin-right: -1px; background-position: -189px -42px; vertical-align:top; *vertical-align:middle} +.ztree li span.button.remove:hover { + background-position: -168px -42px; +} +.ztree li span.button.add {margin-left:2px; margin-right: -1px; background-position: -189px 0; vertical-align:top; *vertical-align:middle} +.ztree li span.button.add:hover { + background-position: -168px 0; +} +.ztree li span.button.ico_loading{margin-right:2px; background:url(./img/loading.gif) no-repeat scroll 0 0 transparent; vertical-align:top; *vertical-align:middle} + +ul.tmpTargetzTree {background-color:#FFE6B0; opacity:0.8; filter:alpha(opacity=80)} + +span.tmpzTreeMove_arrow {width:16px; height:21px; display: inline-block; padding:0; margin:2px 0 0 1px; border:0 none; position:absolute; + background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; + background-position:-168px -84px; background-image:url("./img/metro.png"); *background-image:url("./img/metro.gif")} + +ul.ztree.zTreeDragUL {margin:0; padding:0; position:absolute; width:auto; height:auto;overflow:hidden; background-color:#cfcfcf; border:1px #00B83F dotted; opacity:0.8; filter:alpha(opacity=80)} +.ztreeMask {z-index:10000; background-color:#cfcfcf; opacity:0.0; filter:alpha(opacity=0); position:absolute} diff --git a/xxl-job-admin/src/main/resources/static/plugins/zTree/js/jquery.ztree.core.js b/xxl-job-admin/src/main/resources/static/plugins/zTree/js/jquery.ztree.core.js new file mode 100644 index 00000000..9226f19d --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/zTree/js/jquery.ztree.core.js @@ -0,0 +1,2019 @@ +/* + * JQuery zTree core + * v3.5.48 + * http://treejs.cn/ + * + * Copyright (c) 2010 Hunter.z + * + * Licensed same as jquery - MIT License + * http://www.opensource.org/licenses/mit-license.php + * + * Date: 2020-11-21 + */ + +(function ($) { + var settings = {}, roots = {}, caches = {}, + //default consts of core + _consts = { + className: { + BUTTON: "button", + LEVEL: "level", + ICO_LOADING: "ico_loading", + SWITCH: "switch", + NAME: 'node_name' + }, + event: { + NODECREATED: "ztree_nodeCreated", + CLICK: "ztree_click", + EXPAND: "ztree_expand", + COLLAPSE: "ztree_collapse", + ASYNC_SUCCESS: "ztree_async_success", + ASYNC_ERROR: "ztree_async_error", + REMOVE: "ztree_remove", + SELECTED: "ztree_selected", + UNSELECTED: "ztree_unselected" + }, + id: { + A: "_a", + ICON: "_ico", + SPAN: "_span", + SWITCH: "_switch", + UL: "_ul" + }, + line: { + ROOT: "root", + ROOTS: "roots", + CENTER: "center", + BOTTOM: "bottom", + NOLINE: "noline", + LINE: "line" + }, + folder: { + OPEN: "open", + CLOSE: "close", + DOCU: "docu" + }, + node: { + CURSELECTED: "curSelectedNode" + } + }, + //default setting of core + _setting = { + treeId: "", + treeObj: null, + view: { + addDiyDom: null, + autoCancelSelected: true, + dblClickExpand: true, + expandSpeed: "fast", + fontCss: {}, + nodeClasses: {}, + nameIsHTML: false, + selectedMulti: true, + showIcon: true, + showLine: true, + showTitle: true, + txtSelectedEnable: false + }, + data: { + key: { + isParent: "isParent", + children: "children", + name: "name", + title: "", + url: "url", + icon: "icon" + }, + render: { + name: null, + title: null, + }, + simpleData: { + enable: false, + idKey: "id", + pIdKey: "pId", + rootPId: null + }, + keep: { + parent: false, + leaf: false + } + }, + async: { + enable: false, + contentType: "application/x-www-form-urlencoded", + type: "post", + dataType: "text", + headers: {}, + xhrFields: {}, + url: "", + autoParam: [], + otherParam: [], + dataFilter: null + }, + callback: { + beforeAsync: null, + beforeClick: null, + beforeDblClick: null, + beforeRightClick: null, + beforeMouseDown: null, + beforeMouseUp: null, + beforeExpand: null, + beforeCollapse: null, + beforeRemove: null, + + onAsyncError: null, + onAsyncSuccess: null, + onNodeCreated: null, + onClick: null, + onDblClick: null, + onRightClick: null, + onMouseDown: null, + onMouseUp: null, + onExpand: null, + onCollapse: null, + onRemove: null + } + }, + //default root of core + //zTree use root to save full data + _initRoot = function (setting) { + var r = data.getRoot(setting); + if (!r) { + r = {}; + data.setRoot(setting, r); + } + data.nodeChildren(setting, r, []); + r.expandTriggerFlag = false; + r.curSelectedList = []; + r.noSelection = true; + r.createdNodes = []; + r.zId = 0; + r._ver = (new Date()).getTime(); + }, + //default cache of core + _initCache = function (setting) { + var c = data.getCache(setting); + if (!c) { + c = {}; + data.setCache(setting, c); + } + c.nodes = []; + c.doms = []; + }, + //default bindEvent of core + _bindEvent = function (setting) { + var o = setting.treeObj, + c = consts.event; + o.bind(c.NODECREATED, function (event, treeId, node) { + tools.apply(setting.callback.onNodeCreated, [event, treeId, node]); + }); + + o.bind(c.CLICK, function (event, srcEvent, treeId, node, clickFlag) { + tools.apply(setting.callback.onClick, [srcEvent, treeId, node, clickFlag]); + }); + + o.bind(c.EXPAND, function (event, treeId, node) { + tools.apply(setting.callback.onExpand, [event, treeId, node]); + }); + + o.bind(c.COLLAPSE, function (event, treeId, node) { + tools.apply(setting.callback.onCollapse, [event, treeId, node]); + }); + + o.bind(c.ASYNC_SUCCESS, function (event, treeId, node, msg) { + tools.apply(setting.callback.onAsyncSuccess, [event, treeId, node, msg]); + }); + + o.bind(c.ASYNC_ERROR, function (event, treeId, node, XMLHttpRequest, textStatus, errorThrown) { + tools.apply(setting.callback.onAsyncError, [event, treeId, node, XMLHttpRequest, textStatus, errorThrown]); + }); + + o.bind(c.REMOVE, function (event, treeId, treeNode) { + tools.apply(setting.callback.onRemove, [event, treeId, treeNode]); + }); + + o.bind(c.SELECTED, function (event, treeId, node) { + tools.apply(setting.callback.onSelected, [treeId, node]); + }); + o.bind(c.UNSELECTED, function (event, treeId, node) { + tools.apply(setting.callback.onUnSelected, [treeId, node]); + }); + }, + _unbindEvent = function (setting) { + var o = setting.treeObj, + c = consts.event; + o.unbind(c.NODECREATED) + .unbind(c.CLICK) + .unbind(c.EXPAND) + .unbind(c.COLLAPSE) + .unbind(c.ASYNC_SUCCESS) + .unbind(c.ASYNC_ERROR) + .unbind(c.REMOVE) + .unbind(c.SELECTED) + .unbind(c.UNSELECTED); + }, + //default event proxy of core + _eventProxy = function (event) { + var target = event.target, + setting = data.getSetting(event.data.treeId), + tId = "", node = null, + nodeEventType = "", treeEventType = "", + nodeEventCallback = null, treeEventCallback = null, + tmp = null; + + if (tools.eqs(event.type, "mousedown")) { + treeEventType = "mousedown"; + } else if (tools.eqs(event.type, "mouseup")) { + treeEventType = "mouseup"; + } else if (tools.eqs(event.type, "contextmenu")) { + treeEventType = "contextmenu"; + } else if (tools.eqs(event.type, "click")) { + if (tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.SWITCH) !== null) { + tId = tools.getNodeMainDom(target).id; + nodeEventType = "switchNode"; + } else { + tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]); + if (tmp) { + tId = tools.getNodeMainDom(tmp).id; + nodeEventType = "clickNode"; + } + } + } else if (tools.eqs(event.type, "dblclick")) { + treeEventType = "dblclick"; + tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]); + if (tmp) { + tId = tools.getNodeMainDom(tmp).id; + nodeEventType = "switchNode"; + } + } + if (treeEventType.length > 0 && tId.length == 0) { + tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]); + if (tmp) { + tId = tools.getNodeMainDom(tmp).id; + } + } + // event to node + if (tId.length > 0) { + node = data.getNodeCache(setting, tId); + switch (nodeEventType) { + case "switchNode" : + var isParent = data.nodeIsParent(setting, node); + if (!isParent) { + nodeEventType = ""; + } else if (tools.eqs(event.type, "click") + || (tools.eqs(event.type, "dblclick") && tools.apply(setting.view.dblClickExpand, [setting.treeId, node], setting.view.dblClickExpand))) { + nodeEventCallback = handler.onSwitchNode; + } else { + nodeEventType = ""; + } + break; + case "clickNode" : + nodeEventCallback = handler.onClickNode; + break; + } + } + // event to zTree + switch (treeEventType) { + case "mousedown" : + treeEventCallback = handler.onZTreeMousedown; + break; + case "mouseup" : + treeEventCallback = handler.onZTreeMouseup; + break; + case "dblclick" : + treeEventCallback = handler.onZTreeDblclick; + break; + case "contextmenu" : + treeEventCallback = handler.onZTreeContextmenu; + break; + } + var proxyResult = { + stop: false, + node: node, + nodeEventType: nodeEventType, + nodeEventCallback: nodeEventCallback, + treeEventType: treeEventType, + treeEventCallback: treeEventCallback + }; + return proxyResult + }, + //default init node of core + _initNode = function (setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { + if (!n) return; + var r = data.getRoot(setting), + children = data.nodeChildren(setting, n); + n.level = level; + n.tId = setting.treeId + "_" + (++r.zId); + n.parentTId = parentNode ? parentNode.tId : null; + n.open = (typeof n.open == "string") ? tools.eqs(n.open, "true") : !!n.open; + var isParent = data.nodeIsParent(setting, n); + if (tools.isArray(children)) { + data.nodeIsParent(setting, n, true); + n.zAsync = true; + } else { + isParent = data.nodeIsParent(setting, n, isParent); + n.open = (isParent && !setting.async.enable) ? n.open : false; + n.zAsync = !isParent; + } + n.isFirstNode = isFirstNode; + n.isLastNode = isLastNode; + n.getParentNode = function () { + return data.getNodeCache(setting, n.parentTId); + }; + n.getPreNode = function () { + return data.getPreNode(setting, n); + }; + n.getNextNode = function () { + return data.getNextNode(setting, n); + }; + n.getIndex = function () { + return data.getNodeIndex(setting, n); + }; + n.getPath = function () { + return data.getNodePath(setting, n); + }; + n.isAjaxing = false; + data.fixPIdKeyValue(setting, n); + }, + _init = { + bind: [_bindEvent], + unbind: [_unbindEvent], + caches: [_initCache], + nodes: [_initNode], + proxys: [_eventProxy], + roots: [_initRoot], + beforeA: [], + afterA: [], + innerBeforeA: [], + innerAfterA: [], + zTreeTools: [] + }, + //method of operate data + data = { + addNodeCache: function (setting, node) { + data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = node; + }, + getNodeCacheId: function (tId) { + return tId.substring(tId.lastIndexOf("_") + 1); + }, + addAfterA: function (afterA) { + _init.afterA.push(afterA); + }, + addBeforeA: function (beforeA) { + _init.beforeA.push(beforeA); + }, + addInnerAfterA: function (innerAfterA) { + _init.innerAfterA.push(innerAfterA); + }, + addInnerBeforeA: function (innerBeforeA) { + _init.innerBeforeA.push(innerBeforeA); + }, + addInitBind: function (bindEvent) { + _init.bind.push(bindEvent); + }, + addInitUnBind: function (unbindEvent) { + _init.unbind.push(unbindEvent); + }, + addInitCache: function (initCache) { + _init.caches.push(initCache); + }, + addInitNode: function (initNode) { + _init.nodes.push(initNode); + }, + addInitProxy: function (initProxy, isFirst) { + if (!!isFirst) { + _init.proxys.splice(0, 0, initProxy); + } else { + _init.proxys.push(initProxy); + } + }, + addInitRoot: function (initRoot) { + _init.roots.push(initRoot); + }, + addNodesData: function (setting, parentNode, index, nodes) { + var children = data.nodeChildren(setting, parentNode), params; + if (!children) { + children = data.nodeChildren(setting, parentNode, []); + index = -1; + } else if (index >= children.length) { + index = -1; + } + + if (children.length > 0 && index === 0) { + children[0].isFirstNode = false; + view.setNodeLineIcos(setting, children[0]); + } else if (children.length > 0 && index < 0) { + children[children.length - 1].isLastNode = false; + view.setNodeLineIcos(setting, children[children.length - 1]); + } + data.nodeIsParent(setting, parentNode, true); + + if (index < 0) { + data.nodeChildren(setting, parentNode, children.concat(nodes)); + } else { + params = [index, 0].concat(nodes); + children.splice.apply(children, params); + } + }, + addSelectedNode: function (setting, node) { + var root = data.getRoot(setting); + if (!data.isSelectedNode(setting, node)) { + root.curSelectedList.push(node); + } + }, + addCreatedNode: function (setting, node) { + if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) { + var root = data.getRoot(setting); + root.createdNodes.push(node); + } + }, + addZTreeTools: function (zTreeTools) { + _init.zTreeTools.push(zTreeTools); + }, + exSetting: function (s) { + $.extend(true, _setting, s); + }, + fixPIdKeyValue: function (setting, node) { + if (setting.data.simpleData.enable) { + node[setting.data.simpleData.pIdKey] = node.parentTId ? node.getParentNode()[setting.data.simpleData.idKey] : setting.data.simpleData.rootPId; + } + }, + getAfterA: function (setting, node, array) { + for (var i = 0, j = _init.afterA.length; i < j; i++) { + _init.afterA[i].apply(this, arguments); + } + }, + getBeforeA: function (setting, node, array) { + for (var i = 0, j = _init.beforeA.length; i < j; i++) { + _init.beforeA[i].apply(this, arguments); + } + }, + getInnerAfterA: function (setting, node, array) { + for (var i = 0, j = _init.innerAfterA.length; i < j; i++) { + _init.innerAfterA[i].apply(this, arguments); + } + }, + getInnerBeforeA: function (setting, node, array) { + for (var i = 0, j = _init.innerBeforeA.length; i < j; i++) { + _init.innerBeforeA[i].apply(this, arguments); + } + }, + getCache: function (setting) { + return caches[setting.treeId]; + }, + getNodeIndex: function (setting, node) { + if (!node) return null; + var p = node.parentTId ? node.getParentNode() : data.getRoot(setting), + children = data.nodeChildren(setting, p); + for (var i = 0, l = children.length - 1; i <= l; i++) { + if (children[i] === node) { + return i; + } + } + return -1; + }, + getNextNode: function (setting, node) { + if (!node) return null; + var p = node.parentTId ? node.getParentNode() : data.getRoot(setting), + children = data.nodeChildren(setting, p); + for (var i = 0, l = children.length - 1; i <= l; i++) { + if (children[i] === node) { + return (i == l ? null : children[i + 1]); + } + } + return null; + }, + getNodeByParam: function (setting, nodes, key, value) { + if (!nodes || !key) return null; + for (var i = 0, l = nodes.length; i < l; i++) { + var node = nodes[i]; + if (node[key] == value) { + return nodes[i]; + } + var children = data.nodeChildren(setting, node); + var tmp = data.getNodeByParam(setting, children, key, value); + if (tmp) return tmp; + } + return null; + }, + getNodeCache: function (setting, tId) { + if (!tId) return null; + var n = caches[setting.treeId].nodes[data.getNodeCacheId(tId)]; + return n ? n : null; + }, + getNodePath: function (setting, node) { + if (!node) return null; + + var path; + if (node.parentTId) { + path = node.getParentNode().getPath(); + } else { + path = []; + } + + if (path) { + path.push(node); + } + + return path; + }, + getNodes: function (setting) { + return data.nodeChildren(setting, data.getRoot(setting)); + }, + getNodesByParam: function (setting, nodes, key, value) { + if (!nodes || !key) return []; + var result = []; + for (var i = 0, l = nodes.length; i < l; i++) { + var node = nodes[i]; + if (node[key] == value) { + result.push(node); + } + var children = data.nodeChildren(setting, node); + result = result.concat(data.getNodesByParam(setting, children, key, value)); + } + return result; + }, + getNodesByParamFuzzy: function (setting, nodes, key, value) { + if (!nodes || !key) return []; + var result = []; + value = value.toLowerCase(); + for (var i = 0, l = nodes.length; i < l; i++) { + var node = nodes[i]; + if (typeof node[key] == "string" && nodes[i][key].toLowerCase().indexOf(value) > -1) { + result.push(node); + } + var children = data.nodeChildren(setting, node); + result = result.concat(data.getNodesByParamFuzzy(setting, children, key, value)); + } + return result; + }, + getNodesByFilter: function (setting, nodes, filter, isSingle, invokeParam) { + if (!nodes) return (isSingle ? null : []); + var result = isSingle ? null : []; + for (var i = 0, l = nodes.length; i < l; i++) { + var node = nodes[i]; + if (tools.apply(filter, [node, invokeParam], false)) { + if (isSingle) { + return node; + } + result.push(node); + } + var children = data.nodeChildren(setting, node); + var tmpResult = data.getNodesByFilter(setting, children, filter, isSingle, invokeParam); + if (isSingle && !!tmpResult) { + return tmpResult; + } + result = isSingle ? tmpResult : result.concat(tmpResult); + } + return result; + }, + getPreNode: function (setting, node) { + if (!node) return null; + var p = node.parentTId ? node.getParentNode() : data.getRoot(setting), + children = data.nodeChildren(setting, p); + for (var i = 0, l = children.length; i < l; i++) { + if (children[i] === node) { + return (i == 0 ? null : children[i - 1]); + } + } + return null; + }, + getRoot: function (setting) { + return setting ? roots[setting.treeId] : null; + }, + getRoots: function () { + return roots; + }, + getSetting: function (treeId) { + return settings[treeId]; + }, + getSettings: function () { + return settings; + }, + getZTreeTools: function (treeId) { + var r = this.getRoot(this.getSetting(treeId)); + return r ? r.treeTools : null; + }, + initCache: function (setting) { + for (var i = 0, j = _init.caches.length; i < j; i++) { + _init.caches[i].apply(this, arguments); + } + }, + initNode: function (setting, level, node, parentNode, preNode, nextNode) { + for (var i = 0, j = _init.nodes.length; i < j; i++) { + _init.nodes[i].apply(this, arguments); + } + }, + initRoot: function (setting) { + for (var i = 0, j = _init.roots.length; i < j; i++) { + _init.roots[i].apply(this, arguments); + } + }, + isSelectedNode: function (setting, node) { + var root = data.getRoot(setting); + for (var i = 0, j = root.curSelectedList.length; i < j; i++) { + if (node === root.curSelectedList[i]) return true; + } + return false; + }, + nodeChildren: function (setting, node, newChildren) { + if (!node) { + return null; + } + var key = setting.data.key.children; + if (typeof newChildren !== 'undefined') { + node[key] = newChildren; + } + return node[key]; + }, + nodeIsParent: function (setting, node, newIsParent) { + if (!node) { + return false; + } + var key = setting.data.key.isParent; + if (typeof newIsParent !== 'undefined') { + if (typeof newIsParent === "string") { + newIsParent = tools.eqs(newIsParent, "true"); + } + newIsParent = !!newIsParent; + node[key] = newIsParent; + } else if (typeof node[key] == "string"){ + node[key] = tools.eqs(node[key], "true"); + } else { + node[key] = !!node[key]; + } + return node[key]; + }, + nodeName: function (setting, node, newName) { + var key = setting.data.key.name; + if (typeof newName !== 'undefined') { + node[key] = newName; + } + var rawName = "" + node[key]; + if(typeof setting.data.render.name === 'function') { + return setting.data.render.name.call(this,rawName,node); + } + return rawName; + }, + nodeTitle: function (setting, node) { + var t = setting.data.key.title === "" ? setting.data.key.name : setting.data.key.title; + var rawTitle = "" + node[t]; + if(typeof setting.data.render.title === 'function') { + return setting.data.render.title.call(this,rawTitle,node); + } + return rawTitle; + }, + removeNodeCache: function (setting, node) { + var children = data.nodeChildren(setting, node); + if (children) { + for (var i = 0, l = children.length; i < l; i++) { + data.removeNodeCache(setting, children[i]); + } + } + data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = null; + }, + removeSelectedNode: function (setting, node) { + var root = data.getRoot(setting); + for (var i = 0, j = root.curSelectedList.length; i < j; i++) { + if (node === root.curSelectedList[i] || !data.getNodeCache(setting, root.curSelectedList[i].tId)) { + root.curSelectedList.splice(i, 1); + setting.treeObj.trigger(consts.event.UNSELECTED, [setting.treeId, node]); + i--; + j--; + } + } + }, + setCache: function (setting, cache) { + caches[setting.treeId] = cache; + }, + setRoot: function (setting, root) { + roots[setting.treeId] = root; + }, + setZTreeTools: function (setting, zTreeTools) { + for (var i = 0, j = _init.zTreeTools.length; i < j; i++) { + _init.zTreeTools[i].apply(this, arguments); + } + }, + transformToArrayFormat: function (setting, nodes) { + if (!nodes) return []; + var r = []; + if (tools.isArray(nodes)) { + for (var i = 0, l = nodes.length; i < l; i++) { + var node = nodes[i]; + _do(node); + } + } else { + _do(nodes); + } + return r; + + function _do(_node) { + r.push(_node); + var children = data.nodeChildren(setting, _node); + if (children) { + r = r.concat(data.transformToArrayFormat(setting, children)); + } + } + }, + transformTozTreeFormat: function (setting, sNodes) { + var i, l, + key = setting.data.simpleData.idKey, + parentKey = setting.data.simpleData.pIdKey; + if (!key || key == "" || !sNodes) return []; + + if (tools.isArray(sNodes)) { + var r = []; + var tmpMap = {}; + for (i = 0, l = sNodes.length; i < l; i++) { + tmpMap[sNodes[i][key]] = sNodes[i]; + } + for (i = 0, l = sNodes.length; i < l; i++) { + var p = tmpMap[sNodes[i][parentKey]]; + if (p && sNodes[i][key] != sNodes[i][parentKey]) { + var children = data.nodeChildren(setting, p); + if (!children) { + children = data.nodeChildren(setting, p, []); + } + children.push(sNodes[i]); + } else { + r.push(sNodes[i]); + } + } + return r; + } else { + return [sNodes]; + } + } + }, + //method of event proxy + event = { + bindEvent: function (setting) { + for (var i = 0, j = _init.bind.length; i < j; i++) { + _init.bind[i].apply(this, arguments); + } + }, + unbindEvent: function (setting) { + for (var i = 0, j = _init.unbind.length; i < j; i++) { + _init.unbind[i].apply(this, arguments); + } + }, + bindTree: function (setting) { + var eventParam = { + treeId: setting.treeId + }, + o = setting.treeObj; + if (!setting.view.txtSelectedEnable) { + // for can't select text + o.bind('selectstart', handler.onSelectStart).css({ + "-moz-user-select": "-moz-none" + }); + } + o.bind('click', eventParam, event.proxy); + o.bind('dblclick', eventParam, event.proxy); + o.bind('mouseover', eventParam, event.proxy); + o.bind('mouseout', eventParam, event.proxy); + o.bind('mousedown', eventParam, event.proxy); + o.bind('mouseup', eventParam, event.proxy); + o.bind('contextmenu', eventParam, event.proxy); + }, + unbindTree: function (setting) { + var o = setting.treeObj; + o.unbind('selectstart', handler.onSelectStart) + .unbind('click', event.proxy) + .unbind('dblclick', event.proxy) + .unbind('mouseover', event.proxy) + .unbind('mouseout', event.proxy) + .unbind('mousedown', event.proxy) + .unbind('mouseup', event.proxy) + .unbind('contextmenu', event.proxy); + }, + doProxy: function (e) { + var results = []; + for (var i = 0, j = _init.proxys.length; i < j; i++) { + var proxyResult = _init.proxys[i].apply(this, arguments); + results.push(proxyResult); + if (proxyResult.stop) { + break; + } + } + return results; + }, + proxy: function (e) { + var setting = data.getSetting(e.data.treeId); + if (!tools.uCanDo(setting, e)) return true; + var results = event.doProxy(e), + r = true, x = false; + for (var i = 0, l = results.length; i < l; i++) { + var proxyResult = results[i]; + if (proxyResult.nodeEventCallback) { + x = true; + r = proxyResult.nodeEventCallback.apply(proxyResult, [e, proxyResult.node]) && r; + } + if (proxyResult.treeEventCallback) { + x = true; + r = proxyResult.treeEventCallback.apply(proxyResult, [e, proxyResult.node]) && r; + } + } + return r; + } + }, + //method of event handler + handler = { + onSwitchNode: function (event, node) { + var setting = data.getSetting(event.data.treeId); + if (node.open) { + if (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false) return true; + data.getRoot(setting).expandTriggerFlag = true; + view.switchNode(setting, node); + } else { + if (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false) return true; + data.getRoot(setting).expandTriggerFlag = true; + view.switchNode(setting, node); + } + return true; + }, + onClickNode: function (event, node) { + var setting = data.getSetting(event.data.treeId), + clickFlag = ((setting.view.autoCancelSelected && (event.ctrlKey || event.metaKey)) && data.isSelectedNode(setting, node)) ? 0 : (setting.view.autoCancelSelected && (event.ctrlKey || event.metaKey) && setting.view.selectedMulti) ? 2 : 1; + if (tools.apply(setting.callback.beforeClick, [setting.treeId, node, clickFlag], true) == false) return true; + if (clickFlag === 0) { + view.cancelPreSelectedNode(setting, node); + } else { + view.selectNode(setting, node, clickFlag === 2); + } + setting.treeObj.trigger(consts.event.CLICK, [event, setting.treeId, node, clickFlag]); + return true; + }, + onZTreeMousedown: function (event, node) { + var setting = data.getSetting(event.data.treeId); + if (tools.apply(setting.callback.beforeMouseDown, [setting.treeId, node], true)) { + tools.apply(setting.callback.onMouseDown, [event, setting.treeId, node]); + } + return true; + }, + onZTreeMouseup: function (event, node) { + var setting = data.getSetting(event.data.treeId); + if (tools.apply(setting.callback.beforeMouseUp, [setting.treeId, node], true)) { + tools.apply(setting.callback.onMouseUp, [event, setting.treeId, node]); + } + return true; + }, + onZTreeDblclick: function (event, node) { + var setting = data.getSetting(event.data.treeId); + if (tools.apply(setting.callback.beforeDblClick, [setting.treeId, node], true)) { + tools.apply(setting.callback.onDblClick, [event, setting.treeId, node]); + } + return true; + }, + onZTreeContextmenu: function (event, node) { + var setting = data.getSetting(event.data.treeId); + if (tools.apply(setting.callback.beforeRightClick, [setting.treeId, node], true)) { + tools.apply(setting.callback.onRightClick, [event, setting.treeId, node]); + } + return (typeof setting.callback.onRightClick) != "function"; + }, + onSelectStart: function (e) { + var n = e.originalEvent.srcElement.nodeName.toLowerCase(); + return (n === "input" || n === "textarea"); + } + }, + //method of tools for zTree + tools = { + apply: function (fun, param, defaultValue) { + if ((typeof fun) == "function") { + return fun.apply(zt, param ? param : []); + } + return defaultValue; + }, + canAsync: function (setting, node) { + var children = data.nodeChildren(setting, node); + var isParent = data.nodeIsParent(setting, node); + return (setting.async.enable && node && isParent && !(node.zAsync || (children && children.length > 0))); + }, + clone: function (obj) { + if (obj === null) return null; + var o = tools.isArray(obj) ? [] : {}; + for (var i in obj) { + o[i] = (obj[i] instanceof Date) ? new Date(obj[i].getTime()) : (typeof obj[i] === "object" ? tools.clone(obj[i]) : obj[i]); + } + return o; + }, + eqs: function (str1, str2) { + return str1.toLowerCase() === str2.toLowerCase(); + }, + isArray: function (arr) { + return Object.prototype.toString.apply(arr) === "[object Array]"; + }, + isElement: function (o) { + return ( + typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 + o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string" + ); + }, + $: function (node, exp, setting) { + if (!!exp && typeof exp != "string") { + setting = exp; + exp = ""; + } + if (typeof node == "string") { + return $(node, setting ? setting.treeObj.get(0).ownerDocument : null); + } else { + return $("#" + node.tId + exp, setting ? setting.treeObj : null); + } + }, + getMDom: function (setting, curDom, targetExpr) { + if (!curDom) return null; + while (curDom && curDom.id !== setting.treeId) { + for (var i = 0, l = targetExpr.length; curDom.tagName && i < l; i++) { + if (tools.eqs(curDom.tagName, targetExpr[i].tagName) && curDom.getAttribute(targetExpr[i].attrName) !== null) { + return curDom; + } + } + curDom = curDom.parentNode; + } + return null; + }, + getNodeMainDom: function (target) { + return ($(target).parent("li").get(0) || $(target).parentsUntil("li").parent().get(0)); + }, + isChildOrSelf: function (dom, parentId) { + return ($(dom).closest("#" + parentId).length > 0); + }, + uCanDo: function (setting, e) { + return true; + } + }, + //method of operate ztree dom + view = { + addNodes: function (setting, parentNode, index, newNodes, isSilent) { + var isParent = data.nodeIsParent(setting, parentNode); + if (setting.data.keep.leaf && parentNode && !isParent) { + return; + } + if (!tools.isArray(newNodes)) { + newNodes = [newNodes]; + } + if (setting.data.simpleData.enable) { + newNodes = data.transformTozTreeFormat(setting, newNodes); + } + if (parentNode) { + var target_switchObj = $$(parentNode, consts.id.SWITCH, setting), + target_icoObj = $$(parentNode, consts.id.ICON, setting), + target_ulObj = $$(parentNode, consts.id.UL, setting); + + if (!parentNode.open) { + view.replaceSwitchClass(parentNode, target_switchObj, consts.folder.CLOSE); + view.replaceIcoClass(parentNode, target_icoObj, consts.folder.CLOSE); + parentNode.open = false; + target_ulObj.css({ + "display": "none" + }); + } + + data.addNodesData(setting, parentNode, index, newNodes); + view.createNodes(setting, parentNode.level + 1, newNodes, parentNode, index); + if (!isSilent) { + view.expandCollapseParentNode(setting, parentNode, true); + } + } else { + data.addNodesData(setting, data.getRoot(setting), index, newNodes); + view.createNodes(setting, 0, newNodes, null, index); + } + }, + appendNodes: function (setting, level, nodes, parentNode, index, initFlag, openFlag) { + if (!nodes) return []; + var html = []; + + var tmpPNode = (parentNode) ? parentNode : data.getRoot(setting), + tmpPChild = data.nodeChildren(setting, tmpPNode), + isFirstNode, isLastNode; + + if (!tmpPChild || index >= tmpPChild.length - nodes.length) { + index = -1; + } + + for (var i = 0, l = nodes.length; i < l; i++) { + var node = nodes[i]; + if (initFlag) { + isFirstNode = ((index === 0 || tmpPChild.length == nodes.length) && (i == 0)); + isLastNode = (index < 0 && i == (nodes.length - 1)); + data.initNode(setting, level, node, parentNode, isFirstNode, isLastNode, openFlag); + data.addNodeCache(setting, node); + } + var isParent = data.nodeIsParent(setting, node); + + var childHtml = []; + var children = data.nodeChildren(setting, node); + if (children && children.length > 0) { + //make child html first, because checkType + childHtml = view.appendNodes(setting, level + 1, children, node, -1, initFlag, openFlag && node.open); + } + if (openFlag) { + view.makeDOMNodeMainBefore(html, setting, node); + view.makeDOMNodeLine(html, setting, node); + data.getBeforeA(setting, node, html); + view.makeDOMNodeNameBefore(html, setting, node); + data.getInnerBeforeA(setting, node, html); + view.makeDOMNodeIcon(html, setting, node); + data.getInnerAfterA(setting, node, html); + view.makeDOMNodeNameAfter(html, setting, node); + data.getAfterA(setting, node, html); + if (isParent && node.open) { + view.makeUlHtml(setting, node, html, childHtml.join('')); + } + view.makeDOMNodeMainAfter(html, setting, node); + data.addCreatedNode(setting, node); + } + } + return html; + }, + appendParentULDom: function (setting, node) { + var html = [], + nObj = $$(node, setting); + if (!nObj.get(0) && !!node.parentTId) { + view.appendParentULDom(setting, node.getParentNode()); + nObj = $$(node, setting); + } + var ulObj = $$(node, consts.id.UL, setting); + if (ulObj.get(0)) { + ulObj.remove(); + } + var children = data.nodeChildren(setting, node), + childHtml = view.appendNodes(setting, node.level + 1, children, node, -1, false, true); + view.makeUlHtml(setting, node, html, childHtml.join('')); + nObj.append(html.join('')); + }, + asyncNode: function (setting, node, isSilent, callback) { + var i, l; + var isParent = data.nodeIsParent(setting, node); + if (node && !isParent) { + tools.apply(callback); + return false; + } else if (node && node.isAjaxing) { + return false; + } else if (tools.apply(setting.callback.beforeAsync, [setting.treeId, node], true) == false) { + tools.apply(callback); + return false; + } + if (node) { + node.isAjaxing = true; + var icoObj = $$(node, consts.id.ICON, setting); + icoObj.attr({"style": "", "class": consts.className.BUTTON + " " + consts.className.ICO_LOADING}); + } + + var tmpParam = {}; + var autoParam = tools.apply(setting.async.autoParam, [setting.treeId, node], setting.async.autoParam); + for (i = 0, l = autoParam.length; node && i < l; i++) { + var pKey = autoParam[i].split("="), spKey = pKey; + if (pKey.length > 1) { + spKey = pKey[1]; + pKey = pKey[0]; + } + tmpParam[spKey] = node[pKey]; + } + var otherParam = tools.apply(setting.async.otherParam, [setting.treeId, node], setting.async.otherParam); + if (tools.isArray(otherParam)) { + for (i = 0, l = otherParam.length; i < l; i += 2) { + tmpParam[otherParam[i]] = otherParam[i + 1]; + } + } else { + for (var p in otherParam) { + tmpParam[p] = otherParam[p]; + } + } + + var _tmpV = data.getRoot(setting)._ver; + $.ajax({ + contentType: setting.async.contentType, + cache: false, + type: setting.async.type, + url: tools.apply(setting.async.url, [setting.treeId, node], setting.async.url), + data: setting.async.contentType.indexOf('application/json') > -1 ? JSON.stringify(tmpParam) : tmpParam, + dataType: setting.async.dataType, + headers: setting.async.headers, + xhrFields: setting.async.xhrFields, + success: function (msg) { + if (_tmpV != data.getRoot(setting)._ver) { + return; + } + var newNodes = []; + try { + if (!msg || msg.length == 0) { + newNodes = []; + } else if (typeof msg == "string") { + newNodes = eval("(" + msg + ")"); + } else { + newNodes = msg; + } + } catch (err) { + newNodes = msg; + } + + if (node) { + node.isAjaxing = null; + node.zAsync = true; + } + view.setNodeLineIcos(setting, node); + if (newNodes && newNodes !== "") { + newNodes = tools.apply(setting.async.dataFilter, [setting.treeId, node, newNodes], newNodes); + view.addNodes(setting, node, -1, !!newNodes ? tools.clone(newNodes) : [], !!isSilent); + } else { + view.addNodes(setting, node, -1, [], !!isSilent); + } + setting.treeObj.trigger(consts.event.ASYNC_SUCCESS, [setting.treeId, node, msg]); + tools.apply(callback); + }, + error: function (XMLHttpRequest, textStatus, errorThrown) { + if (_tmpV != data.getRoot(setting)._ver) { + return; + } + if (node) node.isAjaxing = null; + view.setNodeLineIcos(setting, node); + setting.treeObj.trigger(consts.event.ASYNC_ERROR, [setting.treeId, node, XMLHttpRequest, textStatus, errorThrown]); + } + }); + return true; + }, + cancelPreSelectedNode: function (setting, node, excludeNode) { + var list = data.getRoot(setting).curSelectedList, + i, n; + for (i = list.length - 1; i >= 0; i--) { + n = list[i]; + if (node === n || (!node && (!excludeNode || excludeNode !== n))) { + $$(n, consts.id.A, setting).removeClass(consts.node.CURSELECTED); + if (node) { + data.removeSelectedNode(setting, node); + break; + } else { + list.splice(i, 1); + setting.treeObj.trigger(consts.event.UNSELECTED, [setting.treeId, n]); + } + } + } + }, + createNodeCallback: function (setting) { + if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) { + var root = data.getRoot(setting); + while (root.createdNodes.length > 0) { + var node = root.createdNodes.shift(); + tools.apply(setting.view.addDiyDom, [setting.treeId, node]); + if (!!setting.callback.onNodeCreated) { + setting.treeObj.trigger(consts.event.NODECREATED, [setting.treeId, node]); + } + } + } + }, + createNodes: function (setting, level, nodes, parentNode, index) { + if (!nodes || nodes.length == 0) return; + var root = data.getRoot(setting), + openFlag = !parentNode || parentNode.open || !!$$(data.nodeChildren(setting, parentNode)[0], setting).get(0); + root.createdNodes = []; + var zTreeHtml = view.appendNodes(setting, level, nodes, parentNode, index, true, openFlag), + parentObj, nextObj; + + if (!parentNode) { + parentObj = setting.treeObj; + //setting.treeObj.append(zTreeHtml.join('')); + } else { + var ulObj = $$(parentNode, consts.id.UL, setting); + if (ulObj.get(0)) { + parentObj = ulObj; + //ulObj.append(zTreeHtml.join('')); + } + } + if (parentObj) { + if (index >= 0) { + nextObj = parentObj.children()[index]; + } + if (index >= 0 && nextObj) { + $(nextObj).before(zTreeHtml.join('')); + } else { + parentObj.append(zTreeHtml.join('')); + } + } + + view.createNodeCallback(setting); + }, + destroy: function (setting) { + if (!setting) return; + data.initCache(setting); + data.initRoot(setting); + event.unbindTree(setting); + event.unbindEvent(setting); + setting.treeObj.empty(); + delete settings[setting.treeId]; + }, + expandCollapseNode: function (setting, node, expandFlag, animateFlag, callback) { + var root = data.getRoot(setting); + var tmpCb, _callback; + if (!node) { + tools.apply(callback, []); + return; + } + var children = data.nodeChildren(setting, node); + var isParent = data.nodeIsParent(setting, node); + if (root.expandTriggerFlag) { + _callback = callback; + tmpCb = function () { + if (_callback) _callback(); + if (node.open) { + setting.treeObj.trigger(consts.event.EXPAND, [setting.treeId, node]); + } else { + setting.treeObj.trigger(consts.event.COLLAPSE, [setting.treeId, node]); + } + }; + callback = tmpCb; + root.expandTriggerFlag = false; + } + if (!node.open && isParent && ((!$$(node, consts.id.UL, setting).get(0)) || (children && children.length > 0 && !$$(children[0], setting).get(0)))) { + view.appendParentULDom(setting, node); + view.createNodeCallback(setting); + } + if (node.open == expandFlag) { + tools.apply(callback, []); + return; + } + var ulObj = $$(node, consts.id.UL, setting), + switchObj = $$(node, consts.id.SWITCH, setting), + icoObj = $$(node, consts.id.ICON, setting); + + if (isParent) { + node.open = !node.open; + if (node.iconOpen && node.iconClose) { + icoObj.attr("style", view.makeNodeIcoStyle(setting, node)); + } + + if (node.open) { + view.replaceSwitchClass(node, switchObj, consts.folder.OPEN); + view.replaceIcoClass(node, icoObj, consts.folder.OPEN); + if (animateFlag == false || setting.view.expandSpeed == "") { + ulObj.show(); + tools.apply(callback, []); + } else { + if (children && children.length > 0) { + ulObj.slideDown(setting.view.expandSpeed, callback); + } else { + ulObj.show(); + tools.apply(callback, []); + } + } + } else { + view.replaceSwitchClass(node, switchObj, consts.folder.CLOSE); + view.replaceIcoClass(node, icoObj, consts.folder.CLOSE); + if (animateFlag == false || setting.view.expandSpeed == "" || !(children && children.length > 0)) { + ulObj.hide(); + tools.apply(callback, []); + } else { + ulObj.slideUp(setting.view.expandSpeed, callback); + } + } + } else { + tools.apply(callback, []); + } + }, + expandCollapseParentNode: function (setting, node, expandFlag, animateFlag, callback) { + if (!node) return; + if (!node.parentTId) { + view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback); + return; + } else { + view.expandCollapseNode(setting, node, expandFlag, animateFlag); + } + if (node.parentTId) { + view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, animateFlag, callback); + } + }, + expandCollapseSonNode: function (setting, node, expandFlag, animateFlag, callback) { + var root = data.getRoot(setting), + treeNodes = (node) ? data.nodeChildren(setting, node) : data.nodeChildren(setting, root), + selfAnimateSign = (node) ? false : animateFlag, + expandTriggerFlag = data.getRoot(setting).expandTriggerFlag; + data.getRoot(setting).expandTriggerFlag = false; + if (treeNodes) { + for (var i = 0, l = treeNodes.length; i < l; i++) { + if (treeNodes[i]) view.expandCollapseSonNode(setting, treeNodes[i], expandFlag, selfAnimateSign); + } + } + data.getRoot(setting).expandTriggerFlag = expandTriggerFlag; + view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback); + }, + isSelectedNode: function (setting, node) { + if (!node) { + return false; + } + var list = data.getRoot(setting).curSelectedList, + i; + for (i = list.length - 1; i >= 0; i--) { + if (node === list[i]) { + return true; + } + } + return false; + }, + makeDOMNodeIcon: function (html, setting, node) { + var nameStr = data.nodeName(setting, node), + name = setting.view.nameIsHTML ? nameStr : nameStr.replace(/&/g, '&').replace(//g, '>'); + html.push("", name, ""); + }, + makeDOMNodeLine: function (html, setting, node) { + html.push(""); + }, + makeDOMNodeMainAfter: function (html, setting, node) { + html.push("
    • "); + }, + makeDOMNodeMainBefore: function (html, setting, node) { + html.push("
    • "); + }, + makeDOMNodeNameAfter: function (html, setting, node) { + html.push(""); + }, + makeDOMNodeNameBefore: function (html, setting, node) { + var title = data.nodeTitle(setting, node), + url = view.makeNodeUrl(setting, node), + fontcss = view.makeNodeFontCss(setting, node), + nodeClasses = view.makeNodeClasses(setting, node), + fontStyle = []; + for (var f in fontcss) { + fontStyle.push(f, ":", fontcss[f], ";"); + } + html.push(" 0) ? " href='" + url + "'" : ""), " target='", view.makeNodeTarget(node), "' style='", fontStyle.join(''), + "'"); + if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle) && title) { + html.push("title='", title.replace(/'/g, "'").replace(//g, '>'), "'"); + } + html.push(">"); + }, + makeNodeFontCss: function (setting, node) { + var fontCss = tools.apply(setting.view.fontCss, [setting.treeId, node], setting.view.fontCss); + return (fontCss && ((typeof fontCss) != "function")) ? fontCss : {}; + }, + makeNodeClasses: function (setting, node) { + var classes = tools.apply(setting.view.nodeClasses, [setting.treeId, node], setting.view.nodeClasses); + return (classes && (typeof classes !== "function")) ? classes : {add:[], remove:[]}; + }, + makeNodeIcoClass: function (setting, node) { + var icoCss = ["ico"]; + if (!node.isAjaxing) { + var isParent = data.nodeIsParent(setting, node); + icoCss[0] = (node.iconSkin ? node.iconSkin + "_" : "") + icoCss[0]; + if (isParent) { + icoCss.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE); + } else { + icoCss.push(consts.folder.DOCU); + } + } + return consts.className.BUTTON + " " + icoCss.join('_'); + }, + makeNodeIcoStyle: function (setting, node) { + var icoStyle = []; + if (!node.isAjaxing) { + var isParent = data.nodeIsParent(setting, node); + var icon = (isParent && node.iconOpen && node.iconClose) ? (node.open ? node.iconOpen : node.iconClose) : node[setting.data.key.icon]; + if (icon) icoStyle.push("background:url(", icon, ") 0 0 no-repeat;"); + if (setting.view.showIcon == false || !tools.apply(setting.view.showIcon, [setting.treeId, node], true)) { + icoStyle.push("display:none;"); + } + } + return icoStyle.join(''); + }, + makeNodeLineClass: function (setting, node) { + var lineClass = []; + if (setting.view.showLine) { + if (node.level == 0 && node.isFirstNode && node.isLastNode) { + lineClass.push(consts.line.ROOT); + } else if (node.level == 0 && node.isFirstNode) { + lineClass.push(consts.line.ROOTS); + } else if (node.isLastNode) { + lineClass.push(consts.line.BOTTOM); + } else { + lineClass.push(consts.line.CENTER); + } + } else { + lineClass.push(consts.line.NOLINE); + } + if (data.nodeIsParent(setting, node)) { + lineClass.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE); + } else { + lineClass.push(consts.folder.DOCU); + } + return view.makeNodeLineClassEx(node) + lineClass.join('_'); + }, + makeNodeLineClassEx: function (node) { + return consts.className.BUTTON + " " + consts.className.LEVEL + node.level + " " + consts.className.SWITCH + " "; + }, + makeNodeTarget: function (node) { + return (node.target || "_blank"); + }, + makeNodeUrl: function (setting, node) { + var urlKey = setting.data.key.url; + return node[urlKey] ? node[urlKey] : null; + }, + makeUlHtml: function (setting, node, html, content) { + html.push("
        "); + html.push(content); + html.push("
      "); + }, + makeUlLineClass: function (setting, node) { + return ((setting.view.showLine && !node.isLastNode) ? consts.line.LINE : ""); + }, + removeChildNodes: function (setting, node) { + if (!node) return; + var nodes = data.nodeChildren(setting, node); + if (!nodes) return; + + for (var i = 0, l = nodes.length; i < l; i++) { + data.removeNodeCache(setting, nodes[i]); + } + data.removeSelectedNode(setting); + delete node[setting.data.key.children]; + + if (!setting.data.keep.parent) { + data.nodeIsParent(setting, node, false); + node.open = false; + var tmp_switchObj = $$(node, consts.id.SWITCH, setting), + tmp_icoObj = $$(node, consts.id.ICON, setting); + view.replaceSwitchClass(node, tmp_switchObj, consts.folder.DOCU); + view.replaceIcoClass(node, tmp_icoObj, consts.folder.DOCU); + $$(node, consts.id.UL, setting).remove(); + } else { + $$(node, consts.id.UL, setting).empty(); + } + }, + scrollIntoView: function (setting, dom) { + if (!dom) { + return; + } + // support IE 7 / 8 + if (typeof Element === 'undefined' || typeof HTMLElement === 'undefined') { + var contRect = setting.treeObj.get(0).getBoundingClientRect(), + findMeRect = dom.getBoundingClientRect(); + if (findMeRect.top < contRect.top || findMeRect.bottom > contRect.bottom + || findMeRect.right > contRect.right || findMeRect.left < contRect.left) { + dom.scrollIntoView(); + } + return; + } + // CC-BY jocki84@googlemail.com, https://gist.github.com/jocki84/6ffafd003387179a988e + if (!Element.prototype.scrollIntoViewIfNeeded) { + Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) { + "use strict"; + + function makeRange(start, length) { + return {"start": start, "length": length, "end": start + length}; + } + + function coverRange(inner, outer) { + if ( + false === centerIfNeeded || + (outer.start < inner.end && inner.start < outer.end) + ) { + return Math.max( + inner.end - outer.length, + Math.min(outer.start, inner.start) + ); + } + return (inner.start + inner.end - outer.length) / 2; + } + + function makePoint(x, y) { + return { + "x": x, + "y": y, + "translate": function translate(dX, dY) { + return makePoint(x + dX, y + dY); + } + }; + } + + function absolute(elem, pt) { + while (elem) { + pt = pt.translate(elem.offsetLeft, elem.offsetTop); + elem = elem.offsetParent; + } + return pt; + } + + var target = absolute(this, makePoint(0, 0)), + extent = makePoint(this.offsetWidth, this.offsetHeight), + elem = this.parentNode, + origin; + + while (elem instanceof HTMLElement) { + // Apply desired scroll amount. + origin = absolute(elem, makePoint(elem.clientLeft, elem.clientTop)); + elem.scrollLeft = coverRange( + makeRange(target.x - origin.x, extent.x), + makeRange(elem.scrollLeft, elem.clientWidth) + ); + elem.scrollTop = coverRange( + makeRange(target.y - origin.y, extent.y), + makeRange(elem.scrollTop, elem.clientHeight) + ); + + // Determine actual scroll amount by reading back scroll properties. + target = target.translate(-elem.scrollLeft, -elem.scrollTop); + elem = elem.parentNode; + } + }; + } + dom.scrollIntoViewIfNeeded(); + }, + setFirstNode: function (setting, parentNode) { + var children = data.nodeChildren(setting, parentNode); + if (children.length > 0) { + children[0].isFirstNode = true; + } + }, + setLastNode: function (setting, parentNode) { + var children = data.nodeChildren(setting, parentNode); + if (children.length > 0) { + children[children.length - 1].isLastNode = true; + } + }, + removeNode: function (setting, node) { + var root = data.getRoot(setting), + parentNode = (node.parentTId) ? node.getParentNode() : root; + + node.isFirstNode = false; + node.isLastNode = false; + node.getPreNode = function () { + return null; + }; + node.getNextNode = function () { + return null; + }; + + if (!data.getNodeCache(setting, node.tId)) { + return; + } + + $$(node, setting).remove(); + data.removeNodeCache(setting, node); + data.removeSelectedNode(setting, node); + + var children = data.nodeChildren(setting, parentNode); + for (var i = 0, l = children.length; i < l; i++) { + if (children[i].tId == node.tId) { + children.splice(i, 1); + break; + } + } + view.setFirstNode(setting, parentNode); + view.setLastNode(setting, parentNode); + + var tmp_ulObj, tmp_switchObj, tmp_icoObj, + childLength = children.length; + + //repair nodes old parent + if (!setting.data.keep.parent && childLength == 0) { + //old parentNode has no child nodes + data.nodeIsParent(setting, parentNode, false); + parentNode.open = false; + delete parentNode[setting.data.key.children]; + tmp_ulObj = $$(parentNode, consts.id.UL, setting); + tmp_switchObj = $$(parentNode, consts.id.SWITCH, setting); + tmp_icoObj = $$(parentNode, consts.id.ICON, setting); + view.replaceSwitchClass(parentNode, tmp_switchObj, consts.folder.DOCU); + view.replaceIcoClass(parentNode, tmp_icoObj, consts.folder.DOCU); + tmp_ulObj.css("display", "none"); + + } else if (setting.view.showLine && childLength > 0) { + //old parentNode has child nodes + var newLast = children[childLength - 1]; + tmp_ulObj = $$(newLast, consts.id.UL, setting); + tmp_switchObj = $$(newLast, consts.id.SWITCH, setting); + tmp_icoObj = $$(newLast, consts.id.ICON, setting); + if (parentNode == root) { + if (children.length == 1) { + //node was root, and ztree has only one root after move node + view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.ROOT); + } else { + var tmp_first_switchObj = $$(children[0], consts.id.SWITCH, setting); + view.replaceSwitchClass(children[0], tmp_first_switchObj, consts.line.ROOTS); + view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM); + } + } else { + view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM); + } + tmp_ulObj.removeClass(consts.line.LINE); + } + }, + replaceIcoClass: function (node, obj, newName) { + if (!obj || node.isAjaxing) return; + var tmpName = obj.attr("class"); + if (tmpName == undefined) return; + var tmpList = tmpName.split("_"); + switch (newName) { + case consts.folder.OPEN: + case consts.folder.CLOSE: + case consts.folder.DOCU: + tmpList[tmpList.length - 1] = newName; + break; + } + obj.attr("class", tmpList.join("_")); + }, + replaceSwitchClass: function (node, obj, newName) { + if (!obj) return; + var tmpName = obj.attr("class"); + if (tmpName == undefined) return; + var tmpList = tmpName.split("_"); + switch (newName) { + case consts.line.ROOT: + case consts.line.ROOTS: + case consts.line.CENTER: + case consts.line.BOTTOM: + case consts.line.NOLINE: + tmpList[0] = view.makeNodeLineClassEx(node) + newName; + break; + case consts.folder.OPEN: + case consts.folder.CLOSE: + case consts.folder.DOCU: + tmpList[1] = newName; + break; + } + obj.attr("class", tmpList.join("_")); + if (newName !== consts.folder.DOCU) { + obj.removeAttr("disabled"); + } else { + obj.attr("disabled", "disabled"); + } + }, + selectNode: function (setting, node, addFlag) { + if (!addFlag) { + view.cancelPreSelectedNode(setting, null, node); + } + $$(node, consts.id.A, setting).addClass(consts.node.CURSELECTED); + data.addSelectedNode(setting, node); + setting.treeObj.trigger(consts.event.SELECTED, [setting.treeId, node]); + }, + setNodeFontCss: function (setting, treeNode) { + var aObj = $$(treeNode, consts.id.A, setting), + fontCss = view.makeNodeFontCss(setting, treeNode); + if (fontCss) { + aObj.css(fontCss); + } + }, + setNodeClasses: function (setting, treeNode) { + var aObj = $$(treeNode, consts.id.A, setting), + classes = view.makeNodeClasses(setting, treeNode); + if ('add' in classes && classes.add.length) { + aObj.addClass(classes.add.join(' ')); + } + if ('remove' in classes && classes.remove.length) { + aObj.removeClass(classes.remove.join(' ')); + } + }, + setNodeLineIcos: function (setting, node) { + if (!node) return; + var switchObj = $$(node, consts.id.SWITCH, setting), + ulObj = $$(node, consts.id.UL, setting), + icoObj = $$(node, consts.id.ICON, setting), + ulLine = view.makeUlLineClass(setting, node); + if (ulLine.length == 0) { + ulObj.removeClass(consts.line.LINE); + } else { + ulObj.addClass(ulLine); + } + switchObj.attr("class", view.makeNodeLineClass(setting, node)); + if (data.nodeIsParent(setting, node)) { + switchObj.removeAttr("disabled"); + } else { + switchObj.attr("disabled", "disabled"); + } + icoObj.removeAttr("style"); + icoObj.attr("style", view.makeNodeIcoStyle(setting, node)); + icoObj.attr("class", view.makeNodeIcoClass(setting, node)); + }, + setNodeName: function (setting, node) { + var title = data.nodeTitle(setting, node), + nObj = $$(node, consts.id.SPAN, setting); + nObj.empty(); + if (setting.view.nameIsHTML) { + nObj.html(data.nodeName(setting, node)); + } else { + nObj.text(data.nodeName(setting, node)); + } + if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle)) { + var aObj = $$(node, consts.id.A, setting); + aObj.attr("title", !title ? "" : title); + } + }, + setNodeTarget: function (setting, node) { + var aObj = $$(node, consts.id.A, setting); + aObj.attr("target", view.makeNodeTarget(node)); + }, + setNodeUrl: function (setting, node) { + var aObj = $$(node, consts.id.A, setting), + url = view.makeNodeUrl(setting, node); + if (url == null || url.length == 0) { + aObj.removeAttr("href"); + } else { + aObj.attr("href", url); + } + }, + switchNode: function (setting, node) { + if (node.open || !tools.canAsync(setting, node)) { + view.expandCollapseNode(setting, node, !node.open); + } else if (setting.async.enable) { + if (!view.asyncNode(setting, node)) { + view.expandCollapseNode(setting, node, !node.open); + return; + } + } else if (node) { + view.expandCollapseNode(setting, node, !node.open); + } + } + }; + // zTree defind + $.fn.zTree = { + consts: _consts, + _z: { + tools: tools, + view: view, + event: event, + data: data + }, + getZTreeObj: function (treeId) { + var o = data.getZTreeTools(treeId); + return o ? o : null; + }, + destroy: function (treeId) { + if (!!treeId && treeId.length > 0) { + view.destroy(data.getSetting(treeId)); + } else { + for (var s in settings) { + view.destroy(settings[s]); + } + } + }, + init: function (obj, zSetting, zNodes) { + var setting = tools.clone(_setting); + $.extend(true, setting, zSetting); + setting.treeId = obj.attr("id"); + setting.treeObj = obj; + setting.treeObj.empty(); + settings[setting.treeId] = setting; + //For some older browser,(e.g., ie6) + if (typeof document.body.style.maxHeight === "undefined") { + setting.view.expandSpeed = ""; + } + data.initRoot(setting); + var root = data.getRoot(setting); + zNodes = zNodes ? tools.clone(tools.isArray(zNodes) ? zNodes : [zNodes]) : []; + if (setting.data.simpleData.enable) { + data.nodeChildren(setting, root, data.transformTozTreeFormat(setting, zNodes)); + } else { + data.nodeChildren(setting, root, zNodes); + } + + data.initCache(setting); + event.unbindTree(setting); + event.bindTree(setting); + event.unbindEvent(setting); + event.bindEvent(setting); + + var zTreeTools = { + setting: setting, + addNodes: function (parentNode, index, newNodes, isSilent) { + if (!parentNode) parentNode = null; + var isParent = data.nodeIsParent(setting, parentNode); + if (parentNode && !isParent && setting.data.keep.leaf) return null; + + var i = parseInt(index, 10); + if (isNaN(i)) { + isSilent = !!newNodes; + newNodes = index; + index = -1; + } else { + index = i; + } + if (!newNodes) return null; + + + var xNewNodes = tools.clone(tools.isArray(newNodes) ? newNodes : [newNodes]); + + function addCallback() { + view.addNodes(setting, parentNode, index, xNewNodes, (isSilent == true)); + } + + if (tools.canAsync(setting, parentNode)) { + view.asyncNode(setting, parentNode, isSilent, addCallback); + } else { + addCallback(); + } + return xNewNodes; + }, + cancelSelectedNode: function (node) { + view.cancelPreSelectedNode(setting, node); + }, + destroy: function () { + view.destroy(setting); + }, + expandAll: function (expandFlag) { + expandFlag = !!expandFlag; + view.expandCollapseSonNode(setting, null, expandFlag, true); + return expandFlag; + }, + expandNode: function (node, expandFlag, sonSign, focus, callbackFlag) { + if (!node || !data.nodeIsParent(setting, node)) return null; + if (expandFlag !== true && expandFlag !== false) { + expandFlag = !node.open; + } + callbackFlag = !!callbackFlag; + + if (callbackFlag && expandFlag && (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false)) { + return null; + } else if (callbackFlag && !expandFlag && (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false)) { + return null; + } + if (expandFlag && node.parentTId) { + view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, false); + } + if (expandFlag === node.open && !sonSign) { + return null; + } + + data.getRoot(setting).expandTriggerFlag = callbackFlag; + if (!tools.canAsync(setting, node) && sonSign) { + view.expandCollapseSonNode(setting, node, expandFlag, true, showNodeFocus); + } else { + node.open = !expandFlag; + view.switchNode(this.setting, node); + showNodeFocus(); + } + return expandFlag; + + function showNodeFocus() { + var a = $$(node, consts.id.A, setting).get(0); + if (a && focus !== false) { + view.scrollIntoView(setting, a); + } + } + }, + getNodes: function () { + return data.getNodes(setting); + }, + getNodeByParam: function (key, value, parentNode) { + if (!key) return null; + return data.getNodeByParam(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), key, value); + }, + getNodeByTId: function (tId) { + return data.getNodeCache(setting, tId); + }, + getNodesByParam: function (key, value, parentNode) { + if (!key) return null; + return data.getNodesByParam(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), key, value); + }, + getNodesByParamFuzzy: function (key, value, parentNode) { + if (!key) return null; + return data.getNodesByParamFuzzy(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), key, value); + }, + getNodesByFilter: function (filter, isSingle, parentNode, invokeParam) { + isSingle = !!isSingle; + if (!filter || (typeof filter != "function")) return (isSingle ? null : []); + return data.getNodesByFilter(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), filter, isSingle, invokeParam); + }, + getNodeIndex: function (node) { + if (!node) return null; + var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting); + var children = data.nodeChildren(setting, parentNode); + for (var i = 0, l = children.length; i < l; i++) { + if (children[i] == node) return i; + } + return -1; + }, + getSelectedNodes: function () { + var r = [], list = data.getRoot(setting).curSelectedList; + for (var i = 0, l = list.length; i < l; i++) { + r.push(list[i]); + } + return r; + }, + isSelectedNode: function (node) { + return data.isSelectedNode(setting, node); + }, + reAsyncChildNodesPromise: function (parentNode, reloadType, isSilent) { + var promise = new Promise(function (resolve, reject) { + try { + zTreeTools.reAsyncChildNodes(parentNode, reloadType, isSilent, function () { + resolve(parentNode); + }); + } catch (e) { + reject(e); + } + }); + return promise; + }, + reAsyncChildNodes: function (parentNode, reloadType, isSilent, callback) { + if (!this.setting.async.enable) return; + var isRoot = !parentNode; + if (isRoot) { + parentNode = data.getRoot(setting); + } + if (reloadType == "refresh") { + var children = data.nodeChildren(setting, parentNode); + for (var i = 0, l = children ? children.length : 0; i < l; i++) { + data.removeNodeCache(setting, children[i]); + } + data.removeSelectedNode(setting); + data.nodeChildren(setting, parentNode, []); + if (isRoot) { + this.setting.treeObj.empty(); + } else { + var ulObj = $$(parentNode, consts.id.UL, setting); + ulObj.empty(); + } + } + view.asyncNode(this.setting, isRoot ? null : parentNode, !!isSilent, callback); + }, + refresh: function () { + this.setting.treeObj.empty(); + var root = data.getRoot(setting), + nodes = data.nodeChildren(setting, root); + data.initRoot(setting); + data.nodeChildren(setting, root, nodes); + data.initCache(setting); + view.createNodes(setting, 0, data.nodeChildren(setting, root), null, -1); + }, + removeChildNodes: function (node) { + if (!node) return null; + var nodes = data.nodeChildren(setting, node); + view.removeChildNodes(setting, node); + return nodes ? nodes : null; + }, + removeNode: function (node, callbackFlag) { + if (!node) return; + callbackFlag = !!callbackFlag; + if (callbackFlag && tools.apply(setting.callback.beforeRemove, [setting.treeId, node], true) == false) return; + view.removeNode(setting, node); + if (callbackFlag) { + this.setting.treeObj.trigger(consts.event.REMOVE, [setting.treeId, node]); + } + }, + selectNode: function (node, addFlag, isSilent) { + if (!node) return; + if (tools.uCanDo(setting)) { + addFlag = setting.view.selectedMulti && addFlag; + if (node.parentTId) { + view.expandCollapseParentNode(setting, node.getParentNode(), true, false, showNodeFocus); + } else if (!isSilent) { + try { + $$(node, setting).focus().blur(); + } catch (e) { + } + } + view.selectNode(setting, node, addFlag); + } + + function showNodeFocus() { + if (isSilent) { + return; + } + var a = $$(node, setting).get(0); + view.scrollIntoView(setting, a); + } + }, + transformTozTreeNodes: function (simpleNodes) { + return data.transformTozTreeFormat(setting, simpleNodes); + }, + transformToArray: function (nodes) { + return data.transformToArrayFormat(setting, nodes); + }, + updateNode: function (node, checkTypeFlag) { + if (!node) return; + var nObj = $$(node, setting); + if (nObj.get(0) && tools.uCanDo(setting)) { + view.setNodeName(setting, node); + view.setNodeTarget(setting, node); + view.setNodeUrl(setting, node); + view.setNodeLineIcos(setting, node); + view.setNodeFontCss(setting, node); + view.setNodeClasses(setting, node); + } + } + }; + root.treeTools = zTreeTools; + data.setZTreeTools(setting, zTreeTools); + var children = data.nodeChildren(setting, root); + if (children && children.length > 0) { + view.createNodes(setting, 0, children, null, -1); + } else if (setting.async.enable && setting.async.url && setting.async.url !== '') { + view.asyncNode(setting); + } + return zTreeTools; + } + }; + + var zt = $.fn.zTree, + $$ = tools.$, + consts = zt.consts; +})(jQuery); \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/static/plugins/zTree/js/jquery.ztree.excheck.js b/xxl-job-admin/src/main/resources/static/plugins/zTree/js/jquery.ztree.excheck.js new file mode 100644 index 00000000..f855dcfa --- /dev/null +++ b/xxl-job-admin/src/main/resources/static/plugins/zTree/js/jquery.ztree.excheck.js @@ -0,0 +1,652 @@ +/* + * JQuery zTree excheck + * v3.5.42 + * http://treejs.cn/ + * + * Copyright (c) 2010 Hunter.z + * + * Licensed same as jquery - MIT License + * http://www.opensource.org/licenses/mit-license.php + * + * Date: 2020-01-19 + */ + +(function ($) { + //default consts of excheck + var _consts = { + event: { + CHECK: "ztree_check" + }, + id: { + CHECK: "_check" + }, + checkbox: { + STYLE: "checkbox", + DEFAULT: "chk", + DISABLED: "disable", + FALSE: "false", + TRUE: "true", + FULL: "full", + PART: "part", + FOCUS: "focus" + }, + radio: { + STYLE: "radio", + TYPE_ALL: "all", + TYPE_LEVEL: "level" + } + }, + //default setting of excheck + _setting = { + check: { + enable: false, + autoCheckTrigger: false, + chkStyle: _consts.checkbox.STYLE, + nocheckInherit: false, + chkDisabledInherit: false, + radioType: _consts.radio.TYPE_LEVEL, + chkboxType: { + "Y": "ps", + "N": "ps" + } + }, + data: { + key: { + checked: "checked" + } + }, + callback: { + beforeCheck: null, + onCheck: null + } + }, + //default root of excheck + _initRoot = function (setting) { + var r = data.getRoot(setting); + r.radioCheckedList = []; + }, + //default cache of excheck + _initCache = function (treeId) { + }, + //default bind event of excheck + _bindEvent = function (setting) { + var o = setting.treeObj, + c = consts.event; + o.bind(c.CHECK, function (event, srcEvent, treeId, node) { + event.srcEvent = srcEvent; + tools.apply(setting.callback.onCheck, [event, treeId, node]); + }); + }, + _unbindEvent = function (setting) { + var o = setting.treeObj, + c = consts.event; + o.unbind(c.CHECK); + }, + //default event proxy of excheck + _eventProxy = function (e) { + var target = e.target, + setting = data.getSetting(e.data.treeId), + tId = "", node = null, + nodeEventType = "", treeEventType = "", + nodeEventCallback = null, treeEventCallback = null; + + if (tools.eqs(e.type, "mouseover")) { + if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.CHECK) !== null) { + tId = tools.getNodeMainDom(target).id; + nodeEventType = "mouseoverCheck"; + } + } else if (tools.eqs(e.type, "mouseout")) { + if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.CHECK) !== null) { + tId = tools.getNodeMainDom(target).id; + nodeEventType = "mouseoutCheck"; + } + } else if (tools.eqs(e.type, "click")) { + if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.CHECK) !== null) { + tId = tools.getNodeMainDom(target).id; + nodeEventType = "checkNode"; + } + } + if (tId.length > 0) { + node = data.getNodeCache(setting, tId); + switch (nodeEventType) { + case "checkNode" : + nodeEventCallback = _handler.onCheckNode; + break; + case "mouseoverCheck" : + nodeEventCallback = _handler.onMouseoverCheck; + break; + case "mouseoutCheck" : + nodeEventCallback = _handler.onMouseoutCheck; + break; + } + } + var proxyResult = { + stop: nodeEventType === "checkNode", + node: node, + nodeEventType: nodeEventType, + nodeEventCallback: nodeEventCallback, + treeEventType: treeEventType, + treeEventCallback: treeEventCallback + }; + return proxyResult + }, + //default init node of excheck + _initNode = function (setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { + if (!n) return; + var checked = data.nodeChecked(setting, n); + n.checkedOld = checked; + if (typeof n.nocheck == "string") n.nocheck = tools.eqs(n.nocheck, "true"); + n.nocheck = !!n.nocheck || (setting.check.nocheckInherit && parentNode && !!parentNode.nocheck); + if (typeof n.chkDisabled == "string") n.chkDisabled = tools.eqs(n.chkDisabled, "true"); + n.chkDisabled = !!n.chkDisabled || (setting.check.chkDisabledInherit && parentNode && !!parentNode.chkDisabled); + if (typeof n.halfCheck == "string") n.halfCheck = tools.eqs(n.halfCheck, "true"); + n.halfCheck = !!n.halfCheck; + n.check_Child_State = -1; + n.check_Focus = false; + n.getCheckStatus = function () { + return data.getCheckStatus(setting, n); + }; + + if (setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL && checked) { + var r = data.getRoot(setting); + r.radioCheckedList.push(n); + } + }, + //add dom for check + _beforeA = function (setting, node, html) { + if (setting.check.enable) { + data.makeChkFlag(setting, node); + html.push(""); + } + }, + //update zTreeObj, add method of check + _zTreeTools = function (setting, zTreeTools) { + zTreeTools.checkNode = function (node, checked, checkTypeFlag, callbackFlag) { + var nodeChecked = data.nodeChecked(setting, node); + if (node.chkDisabled === true) return; + if (checked !== true && checked !== false) { + checked = !nodeChecked; + } + callbackFlag = !!callbackFlag; + + if (nodeChecked === checked && !checkTypeFlag) { + return; + } else if (callbackFlag && tools.apply(this.setting.callback.beforeCheck, [this.setting.treeId, node], true) == false) { + return; + } + if (tools.uCanDo(this.setting) && this.setting.check.enable && node.nocheck !== true) { + data.nodeChecked(setting, node, checked); + var checkObj = $$(node, consts.id.CHECK, this.setting); + if (checkTypeFlag || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node); + view.setChkClass(this.setting, checkObj, node); + view.repairParentChkClassWithSelf(this.setting, node); + if (callbackFlag) { + this.setting.treeObj.trigger(consts.event.CHECK, [null, this.setting.treeId, node]); + } + } + } + + zTreeTools.checkAllNodes = function (checked) { + view.repairAllChk(this.setting, !!checked); + } + + zTreeTools.getCheckedNodes = function (checked) { + checked = (checked !== false); + var children = data.nodeChildren(setting, data.getRoot(this.setting)); + return data.getTreeCheckedNodes(this.setting, children, checked); + } + + zTreeTools.getChangeCheckedNodes = function () { + var children = data.nodeChildren(setting, data.getRoot(this.setting)); + return data.getTreeChangeCheckedNodes(this.setting, children); + } + + zTreeTools.setChkDisabled = function (node, disabled, inheritParent, inheritChildren) { + disabled = !!disabled; + inheritParent = !!inheritParent; + inheritChildren = !!inheritChildren; + view.repairSonChkDisabled(this.setting, node, disabled, inheritChildren); + view.repairParentChkDisabled(this.setting, node.getParentNode(), disabled, inheritParent); + } + + var _updateNode = zTreeTools.updateNode; + zTreeTools.updateNode = function (node, checkTypeFlag) { + if (_updateNode) _updateNode.apply(zTreeTools, arguments); + if (!node || !this.setting.check.enable) return; + var nObj = $$(node, this.setting); + if (nObj.get(0) && tools.uCanDo(this.setting)) { + var checkObj = $$(node, consts.id.CHECK, this.setting); + if (checkTypeFlag == true || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node); + view.setChkClass(this.setting, checkObj, node); + view.repairParentChkClassWithSelf(this.setting, node); + } + } + }, + //method of operate data + _data = { + getRadioCheckedList: function (setting) { + var checkedList = data.getRoot(setting).radioCheckedList; + for (var i = 0, j = checkedList.length; i < j; i++) { + if (!data.getNodeCache(setting, checkedList[i].tId)) { + checkedList.splice(i, 1); + i--; + j--; + } + } + return checkedList; + }, + getCheckStatus: function (setting, node) { + if (!setting.check.enable || node.nocheck || node.chkDisabled) return null; + var checked = data.nodeChecked(setting, node), + r = { + checked: checked, + half: node.halfCheck ? node.halfCheck : (setting.check.chkStyle == consts.radio.STYLE ? (node.check_Child_State === 2) : (checked ? (node.check_Child_State > -1 && node.check_Child_State < 2) : (node.check_Child_State > 0))) + }; + return r; + }, + getTreeCheckedNodes: function (setting, nodes, checked, results) { + if (!nodes) return []; + var onlyOne = (checked && setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL); + results = !results ? [] : results; + for (var i = 0, l = nodes.length; i < l; i++) { + var node = nodes[i]; + var children = data.nodeChildren(setting, node); + var nodeChecked = data.nodeChecked(setting, node); + if (node.nocheck !== true && node.chkDisabled !== true && nodeChecked == checked) { + results.push(node); + if (onlyOne) { + break; + } + } + data.getTreeCheckedNodes(setting, children, checked, results); + if (onlyOne && results.length > 0) { + break; + } + } + return results; + }, + getTreeChangeCheckedNodes: function (setting, nodes, results) { + if (!nodes) return []; + results = !results ? [] : results; + for (var i = 0, l = nodes.length; i < l; i++) { + var node = nodes[i]; + var children = data.nodeChildren(setting, node); + var nodeChecked = data.nodeChecked(setting, node); + if (node.nocheck !== true && node.chkDisabled !== true && nodeChecked != node.checkedOld) { + results.push(node); + } + data.getTreeChangeCheckedNodes(setting, children, results); + } + return results; + }, + makeChkFlag: function (setting, node) { + if (!node) return; + var chkFlag = -1; + var children = data.nodeChildren(setting, node); + if (children) { + for (var i = 0, l = children.length; i < l; i++) { + var cNode = children[i]; + var nodeChecked = data.nodeChecked(setting, cNode); + var tmp = -1; + if (setting.check.chkStyle == consts.radio.STYLE) { + if (cNode.nocheck === true || cNode.chkDisabled === true) { + tmp = cNode.check_Child_State; + } else if (cNode.halfCheck === true) { + tmp = 2; + } else if (nodeChecked) { + tmp = 2; + } else { + tmp = cNode.check_Child_State > 0 ? 2 : 0; + } + if (tmp == 2) { + chkFlag = 2; + break; + } else if (tmp == 0) { + chkFlag = 0; + } + } else if (setting.check.chkStyle == consts.checkbox.STYLE) { + if (cNode.nocheck === true || cNode.chkDisabled === true) { + tmp = cNode.check_Child_State; + } else if (cNode.halfCheck === true) { + tmp = 1; + } else if (nodeChecked) { + tmp = (cNode.check_Child_State === -1 || cNode.check_Child_State === 2) ? 2 : 1; + } else { + tmp = (cNode.check_Child_State > 0) ? 1 : 0; + } + if (tmp === 1) { + chkFlag = 1; + break; + } else if (tmp === 2 && chkFlag > -1 && i > 0 && tmp !== chkFlag) { + chkFlag = 1; + break; + } else if (chkFlag === 2 && tmp > -1 && tmp < 2) { + chkFlag = 1; + break; + } else if (tmp > -1) { + chkFlag = tmp; + } + } + } + } + node.check_Child_State = chkFlag; + } + }, + //method of event proxy + _event = {}, + //method of event handler + _handler = { + onCheckNode: function (event, node) { + if (node.chkDisabled === true) return false; + var setting = data.getSetting(event.data.treeId); + if (tools.apply(setting.callback.beforeCheck, [setting.treeId, node], true) == false) return true; + var nodeChecked = data.nodeChecked(setting, node); + data.nodeChecked(setting, node, !nodeChecked); + view.checkNodeRelation(setting, node); + var checkObj = $$(node, consts.id.CHECK, setting); + view.setChkClass(setting, checkObj, node); + view.repairParentChkClassWithSelf(setting, node); + setting.treeObj.trigger(consts.event.CHECK, [event, setting.treeId, node]); + return true; + }, + onMouseoverCheck: function (event, node) { + if (node.chkDisabled === true) return false; + var setting = data.getSetting(event.data.treeId), + checkObj = $$(node, consts.id.CHECK, setting); + node.check_Focus = true; + view.setChkClass(setting, checkObj, node); + return true; + }, + onMouseoutCheck: function (event, node) { + if (node.chkDisabled === true) return false; + var setting = data.getSetting(event.data.treeId), + checkObj = $$(node, consts.id.CHECK, setting); + node.check_Focus = false; + view.setChkClass(setting, checkObj, node); + return true; + } + }, + //method of tools for zTree + _tools = {}, + //method of operate ztree dom + _view = { + checkNodeRelation: function (setting, node) { + var pNode, i, l, + r = consts.radio; + var nodeChecked = data.nodeChecked(setting, node); + if (setting.check.chkStyle == r.STYLE) { + var checkedList = data.getRadioCheckedList(setting); + if (nodeChecked) { + if (setting.check.radioType == r.TYPE_ALL) { + for (i = checkedList.length - 1; i >= 0; i--) { + pNode = checkedList[i]; + var pNodeChecked = data.nodeChecked(setting, pNode); + if (pNodeChecked && pNode != node) { + data.nodeChecked(setting, pNode, false); + checkedList.splice(i, 1); + + view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode); + if (pNode.parentTId != node.parentTId) { + view.repairParentChkClassWithSelf(setting, pNode); + } + } + } + checkedList.push(node); + } else { + var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting); + var children = data.nodeChildren(setting, parentNode); + for (i = 0, l = children.length; i < l; i++) { + pNode = children[i]; + var pNodeChecked = data.nodeChecked(setting, pNode); + if (pNodeChecked && pNode != node) { + data.nodeChecked(setting, pNode, false); + view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode); + } + } + } + } else if (setting.check.radioType == r.TYPE_ALL) { + for (i = 0, l = checkedList.length; i < l; i++) { + if (node == checkedList[i]) { + checkedList.splice(i, 1); + break; + } + } + } + + } else { + var children = data.nodeChildren(setting, node); + if (nodeChecked && (!children || children.length == 0 || setting.check.chkboxType.Y.indexOf("s") > -1)) { + view.setSonNodeCheckBox(setting, node, true); + } + if (!nodeChecked && (!children || children.length == 0 || setting.check.chkboxType.N.indexOf("s") > -1)) { + view.setSonNodeCheckBox(setting, node, false); + } + if (nodeChecked && setting.check.chkboxType.Y.indexOf("p") > -1) { + view.setParentNodeCheckBox(setting, node, true); + } + if (!nodeChecked && setting.check.chkboxType.N.indexOf("p") > -1) { + view.setParentNodeCheckBox(setting, node, false); + } + } + }, + makeChkClass: function (setting, node) { + var c = consts.checkbox, r = consts.radio, + fullStyle = ""; + var nodeChecked = data.nodeChecked(setting, node); + if (node.chkDisabled === true) { + fullStyle = c.DISABLED; + } else if (node.halfCheck) { + fullStyle = c.PART; + } else if (setting.check.chkStyle == r.STYLE) { + fullStyle = (node.check_Child_State < 1) ? c.FULL : c.PART; + } else { + fullStyle = nodeChecked ? ((node.check_Child_State === 2 || node.check_Child_State === -1) ? c.FULL : c.PART) : ((node.check_Child_State < 1) ? c.FULL : c.PART); + } + var chkName = setting.check.chkStyle + "_" + (nodeChecked ? c.TRUE : c.FALSE) + "_" + fullStyle; + chkName = (node.check_Focus && node.chkDisabled !== true) ? chkName + "_" + c.FOCUS : chkName; + return consts.className.BUTTON + " " + c.DEFAULT + " " + chkName; + }, + repairAllChk: function (setting, checked) { + if (setting.check.enable && setting.check.chkStyle === consts.checkbox.STYLE) { + var root = data.getRoot(setting); + var children = data.nodeChildren(setting, root); + for (var i = 0, l = children.length; i < l; i++) { + var node = children[i]; + if (node.nocheck !== true && node.chkDisabled !== true) { + data.nodeChecked(setting, node, checked); + } + view.setSonNodeCheckBox(setting, node, checked); + } + } + }, + repairChkClass: function (setting, node) { + if (!node) return; + data.makeChkFlag(setting, node); + if (node.nocheck !== true) { + var checkObj = $$(node, consts.id.CHECK, setting); + view.setChkClass(setting, checkObj, node); + } + }, + repairParentChkClass: function (setting, node) { + if (!node || !node.parentTId) return; + var pNode = node.getParentNode(); + view.repairChkClass(setting, pNode); + view.repairParentChkClass(setting, pNode); + }, + repairParentChkClassWithSelf: function (setting, node) { + if (!node) return; + var children = data.nodeChildren(setting, node); + if (children && children.length > 0) { + view.repairParentChkClass(setting, children[0]); + } else { + view.repairParentChkClass(setting, node); + } + }, + repairSonChkDisabled: function (setting, node, chkDisabled, inherit) { + if (!node) return; + if (node.chkDisabled != chkDisabled) { + node.chkDisabled = chkDisabled; + } + view.repairChkClass(setting, node); + var children = data.nodeChildren(setting, node); + if (children && inherit) { + for (var i = 0, l = children.length; i < l; i++) { + var sNode = children[i]; + view.repairSonChkDisabled(setting, sNode, chkDisabled, inherit); + } + } + }, + repairParentChkDisabled: function (setting, node, chkDisabled, inherit) { + if (!node) return; + if (node.chkDisabled != chkDisabled && inherit) { + node.chkDisabled = chkDisabled; + } + view.repairChkClass(setting, node); + view.repairParentChkDisabled(setting, node.getParentNode(), chkDisabled, inherit); + }, + setChkClass: function (setting, obj, node) { + if (!obj) return; + if (node.nocheck === true) { + obj.hide(); + } else { + obj.show(); + } + obj.attr('class', view.makeChkClass(setting, node)); + }, + setParentNodeCheckBox: function (setting, node, value, srcNode) { + var checkObj = $$(node, consts.id.CHECK, setting); + if (!srcNode) srcNode = node; + data.makeChkFlag(setting, node); + if (node.nocheck !== true && node.chkDisabled !== true) { + data.nodeChecked(setting, node, value); + view.setChkClass(setting, checkObj, node); + if (setting.check.autoCheckTrigger && node != srcNode) { + setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); + } + } + if (node.parentTId) { + var pSign = true; + if (!value) { + var pNodes = data.nodeChildren(setting, node.getParentNode()); + for (var i = 0, l = pNodes.length; i < l; i++) { + var pNode = pNodes[i]; + var nodeChecked = data.nodeChecked(setting, pNode); + if ((pNode.nocheck !== true && pNode.chkDisabled !== true && nodeChecked) + || ((pNode.nocheck === true || pNode.chkDisabled === true) && pNode.check_Child_State > 0)) { + pSign = false; + break; + } + } + } + if (pSign) { + view.setParentNodeCheckBox(setting, node.getParentNode(), value, srcNode); + } + } + }, + setSonNodeCheckBox: function (setting, node, value, srcNode) { + if (!node) return; + var checkObj = $$(node, consts.id.CHECK, setting); + if (!srcNode) srcNode = node; + + var hasDisable = false; + var children = data.nodeChildren(setting, node); + if (children) { + for (var i = 0, l = children.length; i < l; i++) { + var sNode = children[i]; + view.setSonNodeCheckBox(setting, sNode, value, srcNode); + if (sNode.chkDisabled === true) hasDisable = true; + } + } + + if (node != data.getRoot(setting) && node.chkDisabled !== true) { + if (hasDisable && node.nocheck !== true) { + data.makeChkFlag(setting, node); + } + if (node.nocheck !== true && node.chkDisabled !== true) { + data.nodeChecked(setting, node, value); + if (!hasDisable) node.check_Child_State = (children && children.length > 0) ? (value ? 2 : 0) : -1; + } else { + node.check_Child_State = -1; + } + view.setChkClass(setting, checkObj, node); + if (setting.check.autoCheckTrigger && node != srcNode && node.nocheck !== true && node.chkDisabled !== true) { + setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); + } + } + + } + }, + + _z = { + tools: _tools, + view: _view, + event: _event, + data: _data + }; + $.extend(true, $.fn.zTree.consts, _consts); + $.extend(true, $.fn.zTree._z, _z); + + var zt = $.fn.zTree, + tools = zt._z.tools, + consts = zt.consts, + view = zt._z.view, + data = zt._z.data, + event = zt._z.event, + $$ = tools.$; + + data.nodeChecked = function (setting, node, newChecked) { + if (!node) { + return false; + } + var key = setting.data.key.checked; + if (typeof newChecked !== 'undefined') { + if (typeof newChecked === "string") { + newChecked = tools.eqs(newChecked, "true"); + } + newChecked = !!newChecked; + node[key] = newChecked; + } else if (typeof node[key] == "string"){ + node[key] = tools.eqs(node[key], "true"); + } else { + node[key] = !!node[key]; + } + return node[key]; + }; + + data.exSetting(_setting); + data.addInitBind(_bindEvent); + data.addInitUnBind(_unbindEvent); + data.addInitCache(_initCache); + data.addInitNode(_initNode); + data.addInitProxy(_eventProxy, true); + data.addInitRoot(_initRoot); + data.addBeforeA(_beforeA); + data.addZTreeTools(_zTreeTools); + + var _createNodes = view.createNodes; + view.createNodes = function (setting, level, nodes, parentNode, index) { + if (_createNodes) _createNodes.apply(view, arguments); + if (!nodes) return; + view.repairParentChkClassWithSelf(setting, parentNode); + } + var _removeNode = view.removeNode; + view.removeNode = function (setting, node) { + var parentNode = node.getParentNode(); + if (_removeNode) _removeNode.apply(view, arguments); + if (!node || !parentNode) return; + view.repairChkClass(setting, parentNode); + view.repairParentChkClass(setting, parentNode); + } + + var _appendNodes = view.appendNodes; + view.appendNodes = function (setting, level, nodes, parentNode, index, initFlag, openFlag) { + var html = ""; + if (_appendNodes) { + html = _appendNodes.apply(view, arguments); + } + if (parentNode) { + data.makeChkFlag(setting, parentNode); + } + return html; + } +})(jQuery); \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/base/dashboard.ftl b/xxl-job-admin/src/main/resources/templates/base/dashboard.ftl new file mode 100644 index 00000000..8f1a75f5 --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/base/dashboard.ftl @@ -0,0 +1,337 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + + + +
      +
      + + <#-- 2-biz start --> + + +
      + + <#-- 任务信息 --> +
      +
      + + +
      + ${I18n.job_dashboard_job_num} + ${jobInfoCount} + +
      +
      +
      + ${I18n.job_dashboard_job_num_tip} +
      +
      +
      + + <#-- 调度信息 --> +
      +
      + + +
      + ${I18n.job_dashboard_trigger_num} + ${jobLogCount} + +
      +
      +
      + + ${I18n.job_dashboard_trigger_num_tip} + <#--<#if jobLogCount gt 0> + 调度成功率:${(jobLogSuccessCount*100/jobLogCount)?string("0.00")}% + --> + +
      +
      +
      + + <#-- 执行器 --> +
      +
      + + +
      + ${I18n.job_dashboard_jobgroup_num} + ${executorCount} + +
      +
      +
      + ${I18n.job_dashboard_jobgroup_num_tip} +
      +
      +
      + +
      + + <#-- 调度报表:时间区间筛选,左侧折线图 + 右侧饼图 --> +
      +
      +
      +
      +

      ${I18n.job_dashboard_report}

      + <#----> + + +
      + + <#----> +
      + + +
      +
      +
      + <#-- 左侧折线图 --> +
      +
      +
      + <#-- 右侧饼图 --> +
      +
      +
      +
      +
      +
      +
      +
      + + <#-- 2-biz end --> + +
      +
      + + +<@netCommon.commonScript /> + + + + + + + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/base/help.ftl b/xxl-job-admin/src/main/resources/templates/base/help.ftl new file mode 100644 index 00000000..729702d4 --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/base/help.ftl @@ -0,0 +1,41 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + + +
      + + +<@netCommon.commonScript /> + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/base/index.ftl b/xxl-job-admin/src/main/resources/templates/base/index.ftl new file mode 100644 index 00000000..62ef143d --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/base/index.ftl @@ -0,0 +1,180 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + + + +
      + + +
      + + + +
      + + + + + + + +
      + + +
      + + + + + + + + + + + + ${I18n.tab_refresh} + + +
      + +
      + + <#-- --> +
      + +
      + + + +
      + Powered by XXL-JOB ${I18n.admin_version} + +
      + + +
      + + +<@netCommon.commonScript /> + + + + + + + diff --git a/xxl-job-admin/src/main/resources/templates/base/login.ftl b/xxl-job-admin/src/main/resources/templates/base/login.ftl new file mode 100644 index 00000000..2a35e2b0 --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/base/login.ftl @@ -0,0 +1,125 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + + + + + + + + + + +<@netCommon.commonScript /> + + + + + + + diff --git a/xxl-job-admin/src/main/resources/templates/biz/group.list.ftl b/xxl-job-admin/src/main/resources/templates/biz/group.list.ftl new file mode 100644 index 00000000..35dd1198 --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/biz/group.list.ftl @@ -0,0 +1,385 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + + + + +
      +
      + + + + <#-- 查询区域 --> +
      +
      +
      + +
      +
      + AppName + +
      +
      +
      +
      + ${I18n.jobgroup_field_title} + +
      +
      + +
      + +
      +
      + +
      +
      +
      +
      + + <#-- 数据表格区域 --> +
      +
      +
      +
      + + + +
      +
      + + + + +
      +
      +
      +
      +
      + + + + + + + + + + + + +
      +
      + + +<@netCommon.commonScript /> + + + +<#-- admin table --> + + + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/biz/job.code.ftl b/xxl-job-admin/src/main/resources/templates/biz/job.code.ftl new file mode 100644 index 00000000..4a0700e8 --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/biz/job.code.ftl @@ -0,0 +1,280 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + ${I18n.admin_name} + + + + + +
      + + +
      + +
      + + + +
      +
      + + + + + + +
      + Powered by XXL-JOB ${I18n.admin_version} + +
      + + +
      + + +<#-- glueModel --> +<#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" /> +<#assign glueTypeIdeMode = "text/x-java" /> + +<#if jobInfo.glueType == "GLUE_GROOVY" > + <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" /> + <#assign glueTypeIdeMode = "text/x-java" /> +<#elseif jobInfo.glueType == "GLUE_SHELL" > + <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/shell/shell.js" /> + <#assign glueTypeIdeMode = "text/x-sh" /> +<#elseif jobInfo.glueType == "GLUE_PYTHON" > + <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/python/python.js" /> + <#assign glueTypeIdeMode = "text/x-python" /> +<#elseif jobInfo.glueType == "GLUE_PYTHON2" > + <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/python/python.js" /> + <#assign glueTypeIdeMode = "text/x-python" /> +<#elseif jobInfo.glueType == "GLUE_PHP" > + <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/php/php.js" /> + <#assign glueTypeIdeMode = "text/x-php" /> + <#assign glueTypeModeSrc02 = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" /> +<#elseif jobInfo.glueType == "GLUE_NODEJS" > + <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/javascript/javascript.js" /> + <#assign glueTypeIdeMode = "text/javascript" /> +<#elseif jobInfo.glueType == "GLUE_POWERSHELL" > + <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/powershell/powershell.js" /> + <#assign glueTypeIdeMode = "powershell" /> + + +<#-- script --> +<@netCommon.commonScript /> + +<#-- glue ide --> + + +<#if glueTypeModeSrc02?exists> + + + + + + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/biz/job.list.ftl b/xxl-job-admin/src/main/resources/templates/biz/job.list.ftl new file mode 100644 index 00000000..6d9f9d22 --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/biz/job.list.ftl @@ -0,0 +1,1316 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + + + +
      +
      + + + + <#-- 查询区域 --> +
      +
      +
      + +
      +
      + ${I18n.jobinfo_field_jobgroup} + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      + +
      + +
      +
      + +
      +
      +
      +
      + + <#-- 数据表格区域 --> +
      +
      +
      +
      + <#-- add --> + <#-- update --> + <#-- GLUE IDE:'BEAN' != row.glueType --> + <#-- delete --> + | + + <#-- 启动 --> + <#-- 停止 --> + | + <#-- 执行一次 --> + <#-- 执行日志:base_url +'/joblog?jobId='+ row.id --> + <#-- 注册节点 --> + <#-- 下次执行时间:row.scheduleType == 'CRON' || row.scheduleType == 'FIX_RATE' --> +
      +
      + + + + +
      +
      +
      +
      +
      + + + + + + + + <#-- trigger --> + + + + +
      +
      + + +<@netCommon.commonScript /> + + +<#-- admin table --> + +<#-- admin util --> + +<#-- moment --> + +<#-- cronGen --> + + + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/biz/log.detail.ftl b/xxl-job-admin/src/main/resources/templates/biz/log.detail.ftl new file mode 100644 index 00000000..97ae089d --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/biz/log.detail.ftl @@ -0,0 +1,194 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + + +
      + + +
      + +
      + + + +
      +
      + +
      +				
      +
    • +
      +
      +
      + + + +
      + Powered by XXL-JOB ${I18n.admin_version} + +
      + + +
      + + +<@netCommon.commonScript /> + + + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/biz/log.list.ftl b/xxl-job-admin/src/main/resources/templates/biz/log.list.ftl new file mode 100644 index 00000000..9a6ec166 --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/biz/log.list.ftl @@ -0,0 +1,534 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + + + + + +
      +
      + + + + <#-- 查询区域 --> +
      +
      +
      + +
      +
      + ${I18n.jobinfo_field_jobgroup} + +
      +
      +
      +
      + ${I18n.jobinfo_job} + +
      +
      +
      +
      + ${I18n.joblog_status} + +
      +
      +
      +
      + + ${I18n.joblog_field_triggerTime} + + +
      +
      + +
      + +
      +
      + +
      +
      +
      +
      + + <#-- 数据表格区域 --> +
      +
      +
      +
      + + + | + +
      +
      + + + + +
      +
      +
      +
      +
      + + + + + + +
      +
      + + +<@netCommon.commonScript /> + + +<#--daterangepicker--> + + +<#-- admin table --> + + + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/biz/user.list.ftl b/xxl-job-admin/src/main/resources/templates/biz/user.list.ftl new file mode 100644 index 00000000..4f9ff773 --- /dev/null +++ b/xxl-job-admin/src/main/resources/templates/biz/user.list.ftl @@ -0,0 +1,350 @@ + + + + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + + + <@netCommon.commonStyle /> + + + + + + +
      +
      + + + + <#-- 查询区域 --> +
      +
      +
      + +
      +
      + ${I18n.user_role} + +
      +
      +
      +
      + ${I18n.user_username} + +
      +
      + +
      + +
      +
      + +
      +
      +
      +
      + + <#-- 数据表格区域 --> +
      +
      +
      +
      + + + +
      +
      + + + + +
      +
      +
      +
      +
      + + + + + + + + + +
      +
      + + +<@netCommon.commonScript /> + + + +<#-- admin table --> + + + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/common/common.exception.ftl b/xxl-job-admin/src/main/resources/templates/common/common.errorpage.ftl similarity index 57% rename from xxl-job-admin/src/main/resources/templates/common/common.exception.ftl rename to xxl-job-admin/src/main/resources/templates/common/common.errorpage.ftl index e448125e..9e81e419 100644 --- a/xxl-job-admin/src/main/resources/templates/common/common.exception.ftl +++ b/xxl-job-admin/src/main/resources/templates/common/common.errorpage.ftl @@ -1,6 +1,9 @@ + <#-- import macro --> + <#import "../common/common.macro.ftl" as netCommon> + Error - - - -
      - -
      - -
      - -
      - - - <#--<@netCommon.commonFooter />--> -
      - - - - -<@netCommon.commonScript /> - - - <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" /> - <#assign glueTypeIdeMode = "text/x-java" /> - - <#if jobInfo.glueType == "GLUE_GROOVY" > - <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" /> - <#assign glueTypeIdeMode = "text/x-java" /> - <#elseif jobInfo.glueType == "GLUE_SHELL" > - <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/shell/shell.js" /> - <#assign glueTypeIdeMode = "text/x-sh" /> - <#elseif jobInfo.glueType == "GLUE_PYTHON" > - <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/python/python.js" /> - <#assign glueTypeIdeMode = "text/x-python" /> - <#elseif jobInfo.glueType == "GLUE_PHP" > - <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/php/php.js" /> - <#assign glueTypeIdeMode = "text/x-php" /> - <#assign glueTypeModeSrc02 = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" /> - <#elseif jobInfo.glueType == "GLUE_NODEJS" > - <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/javascript/javascript.js" /> - <#assign glueTypeIdeMode = "text/javascript" /> - <#elseif jobInfo.glueType == "GLUE_POWERSHELL" > - <#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/powershell/powershell.js" /> - <#assign glueTypeIdeMode = "powershell" /> - - - - - -<#if glueTypeModeSrc02?exists> - - - - - - - - - - diff --git a/xxl-job-admin/src/main/resources/templates/jobgroup/jobgroup.index.ftl b/xxl-job-admin/src/main/resources/templates/jobgroup/jobgroup.index.ftl deleted file mode 100644 index 09346887..00000000 --- a/xxl-job-admin/src/main/resources/templates/jobgroup/jobgroup.index.ftl +++ /dev/null @@ -1,191 +0,0 @@ - - - - <#import "../common/common.macro.ftl" as netCommon> - <@netCommon.commonStyle /> - - - ${I18n.admin_name} - -sidebar-collapse "> -
      - - <@netCommon.commonHeader /> - - <@netCommon.commonLeft "jobgroup" /> - - -
      - -
      -

      ${I18n.jobgroup_name}

      -
      - - -
      - -
      -
      -
      - AppName - -
      -
      -
      -
      - ${I18n.jobgroup_field_title} - -
      -
      -
      - -
      -
      - -
      -
      - -
      -
      -
      -
      - - - - - - - - - - - - - -
      IDAppName${I18n.jobgroup_field_title}${I18n.jobgroup_field_addressType}OnLine ${I18n.jobgroup_field_registryList}${I18n.system_opt}
      -
      -
      -
      -
      -
      -
      - - - - - - - - - - - - <@netCommon.commonFooter /> -
      - -<@netCommon.commonScript /> - - - - - - diff --git a/xxl-job-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl b/xxl-job-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl deleted file mode 100644 index 3a5d7d8a..00000000 --- a/xxl-job-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl +++ /dev/null @@ -1,540 +0,0 @@ - - - - <#import "../common/common.macro.ftl" as netCommon> - <@netCommon.commonStyle /> - - - ${I18n.admin_name} - -sidebar-collapse"> -
      - - <@netCommon.commonHeader /> - - <@netCommon.commonLeft "jobinfo" /> - - -
      - -
      -

      ${I18n.jobinfo_name}

      -
      - - -
      - -
      -
      -
      - ${I18n.jobinfo_field_jobgroup} - -
      -
      -
      -
      - -
      -
      -
      -
      - -
      -
      -
      -
      - -
      -
      -
      -
      - -
      -
      -
      - -
      -
      - -
      -
      - -
      -
      -
      - <#--
      -

      调度列表

      -
      --> -
      - - - - - - - - - - - - - - - - - - - -
      ${I18n.jobinfo_field_id}${I18n.jobinfo_field_jobgroup}${I18n.jobinfo_field_jobdesc}${I18n.schedule_type}${I18n.jobinfo_field_gluetype}${I18n.jobinfo_field_executorparam}addTimeupdateTime${I18n.jobinfo_field_author}${I18n.jobinfo_field_alarmemail}${I18n.system_status}${I18n.system_opt}
      -
      -
      -
      -
      -
      -
      - - - <@netCommon.commonFooter /> -
      - - - - - - - -<#-- trigger --> - - -<@netCommon.commonScript /> - - - - - -<#-- cronGen --> - - - - diff --git a/xxl-job-admin/src/main/resources/templates/joblog/joblog.detail.ftl b/xxl-job-admin/src/main/resources/templates/joblog/joblog.detail.ftl deleted file mode 100644 index bb8072f9..00000000 --- a/xxl-job-admin/src/main/resources/templates/joblog/joblog.detail.ftl +++ /dev/null @@ -1,70 +0,0 @@ - - - - <#import "../common/common.macro.ftl" as netCommon> - <@netCommon.commonStyle /> - ${I18n.admin_name} - - - -
      - -
      - -
      - -
      -
      -
      -                
      -
    • -
      -
      -
      - - - <@netCommon.commonFooter /> - -
      - -<@netCommon.commonScript /> - - - - - \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/templates/joblog/joblog.index.ftl b/xxl-job-admin/src/main/resources/templates/joblog/joblog.index.ftl deleted file mode 100644 index a2e983de..00000000 --- a/xxl-job-admin/src/main/resources/templates/joblog/joblog.index.ftl +++ /dev/null @@ -1,180 +0,0 @@ - - - - <#import "../common/common.macro.ftl" as netCommon> - <@netCommon.commonStyle /> - - - - - ${I18n.admin_name} - -sidebar-collapse "> -
      - - <@netCommon.commonHeader /> - - <@netCommon.commonLeft "joblog" /> - - -
      - -
      -

      ${I18n.joblog_name}

      -
      - - -
      -
      -
      -
      - ${I18n.jobinfo_field_jobgroup} - -
      -
      -
      -
      - ${I18n.jobinfo_job} - -
      -
      - -
      -
      - ${I18n.joblog_status} - -
      -
      - -
      -
      - - ${I18n.joblog_field_triggerTime} - - -
      -
      - -
      - -
      - -
      - -
      -
      - -
      -
      -
      - <#--

      调度日志

      --> -
      - - - - - - <#-- - - --> - - - - - - - - - - -
      ${I18n.jobinfo_field_id}jobGroup执行器地址运行模式任务参数${I18n.joblog_field_triggerTime}${I18n.joblog_field_triggerCode}${I18n.joblog_field_triggerMsg}${I18n.joblog_field_handleTime}${I18n.joblog_field_handleCode}${I18n.joblog_field_handleMsg}${I18n.system_opt}
      -
      -
      -
      -
      -
      -
      - - - <@netCommon.commonFooter /> -
      - - - - -<@netCommon.commonScript /> - - - - - - - - - diff --git a/xxl-job-admin/src/main/resources/templates/login.ftl b/xxl-job-admin/src/main/resources/templates/login.ftl deleted file mode 100644 index e6a29e01..00000000 --- a/xxl-job-admin/src/main/resources/templates/login.ftl +++ /dev/null @@ -1,45 +0,0 @@ - - - - <#import "./common/common.macro.ftl" as netCommon> - <@netCommon.commonStyle /> - - ${I18n.admin_name} - - - -<@netCommon.commonScript /> - - - - - diff --git a/xxl-job-admin/src/main/resources/templates/user/user.index.ftl b/xxl-job-admin/src/main/resources/templates/user/user.index.ftl deleted file mode 100644 index 01203982..00000000 --- a/xxl-job-admin/src/main/resources/templates/user/user.index.ftl +++ /dev/null @@ -1,188 +0,0 @@ - - - - <#import "../common/common.macro.ftl" as netCommon> - <@netCommon.commonStyle /> - - - ${I18n.admin_name} - -sidebar-collapse"> -
      - - <@netCommon.commonHeader /> - - <@netCommon.commonLeft "user" /> - - -
      - -
      -

      ${I18n.user_manage}

      -
      - - -
      - -
      -
      -
      - ${I18n.user_role} - -
      -
      -
      -
      - ${I18n.user_username} - -
      -
      -
      - -
      -
      - -
      -
      - -
      -
      -
      -
      - - - - - - - - - - - - - -
      ID${I18n.user_username}${I18n.user_password}${I18n.user_role}${I18n.user_permission}${I18n.system_opt}
      -
      -
      -
      -
      -
      -
      - - - <@netCommon.commonFooter /> -
      - - - - - - - -<@netCommon.commonScript /> - - - - - - diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/controller/JobInfoControllerTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/controller/JobInfoControllerTest.java index 9c61fcb7..0657468c 100644 --- a/xxl-job-admin/src/test/java/com/xxl/job/admin/controller/JobInfoControllerTest.java +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/controller/JobInfoControllerTest.java @@ -1,6 +1,7 @@ package com.xxl.job.admin.controller; -import com.xxl.job.admin.service.impl.LoginService; +import com.xxl.sso.core.constant.Const; +import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -10,8 +11,6 @@ import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import javax.servlet.http.Cookie; - import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; public class JobInfoControllerTest extends AbstractSpringMvcTest { @@ -22,12 +21,12 @@ public class JobInfoControllerTest extends AbstractSpringMvcTest { @BeforeEach public void login() throws Exception { MvcResult ret = mockMvc.perform( - post("/login") + post("/auth/doLogin") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .param("userName", "admin") .param("password", "123456") ).andReturn(); - cookie = ret.getResponse().getCookie(LoginService.LOGIN_IDENTITY_KEY); + cookie = ret.getResponse().getCookie(Const.XXL_SSO_TOKEN); } @Test diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/core/util/CronExpressionTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/core/util/CronExpressionTest.java new file mode 100644 index 00000000..e41ef343 --- /dev/null +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/core/util/CronExpressionTest.java @@ -0,0 +1,23 @@ +package com.xxl.job.admin.core.util; + +import com.xxl.job.admin.scheduler.cron.CronExpression; +import com.xxl.tool.core.DateTool; +import org.junit.jupiter.api.Test; + +import java.text.ParseException; +import java.util.Date; + +public class CronExpressionTest { + + @Test + public void shouldWriteValueAsString() throws ParseException { + CronExpression cronExpression = new CronExpression("0 0 0 ? * 1"); + Date lastTriggerTime = new Date(); + for (int i = 0; i < 5; i++) { + Date nextTriggerTime = cronExpression.getNextValidTimeAfter(lastTriggerTime); + System.out.println(DateTool.formatDateTime(nextTriggerTime)); + + lastTriggerTime = nextTriggerTime; + } + } +} diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/core/util/JacksonUtilTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/core/util/JacksonUtilTest.java index 34fb9d44..d64b5206 100644 --- a/xxl-job-admin/src/test/java/com/xxl/job/admin/core/util/JacksonUtilTest.java +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/core/util/JacksonUtilTest.java @@ -1,40 +1,41 @@ -package com.xxl.job.admin.core.util; - -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.Map; - -import static com.xxl.job.admin.core.util.JacksonUtil.writeValueAsString; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class JacksonUtilTest { - - @Test - public void shouldWriteValueAsString() { - //given - Map map = new HashMap<>(); - map.put("aaa", "111"); - map.put("bbb", "222"); - - //when - String json = writeValueAsString(map); - - //then - assertEquals(json, "{\"aaa\":\"111\",\"bbb\":\"222\"}"); - } - - @Test - public void shouldReadValueAsObject() { - //given - String jsonString = "{\"aaa\":\"111\",\"bbb\":\"222\"}"; - - //when - Map result = JacksonUtil.readValue(jsonString, Map.class); - - //then - assertEquals(result.get("aaa"), "111"); - assertEquals(result.get("bbb"),"222"); - - } -} +//package com.xxl.job.admin.core.util; +// +//import com.xxl.job.admin.util.JacksonUtil; +//import org.junit.jupiter.api.Test; +// +//import java.util.HashMap; +//import java.util.Map; +// +//import static com.xxl.job.admin.util.JacksonUtil.writeValueAsString; +//import static org.junit.jupiter.api.Assertions.assertEquals; +// +//public class JacksonUtilTest { +// +// @Test +// public void shouldWriteValueAsString() { +// //given +// Map map = new HashMap<>(); +// map.put("aaa", "111"); +// map.put("bbb", "222"); +// +// //when +// String json = writeValueAsString(map); +// +// //then +// assertEquals(json, "{\"aaa\":\"111\",\"bbb\":\"222\"}"); +// } +// +// @Test +// public void shouldReadValueAsObject() { +// //given +// String jsonString = "{\"aaa\":\"111\",\"bbb\":\"222\"}"; +// +// //when +// Map result = JacksonUtil.readValue(jsonString, Map.class); +// +// //then +// assertEquals(result.get("aaa"), "111"); +// assertEquals(result.get("bbb"),"222"); +// +// } +//} diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobLogDaoTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobLogDaoTest.java deleted file mode 100644 index c5888bb2..00000000 --- a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobLogDaoTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.xxl.job.admin.dao; - -import com.xxl.job.admin.core.model.XxlJobLog; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -import javax.annotation.Resource; -import java.util.Date; -import java.util.List; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class XxlJobLogDaoTest { - - @Resource - private XxlJobLogDao xxlJobLogDao; - - @Test - public void test(){ - List list = xxlJobLogDao.pageList(0, 10, 1, 1, null, null, 1); - int list_count = xxlJobLogDao.pageListCount(0, 10, 1, 1, null, null, 1); - - XxlJobLog log = new XxlJobLog(); - log.setJobGroup(1); - log.setJobId(1); - - long ret1 = xxlJobLogDao.save(log); - XxlJobLog dto = xxlJobLogDao.load(log.getId()); - - log.setTriggerTime(new Date()); - log.setTriggerCode(1); - log.setTriggerMsg("1"); - log.setExecutorAddress("1"); - log.setExecutorHandler("1"); - log.setExecutorParam("1"); - ret1 = xxlJobLogDao.updateTriggerInfo(log); - dto = xxlJobLogDao.load(log.getId()); - - - log.setHandleTime(new Date()); - log.setHandleCode(2); - log.setHandleMsg("2"); - ret1 = xxlJobLogDao.updateHandleInfo(log); - dto = xxlJobLogDao.load(log.getId()); - - - List ret4 = xxlJobLogDao.findClearLogIds(1, 1, new Date(), 100, 100); - - int ret2 = xxlJobLogDao.delete(log.getJobId()); - - } - -} diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobGroupDaoTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobGroupMapperTest.java similarity index 56% rename from xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobGroupDaoTest.java rename to xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobGroupMapperTest.java index 90b68a94..095625ef 100644 --- a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobGroupDaoTest.java +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobGroupMapperTest.java @@ -1,24 +1,24 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobGroup; +import com.xxl.job.admin.model.XxlJobGroup; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import javax.annotation.Resource; import java.util.Date; import java.util.List; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class XxlJobGroupDaoTest { +public class XxlJobGroupMapperTest { @Resource - private XxlJobGroupDao xxlJobGroupDao; + private XxlJobGroupMapper xxlJobGroupMapper; @Test public void test(){ - List list = xxlJobGroupDao.findAll(); + List list = xxlJobGroupMapper.findAll(); - List list2 = xxlJobGroupDao.findByAddressType(0); + List list2 = xxlJobGroupMapper.findByAddressType(0); XxlJobGroup group = new XxlJobGroup(); group.setAppname("setAppName"); @@ -27,18 +27,18 @@ public class XxlJobGroupDaoTest { group.setAddressList("setAddressList"); group.setUpdateTime(new Date()); - int ret = xxlJobGroupDao.save(group); + int ret = xxlJobGroupMapper.save(group); - XxlJobGroup group2 = xxlJobGroupDao.load(group.getId()); + XxlJobGroup group2 = xxlJobGroupMapper.load(group.getId()); group2.setAppname("setAppName2"); group2.setTitle("setTitle2"); group2.setAddressType(2); group2.setAddressList("setAddressList2"); group2.setUpdateTime(new Date()); - int ret2 = xxlJobGroupDao.update(group2); + int ret2 = xxlJobGroupMapper.update(group2); - int ret3 = xxlJobGroupDao.remove(group.getId()); + int ret3 = xxlJobGroupMapper.remove(group.getId()); } } diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobInfoDaoTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobInfoMapperTest.java similarity index 50% rename from xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobInfoDaoTest.java rename to xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobInfoMapperTest.java index 0cb7d53c..28b5bebb 100644 --- a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobInfoDaoTest.java +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobInfoMapperTest.java @@ -1,33 +1,37 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobInfo; -import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum; -import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum; +import com.xxl.job.admin.constant.TriggerStatus; +import com.xxl.job.admin.model.XxlJobInfo; +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.job.admin.scheduler.misfire.MisfireStrategyEnum; +import com.xxl.job.admin.scheduler.type.ScheduleTypeEnum; +import com.xxl.tool.core.CollectionTool; +import com.xxl.tool.core.DateTool; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.test.context.SpringBootTest; -import javax.annotation.Resource; import java.util.Date; import java.util.List; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class XxlJobInfoDaoTest { - private static Logger logger = LoggerFactory.getLogger(XxlJobInfoDaoTest.class); +public class XxlJobInfoMapperTest { + private static Logger logger = LoggerFactory.getLogger(XxlJobInfoMapperTest.class); @Resource - private XxlJobInfoDao xxlJobInfoDao; + private XxlJobInfoMapper xxlJobInfoMapper; @Test public void pageList(){ - List list = xxlJobInfoDao.pageList(0, 20, 0, -1, null, null, null); - int list_count = xxlJobInfoDao.pageListCount(0, 20, 0, -1, null, null, null); + List list = xxlJobInfoMapper.pageList(0, 20, 0, -1, null, null, null); + int list_count = xxlJobInfoMapper.pageListCount(0, 20, 0, -1, null, null, null); logger.info("", list); logger.info("", list_count); - List list2 = xxlJobInfoDao.getJobsByGroup(1); + List list2 = xxlJobInfoMapper.getJobsByGroup(1); } @Test @@ -53,9 +57,9 @@ public class XxlJobInfoDaoTest { info.setUpdateTime(new Date()); info.setGlueUpdatetime(new Date()); - int count = xxlJobInfoDao.save(info); + int count = xxlJobInfoMapper.save(info); - XxlJobInfo info2 = xxlJobInfoDao.loadById(info.getId()); + XxlJobInfo info2 = xxlJobInfoMapper.loadById(info.getId()); info.setScheduleType(ScheduleTypeEnum.FIX_RATE.name()); info.setScheduleConf(String.valueOf(44)); info.setMisfireStrategy(MisfireStrategyEnum.FIRE_ONCE_NOW.name()); @@ -73,14 +77,38 @@ public class XxlJobInfoDaoTest { info2.setChildJobId("1"); info2.setUpdateTime(new Date()); - int item2 = xxlJobInfoDao.update(info2); + int item2 = xxlJobInfoMapper.update(info2); - xxlJobInfoDao.delete(info2.getId()); + xxlJobInfoMapper.delete(info2.getId()); - List list2 = xxlJobInfoDao.getJobsByGroup(1); + List list2 = xxlJobInfoMapper.getJobsByGroup(1); - int ret3 = xxlJobInfoDao.findAllCount(); + int ret3 = xxlJobInfoMapper.findAllCount(); } + @Test + public void scheduleBatchUpdateTest(){ + + List list1 = xxlJobInfoMapper.pageList(0, 20, 0, -1, null, null, null); + int batchSize = 5; + + // update + List list2 = list1.stream().filter(item -> (item.getId()>=4 && item.getId()<=14)).toList(); + list2.forEach(item -> { + item.setTriggerLastTime(DateTool.addHours(new Date(), -1).getTime()); + item.setTriggerNextTime(DateTool.addHours(new Date(), 1).getTime()); + if (item.getId() == 5) { + item.setTriggerStatus(TriggerStatus.STOPPED.getValue()); + } + }); + + // batch update + List> scheduleListBatches = CollectionTool.split(list2, batchSize); + for (List scheduleListBatch : scheduleListBatches) { + int totalAffected = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleBatchUpdate(scheduleListBatch); + logger.info("scheduleBatchUpdate records:" + totalAffected); + } + } + } diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobLogGlueDaoTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobLogGlueMapperTest.java similarity index 55% rename from xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobLogGlueDaoTest.java rename to xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobLogGlueMapperTest.java index 1e9eee33..a33a85ca 100644 --- a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobLogGlueDaoTest.java +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobLogGlueMapperTest.java @@ -1,18 +1,18 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobLogGlue; +import com.xxl.job.admin.model.XxlJobLogGlue; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import javax.annotation.Resource; import java.util.Date; import java.util.List; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class XxlJobLogGlueDaoTest { +public class XxlJobLogGlueMapperTest { @Resource - private XxlJobLogGlueDao xxlJobLogGlueDao; + private XxlJobLogGlueMapper xxlJobLogGlueMapper; @Test public void test(){ @@ -24,13 +24,13 @@ public class XxlJobLogGlueDaoTest { logGlue.setAddTime(new Date()); logGlue.setUpdateTime(new Date()); - int ret = xxlJobLogGlueDao.save(logGlue); + int ret = xxlJobLogGlueMapper.save(logGlue); - List list = xxlJobLogGlueDao.findByJobId(1); + List list = xxlJobLogGlueMapper.findByJobId(1); - int ret2 = xxlJobLogGlueDao.removeOld(1, 1); + int ret2 = xxlJobLogGlueMapper.removeOld(1, 1); - int ret3 =xxlJobLogGlueDao.deleteByJobId(1); + int ret3 = xxlJobLogGlueMapper.deleteByJobId(1); } } diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobLogMapperTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobLogMapperTest.java new file mode 100644 index 00000000..4d5e5b4d --- /dev/null +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobLogMapperTest.java @@ -0,0 +1,52 @@ +package com.xxl.job.admin.mapper; + +import com.xxl.job.admin.model.XxlJobLog; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Date; +import java.util.List; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class XxlJobLogMapperTest { + + @Resource + private XxlJobLogMapper xxlJobLogMapper; + + @Test + public void test(){ + List list = xxlJobLogMapper.pageList(0, 10, 1, 1, null, null, 1); + int list_count = xxlJobLogMapper.pageListCount(0, 10, 1, 1, null, null, 1); + + XxlJobLog log = new XxlJobLog(); + log.setJobGroup(1); + log.setJobId(1); + + long ret1 = xxlJobLogMapper.save(log); + XxlJobLog dto = xxlJobLogMapper.load(log.getId()); + + log.setTriggerTime(new Date()); + log.setTriggerCode(1); + log.setTriggerMsg("1"); + log.setExecutorAddress("1"); + log.setExecutorHandler("1"); + log.setExecutorParam("1"); + ret1 = xxlJobLogMapper.updateTriggerInfo(log); + dto = xxlJobLogMapper.load(log.getId()); + + + log.setHandleTime(new Date()); + log.setHandleCode(2); + log.setHandleMsg("2"); + ret1 = xxlJobLogMapper.updateHandleInfo(log); + dto = xxlJobLogMapper.load(log.getId()); + + + List ret4 = xxlJobLogMapper.findClearLogIds(1, 1, new Date(), 100, 100); + + int ret2 = xxlJobLogMapper.delete(log.getJobId()); + + } + +} diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobLogReportMapperTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobLogReportMapperTest.java new file mode 100644 index 00000000..31593d6f --- /dev/null +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobLogReportMapperTest.java @@ -0,0 +1,35 @@ +package com.xxl.job.admin.mapper; + +import com.xxl.job.admin.model.XxlJobLogReport; +import com.xxl.tool.core.DateTool; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Date; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class XxlJobLogReportMapperTest { + private static final Logger logger = LoggerFactory.getLogger(XxlJobLogMapperTest.class); + + @Resource + private XxlJobLogReportMapper xxlJobLogReportMapper; + + @Test + public void test(){ + + Date date = DateTool.parseDate("2025-10-01"); + + XxlJobLogReport xxlJobLogReport = new XxlJobLogReport(); + xxlJobLogReport.setTriggerDay(date); + xxlJobLogReport.setRunningCount(444); + xxlJobLogReport.setSucCount(555); + xxlJobLogReport.setFailCount(666); + xxlJobLogReport.setUpdateTime(new Date()); + + int ret = xxlJobLogReportMapper.saveOrUpdate(xxlJobLogReport); + logger.info("ret:{}", ret); + } +} diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobRegistryDaoTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobRegistryMapperTest.java similarity index 61% rename from xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobRegistryDaoTest.java rename to xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobRegistryMapperTest.java index bd8fe9b9..e166cb10 100644 --- a/xxl-job-admin/src/test/java/com/xxl/job/admin/dao/XxlJobRegistryDaoTest.java +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/mapper/XxlJobRegistryMapperTest.java @@ -1,39 +1,40 @@ -package com.xxl.job.admin.dao; +package com.xxl.job.admin.mapper; -import com.xxl.job.admin.core.model.XxlJobRegistry; +import com.xxl.job.admin.model.XxlJobRegistry; +import com.xxl.job.core.constant.RegistType; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import javax.annotation.Resource; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class XxlJobRegistryDaoTest { +public class XxlJobRegistryMapperTest { @Resource - private XxlJobRegistryDao xxlJobRegistryDao; + private XxlJobRegistryMapper xxlJobRegistryMapper; @Test public void test(){ - int ret = xxlJobRegistryDao.registrySaveOrUpdate("g1", "k1", "v1", new Date()); + int ret = xxlJobRegistryMapper.registrySaveOrUpdate(RegistType.EXECUTOR.name(), "xxl-job-executor-z1", "v1", new Date()); /*int ret = xxlJobRegistryDao.registryUpdate("g1", "k1", "v1", new Date()); if (ret < 1) { ret = xxlJobRegistryDao.registrySave("g1", "k1", "v1", new Date()); }*/ - List list = xxlJobRegistryDao.findAll(1, new Date()); + List list = xxlJobRegistryMapper.findAll(1, new Date()); - int ret2 = xxlJobRegistryDao.removeDead(Arrays.asList(1)); + int ret2 = xxlJobRegistryMapper.removeDead(Arrays.asList(1)); } @Test public void test2() throws InterruptedException { for (int i = 0; i < 100; i++) { new Thread(()->{ - int ret = xxlJobRegistryDao.registrySaveOrUpdate("g1", "k1", "v1", new Date()); + int ret = xxlJobRegistryMapper.registrySaveOrUpdate("g1", "k1", "v1", new Date()); System.out.println(ret); /*int ret = xxlJobRegistryDao.registryUpdate("g1", "k1", "v1", new Date()); diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/schedule/JobScheduleTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/schedule/JobScheduleTest.java new file mode 100644 index 00000000..7ab93170 --- /dev/null +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/schedule/JobScheduleTest.java @@ -0,0 +1,49 @@ +package com.xxl.job.admin.schedule; + +import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap; +import com.xxl.tool.core.DateTool; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@SpringBootTest +public class JobScheduleTest { + private static Logger logger = LoggerFactory.getLogger(JobScheduleTest.class); + + @Test + public void test() throws InterruptedException { + + // thread + for (int i = 0; i < 10; i++) { + int finalI = i; + new Thread(() -> { + lockTest("threadName-" + finalI); + }).start(); + } + + TimeUnit.MINUTES.sleep(10); + } + + private void lockTest(String threadName) { + + TransactionStatus transactionStatus = XxlJobAdminBootstrap.getInstance().getTransactionManager().getTransaction(new DefaultTransactionDefinition()); + try { + String lockedRecord = XxlJobAdminBootstrap.getInstance().getXxlJobLockMapper().scheduleLock(); // for update + + logger.info(threadName + " : start at " + DateTool.format(new Date(), "yyyy-MM-dd HH:mm:ss SSS") ); + TimeUnit.MILLISECONDS.sleep(500); + logger.info(threadName + " : end at " + DateTool.format(new Date(), "yyyy-MM-dd HH:mm:ss SSS") ); + } catch (Throwable e) { + logger.error("error: ", e); + } finally { + logger.info(threadName + " : commit at " + DateTool.format(new Date(), "yyyy-MM-dd HH:mm:ss SSS") ); + XxlJobAdminBootstrap.getInstance().getTransactionManager().commit(transactionStatus); + } + } +} diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/schedule/route/ExecutorRouteConsistentHashTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/schedule/route/ExecutorRouteConsistentHashTest.java new file mode 100644 index 00000000..16df5846 --- /dev/null +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/schedule/route/ExecutorRouteConsistentHashTest.java @@ -0,0 +1,25 @@ +package com.xxl.job.admin.schedule.route; + +import com.xxl.job.admin.scheduler.route.strategy.ExecutorRouteConsistentHash; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class ExecutorRouteConsistentHashTest { + private static final Logger logger = LoggerFactory.getLogger(ExecutorRouteConsistentHashTest.class); + + @Test + public void test() { + + List addressList = List.of("192.168.0.1:1111", "192.168.0.2:2222", "192.168.0.3:3333"); + ExecutorRouteConsistentHash executorRouteConsistentHash = new ExecutorRouteConsistentHash(); + + for (int i = 0; i < 100; i++) { + int jobId = 1000 + i; + String address = executorRouteConsistentHash.hashJob(jobId, addressList); + logger.info("jobId:{}, address:{}", jobId, address); + } + } +} diff --git a/xxl-job-admin/src/test/java/com/xxl/job/admin/util/I18nUtilTest.java b/xxl-job-admin/src/test/java/com/xxl/job/admin/util/I18nUtilTest.java index 29079f18..85a84caa 100644 --- a/xxl-job-admin/src/test/java/com/xxl/job/admin/util/I18nUtilTest.java +++ b/xxl-job-admin/src/test/java/com/xxl/job/admin/util/I18nUtilTest.java @@ -1,6 +1,5 @@ package com.xxl.job.admin.util; -import com.xxl.job.admin.core.util.I18nUtil; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/xxl-job-admin/src/test/java/com/xxl/job/adminbiz/AdminBizTest.java b/xxl-job-admin/src/test/java/com/xxl/job/adminbiz/AdminBizTest.java deleted file mode 100644 index 29d37c9c..00000000 --- a/xxl-job-admin/src/test/java/com/xxl/job/adminbiz/AdminBizTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.xxl.job.adminbiz; - -import com.xxl.job.core.biz.AdminBiz; -import com.xxl.job.core.biz.client.AdminBizClient; -import com.xxl.job.core.biz.model.HandleCallbackParam; -import com.xxl.job.core.biz.model.RegistryParam; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.context.XxlJobContext; -import com.xxl.job.core.enums.RegistryConfig; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * admin api test - * - * @author xuxueli 2017-07-28 22:14:52 - */ -public class AdminBizTest { - - // admin-client - private static String addressUrl = "http://127.0.0.1:8080/xxl-job-admin/"; - private static String accessToken = null; - private static int timeoutSecond = 3; - - - @Test - public void callback() throws Exception { - AdminBiz adminBiz = new AdminBizClient(addressUrl, accessToken, timeoutSecond); - - HandleCallbackParam param = new HandleCallbackParam(); - param.setLogId(1); - param.setHandleCode(XxlJobContext.HANDLE_CODE_SUCCESS); - - List callbackParamList = Arrays.asList(param); - - ReturnT returnT = adminBiz.callback(callbackParamList); - - assertTrue(returnT.getCode() == ReturnT.SUCCESS_CODE); - } - - /** - * registry executor - * - * @throws Exception - */ - @Test - public void registry() throws Exception { - AdminBiz adminBiz = new AdminBizClient(addressUrl, accessToken, timeoutSecond); - - RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), "xxl-job-executor-example", "127.0.0.1:9999"); - ReturnT returnT = adminBiz.registry(registryParam); - - assertTrue(returnT.getCode() == ReturnT.SUCCESS_CODE); - } - - /** - * registry executor remove - * - * @throws Exception - */ - @Test - public void registryRemove() throws Exception { - AdminBiz adminBiz = new AdminBizClient(addressUrl, accessToken, timeoutSecond); - - RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), "xxl-job-executor-example", "127.0.0.1:9999"); - ReturnT returnT = adminBiz.registryRemove(registryParam); - - assertTrue(returnT.getCode() == ReturnT.SUCCESS_CODE); - - } - -} diff --git a/xxl-job-admin/src/test/java/com/xxl/job/openapi/AdminBizTest.java b/xxl-job-admin/src/test/java/com/xxl/job/openapi/AdminBizTest.java new file mode 100644 index 00000000..a21b5cb3 --- /dev/null +++ b/xxl-job-admin/src/test/java/com/xxl/job/openapi/AdminBizTest.java @@ -0,0 +1,93 @@ +package com.xxl.job.openapi; + +import com.xxl.job.core.constant.RegistType; +import com.xxl.job.core.openapi.AdminBiz; +import com.xxl.job.core.openapi.model.CallbackRequest; +import com.xxl.job.core.openapi.model.RegistryRequest; +import com.xxl.job.core.context.XxlJobContext; +import com.xxl.job.core.constant.Const; +import com.xxl.tool.http.HttpTool; +import com.xxl.tool.response.Response; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * admin api test + * + * @author xuxueli 2017-07-28 22:14:52 + */ +public class AdminBizTest { + private static final Logger logger = LoggerFactory.getLogger(AdminBizTest.class); + + private static String addressUrl = "http://127.0.0.1:8080/xxl-job-admin"; + private static String accessToken = "default_token"; + + private AdminBiz buildClient(){ + String finalUrl = addressUrl + "/api"; + + return HttpTool.createClient() + .url(finalUrl) + .timeout(3 * 1000) + .header(Const.XXL_JOB_ACCESS_TOKEN, accessToken) + .proxy(AdminBiz.class); + } + + @Test + public void callback() throws Exception { + AdminBiz adminBiz = buildClient(); + + CallbackRequest param = new CallbackRequest(); + param.setLogId(1); + param.setHandleCode(XxlJobContext.HANDLE_CODE_SUCCESS); + + List callbackParamList = Arrays.asList(param); + + Response returnT = adminBiz.callback(callbackParamList); + assertTrue(returnT.isSuccess()); + } + + /** + * registry executor + * + * @throws Exception + */ + @Test + public void registry() throws Exception { + AdminBiz adminBiz = buildClient(); + + RegistryRequest registryParam = new RegistryRequest(RegistType.EXECUTOR.name(), "xxl-job-executor-example", "127.0.0.1:9999"); + + Response returnT = adminBiz.registry(registryParam); + assertTrue(returnT.isSuccess()); + } + + /** + * registry executor remove + * + * @throws Exception + */ + @Test + public void registryRemove() throws Exception { + AdminBiz adminBiz = buildClient(); + + RegistryRequest registryParam = new RegistryRequest(RegistType.EXECUTOR.name(), "xxl-job-executor-example", "127.0.0.1:9999"); + + Response returnT = adminBiz.registryRemove(registryParam); + assertTrue(returnT.isSuccess()); + + } + + // ---------------------- job opt ---------------------- + + @Test + public void jobManage() throws Exception { + // jobAdd、jobUpdate、jobRemove、jobStart、jobStop + } + +} diff --git a/xxl-job-admin/src/test/java/com/xxl/job/executorbiz/ExecutorBizTest.java b/xxl-job-admin/src/test/java/com/xxl/job/openapi/ExecutorBizTest.java similarity index 54% rename from xxl-job-admin/src/test/java/com/xxl/job/executorbiz/ExecutorBizTest.java rename to xxl-job-admin/src/test/java/com/xxl/job/openapi/ExecutorBizTest.java index 46e710e5..cdde86ef 100644 --- a/xxl-job-admin/src/test/java/com/xxl/job/executorbiz/ExecutorBizTest.java +++ b/xxl-job-admin/src/test/java/com/xxl/job/openapi/ExecutorBizTest.java @@ -1,12 +1,16 @@ -package com.xxl.job.executorbiz; +package com.xxl.job.openapi; -import com.xxl.job.core.biz.ExecutorBiz; -import com.xxl.job.core.biz.client.ExecutorBizClient; -import com.xxl.job.core.biz.model.*; -import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; +import com.xxl.job.core.constant.Const; +import com.xxl.job.core.openapi.ExecutorBiz; +import com.xxl.job.core.openapi.model.*; +import com.xxl.job.core.constant.ExecutorBlockStrategyEnum; import com.xxl.job.core.glue.GlueTypeEnum; +import com.xxl.tool.http.HttpTool; +import com.xxl.tool.response.Response; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * executor api test @@ -14,47 +18,54 @@ import org.junit.jupiter.api.Test; * Created by xuxueli on 17/5/12. */ public class ExecutorBizTest { + private static final Logger logger = LoggerFactory.getLogger(ExecutorBizTest.class); - // admin-client private static String addressUrl = "http://127.0.0.1:9999/"; - private static String accessToken = null; - private static int timeout = 3; + private static String accessToken = "default_token"; + + private ExecutorBiz buildClient(){ + return HttpTool.createClient() + .url(addressUrl) + .timeout(3 * 1000) + .header(Const.XXL_JOB_ACCESS_TOKEN, accessToken) + .proxy(ExecutorBiz.class); + } @Test public void beat() throws Exception { - ExecutorBiz executorBiz = new ExecutorBizClient(addressUrl, accessToken, timeout); + ExecutorBiz executorBiz = buildClient(); // Act - final ReturnT retval = executorBiz.beat(); + final Response retval = executorBiz.beat(); // Assert result Assertions.assertNotNull(retval); - Assertions.assertNull(((ReturnT) retval).getContent()); + Assertions.assertNull(((Response) retval).getData()); Assertions.assertEquals(200, retval.getCode()); Assertions.assertNull(retval.getMsg()); } @Test public void idleBeat(){ - ExecutorBiz executorBiz = new ExecutorBizClient(addressUrl, accessToken, timeout); + ExecutorBiz executorBiz = buildClient(); final int jobId = 0; // Act - final ReturnT retval = executorBiz.idleBeat(new IdleBeatParam(jobId)); + final Response retval = executorBiz.idleBeat(new IdleBeatRequest(jobId)); // Assert result Assertions.assertNotNull(retval); - Assertions.assertNull(((ReturnT) retval).getContent()); + Assertions.assertNull(((Response) retval).getData()); Assertions.assertEquals(500, retval.getCode()); Assertions.assertEquals("job thread is running or has trigger queue.", retval.getMsg()); } @Test public void run(){ - ExecutorBiz executorBiz = new ExecutorBizClient(addressUrl, accessToken, timeout); + ExecutorBiz executorBiz = buildClient(); // trigger data - final TriggerParam triggerParam = new TriggerParam(); + final TriggerRequest triggerParam = new TriggerRequest(); triggerParam.setJobId(1); triggerParam.setExecutorHandler("demoJobHandler"); triggerParam.setExecutorParams(null); @@ -66,38 +77,39 @@ public class ExecutorBizTest { triggerParam.setLogDateTime(System.currentTimeMillis()); // Act - final ReturnT retval = executorBiz.run(triggerParam); + final Response retval = executorBiz.run(triggerParam); // Assert result Assertions.assertNotNull(retval); + } @Test public void kill(){ - ExecutorBiz executorBiz = new ExecutorBizClient(addressUrl, accessToken, timeout); + ExecutorBiz executorBiz = buildClient(); final int jobId = 0; // Act - final ReturnT retval = executorBiz.kill(new KillParam(jobId)); + final Response retval = executorBiz.kill(new KillRequest(jobId)); // Assert result Assertions.assertNotNull(retval); - Assertions.assertNull(((ReturnT) retval).getContent()); + Assertions.assertNull(((Response) retval).getData()); Assertions.assertEquals(200, retval.getCode()); Assertions.assertNull(retval.getMsg()); } @Test public void log(){ - ExecutorBiz executorBiz = new ExecutorBizClient(addressUrl, accessToken, timeout); + ExecutorBiz executorBiz = buildClient(); final long logDateTim = 0L; final long logId = 0; final int fromLineNum = 0; // Act - final ReturnT retval = executorBiz.log(new LogParam(logDateTim, logId, fromLineNum)); + final Response retval = executorBiz.log(new LogRequest(logDateTim, logId, fromLineNum)); // Assert result Assertions.assertNotNull(retval); diff --git a/xxl-job-core/pom.xml b/xxl-job-core/pom.xml index 1b89f93c..b6c699ff 100644 --- a/xxl-job-core/pom.xml +++ b/xxl-job-core/pom.xml @@ -4,7 +4,7 @@ com.xuxueli xxl-job - 2.5.0 + 3.4.0 xxl-job-core jar @@ -15,47 +15,47 @@ + + + + org.slf4j + slf4j-api + + + + + jakarta.annotation + jakarta.annotation-api + provided + + io.netty netty-codec-http - ${netty.version} com.google.code.gson gson - ${gson.version} - + + + com.xuxueli + xxl-tool + + + org.apache.groovy groovy - ${groovy.version} org.springframework spring-context - ${spring.version} - provided - - - - - - org.slf4j - slf4j-api - ${slf4j-api.version} - - - - - javax.annotation - javax.annotation-api - ${javax.annotation-api.version} provided diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/AdminBiz.java b/xxl-job-core/src/main/java/com/xxl/job/core/biz/AdminBiz.java deleted file mode 100644 index 8d7b9440..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/AdminBiz.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xxl.job.core.biz; - -import com.xxl.job.core.biz.model.HandleCallbackParam; -import com.xxl.job.core.biz.model.RegistryParam; -import com.xxl.job.core.biz.model.ReturnT; - -import java.util.List; - -/** - * @author xuxueli 2017-07-27 21:52:49 - */ -public interface AdminBiz { - - - // ---------------------- callback ---------------------- - - /** - * callback - * - * @param callbackParamList - * @return - */ - public ReturnT callback(List callbackParamList); - - - // ---------------------- registry ---------------------- - - /** - * registry - * - * @param registryParam - * @return - */ - public ReturnT registry(RegistryParam registryParam); - - /** - * registry remove - * - * @param registryParam - * @return - */ - public ReturnT registryRemove(RegistryParam registryParam); - - - // ---------------------- biz (custome) ---------------------- - // group、job ... manage - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/ExecutorBiz.java b/xxl-job-core/src/main/java/com/xxl/job/core/biz/ExecutorBiz.java deleted file mode 100644 index 986358fc..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/ExecutorBiz.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.xxl.job.core.biz; - -import com.xxl.job.core.biz.model.*; - -/** - * Created by xuxueli on 17/3/1. - */ -public interface ExecutorBiz { - - /** - * beat - * @return - */ - public ReturnT beat(); - - /** - * idle beat - * - * @param idleBeatParam - * @return - */ - public ReturnT idleBeat(IdleBeatParam idleBeatParam); - - /** - * run - * @param triggerParam - * @return - */ - public ReturnT run(TriggerParam triggerParam); - - /** - * kill - * @param killParam - * @return - */ - public ReturnT kill(KillParam killParam); - - /** - * log - * @param logParam - * @return - */ - public ReturnT log(LogParam logParam); - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/client/AdminBizClient.java b/xxl-job-core/src/main/java/com/xxl/job/core/biz/client/AdminBizClient.java deleted file mode 100644 index b5c26b46..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/client/AdminBizClient.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.xxl.job.core.biz.client; - -import com.xxl.job.core.biz.AdminBiz; -import com.xxl.job.core.biz.model.HandleCallbackParam; -import com.xxl.job.core.biz.model.RegistryParam; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.util.XxlJobRemotingUtil; - -import java.util.List; - -/** - * admin api test - * - * @author xuxueli 2017-07-28 22:14:52 - */ -public class AdminBizClient implements AdminBiz { - - public AdminBizClient() { - } - public AdminBizClient(String addressUrl, String accessToken, int timeout) { - this.addressUrl = addressUrl; - this.accessToken = accessToken; - this.timeout = timeout; - - // valid - if (!this.addressUrl.endsWith("/")) { - this.addressUrl = this.addressUrl + "/"; - } - if (!(this.timeout >=1 && this.timeout <= 10)) { - this.timeout = 3; - } - } - - private String addressUrl ; - private String accessToken; - private int timeout; - - - @Override - public ReturnT callback(List callbackParamList) { - return XxlJobRemotingUtil.postBody(addressUrl+"api/callback", accessToken, timeout, callbackParamList, String.class); - } - - @Override - public ReturnT registry(RegistryParam registryParam) { - return XxlJobRemotingUtil.postBody(addressUrl + "api/registry", accessToken, timeout, registryParam, String.class); - } - - @Override - public ReturnT registryRemove(RegistryParam registryParam) { - return XxlJobRemotingUtil.postBody(addressUrl + "api/registryRemove", accessToken, timeout, registryParam, String.class); - } - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/client/ExecutorBizClient.java b/xxl-job-core/src/main/java/com/xxl/job/core/biz/client/ExecutorBizClient.java deleted file mode 100644 index 8912f68a..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/client/ExecutorBizClient.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.xxl.job.core.biz.client; - -import com.xxl.job.core.biz.ExecutorBiz; -import com.xxl.job.core.biz.model.*; -import com.xxl.job.core.util.XxlJobRemotingUtil; - -/** - * admin api test - * - * @author xuxueli 2017-07-28 22:14:52 - */ -public class ExecutorBizClient implements ExecutorBiz { - - public ExecutorBizClient() { - } - public ExecutorBizClient(String addressUrl, String accessToken, int timeout) { - this.addressUrl = addressUrl; - this.accessToken = accessToken; - this.timeout = timeout; - - // valid - if (!this.addressUrl.endsWith("/")) { - this.addressUrl = this.addressUrl + "/"; - } - if (!(this.timeout >=1 && this.timeout <= 10)) { - this.timeout = 3; - } - } - - private String addressUrl ; - private String accessToken; - private int timeout; - - - @Override - public ReturnT beat() { - return XxlJobRemotingUtil.postBody(addressUrl+"beat", accessToken, timeout, "", String.class); - } - - @Override - public ReturnT idleBeat(IdleBeatParam idleBeatParam){ - return XxlJobRemotingUtil.postBody(addressUrl+"idleBeat", accessToken, timeout, idleBeatParam, String.class); - } - - @Override - public ReturnT run(TriggerParam triggerParam) { - return XxlJobRemotingUtil.postBody(addressUrl + "run", accessToken, timeout, triggerParam, String.class); - } - - @Override - public ReturnT kill(KillParam killParam) { - return XxlJobRemotingUtil.postBody(addressUrl + "kill", accessToken, timeout, killParam, String.class); - } - - @Override - public ReturnT log(LogParam logParam) { - return XxlJobRemotingUtil.postBody(addressUrl + "log", accessToken, timeout, logParam, LogResult.class); - } - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/ReturnT.java b/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/ReturnT.java deleted file mode 100644 index 83d7a361..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/ReturnT.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.xxl.job.core.biz.model; - -import java.io.Serializable; - -/** - * common return - * @author xuxueli 2015-12-4 16:32:31 - * @param - */ -public class ReturnT implements Serializable { - public static final long serialVersionUID = 42L; - - public static final int SUCCESS_CODE = 200; - public static final int FAIL_CODE = 500; - - public static final ReturnT SUCCESS = new ReturnT(null); - public static final ReturnT FAIL = new ReturnT(FAIL_CODE, null); - - private int code; - private String msg; - private T content; - - public ReturnT(){} - public ReturnT(int code, String msg) { - this.code = code; - this.msg = msg; - } - public ReturnT(T content) { - this.code = SUCCESS_CODE; - this.content = content; - } - - public int getCode() { - return code; - } - public void setCode(int code) { - this.code = code; - } - public String getMsg() { - return msg; - } - public void setMsg(String msg) { - this.msg = msg; - } - public T getContent() { - return content; - } - public void setContent(T content) { - this.content = content; - } - - @Override - public String toString() { - return "ReturnT [code=" + code + ", msg=" + msg + ", content=" + content + "]"; - } - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/constant/Const.java b/xxl-job-core/src/main/java/com/xxl/job/core/constant/Const.java new file mode 100644 index 00000000..0878fd90 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/constant/Const.java @@ -0,0 +1,28 @@ +package com.xxl.job.core.constant; + +/** + * Created by xuxueli on 17/5/10. + */ +public class Const { + + // ---------------------- for openapi ---------------------- + + /** + * access token + */ + public static final String XXL_JOB_ACCESS_TOKEN = "XXL-JOB-ACCESS-TOKEN"; + + + // ---------------------- for registry ---------------------- + + /** + * registry beat interval, default 30s + */ + public static final int BEAT_TIMEOUT = 30; + + /** + * registry dead timeout, default 90s + */ + public static final int DEAD_TIMEOUT = BEAT_TIMEOUT * 3; + +} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/enums/ExecutorBlockStrategyEnum.java b/xxl-job-core/src/main/java/com/xxl/job/core/constant/ExecutorBlockStrategyEnum.java similarity index 96% rename from xxl-job-core/src/main/java/com/xxl/job/core/enums/ExecutorBlockStrategyEnum.java rename to xxl-job-core/src/main/java/com/xxl/job/core/constant/ExecutorBlockStrategyEnum.java index a9dc1bea..c1975eda 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/enums/ExecutorBlockStrategyEnum.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/constant/ExecutorBlockStrategyEnum.java @@ -1,4 +1,4 @@ -package com.xxl.job.core.enums; +package com.xxl.job.core.constant; /** * Created by xuxueli on 17/5/9. diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/constant/RegistType.java b/xxl-job-core/src/main/java/com/xxl/job/core/constant/RegistType.java new file mode 100644 index 00000000..84277203 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/constant/RegistType.java @@ -0,0 +1,18 @@ +package com.xxl.job.core.constant; + +/** + * Created by xuxueli on 17/5/9. + */ +public enum RegistType{ + + /** + * executor registry + */ + EXECUTOR, + + /** + * admin registry + */ + ADMIN; + +} \ No newline at end of file diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobContext.java b/xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobContext.java index 7e350129..3f2dfc81 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobContext.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobContext.java @@ -27,9 +27,19 @@ public class XxlJobContext { // ---------------------- for log ---------------------- /** - * job log filename + * log id */ - private final String jobLogFileName; + private final long logId; + + /** + * log timestamp + */ + private final long logDateTime; + + /** + * log filename + */ + private final String logFileName; // ---------------------- for shard ---------------------- @@ -61,10 +71,18 @@ public class XxlJobContext { private String handleMsg; - public XxlJobContext(long jobId, String jobParam, String jobLogFileName, int shardIndex, int shardTotal) { + public XxlJobContext(long jobId, + String jobParam, + long logId, + long logDateTime, + String logFileName, + int shardIndex, + int shardTotal) { this.jobId = jobId; this.jobParam = jobParam; - this.jobLogFileName = jobLogFileName; + this.logId = logId; + this.logDateTime = logDateTime; + this.logFileName = logFileName; this.shardIndex = shardIndex; this.shardTotal = shardTotal; @@ -79,8 +97,16 @@ public class XxlJobContext { return jobParam; } - public String getJobLogFileName() { - return jobLogFileName; + public long getLogId() { + return logId; + } + + public long getLogDateTime() { + return logDateTime; + } + + public String getLogFileName() { + return logFileName; } public int getShardIndex() { @@ -109,12 +135,21 @@ public class XxlJobContext { // ---------------------- tool ---------------------- - private static InheritableThreadLocal contextHolder = new InheritableThreadLocal(); // support for child thread of job handler) + /** + * xxl-job context store + */ + private static final InheritableThreadLocal contextHolder = new InheritableThreadLocal(); // support for child thread of job handler) + /** + * set xxl-job context + */ public static void setXxlJobContext(XxlJobContext xxlJobContext){ contextHolder.set(xxlJobContext); } + /** + * get xxl-job context + */ public static XxlJobContext getXxlJobContext(){ return contextHolder.get(); } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobHelper.java b/xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobHelper.java index eb20c181..ae9e62b6 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobHelper.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobHelper.java @@ -1,7 +1,7 @@ package com.xxl.job.core.context; import com.xxl.job.core.log.XxlJobFileAppender; -import com.xxl.job.core.util.DateUtil; +import com.xxl.tool.core.DateTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.helpers.FormattingTuple; @@ -18,12 +18,12 @@ import java.util.Date; */ public class XxlJobHelper { - // ---------------------- base info ---------------------- + // ---------------------- job info ---------------------- /** * current JobId * - * @return + * @return jobId */ public static long getJobId() { XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); @@ -37,7 +37,7 @@ public class XxlJobHelper { /** * current JobParam * - * @return + * @return jobParam */ public static String getJobParam() { XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); @@ -48,28 +48,56 @@ public class XxlJobHelper { return xxlJobContext.getJobParam(); } - // ---------------------- for log ---------------------- + // ---------------------- log info ---------------------- /** - * current JobLogFileName + * current job log time * - * @return + * @return logDateTime */ - public static String getJobLogFileName() { + public static long getLogId() { + XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); + if (xxlJobContext == null) { + return -1; + } + + return xxlJobContext.getLogId(); + } + + /** + * current job log time + * + * @return logDateTime + */ + public static long getLogDateTime() { + XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); + if (xxlJobContext == null) { + return -1; + } + + return xxlJobContext.getLogDateTime(); + } + + /** + * current job log filename + * + * @return logFileName + */ + public static String getLogFileName() { XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); if (xxlJobContext == null) { return null; } - return xxlJobContext.getJobLogFileName(); + return xxlJobContext.getLogFileName(); } - // ---------------------- for shard ---------------------- + // ---------------------- shard info ---------------------- /** * current ShardIndex * - * @return + * @return shardIndex */ public static int getShardIndex() { XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); @@ -83,7 +111,7 @@ public class XxlJobHelper { /** * current ShardTotal * - * @return + * @return shardTotal */ public static int getShardTotal() { XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); @@ -96,7 +124,7 @@ public class XxlJobHelper { // ---------------------- tool for log ---------------------- - private static Logger logger = LoggerFactory.getLogger("xxl-job logger"); + private static final Logger logger = LoggerFactory.getLogger("xxl-job logger"); /** * append log with pattern @@ -121,7 +149,8 @@ public class XxlJobHelper { /** * append exception stack * - * @param e + * @param e exception to log + * return true if log success */ public static boolean log(Throwable e) { @@ -136,8 +165,8 @@ public class XxlJobHelper { /** * append log * - * @param callInfo - * @param appendLog + * @param callInfo call info + * @param appendLog append log */ private static boolean logDetail(StackTraceElement callInfo, String appendLog) { XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); @@ -149,18 +178,16 @@ public class XxlJobHelper { StackTraceElement[] stackTraceElements = new Throwable().getStackTrace(); StackTraceElement callInfo = stackTraceElements[1];*/ - StringBuffer stringBuffer = new StringBuffer(); - stringBuffer.append(DateUtil.formatDateTime(new Date())).append(" ") - .append("["+ callInfo.getClassName() + "#" + callInfo.getMethodName() +"]").append("-") - .append("["+ callInfo.getLineNumber() +"]").append("-") - .append("["+ Thread.currentThread().getName() +"]").append(" ") - .append(appendLog!=null?appendLog:""); - String formatAppendLog = stringBuffer.toString(); + String formatAppendLog = DateTool.formatDateTime(new Date()) + " " + + "[" + callInfo.getClassName() + "#" + callInfo.getMethodName() + "]" + "-" + + "[" + callInfo.getLineNumber() + "]" + "-" + + "[" + Thread.currentThread().getName() + "]" + " " + + (appendLog != null ? appendLog : ""); // appendlog - String logFileName = xxlJobContext.getJobLogFileName(); + String logFileName = xxlJobContext.getLogFileName(); - if (logFileName!=null && logFileName.trim().length()>0) { + if (logFileName!=null && !logFileName.trim().isEmpty()) { XxlJobFileAppender.appendLog(logFileName, formatAppendLog); return true; } else { @@ -174,7 +201,7 @@ public class XxlJobHelper { /** * handle success * - * @return + * @return true if handle success */ public static boolean handleSuccess(){ return handleResult(XxlJobContext.HANDLE_CODE_SUCCESS, null); @@ -183,8 +210,8 @@ public class XxlJobHelper { /** * handle success with log msg * - * @param handleMsg - * @return + * @param handleMsg log msg + * @return true if handle success */ public static boolean handleSuccess(String handleMsg) { return handleResult(XxlJobContext.HANDLE_CODE_SUCCESS, handleMsg); @@ -193,7 +220,7 @@ public class XxlJobHelper { /** * handle fail * - * @return + * @return true if handle fail */ public static boolean handleFail(){ return handleResult(XxlJobContext.HANDLE_CODE_FAIL, null); @@ -202,8 +229,8 @@ public class XxlJobHelper { /** * handle fail with log msg * - * @param handleMsg - * @return + * @param handleMsg log msg + * @return true if handle fail */ public static boolean handleFail(String handleMsg) { return handleResult(XxlJobContext.HANDLE_CODE_FAIL, handleMsg); @@ -212,7 +239,7 @@ public class XxlJobHelper { /** * handle timeout * - * @return + * @return true if handle timeout */ public static boolean handleTimeout(){ return handleResult(XxlJobContext.HANDLE_CODE_TIMEOUT, null); @@ -221,8 +248,8 @@ public class XxlJobHelper { /** * handle timeout with log msg * - * @param handleMsg - * @return + * @param handleMsg log msg + * @return true if handle timeout */ public static boolean handleTimeout(String handleMsg){ return handleResult(XxlJobContext.HANDLE_CODE_TIMEOUT, handleMsg); @@ -235,8 +262,8 @@ public class XxlJobHelper { * 500 : fail * 502 : timeout * - * @param handleMsg - * @return + * @param handleMsg log msg + * @return true if handle success */ public static boolean handleResult(int handleCode, String handleMsg) { XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext(); diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/enums/RegistryConfig.java b/xxl-job-core/src/main/java/com/xxl/job/core/enums/RegistryConfig.java deleted file mode 100644 index 798beaef..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/enums/RegistryConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.xxl.job.core.enums; - -/** - * Created by xuxueli on 17/5/10. - */ -public class RegistryConfig { - - public static final int BEAT_TIMEOUT = 30; - public static final int DEAD_TIMEOUT = BEAT_TIMEOUT * 3; - - public enum RegistType{ EXECUTOR, ADMIN } - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java b/xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java index e0d36192..9d27cdd0 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java @@ -1,7 +1,7 @@ package com.xxl.job.core.executor; -import com.xxl.job.core.biz.AdminBiz; -import com.xxl.job.core.biz.client.AdminBizClient; +import com.xxl.job.core.constant.Const; +import com.xxl.job.core.openapi.AdminBiz; import com.xxl.job.core.handler.IJobHandler; import com.xxl.job.core.handler.annotation.XxlJob; import com.xxl.job.core.handler.impl.MethodJobHandler; @@ -10,8 +10,9 @@ import com.xxl.job.core.server.EmbedServer; import com.xxl.job.core.thread.JobLogFileCleanThread; import com.xxl.job.core.thread.JobThread; import com.xxl.job.core.thread.TriggerCallbackThread; -import com.xxl.job.core.util.IpUtil; -import com.xxl.job.core.util.NetUtil; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.http.HttpTool; +import com.xxl.tool.http.IPTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; /** * Created by xuxueli on 2016/3/2 21:14. @@ -28,10 +30,16 @@ import java.util.concurrent.ConcurrentMap; public class XxlJobExecutor { private static final Logger logger = LoggerFactory.getLogger(XxlJobExecutor.class); - // ---------------------- param ---------------------- + /* + * elegant shutdown wait seconds + */ + private static final long ELEGANT_SHUTDOWN_WAITING_SECONDS = 5; + + // ---------------------- field ---------------------- private String adminAddresses; private String accessToken; private int timeout; + private Boolean enabled; private String appname; private String address; private String ip; @@ -48,6 +56,9 @@ public class XxlJobExecutor { public void setTimeout(int timeout) { this.timeout = timeout; } + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } public void setAppname(String appname) { this.appname = appname; } @@ -71,6 +82,12 @@ public class XxlJobExecutor { // ---------------------- start + stop ---------------------- public void start() throws Exception { + // valid enabled + if (enabled!=null && !enabled) { + logger.info(">>>>>>>>>>> xxl-job executor start fail, enabled:{}", enabled); + return; + } + // init logpath XxlJobFileAppender.initLogPath(logPath); @@ -78,22 +95,31 @@ public class XxlJobExecutor { initAdminBizList(adminAddresses, accessToken, timeout); - // init JobLogFileCleanThread + // 1、init JobLogFileCleanThread JobLogFileCleanThread.getInstance().start(logRetentionDays); - // init TriggerCallbackThread + // 2、init TriggerCallbackThread TriggerCallbackThread.getInstance().start(); - // init executor-server + // 3、init executor-server initEmbedServer(address, ip, port, appname, accessToken); } public void destroy(){ - // destroy executor-server + // 1、destroy executor-server stopEmbedServer(); // destroy jobThreadRepository - if (jobThreadRepository.size() > 0) { + if (!jobThreadRepository.isEmpty()) { + + // 1.1、elegant shutdown wait job finish + try { + TimeUnit.SECONDS.sleep(ELEGANT_SHUTDOWN_WAITING_SECONDS); + } catch (Throwable e) { + logger.error(e.getMessage(), e); + } + + // 1.2、interupt all job-thread for (Map.Entry item: jobThreadRepository.entrySet()) { JobThread oldJobThread = removeJobThread(item.getKey(), "web container destroy and kill the job."); // wait for job thread push result to callback queue @@ -110,10 +136,10 @@ public class XxlJobExecutor { jobHandlerRepository.clear(); - // destroy JobLogFileCleanThread + // 2、destroy JobLogFileCleanThread JobLogFileCleanThread.getInstance().toStop(); - // destroy TriggerCallbackThread + // 3、destroy TriggerCallbackThread TriggerCallbackThread.getInstance().toStop(); } @@ -122,18 +148,36 @@ public class XxlJobExecutor { // ---------------------- admin-client (rpc invoker) ---------------------- private static List adminBizList; private void initAdminBizList(String adminAddresses, String accessToken, int timeout) throws Exception { - if (adminAddresses!=null && adminAddresses.trim().length()>0) { - for (String address: adminAddresses.trim().split(",")) { - if (address!=null && address.trim().length()>0) { + // valid + if (StringTool.isBlank(adminAddresses)) { + return; + } - AdminBiz adminBiz = new AdminBizClient(address.trim(), accessToken, timeout); + // build adminBizList + for (String address: adminAddresses.trim().split(",")) { + if (StringTool.isBlank(address)) { + continue; + } - if (adminBizList == null) { - adminBizList = new ArrayList(); - } - adminBizList.add(adminBiz); - } + // parse param + String finalAddress = address.trim(); + finalAddress = finalAddress.endsWith("/") ? (finalAddress + "api") : (finalAddress + "/api"); + int finalTimeout = (timeout >=1 && timeout <= 10) + ?timeout + :3; + + // build + AdminBiz adminBiz = HttpTool.createClient() + .url(finalAddress) + .timeout(finalTimeout * 1000) + .header(Const.XXL_JOB_ACCESS_TOKEN, accessToken) + .proxy(AdminBiz.class); + + // registry + if (adminBizList == null) { + adminBizList = new ArrayList(); } + adminBizList.add(adminBiz); } } @@ -147,17 +191,18 @@ public class XxlJobExecutor { private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception { // fill ip port - port = port>0?port: NetUtil.findAvailablePort(9999); - ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp(); + port = port>0?port: IPTool.getAvailablePort(9999); + ip = StringTool.isNotBlank(ip) ? ip : IPTool.getIp(); // generate address - if (address==null || address.trim().length()==0) { - String ip_port_address = IpUtil.getIpPort(ip, port); // registry-address:default use address to registry , otherwise use ip:port if address is null + if (StringTool.isBlank(address)) { + // registry-address:default use address to registry , otherwise use ip:port if address is null + String ip_port_address = IPTool.toAddressString(ip, port); address = "http://{ip_port}/".replace("{ip_port}", ip_port_address); } // accessToken - if (accessToken==null || accessToken.trim().length()==0) { + if (StringTool.isBlank(accessToken)) { logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken."); } @@ -183,11 +228,11 @@ public class XxlJobExecutor { public static IJobHandler loadJobHandler(String name){ return jobHandlerRepository.get(name); } - public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){ + public static IJobHandler registryJobHandler(String name, IJobHandler jobHandler){ logger.info(">>>>>>>>>>> xxl-job register jobhandler success, name:{}, jobHandler:{}", name, jobHandler); return jobHandlerRepository.put(name, jobHandler); } - protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod){ + protected void registryJobHandler(XxlJob xxlJob, Object bean, Method executeMethod){ if (xxlJob == null) { return; } @@ -237,7 +282,7 @@ public class XxlJobExecutor { } // registry jobhandler - registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod)); + registryJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod)); } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSimpleExecutor.java b/xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSimpleExecutor.java index 53efbb95..e189000e 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSimpleExecutor.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSimpleExecutor.java @@ -2,14 +2,12 @@ package com.xxl.job.core.executor.impl; import com.xxl.job.core.executor.XxlJobExecutor; import com.xxl.job.core.handler.annotation.XxlJob; -import com.xxl.job.core.handler.impl.MethodJobHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Map; /** @@ -51,7 +49,7 @@ public class XxlJobSimpleExecutor extends XxlJobExecutor { private void initJobHandlerMethodRepository(List xxlJobBeanList) { - if (xxlJobBeanList==null || xxlJobBeanList.size()==0) { + if (xxlJobBeanList==null || xxlJobBeanList.isEmpty()) { return; } @@ -65,7 +63,7 @@ public class XxlJobSimpleExecutor extends XxlJobExecutor { for (Method executeMethod : methods) { XxlJob xxlJob = executeMethod.getAnnotation(XxlJob.class); // registry - registJobHandler(xxlJob, bean, executeMethod); + registryJobHandler(xxlJob, bean, executeMethod); } } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSpringExecutor.java b/xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSpringExecutor.java index 953903c3..f313ed90 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSpringExecutor.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSpringExecutor.java @@ -8,13 +8,16 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.context.annotation.Lazy; import org.springframework.core.MethodIntrospector; import org.springframework.core.annotation.AnnotatedElementUtils; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; import java.util.Map; @@ -26,16 +29,28 @@ import java.util.Map; public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean { private static final Logger logger = LoggerFactory.getLogger(XxlJobSpringExecutor.class); + // ---------------------- field ---------------------- - // start + /** + * excluded package, like "org.springframework"、"org.aaa,org.bbb" + */ + private String excludedPackage = "org.springframework.,spring."; + + public void setExcludedPackage(String excludedPackage) { + this.excludedPackage = excludedPackage; + } + + + // ---------------------- start / stop ---------------------- + + /** + * start + */ @Override public void afterSingletonsInstantiated() { - // init JobHandler Repository - /*initJobHandlerRepository(applicationContext);*/ - - // init JobHandler Repository (for method) - initJobHandlerMethodRepository(applicationContext); + // scan JobHandler method + scanJobHandlerMethod(applicationContext); // refresh GlueFactory GlueFactory.refreshInstance(1); @@ -48,57 +63,80 @@ public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationC } } - // destroy + /** + * stop + */ @Override public void destroy() { super.destroy(); } - /*private void initJobHandlerRepository(ApplicationContext applicationContext) { + /** + * init job handler from method + * + * @param applicationContext applicationContext + */ + private void scanJobHandlerMethod(ApplicationContext applicationContext) { + // valid if (applicationContext == null) { return; } - // init job handler action - Map serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class); - - if (serviceBeanMap != null && serviceBeanMap.size() > 0) { - for (Object serviceBean : serviceBeanMap.values()) { - if (serviceBean instanceof IJobHandler) { - String name = serviceBean.getClass().getAnnotation(JobHandler.class).value(); - IJobHandler handler = (IJobHandler) serviceBean; - if (loadJobHandler(name) != null) { - throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts."); - } - registJobHandler(name, handler); + // 1、build excluded-package list + List excludedPackageList = new ArrayList<>(); + if (excludedPackage != null) { + for (String excludedPackage : excludedPackage.split(",")) { + if (!excludedPackage.trim().isEmpty()){ + excludedPackageList.add(excludedPackage.trim()); } } } - }*/ - private void initJobHandlerMethodRepository(ApplicationContext applicationContext) { - if (applicationContext == null) { - return; - } - // init job handler from method - String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true); - for (String beanDefinitionName : beanDefinitionNames) { - - // get bean - Object bean = null; - Lazy onBean = applicationContext.findAnnotationOnBean(beanDefinitionName, Lazy.class); - if (onBean!=null){ - logger.debug("xxl-job annotation scan, skip @Lazy Bean:{}", beanDefinitionName); - continue; - }else { - bean = applicationContext.getBean(beanDefinitionName); + // 2、scan bean form jobhandler + String[] beanNames = applicationContext.getBeanNamesForType(Object.class, false, false); // allowEagerInit=false, avoid early initialization + for (String beanName : beanNames) { + + /** + * 2.1、skip by BeanDefinition: + * - skip excluded-package bean + * - skip lazy-init bean + */ + if (applicationContext instanceof BeanDefinitionRegistry beanDefinitionRegistry) { + // get BeanDefinition + if (!beanDefinitionRegistry.containsBeanDefinition(beanName)) { + continue; + } + BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanName); + + // skip excluded-package bean + String beanClassName = beanDefinition.getBeanClassName(); + if (isExcluded(excludedPackageList, beanClassName)) { + logger.debug(">>>>>>>>>>> xxl-job bean-definition scan, skip excluded-package beanName:{}, beanClassName:{}", beanName, beanClassName); + continue; + } + + // skip lazy-init bean + if (beanDefinition.isLazyInit()) { + logger.debug(">>>>>>>>>>> xxl-job bean-definition scan, skip lazy-init beanName:{}", beanName); + continue; + } } + /** + * 2.2、skip by BeanDefinition Class + * - skip beanClass is null + * - skip method annotation(@XxlJob) is null + */ + Class beanClass = applicationContext.getType(beanName, false); + if (beanClass == null) { + logger.debug(">>>>>>>>>>> xxl-job bean-definition scan, skip beanClass-null beanName:{}", beanName); + continue; + } // filter method - Map annotatedMethods = null; // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean + Map annotatedMethods = null; try { - annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(), + annotatedMethods = MethodIntrospector.selectMethods(beanClass, new MethodIntrospector.MetadataLookup() { @Override public XxlJob inspect(Method method) { @@ -106,23 +144,52 @@ public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationC } }); } catch (Throwable ex) { - logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex); + logger.error(">>>>>>>>>>> xxl-job method-jobhandler resolve error for bean[" + beanName + "].", ex); } if (annotatedMethods==null || annotatedMethods.isEmpty()) { continue; } - // generate and regist method job handler - for (Map.Entry methodXxlJobEntry : annotatedMethods.entrySet()) { - Method executeMethod = methodXxlJobEntry.getKey(); - XxlJob xxlJob = methodXxlJobEntry.getValue(); + // 2.3、scan + registry Jobhandler + Object jobBean = applicationContext.getBean(beanName); + for (Map.Entry jobMethodEntry : annotatedMethods.entrySet()) { + Method jobMethod = jobMethodEntry.getKey(); + XxlJob xxlJob = jobMethodEntry.getValue(); // regist - registJobHandler(xxlJob, bean, executeMethod); + registryJobHandler(xxlJob, jobBean, jobMethod); } } } + /** + * check bean if excluded + * + * @param excludedPackageList excludedPackageList + * @param beanClassName beanClassName + * @return true if excluded + */ + private boolean isExcluded(List excludedPackageList, String beanClassName) { + // excludedPackageList is empty, no excluded + if (excludedPackageList == null || excludedPackageList.isEmpty()) { + return false; + } + + // beanClassName is null, no excluded + if (beanClassName == null) { + return false; + } + + // excludedPackageList match, excluded (not scan) + for (String excludedPackage : excludedPackageList) { + if (beanClassName.startsWith(excludedPackage)) { + return true; + } + } + return false; + } + + // ---------------------- applicationContext ---------------------- private static ApplicationContext applicationContext; @@ -135,13 +202,4 @@ public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationC return applicationContext; } - /* - BeanDefinitionRegistryPostProcessor - registry.getBeanDefine() - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - this.registry = registry; - } - * */ - } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueFactory.java b/xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueFactory.java index b727a851..da2037f4 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueFactory.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueFactory.java @@ -21,6 +21,12 @@ public class GlueFactory { public static GlueFactory getInstance(){ return glueFactory; } + + /** + * refresh instance by type + * + * @param type 0-frameless, 1-spring; + */ public static void refreshInstance(int type){ if (type == 0) { glueFactory = new GlueFactory(); diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueTypeEnum.java b/xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueTypeEnum.java index a5c835c9..09875583 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueTypeEnum.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueTypeEnum.java @@ -8,10 +8,11 @@ public enum GlueTypeEnum { BEAN("BEAN", false, null, null), GLUE_GROOVY("GLUE(Java)", false, null, null), GLUE_SHELL("GLUE(Shell)", true, "bash", ".sh"), - GLUE_PYTHON("GLUE(Python)", true, "python", ".py"), - GLUE_PHP("GLUE(PHP)", true, "php", ".php"), + GLUE_PYTHON("GLUE(Python3)", true, "python3", ".py"), + GLUE_PYTHON2("GLUE(Python2)", true, "python", ".py"), GLUE_NODEJS("GLUE(Nodejs)", true, "node", ".js"), - GLUE_POWERSHELL("GLUE(PowerShell)", true, "powershell", ".ps1"); + GLUE_POWERSHELL("GLUE(PowerShell)", true, "powershell", ".ps1"), + GLUE_PHP("GLUE(PHP)", true, "php", ".php"); private String desc; private boolean isScript; diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/glue/impl/SpringGlueFactory.java b/xxl-job-core/src/main/java/com/xxl/job/core/glue/impl/SpringGlueFactory.java index 37e44d5f..e370bb7d 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/glue/impl/SpringGlueFactory.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/glue/impl/SpringGlueFactory.java @@ -2,13 +2,13 @@ package com.xxl.job.core.glue.impl; import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; import com.xxl.job.core.glue.GlueFactory; +import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.annotation.AnnotationUtils; -import javax.annotation.Resource; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -68,9 +68,7 @@ public class SpringGlueFactory extends GlueFactory { field.setAccessible(true); try { field.set(instance, fieldBean); - } catch (IllegalArgumentException e) { - logger.error(e.getMessage(), e); - } catch (IllegalAccessException e) { + } catch (Exception e) { logger.error(e.getMessage(), e); } } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/ScriptJobHandler.java b/xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/ScriptJobHandler.java index 7e0cee44..54ea0fa3 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/ScriptJobHandler.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/ScriptJobHandler.java @@ -29,9 +29,9 @@ public class ScriptJobHandler extends IJobHandler { File glueSrcPath = new File(XxlJobFileAppender.getGlueSrcPath()); if (glueSrcPath.exists()) { File[] glueSrcFileList = glueSrcPath.listFiles(); - if (glueSrcFileList!=null && glueSrcFileList.length>0) { + if (glueSrcFileList != null) { for (File glueSrcFileItem : glueSrcFileList) { - if (glueSrcFileItem.getName().startsWith(String.valueOf(jobId)+"_")) { + if (glueSrcFileItem.getName().startsWith(jobId +"_")) { glueSrcFileItem.delete(); } } @@ -47,6 +47,7 @@ public class ScriptJobHandler extends IJobHandler { @Override public void execute() throws Exception { + // valid if (!glueType.isScript()) { XxlJobHelper.handleFail("glueType["+ glueType +"] invalid."); return; @@ -68,11 +69,12 @@ public class ScriptJobHandler extends IJobHandler { } // log file - String logFileName = XxlJobContext.getXxlJobContext().getJobLogFileName(); + String logFileName = XxlJobContext.getXxlJobContext().getLogFileName(); // script params:0=param、1=分片序号、2=分片总数 + String jobParam = XxlJobHelper.getJobParam(); String[] scriptParams = new String[3]; - scriptParams[0] = XxlJobHelper.getJobParam(); + scriptParams[0] = jobParam!=null?jobParam:""; scriptParams[1] = String.valueOf(XxlJobContext.getXxlJobContext().getShardIndex()); scriptParams[2] = String.valueOf(XxlJobContext.getXxlJobContext().getShardTotal()); diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/log/XxlJobFileAppender.java b/xxl-job-core/src/main/java/com/xxl/job/core/log/XxlJobFileAppender.java index ff0585b5..34263b31 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/log/XxlJobFileAppender.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/log/XxlJobFileAppender.java @@ -1,52 +1,54 @@ package com.xxl.job.core.log; -import com.xxl.job.core.biz.model.LogResult; +import com.xxl.job.core.openapi.model.LogResult; +import com.xxl.tool.core.DateTool; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.io.FileTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; -import java.text.SimpleDateFormat; +import java.io.File; +import java.io.IOException; import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; /** * store trigger log in each log-file + * * @author xuxueli 2016-3-12 19:25:12 */ public class XxlJobFileAppender { - private static Logger logger = LoggerFactory.getLogger(XxlJobFileAppender.class); + private static final Logger logger = LoggerFactory.getLogger(XxlJobFileAppender.class); /** * log base path * * strut like: * ---/ - * ---/gluesource/ * ---/gluesource/10_1514171108000.js - * ---/gluesource/10_1514171108000.js - * ---/2017-12-25/ + * ---/callbacklogs/xxl-job-callback-1761412677119.log * ---/2017-12-25/639.log * ---/2017-12-25/821.log * */ private static String logBasePath = "/data/applogs/xxl-job/jobhandler"; - private static String glueSrcPath = logBasePath.concat("/gluesource"); - public static void initLogPath(String logPath){ + private static String glueSrcPath = logBasePath.concat(File.separator).concat("gluesource"); + private static String callbackLogPath = logBasePath.concat(File.separator).concat("callbacklogs"); + public static void initLogPath(String logPath) throws IOException { // init - if (logPath!=null && logPath.trim().length()>0) { - logBasePath = logPath; + if (StringTool.isNotBlank(logPath)) { + logBasePath = logPath.trim(); } // mk base dir File logPathDir = new File(logBasePath); - if (!logPathDir.exists()) { - logPathDir.mkdirs(); - } + FileTool.createDirectories(logPathDir); logBasePath = logPathDir.getPath(); // mk glue dir File glueBaseDir = new File(logPathDir, "gluesource"); - if (!glueBaseDir.exists()) { - glueBaseDir.mkdirs(); - } + FileTool.createDirectories(glueBaseDir); glueSrcPath = glueBaseDir.getPath(); } public static String getLogPath() { @@ -55,166 +57,107 @@ public class XxlJobFileAppender { public static String getGlueSrcPath() { return glueSrcPath; } + public static String getCallbackLogPath() { + return callbackLogPath; + } /** * log filename, like "logPath/yyyy-MM-dd/9999.log" * - * @param triggerDate - * @param logId - * @return + * @param logId log id + * @return log file name */ public static String makeLogFileName(Date triggerDate, long logId) { - // filePath/yyyy-MM-dd - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // avoid concurrent problem, can not be static - File logFilePath = new File(getLogPath(), sdf.format(triggerDate)); - if (!logFilePath.exists()) { - logFilePath.mkdir(); - } - - // filePath/yyyy-MM-dd/9999.log - String logFileName = logFilePath.getPath() - .concat(File.separator) - .concat(String.valueOf(logId)) - .concat(".log"); - return logFileName; + // "filePath/yyyy-MM-dd" + File logFilePath = new File(getLogPath(), DateTool.formatDate(triggerDate)); + try { + FileTool.createDirectories(logFilePath); + } catch (IOException e) { + throw new RuntimeException("XxlJobFileAppender makeLogFileName error, logFilePath:"+ logFilePath.getPath(), e); + } + + // filePath/yyyy-MM-dd/9999.log + return logFilePath.getPath() + .concat(File.separator) + .concat(String.valueOf(logId)) + .concat(".log"); } /** * append log * - * @param logFileName - * @param appendLog + * @param logFileName log file name + * @param appendLog append log */ public static void appendLog(String logFileName, String appendLog) { - // log file - if (logFileName==null || logFileName.trim().length()==0) { + // valid + if (StringTool.isBlank(logFileName) || appendLog == null) { return; } - File logFile = new File(logFileName); - - if (!logFile.exists()) { - try { - logFile.createNewFile(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - return; - } - } - // log - if (appendLog == null) { - appendLog = ""; - } - appendLog += "\r\n"; - - // append file content - FileOutputStream fos = null; - try { - fos = new FileOutputStream(logFile, true); - fos.write(appendLog.getBytes("utf-8")); - fos.flush(); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } finally { - if (fos != null) { - try { - fos.close(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - } - + // append log + try { + FileTool.writeLines(logFileName, List.of(appendLog), true); + } catch (IOException e) { + throw new RuntimeException("XxlJobFileAppender appendLog error, logFileName:"+ logFileName, e); + } } /** * support read log-file * - * @param logFileName + * @param logFileName log file name + * @param fromLineNum from line num * @return log content */ - public static LogResult readLog(String logFileName, int fromLineNum){ + public static LogResult readLog(String logFileName, final int fromLineNum){ - // valid log file - if (logFileName==null || logFileName.trim().length()==0) { + // valid + if (StringTool.isBlank(logFileName)) { return new LogResult(fromLineNum, 0, "readLog fail, logFile not found", true); } - File logFile = new File(logFileName); - - if (!logFile.exists()) { + if (!FileTool.exists(logFileName)) { return new LogResult(fromLineNum, 0, "readLog fail, logFile not exists", true); } - // read file - StringBuffer logContentBuffer = new StringBuffer(); - int toLineNum = 0; - LineNumberReader reader = null; - try { - //reader = new LineNumberReader(new FileReader(logFile)); - reader = new LineNumberReader(new InputStreamReader(new FileInputStream(logFile), "utf-8")); - String line = null; - - while ((line = reader.readLine())!=null) { - toLineNum = reader.getLineNumber(); // [from, to], start as 1 - if (toLineNum >= fromLineNum) { - logContentBuffer.append(line).append("\n"); - } - } - } catch (IOException e) { - logger.error(e.getMessage(), e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - } - - // result - LogResult logResult = new LogResult(fromLineNum, toLineNum, logContentBuffer.toString(), false); - return logResult; - - /* - // it will return the number of characters actually skipped - reader.skip(Long.MAX_VALUE); - int maxLineNum = reader.getLineNumber(); - maxLineNum++; // 最大行号 - */ - } - - /** - * read log data - * @param logFile - * @return log line content - */ - public static String readLines(File logFile){ - BufferedReader reader = null; - try { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(logFile), "utf-8")); - if (reader != null) { - StringBuilder sb = new StringBuilder(); - String line = null; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - } - return sb.toString(); - } - } catch (IOException e) { - logger.error(e.getMessage(), e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - } - return null; + // read data + StringBuilder logContentBuilder = new StringBuilder(); + // num: [from, to], start as 1 + AtomicInteger toLineNum = new AtomicInteger(0); + AtomicInteger currentLineNum = new AtomicInteger(0); + /*int readLineCount = 0;*/ + + // do read + try { + FileTool.readLines(logFileName, new Consumer() { + @Override + public void accept(String line) { + // refresh line num + currentLineNum.incrementAndGet(); + + // valid + if (currentLineNum.get() < fromLineNum) { + return; + } + + // Limit return less than 1000 rows per query request // todo + /*if(++readLineCount >= 1000) { + break; + }*/ + + // collect line data + toLineNum.set(currentLineNum.get()); + logContentBuilder.append(line).append(System.lineSeparator()); // [from, to], start as 1 + } + }); + } catch (IOException e) { + logger.error("XxlJobFileAppender readLog error, logFileName:{}, fromLineNum:{}", logFileName, fromLineNum, e); + } + + // result + return new LogResult(fromLineNum, toLineNum.get(), logContentBuilder.toString(), false); } } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/openapi/AdminBiz.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/AdminBiz.java new file mode 100644 index 00000000..d18774f4 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/AdminBiz.java @@ -0,0 +1,61 @@ +package com.xxl.job.core.openapi; + +import com.xxl.job.core.openapi.model.CallbackRequest; +import com.xxl.job.core.openapi.model.RegistryRequest; +import com.xxl.tool.response.Response; + +import java.util.List; + +/** + * @author xuxueli 2017-07-27 21:52:49 + */ +public interface AdminBiz { + + + // ---------------------- callback ---------------------- + + /** + * callback + * + * @param callbackRequestList + * @return + */ + public Response callback(List callbackRequestList); + + + // ---------------------- registry ---------------------- + + /** + * registry + * + * @param registryRequest + * @return + */ + public Response registry(RegistryRequest registryRequest); + + /** + * registry remove + * + * @param registryRequest + * @return + */ + public Response registryRemove(RegistryRequest registryRequest); + + + // ---------------------- job operate ---------------------- + + // jobAdd + + // jobUpdate + + // jobDelete + + // jobQuery + + // jobStart + + // jobStop + + // jobTrigger + +} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/openapi/ExecutorBiz.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/ExecutorBiz.java new file mode 100644 index 00000000..7245f595 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/ExecutorBiz.java @@ -0,0 +1,46 @@ +package com.xxl.job.core.openapi; + +import com.xxl.job.core.openapi.model.*; +import com.xxl.tool.response.Response; + +/** + * Created by xuxueli on 17/3/1. + */ +public interface ExecutorBiz { + + /** + * beat + * @return response + */ + public Response beat(); + + /** + * idle beat + * + * @param idleBeatRequest idleBeatRequest + * @return response + */ + public Response idleBeat(IdleBeatRequest idleBeatRequest); + + /** + * run + * @param triggerRequest triggerRequest + * @return response + */ + public Response run(TriggerRequest triggerRequest); + + /** + * kill + * @param killRequest killRequest + * @return response + */ + public Response kill(KillRequest killRequest); + + /** + * log + * @param logRequest logRequest + * @return response + */ + public Response log(LogRequest logRequest); + +} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/impl/ExecutorBizImpl.java similarity index 64% rename from xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java rename to xxl-job-core/src/main/java/com/xxl/job/core/openapi/impl/ExecutorBizImpl.java index 8bdf7093..c5afe7db 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/impl/ExecutorBizImpl.java @@ -1,8 +1,9 @@ -package com.xxl.job.core.biz.impl; +package com.xxl.job.core.openapi.impl; -import com.xxl.job.core.biz.ExecutorBiz; -import com.xxl.job.core.biz.model.*; -import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; +import com.xxl.job.core.context.XxlJobContext; +import com.xxl.job.core.openapi.ExecutorBiz; +import com.xxl.job.core.openapi.model.*; +import com.xxl.job.core.constant.ExecutorBlockStrategyEnum; import com.xxl.job.core.executor.XxlJobExecutor; import com.xxl.job.core.glue.GlueFactory; import com.xxl.job.core.glue.GlueTypeEnum; @@ -11,6 +12,7 @@ import com.xxl.job.core.handler.impl.GlueJobHandler; import com.xxl.job.core.handler.impl.ScriptJobHandler; import com.xxl.job.core.log.XxlJobFileAppender; import com.xxl.job.core.thread.JobThread; +import com.xxl.tool.response.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,39 +25,39 @@ public class ExecutorBizImpl implements ExecutorBiz { private static Logger logger = LoggerFactory.getLogger(ExecutorBizImpl.class); @Override - public ReturnT beat() { - return ReturnT.SUCCESS; + public Response beat() { + return Response.ofSuccess(); } @Override - public ReturnT idleBeat(IdleBeatParam idleBeatParam) { + public Response idleBeat(IdleBeatRequest idleBeatRequest) { // isRunningOrHasQueue boolean isRunningOrHasQueue = false; - JobThread jobThread = XxlJobExecutor.loadJobThread(idleBeatParam.getJobId()); + JobThread jobThread = XxlJobExecutor.loadJobThread(idleBeatRequest.getJobId()); if (jobThread != null && jobThread.isRunningOrHasQueue()) { isRunningOrHasQueue = true; } if (isRunningOrHasQueue) { - return new ReturnT(ReturnT.FAIL_CODE, "job thread is running or has trigger queue."); + return Response.ofFail("job thread is running or has trigger queue."); } - return ReturnT.SUCCESS; + return Response.ofSuccess(); } @Override - public ReturnT run(TriggerParam triggerParam) { + public Response run(TriggerRequest triggerRequest) { // load old:jobHandler + jobThread - JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId()); + JobThread jobThread = XxlJobExecutor.loadJobThread(triggerRequest.getJobId()); IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null; String removeOldReason = null; // valid:jobHandler + jobThread - GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType()); + GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerRequest.getGlueType()); if (GlueTypeEnum.BEAN == glueTypeEnum) { // new jobhandler - IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler()); + IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerRequest.getExecutorHandler()); // valid old jobThread if (jobThread!=null && jobHandler != newJobHandler) { @@ -70,7 +72,7 @@ public class ExecutorBizImpl implements ExecutorBiz { if (jobHandler == null) { jobHandler = newJobHandler; if (jobHandler == null) { - return new ReturnT(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found."); + return Response.of(XxlJobContext.HANDLE_CODE_FAIL, "job handler [" + triggerRequest.getExecutorHandler() + "] not found."); } } @@ -79,7 +81,7 @@ public class ExecutorBizImpl implements ExecutorBiz { // valid old jobThread if (jobThread != null && !(jobThread.getHandler() instanceof GlueJobHandler - && ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) { + && ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()== triggerRequest.getGlueUpdatetime() )) { // change handler or gluesource updated, need kill old thread removeOldReason = "change job source or glue type, and terminate the old job thread."; @@ -90,11 +92,11 @@ public class ExecutorBizImpl implements ExecutorBiz { // valid handler if (jobHandler == null) { try { - IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource()); - jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime()); + IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerRequest.getGlueSource()); + jobHandler = new GlueJobHandler(originJobHandler, triggerRequest.getGlueUpdatetime()); } catch (Exception e) { logger.error(e.getMessage(), e); - return new ReturnT(ReturnT.FAIL_CODE, e.getMessage()); + return Response.of(XxlJobContext.HANDLE_CODE_FAIL, e.getMessage()); } } } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) { @@ -102,7 +104,7 @@ public class ExecutorBizImpl implements ExecutorBiz { // valid old jobThread if (jobThread != null && !(jobThread.getHandler() instanceof ScriptJobHandler - && ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) { + && ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()== triggerRequest.getGlueUpdatetime() )) { // change script or gluesource updated, need kill old thread removeOldReason = "change job source or glue type, and terminate the old job thread."; @@ -112,19 +114,19 @@ public class ExecutorBizImpl implements ExecutorBiz { // valid handler if (jobHandler == null) { - jobHandler = new ScriptJobHandler(triggerParam.getJobId(), triggerParam.getGlueUpdatetime(), triggerParam.getGlueSource(), GlueTypeEnum.match(triggerParam.getGlueType())); + jobHandler = new ScriptJobHandler(triggerRequest.getJobId(), triggerRequest.getGlueUpdatetime(), triggerRequest.getGlueSource(), GlueTypeEnum.match(triggerRequest.getGlueType())); } } else { - return new ReturnT(ReturnT.FAIL_CODE, "glueType[" + triggerParam.getGlueType() + "] is not valid."); + return Response.of(XxlJobContext.HANDLE_CODE_FAIL, "glueType[" + triggerRequest.getGlueType() + "] is not valid."); } // executor block strategy if (jobThread != null) { - ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null); + ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerRequest.getExecutorBlockStrategy(), null); if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) { // discard when running if (jobThread.isRunningOrHasQueue()) { - return new ReturnT(ReturnT.FAIL_CODE, "block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle()); + return Response.of(XxlJobContext.HANDLE_CODE_FAIL, "block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle()); } } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) { // kill running jobThread @@ -140,33 +142,32 @@ public class ExecutorBizImpl implements ExecutorBiz { // replace thread (new or exists invalid) if (jobThread == null) { - jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason); + jobThread = XxlJobExecutor.registJobThread(triggerRequest.getJobId(), jobHandler, removeOldReason); } // push data to queue - ReturnT pushResult = jobThread.pushTriggerQueue(triggerParam); - return pushResult; + return jobThread.pushTriggerQueue(triggerRequest); } @Override - public ReturnT kill(KillParam killParam) { + public Response kill(KillRequest killRequest) { // kill handlerThread, and create new one - JobThread jobThread = XxlJobExecutor.loadJobThread(killParam.getJobId()); + JobThread jobThread = XxlJobExecutor.loadJobThread(killRequest.getJobId()); if (jobThread != null) { - XxlJobExecutor.removeJobThread(killParam.getJobId(), "scheduling center kill job."); - return ReturnT.SUCCESS; + XxlJobExecutor.removeJobThread(killRequest.getJobId(), "scheduling center kill job."); + return Response.ofSuccess(); } - return new ReturnT(ReturnT.SUCCESS_CODE, "job thread already killed."); + return Response.ofSuccess( "job thread already killed."); } @Override - public ReturnT log(LogParam logParam) { + public Response log(LogRequest logRequest) { // log filename: logPath/yyyy-MM-dd/9999.log - String logFileName = XxlJobFileAppender.makeLogFileName(new Date(logParam.getLogDateTim()), logParam.getLogId()); + String logFileName = XxlJobFileAppender.makeLogFileName(new Date(logRequest.getLogDateTim()), logRequest.getLogId()); - LogResult logResult = XxlJobFileAppender.readLog(logFileName, logParam.getFromLineNum()); - return new ReturnT(logResult); + LogResult logResult = XxlJobFileAppender.readLog(logFileName, logRequest.getFromLineNum()); + return Response.ofSuccess(logResult); } } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/HandleCallbackParam.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/CallbackRequest.java similarity index 85% rename from xxl-job-core/src/main/java/com/xxl/job/core/biz/model/HandleCallbackParam.java rename to xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/CallbackRequest.java index b88ae28e..bb739e57 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/HandleCallbackParam.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/CallbackRequest.java @@ -1,11 +1,11 @@ -package com.xxl.job.core.biz.model; +package com.xxl.job.core.openapi.model; import java.io.Serializable; /** * Created by xuxueli on 17/3/2. */ -public class HandleCallbackParam implements Serializable { +public class CallbackRequest implements Serializable { private static final long serialVersionUID = 42L; private long logId; @@ -14,8 +14,8 @@ public class HandleCallbackParam implements Serializable { private int handleCode; private String handleMsg; - public HandleCallbackParam(){} - public HandleCallbackParam(long logId, long logDateTim, int handleCode, String handleMsg) { + public CallbackRequest(){} + public CallbackRequest(long logId, long logDateTim, int handleCode, String handleMsg) { this.logId = logId; this.logDateTim = logDateTim; this.handleCode = handleCode; diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/IdleBeatParam.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/IdleBeatRequest.java similarity index 66% rename from xxl-job-core/src/main/java/com/xxl/job/core/biz/model/IdleBeatParam.java rename to xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/IdleBeatRequest.java index 80cd288d..f794ec29 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/IdleBeatParam.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/IdleBeatRequest.java @@ -1,16 +1,16 @@ -package com.xxl.job.core.biz.model; +package com.xxl.job.core.openapi.model; import java.io.Serializable; /** * @author xuxueli 2020-04-11 22:27 */ -public class IdleBeatParam implements Serializable { +public class IdleBeatRequest implements Serializable { private static final long serialVersionUID = 42L; - public IdleBeatParam() { + public IdleBeatRequest() { } - public IdleBeatParam(int jobId) { + public IdleBeatRequest(int jobId) { this.jobId = jobId; } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/KillParam.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/KillRequest.java similarity index 67% rename from xxl-job-core/src/main/java/com/xxl/job/core/biz/model/KillParam.java rename to xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/KillRequest.java index b0d96e36..f97017e8 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/KillParam.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/KillRequest.java @@ -1,16 +1,16 @@ -package com.xxl.job.core.biz.model; +package com.xxl.job.core.openapi.model; import java.io.Serializable; /** * @author xuxueli 2020-04-11 22:27 */ -public class KillParam implements Serializable { +public class KillRequest implements Serializable { private static final long serialVersionUID = 42L; - public KillParam() { + public KillRequest() { } - public KillParam(int jobId) { + public KillRequest(int jobId) { this.jobId = jobId; } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogParam.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/LogRequest.java similarity index 80% rename from xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogParam.java rename to xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/LogRequest.java index cdaecb96..d4622c3c 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogParam.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/LogRequest.java @@ -1,16 +1,16 @@ -package com.xxl.job.core.biz.model; +package com.xxl.job.core.openapi.model; import java.io.Serializable; /** * @author xuxueli 2020-04-11 22:27 */ -public class LogParam implements Serializable { +public class LogRequest implements Serializable { private static final long serialVersionUID = 42L; - public LogParam() { + public LogRequest() { } - public LogParam(long logDateTim, long logId, int fromLineNum) { + public LogRequest(long logDateTim, long logId, int fromLineNum) { this.logDateTim = logDateTim; this.logId = logId; this.fromLineNum = fromLineNum; diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogResult.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/LogResult.java similarity index 96% rename from xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogResult.java rename to xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/LogResult.java index 1ffdf7ce..f8bf1d8d 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogResult.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/LogResult.java @@ -1,4 +1,4 @@ -package com.xxl.job.core.biz.model; +package com.xxl.job.core.openapi.model; import java.io.Serializable; diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/RegistryParam.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/RegistryRequest.java similarity index 84% rename from xxl-job-core/src/main/java/com/xxl/job/core/biz/model/RegistryParam.java rename to xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/RegistryRequest.java index 8526c3e2..5a66bff6 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/RegistryParam.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/RegistryRequest.java @@ -1,19 +1,19 @@ -package com.xxl.job.core.biz.model; +package com.xxl.job.core.openapi.model; import java.io.Serializable; /** * Created by xuxueli on 2017-05-10 20:22:42 */ -public class RegistryParam implements Serializable { +public class RegistryRequest implements Serializable { private static final long serialVersionUID = 42L; private String registryGroup; private String registryKey; private String registryValue; - public RegistryParam(){} - public RegistryParam(String registryGroup, String registryKey, String registryValue) { + public RegistryRequest(){} + public RegistryRequest(String registryGroup, String registryKey, String registryValue) { this.registryGroup = registryGroup; this.registryKey = registryKey; this.registryValue = registryValue; diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/TriggerRequest.java similarity index 94% rename from xxl-job-core/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java rename to xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/TriggerRequest.java index 4f56368a..624d630e 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/openapi/model/TriggerRequest.java @@ -1,27 +1,32 @@ -package com.xxl.job.core.biz.model; +package com.xxl.job.core.openapi.model; import java.io.Serializable; /** * Created by xuxueli on 16/7/22. */ -public class TriggerParam implements Serializable{ +public class TriggerRequest implements Serializable{ private static final long serialVersionUID = 42L; + // job base info private int jobId; + // job execute info private String executorHandler; private String executorParams; private String executorBlockStrategy; private int executorTimeout; + // log info private long logId; private long logDateTime; + // glue info private String glueType; private String glueSource; private long glueUpdatetime; + // broadcast info private int broadcastIndex; private int broadcastTotal; diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java b/xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java index 436f3afa..c37cd513 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java @@ -1,12 +1,13 @@ package com.xxl.job.core.server; -import com.xxl.job.core.biz.ExecutorBiz; -import com.xxl.job.core.biz.impl.ExecutorBizImpl; -import com.xxl.job.core.biz.model.*; +import com.xxl.job.core.constant.Const; +import com.xxl.job.core.openapi.ExecutorBiz; +import com.xxl.job.core.openapi.impl.ExecutorBizImpl; +import com.xxl.job.core.openapi.model.*; import com.xxl.job.core.thread.ExecutorRegistryThread; -import com.xxl.job.core.util.GsonTool; -import com.xxl.job.core.util.ThrowableUtil; -import com.xxl.job.core.util.XxlJobRemotingUtil; +import com.xxl.tool.error.ThrowableTool; +import com.xxl.tool.json.GsonTool; +import com.xxl.tool.response.Response; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.*; @@ -103,6 +104,7 @@ public class EmbedServer { } }); thread.setDaemon(true); // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave + thread.setName("xxl-job, EmbedServer"); thread.start(); } @@ -148,14 +150,14 @@ public class EmbedServer { String uri = msg.uri(); HttpMethod httpMethod = msg.method(); boolean keepAlive = HttpUtil.isKeepAlive(msg); - String accessTokenReq = msg.headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN); + String accessTokenReq = msg.headers().get(Const.XXL_JOB_ACCESS_TOKEN); // invoke bizThreadPool.execute(new Runnable() { @Override public void run() { // do invoke - Object responseObj = process(httpMethod, uri, requestData, accessTokenReq); + Object responseObj = dispatchRequest(httpMethod, uri, requestData, accessTokenReq); // to json String responseJson = GsonTool.toJson(responseObj); @@ -166,18 +168,18 @@ public class EmbedServer { }); } - private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) { + private Object dispatchRequest(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) { // valid if (HttpMethod.POST != httpMethod) { - return new ReturnT(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support."); + return Response.ofFail("invalid request, HttpMethod not support."); } - if (uri == null || uri.trim().length() == 0) { - return new ReturnT(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty."); + if (uri == null || uri.trim().isEmpty()) { + return Response.ofFail( "invalid request, uri-mapping empty."); } if (accessToken != null - && accessToken.trim().length() > 0 + && !accessToken.trim().isEmpty() && !accessToken.equals(accessTokenReq)) { - return new ReturnT(ReturnT.FAIL_CODE, "The access token is wrong."); + return Response.ofFail("The access token is wrong."); } // services mapping @@ -186,23 +188,23 @@ public class EmbedServer { case "/beat": return executorBiz.beat(); case "/idleBeat": - IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class); + IdleBeatRequest idleBeatParam = GsonTool.fromJson(requestData, IdleBeatRequest.class); return executorBiz.idleBeat(idleBeatParam); case "/run": - TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class); + TriggerRequest triggerParam = GsonTool.fromJson(requestData, TriggerRequest.class); return executorBiz.run(triggerParam); case "/kill": - KillParam killParam = GsonTool.fromJson(requestData, KillParam.class); + KillRequest killParam = GsonTool.fromJson(requestData, KillRequest.class); return executorBiz.kill(killParam); case "/log": - LogParam logParam = GsonTool.fromJson(requestData, LogParam.class); + LogRequest logParam = GsonTool.fromJson(requestData, LogRequest.class); return executorBiz.log(logParam); default: - return new ReturnT(ReturnT.FAIL_CODE, "invalid request, uri-mapping(" + uri + ") not found."); + return Response.ofFail( "invalid request, uri-mapping(" + uri + ") not found."); } } catch (Throwable e) { logger.error(e.getMessage(), e); - return new ReturnT(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.toString(e)); + return Response.ofFail("request error:" + ThrowableTool.toString(e)); } } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/thread/ExecutorRegistryThread.java b/xxl-job-core/src/main/java/com/xxl/job/core/thread/ExecutorRegistryThread.java index 36b32c95..251465ad 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/thread/ExecutorRegistryThread.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/thread/ExecutorRegistryThread.java @@ -1,10 +1,11 @@ package com.xxl.job.core.thread; -import com.xxl.job.core.biz.AdminBiz; -import com.xxl.job.core.biz.model.RegistryParam; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.enums.RegistryConfig; +import com.xxl.job.core.constant.RegistType; +import com.xxl.job.core.openapi.AdminBiz; +import com.xxl.job.core.openapi.model.RegistryRequest; +import com.xxl.job.core.constant.Const; import com.xxl.job.core.executor.XxlJobExecutor; +import com.xxl.tool.response.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,12 +43,12 @@ public class ExecutorRegistryThread { // registry while (!toStop) { try { - RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address); + RegistryRequest registryParam = new RegistryRequest(RegistType.EXECUTOR.name(), appname, address); for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) { try { - ReturnT registryResult = adminBiz.registry(registryParam); - if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) { - registryResult = ReturnT.SUCCESS; + Response registryResult = adminBiz.registry(registryParam); + if (registryResult!=null && registryResult.isSuccess()) { + registryResult = Response.ofSuccess(); logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult}); break; } else { @@ -67,7 +68,7 @@ public class ExecutorRegistryThread { try { if (!toStop) { - TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT); + TimeUnit.SECONDS.sleep(Const.BEAT_TIMEOUT); } } catch (Throwable e) { if (!toStop) { @@ -78,12 +79,12 @@ public class ExecutorRegistryThread { // registry remove try { - RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address); + RegistryRequest registryParam = new RegistryRequest(RegistType.EXECUTOR.name(), appname, address); for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) { try { - ReturnT registryResult = adminBiz.registryRemove(registryParam); - if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) { - registryResult = ReturnT.SUCCESS; + Response registryResult = adminBiz.registryRemove(registryParam); + if (registryResult!=null && registryResult.isSuccess()) { + registryResult = Response.ofSuccess(); logger.info(">>>>>>>>>>> xxl-job registry-remove success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult}); break; } else { diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/thread/JobLogFileCleanThread.java b/xxl-job-core/src/main/java/com/xxl/job/core/thread/JobLogFileCleanThread.java index b8dad765..178a82eb 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/thread/JobLogFileCleanThread.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/thread/JobLogFileCleanThread.java @@ -1,13 +1,12 @@ package com.xxl.job.core.thread; import com.xxl.job.core.log.XxlJobFileAppender; -import com.xxl.job.core.util.FileUtil; +import com.xxl.tool.core.DateTool; +import com.xxl.tool.io.FileTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.concurrent.TimeUnit; @@ -31,7 +30,7 @@ public class JobLogFileCleanThread { // limit min value if (logRetentionDays < 3 ) { - return; + return; // effective only when logRetentionDays >= 3 } localThread = new Thread(new Runnable() { @@ -52,32 +51,37 @@ public class JobLogFileCleanThread { Date todayDate = todayCal.getTime(); + // clean expired logfile for (File childFile: childDirs) { - // valid + // valid log-path: must be directory if (!childFile.isDirectory()) { continue; } - if (childFile.getName().indexOf("-") == -1) { + + // valid day log-path: like "---/2017-12-25/639.log" + if (!childFile.getName().contains("-")) { continue; } - // file create date + // parse create-day of file-path Date logFileCreateDate = null; try { - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); - logFileCreateDate = simpleDateFormat.parse(childFile.getName()); - } catch (ParseException e) { + logFileCreateDate = DateTool.parseDate(childFile.getName()); + } catch (Exception e) { logger.error(e.getMessage(), e); } if (logFileCreateDate == null) { continue; } - if ((todayDate.getTime()-logFileCreateDate.getTime()) >= logRetentionDays * (24 * 60 * 60 * 1000) ) { - FileUtil.deleteRecursively(childFile); + // check expired + Date expiredDate = DateTool.addDays(logFileCreateDate, logRetentionDays); + if (todayDate.getTime() > expiredDate.getTime()) { + // expired, remove all log of this day + FileTool.delete(childFile); + //FileUtil.deleteRecursively(childFile); } - } } @@ -85,7 +89,6 @@ public class JobLogFileCleanThread { if (!toStop) { logger.error(e.getMessage(), e); } - } try { diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java b/xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java index df33b8b0..b6028f02 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java @@ -1,21 +1,19 @@ package com.xxl.job.core.thread; -import com.xxl.job.core.biz.model.HandleCallbackParam; -import com.xxl.job.core.biz.model.ReturnT; -import com.xxl.job.core.biz.model.TriggerParam; +import com.xxl.job.core.openapi.model.CallbackRequest; +import com.xxl.job.core.openapi.model.TriggerRequest; import com.xxl.job.core.context.XxlJobContext; import com.xxl.job.core.context.XxlJobHelper; import com.xxl.job.core.executor.XxlJobExecutor; import com.xxl.job.core.handler.IJobHandler; import com.xxl.job.core.log.XxlJobFileAppender; +import com.xxl.tool.response.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Collections; import java.util.Date; -import java.util.HashSet; import java.util.Set; import java.util.concurrent.*; @@ -25,11 +23,11 @@ import java.util.concurrent.*; * @author xuxueli 2016-1-16 19:52:47 */ public class JobThread extends Thread{ - private static Logger logger = LoggerFactory.getLogger(JobThread.class); + private static final Logger logger = LoggerFactory.getLogger(JobThread.class); private int jobId; private IJobHandler handler; - private LinkedBlockingQueue triggerQueue; + private LinkedBlockingQueue triggerQueue; private Set triggerLogIdSet; // avoid repeat trigger for the same TRIGGER_LOG_ID private volatile boolean toStop = false; @@ -42,8 +40,9 @@ public class JobThread extends Thread{ public JobThread(int jobId, IJobHandler handler) { this.jobId = jobId; this.handler = handler; - this.triggerQueue = new LinkedBlockingQueue(); - this.triggerLogIdSet = Collections.synchronizedSet(new HashSet()); + this.triggerQueue = new LinkedBlockingQueue<>(); + //this.triggerLogIdSet = Collections.synchronizedSet(new HashSet()); + this.triggerLogIdSet = ConcurrentHashMap.newKeySet(); // assign job thread name this.setName("xxl-job, JobThread-"+jobId+"-"+System.currentTimeMillis()); @@ -54,26 +53,21 @@ public class JobThread extends Thread{ /** * new trigger to queue - * - * @param triggerParam - * @return */ - public ReturnT pushTriggerQueue(TriggerParam triggerParam) { - // avoid repeat - if (triggerLogIdSet.contains(triggerParam.getLogId())) { + public Response pushTriggerQueue(TriggerRequest triggerParam) { + // avoid repeat + if (!triggerLogIdSet.add(triggerParam.getLogId())) { logger.info(">>>>>>>>>>> repeate trigger job, logId:{}", triggerParam.getLogId()); - return new ReturnT(ReturnT.FAIL_CODE, "repeate trigger job, logId:" + triggerParam.getLogId()); + return Response.of(XxlJobContext.HANDLE_CODE_FAIL, "repeate trigger job, logId:" + triggerParam.getLogId()); } - triggerLogIdSet.add(triggerParam.getLogId()); + // push trigger queue triggerQueue.add(triggerParam); - return ReturnT.SUCCESS; + return Response.ofSuccess(); } /** * kill job thread - * - * @param stopReason */ public void toStop(String stopReason) { /** @@ -87,7 +81,6 @@ public class JobThread extends Thread{ /** * is running job - * @return */ public boolean isRunningOrHasQueue() { return running || triggerQueue.size()>0; @@ -108,9 +101,9 @@ public class JobThread extends Thread{ running = false; idleTimes++; - TriggerParam triggerParam = null; + TriggerRequest triggerParam = null; try { - // to check toStop signal, we need cycle, so wo cannot use queue.take(), instand of poll(timeout) + // to check toStop signal, we need cycle, so we cannot use queue.take(), instead of poll(timeout) triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS); if (triggerParam!=null) { running = true; @@ -122,7 +115,9 @@ public class JobThread extends Thread{ XxlJobContext xxlJobContext = new XxlJobContext( triggerParam.getJobId(), triggerParam.getExecutorParams(), - logFileName, + triggerParam.getLogId(), + triggerParam.getLogDateTime(), + logFileName, triggerParam.getBroadcastIndex(), triggerParam.getBroadcastTotal()); @@ -148,6 +143,7 @@ public class JobThread extends Thread{ } }); futureThread = new Thread(futureTask); + futureThread.setName("xxl-job, JobThread-future-"+jobId+"-"+System.currentTimeMillis()); futureThread.start(); Boolean tempResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS); @@ -184,7 +180,7 @@ public class JobThread extends Thread{ } else { if (idleTimes > 30) { - if(triggerQueue.size() == 0) { // avoid concurrent trigger causes jobId-lost + if(triggerQueue.isEmpty()) { // avoid concurrent trigger causes jobId-lost XxlJobExecutor.removeJobThread(jobId, "excutor idle times over limit."); } } @@ -206,8 +202,8 @@ public class JobThread extends Thread{ if(triggerParam != null) { // callback handler info if (!toStop) { - // commonm - TriggerCallbackThread.pushCallBack(new HandleCallbackParam( + // common + TriggerCallbackThread.pushCallBack(new CallbackRequest( triggerParam.getLogId(), triggerParam.getLogDateTime(), XxlJobContext.getXxlJobContext().getHandleCode(), @@ -215,7 +211,7 @@ public class JobThread extends Thread{ ); } else { // is killed - TriggerCallbackThread.pushCallBack(new HandleCallbackParam( + TriggerCallbackThread.pushCallBack(new CallbackRequest( triggerParam.getLogId(), triggerParam.getLogDateTime(), XxlJobContext.HANDLE_CODE_FAIL, @@ -227,11 +223,11 @@ public class JobThread extends Thread{ } // callback trigger request in queue - while(triggerQueue !=null && triggerQueue.size()>0){ - TriggerParam triggerParam = triggerQueue.poll(); + while(triggerQueue !=null && !triggerQueue.isEmpty()){ + TriggerRequest triggerParam = triggerQueue.poll(); if (triggerParam!=null) { // is killed - TriggerCallbackThread.pushCallBack(new HandleCallbackParam( + TriggerCallbackThread.pushCallBack(new CallbackRequest( triggerParam.getLogId(), triggerParam.getLogDateTime(), XxlJobContext.HANDLE_CODE_FAIL, diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java b/xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java index 1d272466..c0bc07f7 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java @@ -1,19 +1,24 @@ package com.xxl.job.core.thread; -import com.xxl.job.core.biz.AdminBiz; -import com.xxl.job.core.biz.model.HandleCallbackParam; -import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.openapi.AdminBiz; +import com.xxl.job.core.openapi.model.CallbackRequest; import com.xxl.job.core.context.XxlJobContext; import com.xxl.job.core.context.XxlJobHelper; -import com.xxl.job.core.enums.RegistryConfig; +import com.xxl.job.core.constant.Const; import com.xxl.job.core.executor.XxlJobExecutor; import com.xxl.job.core.log.XxlJobFileAppender; -import com.xxl.job.core.util.FileUtil; -import com.xxl.job.core.util.JdkSerializeTool; +import com.xxl.tool.core.ArrayTool; +import com.xxl.tool.core.CollectionTool; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.crypto.Md5Tool; +import com.xxl.tool.json.GsonTool; +import com.xxl.tool.io.FileTool; +import com.xxl.tool.response.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -21,12 +26,14 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** + * Trigger Callback Thread + * * Created by xuxueli on 16/7/22. */ public class TriggerCallbackThread { - private static Logger logger = LoggerFactory.getLogger(TriggerCallbackThread.class); + private static final Logger logger = LoggerFactory.getLogger(TriggerCallbackThread.class); - private static TriggerCallbackThread instance = new TriggerCallbackThread(); + private static final TriggerCallbackThread instance = new TriggerCallbackThread(); public static TriggerCallbackThread getInstance(){ return instance; } @@ -34,8 +41,8 @@ public class TriggerCallbackThread { /** * job results callback queue */ - private LinkedBlockingQueue callBackQueue = new LinkedBlockingQueue(); - public static void pushCallBack(HandleCallbackParam callback){ + private final LinkedBlockingQueue callBackQueue = new LinkedBlockingQueue<>(); + public static void pushCallBack(CallbackRequest callback){ getInstance().callBackQueue.add(callback); logger.debug(">>>>>>>>>>> xxl-job, push callback request, logId:{}", callback.getLogId()); } @@ -54,7 +61,9 @@ public class TriggerCallbackThread { return; } - // callback + /** + * trigger callback thread + */ triggerCallbackThread = new Thread(new Runnable() { @Override @@ -63,16 +72,16 @@ public class TriggerCallbackThread { // normal callback while(!toStop){ try { - HandleCallbackParam callback = getInstance().callBackQueue.take(); + CallbackRequest callback = getInstance().callBackQueue.take(); if (callback != null) { - // callback list param - List callbackParamList = new ArrayList(); - int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList); - callbackParamList.add(callback); + // collect callback data + List callbackParamList = new ArrayList<>(); + callbackParamList.add(callback); // add one element + int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList); // drainTo other all elements - // callback, will retry if error - if (callbackParamList!=null && callbackParamList.size()>0) { + // do callback, will retry if error + if (CollectionTool.isNotEmpty(callbackParamList)) { doCallback(callbackParamList); } } @@ -83,11 +92,14 @@ public class TriggerCallbackThread { } } - // last callback + // thead stop, callback lasttime try { - List callbackParamList = new ArrayList(); + // collect callback data + List callbackParamList = new ArrayList<>(); int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList); - if (callbackParamList!=null && callbackParamList.size()>0) { + + // do callback + if (CollectionTool.isNotEmpty(callbackParamList)) { doCallback(callbackParamList); } } catch (Throwable e) { @@ -104,7 +116,9 @@ public class TriggerCallbackThread { triggerCallbackThread.start(); - // retry + /** + * callback fail retry thread + */ triggerRetryCallbackThread = new Thread(new Runnable() { @Override public void run() { @@ -118,7 +132,7 @@ public class TriggerCallbackThread { } try { - TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT); + TimeUnit.SECONDS.sleep(Const.BEAT_TIMEOUT); } catch (Throwable e) { if (!toStop) { logger.error(e.getMessage(), e); @@ -129,6 +143,7 @@ public class TriggerCallbackThread { } }); triggerRetryCallbackThread.setDaemon(true); + triggerRetryCallbackThread.setName("xxl-job, executor TriggerRetryCallbackThread"); triggerRetryCallbackThread.start(); } @@ -158,15 +173,16 @@ public class TriggerCallbackThread { /** * do callback, will retry if error - * @param callbackParamList + * + * @param callbackParamList callback param list */ - private void doCallback(List callbackParamList){ + private void doCallback(List callbackParamList){ boolean callbackRet = false; // callback, will retry if error for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) { try { - ReturnT callbackResult = adminBiz.callback(callbackParamList); - if (callbackResult!=null && ReturnT.SUCCESS_CODE == callbackResult.getCode()) { + Response callbackResult = adminBiz.callback(callbackParamList); + if (callbackResult!=null && callbackResult.isSuccess()) { callbackLog(callbackParamList, "
      ----------- xxl-job job callback finish."); callbackRet = true; break; @@ -185,12 +201,14 @@ public class TriggerCallbackThread { /** * callback log */ - private void callbackLog(List callbackParamList, String logContent){ - for (HandleCallbackParam callbackParam: callbackParamList) { + private void callbackLog(List callbackParamList, String logContent){ + for (CallbackRequest callbackParam: callbackParamList) { String logFileName = XxlJobFileAppender.makeLogFileName(new Date(callbackParam.getLogDateTim()), callbackParam.getLogId()); XxlJobContext.setXxlJobContext(new XxlJobContext( -1, null, + -1, + -1, logFileName, -1, -1)); @@ -201,58 +219,81 @@ public class TriggerCallbackThread { // ---------------------- fail-callback file ---------------------- - private static String failCallbackFilePath = XxlJobFileAppender.getLogPath().concat(File.separator).concat("callbacklog").concat(File.separator); - private static String failCallbackFileName = failCallbackFilePath.concat("xxl-job-callback-{x}").concat(".log"); + /** + * fail-callback file name + */ + private static final String failCallbackFileName = XxlJobFileAppender + .getCallbackLogPath() + .concat(File.separator) + .concat("xxl-job-callback-{x}") + .concat(".log"); - private void appendFailCallbackFile(List callbackParamList){ + /** + * append fail-callback file + * + * @param callbackParamList callback param list + */ + private void appendFailCallbackFile(List callbackParamList) { // valid - if (callbackParamList==null || callbackParamList.size()==0) { + if (CollectionTool.isEmpty(callbackParamList)) { return; } - // append file - byte[] callbackParamList_bytes = JdkSerializeTool.serialize(callbackParamList); + // generate callback data + String callbackData = GsonTool.toJson(callbackParamList); + String callbackDataMd5 = Md5Tool.md5(callbackData); - File callbackLogFile = new File(failCallbackFileName.replace("{x}", String.valueOf(System.currentTimeMillis()))); - if (callbackLogFile.exists()) { - for (int i = 0; i < 100; i++) { - callbackLogFile = new File(failCallbackFileName.replace("{x}", String.valueOf(System.currentTimeMillis()).concat("-").concat(String.valueOf(i)) )); - if (!callbackLogFile.exists()) { - break; - } - } + + // create file + String finalLogFileName = failCallbackFileName.replace("{x}", callbackDataMd5); + + // write callback log + try { + FileTool.writeString(finalLogFileName, callbackData); + } catch (IOException e) { + logger.error(">>>>>>>>>>> TriggerCallbackThread appendFailCallbackFile error, finalLogFileName:{}", finalLogFileName, e); } - FileUtil.writeFileContent(callbackLogFile, callbackParamList_bytes); } - private void retryFailCallbackFile(){ + /** + * retry fail-callback file + */ + private void retryFailCallbackFile() { // valid - File callbackLogPath = new File(failCallbackFilePath); + File callbackLogPath = new File(XxlJobFileAppender.getCallbackLogPath()); if (!callbackLogPath.exists()) { return; } - if (callbackLogPath.isFile()) { - callbackLogPath.delete(); + // valid file type: must be directory + if (!FileTool.isDirectory(callbackLogPath)) { + FileTool.delete(callbackLogPath); + return; } - if (!(callbackLogPath.isDirectory() && callbackLogPath.list()!=null && callbackLogPath.list().length>0)) { + // valid file in path: pass if empty + if (ArrayTool.isEmpty(callbackLogPath.listFiles())) { return; } - // load and clear file, retry - for (File callbaclLogFile: callbackLogPath.listFiles()) { - byte[] callbackParamList_bytes = FileUtil.readFileContent(callbaclLogFile); - - // avoid empty file - if(callbackParamList_bytes == null || callbackParamList_bytes.length < 1){ - callbaclLogFile.delete(); - continue; - } + // load and clear file, do retry + for (File callbackLogFile: callbackLogPath.listFiles()) { + try { + // load data + String callbackData = FileTool.readString(callbackLogFile.getPath()); + if (StringTool.isBlank(callbackData)) { + FileTool.delete(callbackLogFile); + continue; + } - List callbackParamList = (List) JdkSerializeTool.deserialize(callbackParamList_bytes, List.class); + // parse callback param + List callbackParamList = GsonTool.fromJsonList(callbackData, CallbackRequest.class); + FileTool.delete(callbackLogFile); - callbaclLogFile.delete(); - doCallback(callbackParamList); + // retry callback + doCallback(callbackParamList); + } catch (IOException e) { + logger.error(">>>>>>>>>>> TriggerCallbackThread retryFailCallbackFile error, callbackLogFile:{}", callbackLogFile.getPath(), e); + } } } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/DateUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/DateUtil.java deleted file mode 100644 index 71afe0a6..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/util/DateUtil.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.xxl.job.core.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -/** - * date util - * - * @author xuxueli 2018-08-19 01:24:11 - */ -public class DateUtil { - - // ---------------------- format parse ---------------------- - private static Logger logger = LoggerFactory.getLogger(DateUtil.class); - - private static final String DATE_FORMAT = "yyyy-MM-dd"; - private static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; - - private static final ThreadLocal> dateFormatThreadLocal = new ThreadLocal>(); - private static DateFormat getDateFormat(String pattern) { - if (pattern==null || pattern.trim().length()==0) { - throw new IllegalArgumentException("pattern cannot be empty."); - } - - Map dateFormatMap = dateFormatThreadLocal.get(); - if(dateFormatMap!=null && dateFormatMap.containsKey(pattern)){ - return dateFormatMap.get(pattern); - } - - synchronized (dateFormatThreadLocal) { - if (dateFormatMap == null) { - dateFormatMap = new HashMap(); - } - dateFormatMap.put(pattern, new SimpleDateFormat(pattern)); - dateFormatThreadLocal.set(dateFormatMap); - } - - return dateFormatMap.get(pattern); - } - - /** - * format datetime. like "yyyy-MM-dd" - * - * @param date - * @return - * @throws ParseException - */ - public static String formatDate(Date date) { - return format(date, DATE_FORMAT); - } - - /** - * format date. like "yyyy-MM-dd HH:mm:ss" - * - * @param date - * @return - * @throws ParseException - */ - public static String formatDateTime(Date date) { - return format(date, DATETIME_FORMAT); - } - - /** - * format date - * - * @param date - * @param patten - * @return - * @throws ParseException - */ - public static String format(Date date, String patten) { - return getDateFormat(patten).format(date); - } - - /** - * parse date string, like "yyyy-MM-dd HH:mm:s" - * - * @param dateString - * @return - * @throws ParseException - */ - public static Date parseDate(String dateString){ - return parse(dateString, DATE_FORMAT); - } - - /** - * parse datetime string, like "yyyy-MM-dd HH:mm:ss" - * - * @param dateString - * @return - * @throws ParseException - */ - public static Date parseDateTime(String dateString) { - return parse(dateString, DATETIME_FORMAT); - } - - /** - * parse date - * - * @param dateString - * @param pattern - * @return - * @throws ParseException - */ - public static Date parse(String dateString, String pattern) { - try { - Date date = getDateFormat(pattern).parse(dateString); - return date; - } catch (Exception e) { - logger.warn("parse date error, dateString = {}, pattern={}; errorMsg = {}", dateString, pattern, e.getMessage()); - return null; - } - } - - - // ---------------------- add date ---------------------- - - public static Date addYears(final Date date, final int amount) { - return add(date, Calendar.YEAR, amount); - } - - public static Date addMonths(final Date date, final int amount) { - return add(date, Calendar.MONTH, amount); - } - - public static Date addDays(final Date date, final int amount) { - return add(date, Calendar.DAY_OF_MONTH, amount); - } - - public static Date addHours(final Date date, final int amount) { - return add(date, Calendar.HOUR_OF_DAY, amount); - } - - public static Date addMinutes(final Date date, final int amount) { - return add(date, Calendar.MINUTE, amount); - } - - private static Date add(final Date date, final int calendarField, final int amount) { - if (date == null) { - return null; - } - final Calendar c = Calendar.getInstance(); - c.setTime(date); - c.add(calendarField, amount); - return c.getTime(); - } - -} \ No newline at end of file diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/FileUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/FileUtil.java deleted file mode 100644 index d44ef4b2..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/util/FileUtil.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.xxl.job.core.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -/** - * file tool - * - * @author xuxueli 2017-12-29 17:56:48 - */ -public class FileUtil { - private static Logger logger = LoggerFactory.getLogger(FileUtil.class); - - - /** - * delete recursively - * - * @param root - * @return - */ - public static boolean deleteRecursively(File root) { - if (root != null && root.exists()) { - if (root.isDirectory()) { - File[] children = root.listFiles(); - if (children != null) { - for (File child : children) { - deleteRecursively(child); - } - } - } - return root.delete(); - } - return false; - } - - - public static void deleteFile(String fileName) { - // file - File file = new File(fileName); - if (file.exists()) { - file.delete(); - } - } - - - public static void writeFileContent(File file, byte[] data) { - - // file - if (!file.exists()) { - file.getParentFile().mkdirs(); - } - - // append file content - FileOutputStream fos = null; - try { - fos = new FileOutputStream(file); - fos.write(data); - fos.flush(); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } finally { - if (fos != null) { - try { - fos.close(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - } - - } - - public static byte[] readFileContent(File file) { - Long filelength = file.length(); - byte[] filecontent = new byte[filelength.intValue()]; - - FileInputStream in = null; - try { - in = new FileInputStream(file); - in.read(filecontent); - in.close(); - - return filecontent; - } catch (Exception e) { - logger.error(e.getMessage(), e); - return null; - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - } - } - - - /*public static void appendFileLine(String fileName, String content) { - - // file - File file = new File(fileName); - if (!file.exists()) { - try { - file.createNewFile(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - return; - } - } - - // content - if (content == null) { - content = ""; - } - content += "\r\n"; - - // append file content - FileOutputStream fos = null; - try { - fos = new FileOutputStream(file, true); - fos.write(content.getBytes("utf-8")); - fos.flush(); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } finally { - if (fos != null) { - try { - fos.close(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - } - - } - - public static List loadFileLines(String fileName){ - - List result = new ArrayList<>(); - - // valid log file - File file = new File(fileName); - if (!file.exists()) { - return result; - } - - // read file - StringBuffer logContentBuffer = new StringBuffer(); - int toLineNum = 0; - LineNumberReader reader = null; - try { - //reader = new LineNumberReader(new FileReader(logFile)); - reader = new LineNumberReader(new InputStreamReader(new FileInputStream(file), "utf-8")); - String line = null; - while ((line = reader.readLine())!=null) { - if (line!=null && line.trim().length()>0) { - result.add(line); - } - } - } catch (IOException e) { - logger.error(e.getMessage(), e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - } - - return result; - }*/ - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/GsonTool.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/GsonTool.java deleted file mode 100644 index 85682a6d..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/util/GsonTool.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.xxl.job.core.util; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.List; - -/** - * @author xuxueli 2020-04-11 20:56:31 - */ -public class GsonTool { - - private static Gson gson = null; - static { - gson= new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create(); - } - - /** - * Object 转成 json - * - * @param src - * @return String - */ - public static String toJson(Object src) { - return gson.toJson(src); - } - - /** - * json 转成 特定的cls的Object - * - * @param json - * @param classOfT - * @return - */ - public static T fromJson(String json, Class classOfT) { - return gson.fromJson(json, classOfT); - } - - /** - * json 转成 特定的 rawClass 的Object - * - * @param json - * @param classOfT - * @param argClassOfT - * @return - */ - public static T fromJson(String json, Class classOfT, Class argClassOfT) { - Type type = new ParameterizedType4ReturnT(classOfT, new Class[]{argClassOfT}); - return gson.fromJson(json, type); - } - public static class ParameterizedType4ReturnT implements ParameterizedType { - private final Class raw; - private final Type[] args; - public ParameterizedType4ReturnT(Class raw, Type[] args) { - this.raw = raw; - this.args = args != null ? args : new Type[0]; - } - @Override - public Type[] getActualTypeArguments() { - return args; - } - @Override - public Type getRawType() { - return raw; - } - @Override - public Type getOwnerType() {return null;} - } - - /** - * json 转成 特定的cls的list - * - * @param json - * @param classOfT - * @return - */ - public static List fromJsonList(String json, Class classOfT) { - return gson.fromJson( - json, - new TypeToken>() { - }.getType() - ); - } - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/IpUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/IpUtil.java deleted file mode 100644 index c97c4fea..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/util/IpUtil.java +++ /dev/null @@ -1,203 +0,0 @@ -package com.xxl.job.core.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.UnknownHostException; -import java.util.Enumeration; -import java.util.regex.Pattern; - -/** - * ip tool - * - * @author xuxueli 2016-5-22 11:38:05 - */ -public class IpUtil { - private static final Logger logger = LoggerFactory.getLogger(IpUtil.class); - - private static final String ANYHOST_VALUE = "0.0.0.0"; - private static final String LOCALHOST_VALUE = "127.0.0.1"; - private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$"); - - - - private static volatile InetAddress LOCAL_ADDRESS = null; - - // ---------------------- valid ---------------------- - - private static InetAddress toValidAddress(InetAddress address) { - if (address instanceof Inet6Address) { - Inet6Address v6Address = (Inet6Address) address; - if (isPreferIPV6Address()) { - return normalizeV6Address(v6Address); - } - } - if (isValidV4Address(address)) { - return address; - } - return null; - } - - private static boolean isPreferIPV6Address() { - return Boolean.getBoolean("java.net.preferIPv6Addresses"); - } - - /** - * valid Inet4Address - * - * @param address - * @return - */ - private static boolean isValidV4Address(InetAddress address) { - if (address == null || address.isLoopbackAddress()) { - return false; - } - String name = address.getHostAddress(); - boolean result = (name != null - && IP_PATTERN.matcher(name).matches() - && !ANYHOST_VALUE.equals(name) - && !LOCALHOST_VALUE.equals(name)); - return result; - } - - - /** - * normalize the ipv6 Address, convert scope name to scope id. - * e.g. - * convert - * fe80:0:0:0:894:aeec:f37d:23e1%en0 - * to - * fe80:0:0:0:894:aeec:f37d:23e1%5 - *

      - * The %5 after ipv6 address is called scope id. - * see java doc of {@link Inet6Address} for more details. - * - * @param address the input address - * @return the normalized address, with scope id converted to int - */ - private static InetAddress normalizeV6Address(Inet6Address address) { - String addr = address.getHostAddress(); - int i = addr.lastIndexOf('%'); - if (i > 0) { - try { - return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId()); - } catch (UnknownHostException e) { - // ignore - logger.debug("Unknown IPV6 address: ", e); - } - } - return address; - } - - // ---------------------- find ip ---------------------- - - - private static InetAddress getLocalAddress0() { - InetAddress localAddress = null; - try { - localAddress = InetAddress.getLocalHost(); - InetAddress addressItem = toValidAddress(localAddress); - if (addressItem != null) { - return addressItem; - } - } catch (Throwable e) { - logger.error(e.getMessage(), e); - } - - try { - Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); - if (null == interfaces) { - return localAddress; - } - while (interfaces.hasMoreElements()) { - try { - NetworkInterface network = interfaces.nextElement(); - if (network.isLoopback() || network.isVirtual() || !network.isUp()) { - continue; - } - Enumeration addresses = network.getInetAddresses(); - while (addresses.hasMoreElements()) { - try { - InetAddress addressItem = toValidAddress(addresses.nextElement()); - if (addressItem != null) { - try { - if(addressItem.isReachable(100)){ - return addressItem; - } - } catch (IOException e) { - // ignore - } - } - } catch (Throwable e) { - logger.error(e.getMessage(), e); - } - } - } catch (Throwable e) { - logger.error(e.getMessage(), e); - } - } - } catch (Throwable e) { - logger.error(e.getMessage(), e); - } - return localAddress; - } - - - // ---------------------- tool ---------------------- - - /** - * Find first valid IP from local network card - * - * @return first valid local IP - */ - public static InetAddress getLocalAddress() { - if (LOCAL_ADDRESS != null) { - return LOCAL_ADDRESS; - } - InetAddress localAddress = getLocalAddress0(); - LOCAL_ADDRESS = localAddress; - return localAddress; - } - - /** - * get ip address - * - * @return String - */ - public static String getIp(){ - return getLocalAddress().getHostAddress(); - } - - /** - * get ip:port - * - * @param port - * @return String - */ - public static String getIpPort(int port){ - String ip = getIp(); - return getIpPort(ip, port); - } - - public static String getIpPort(String ip, int port){ - if (ip==null) { - return null; - } - return ip.concat(":").concat(String.valueOf(port)); - } - - public static Object[] parseIpPort(String address){ - String[] array = address.split(":"); - - String host = array[0]; - int port = Integer.parseInt(array[1]); - - return new Object[]{host, port}; - } - - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/JdkSerializeTool.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/JdkSerializeTool.java deleted file mode 100644 index df74d815..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/util/JdkSerializeTool.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.xxl.job.core.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; - -/** - * @author xuxueli 2020-04-12 0:14:00 - */ -public class JdkSerializeTool { - private static Logger logger = LoggerFactory.getLogger(JdkSerializeTool.class); - - - // ------------------------ serialize and unserialize ------------------------ - - /** - * 将对象-->byte[] (由于jedis中不支持直接存储object所以转换成byte[]存入) - * - * @param object - * @return - */ - public static byte[] serialize(Object object) { - ObjectOutputStream oos = null; - ByteArrayOutputStream baos = null; - try { - // 序列化 - baos = new ByteArrayOutputStream(); - oos = new ObjectOutputStream(baos); - oos.writeObject(object); - byte[] bytes = baos.toByteArray(); - return bytes; - } catch (Exception e) { - logger.error(e.getMessage(), e); - } finally { - try { - oos.close(); - baos.close(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - return null; - } - - - /** - * 将byte[] -->Object - * - * @param bytes - * @return - */ - public static Object deserialize(byte[] bytes, Class clazz) { - ObjectInputStream ois = null; - ByteArrayInputStream bais = null; - try { - // 反序列化 - bais = new ByteArrayInputStream(bytes); - ois = new ObjectInputStream(bais); - return ois.readObject(); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } finally { - try { - ois.close(); - bais.close(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - return null; - } - - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/NetUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/NetUtil.java deleted file mode 100644 index 41d285f1..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/util/NetUtil.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.xxl.job.core.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.ServerSocket; - -/** - * net util - * - * @author xuxueli 2017-11-29 17:00:25 - */ -public class NetUtil { - private static Logger logger = LoggerFactory.getLogger(NetUtil.class); - - /** - * find avaliable port - * - * @param defaultPort - * @return - */ - public static int findAvailablePort(int defaultPort) { - int portTmp = defaultPort; - while (portTmp < 65535) { - if (!isPortUsed(portTmp)) { - return portTmp; - } else { - portTmp++; - } - } - portTmp = defaultPort--; - while (portTmp > 0) { - if (!isPortUsed(portTmp)) { - return portTmp; - } else { - portTmp--; - } - } - throw new RuntimeException("no available port."); - } - - /** - * check port used - * - * @param port - * @return - */ - public static boolean isPortUsed(int port) { - boolean used = false; - ServerSocket serverSocket = null; - try { - serverSocket = new ServerSocket(port); - used = false; - } catch (IOException e) { - logger.info(">>>>>>>>>>> xxl-job, port[{}] is in use.", port); - used = true; - } finally { - if (serverSocket != null) { - try { - serverSocket.close(); - } catch (IOException e) { - logger.info(""); - } - } - } - return used; - } - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/ScriptUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/ScriptUtil.java index a27e27bd..12b7582d 100644 --- a/xxl-job-core/src/main/java/com/xxl/job/core/util/ScriptUtil.java +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/ScriptUtil.java @@ -1,11 +1,12 @@ package com.xxl.job.core.util; import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.tool.core.ArrayTool; +import com.xxl.tool.io.FileTool; +import com.xxl.tool.io.IOTool; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -22,16 +23,18 @@ public class ScriptUtil { /** * make script file * - * @param scriptFileName - * @param content - * @throws IOException + * @param scriptFileName script file name + * @param scriptContent script content + * @throws IOException exception */ - public static void markScriptFile(String scriptFileName, String content) throws IOException { - // make file, filePath/gluesource/666-123456789.py - FileOutputStream fileOutputStream = null; + public static void markScriptFile(String scriptFileName, String scriptContent) throws IOException { + // make file: filePath/gluesource/666-123456789.py + FileTool.writeString(scriptFileName, scriptContent); + + /*FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(scriptFileName); - fileOutputStream.write(content.getBytes("UTF-8")); + fileOutputStream.write(scriptContent.getBytes("UTF-8")); fileOutputStream.close(); } catch (Exception e) { throw e; @@ -39,128 +42,95 @@ public class ScriptUtil { if(fileOutputStream != null){ fileOutputStream.close(); } - } + }*/ } /** * 脚本执行,日志文件实时输出 * - * @param command - * @param scriptFile - * @param logFile - * @param params - * @return - * @throws IOException + * @param command command + * @param scriptFile script file + * @param logFile log file + * @param params params + * @return exit code + * @throws IOException exception */ public static int execToFile(String command, String scriptFile, String logFile, String... params) throws IOException { FileOutputStream fileOutputStream = null; Thread inputThread = null; - Thread errThread = null; + Thread errorThread = null; + Process process = null; try { - // file + // 1、build file OutputStream fileOutputStream = new FileOutputStream(logFile, true); - // command + // 2、build command List cmdarray = new ArrayList<>(); cmdarray.add(command); cmdarray.add(scriptFile); - if (params!=null && params.length>0) { + if (ArrayTool.isNotEmpty(params)) { for (String param:params) { cmdarray.add(param); } } - String[] cmdarrayFinal = cmdarray.toArray(new String[cmdarray.size()]); + String[] cmdarrayFinal = cmdarray.toArray(new String[0]); - // process-exec - final Process process = Runtime.getRuntime().exec(cmdarrayFinal); + // 3、process:exec + process = Runtime.getRuntime().exec(cmdarrayFinal); + Process finalProcess = process; - // log-thread + // 4、read script log: inputStream + errStream final FileOutputStream finalFileOutputStream = fileOutputStream; - inputThread = new Thread(new Runnable() { - @Override - public void run() { - try { - copy(process.getInputStream(), finalFileOutputStream, new byte[1024]); - } catch (IOException e) { - XxlJobHelper.log(e); - } + inputThread = new Thread(() -> { + try { + // 数据流Copy(Input自动关闭,Output不处理) + IOTool.copy(finalProcess.getInputStream(), finalFileOutputStream, true, false); + } catch (IOException e) { + XxlJobHelper.log(e); } }); - errThread = new Thread(new Runnable() { - @Override - public void run() { - try { - copy(process.getErrorStream(), finalFileOutputStream, new byte[1024]); - } catch (IOException e) { - XxlJobHelper.log(e); - } + errorThread = new Thread(() -> { + try { + IOTool.copy(finalProcess.getErrorStream(), finalFileOutputStream, true, false); + } catch (IOException e) { + XxlJobHelper.log(e); } }); inputThread.start(); - errThread.start(); + errorThread.start(); - // process-wait + // 5、process:wait for result int exitValue = process.waitFor(); // exit code: 0=success, 1=error - // log-thread join + // 6、thread join, wait for log inputThread.join(); - errThread.join(); + errorThread.join(); return exitValue; } catch (Exception e) { XxlJobHelper.log(e); return -1; } finally { + // 7、close file OutputStream if (fileOutputStream != null) { try { fileOutputStream.close(); } catch (IOException e) { XxlJobHelper.log(e); } - } + // 8、interrupt thread if (inputThread != null && inputThread.isAlive()) { inputThread.interrupt(); } - if (errThread != null && errThread.isAlive()) { - errThread.interrupt(); - } - } - } - - /** - * 数据流Copy(Input自动关闭,Output不处理) - * - * @param inputStream - * @param outputStream - * @param buffer - * @return - * @throws IOException - */ - private static long copy(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws IOException { - try { - long total = 0; - for (;;) { - int res = inputStream.read(buffer); - if (res == -1) { - break; - } - if (res > 0) { - total += res; - if (outputStream != null) { - outputStream.write(buffer, 0, res); - } - } + if (errorThread != null && errorThread.isAlive()) { + errorThread.interrupt(); } - outputStream.flush(); - //out = null; - inputStream.close(); - inputStream = null; - return total; - } finally { - if (inputStream != null) { - inputStream.close(); + // 9、process destroy + if (process != null) { + process.destroy(); + // process.destroyForcibly(); } } } diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/ThrowableUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/ThrowableUtil.java deleted file mode 100644 index 63c39c48..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/util/ThrowableUtil.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.xxl.job.core.util; - -import java.io.PrintWriter; -import java.io.StringWriter; - -/** - * @author xuxueli 2018-10-20 20:07:26 - */ -public class ThrowableUtil { - - /** - * parse error to string - * - * @param e - * @return - */ - public static String toString(Throwable e) { - StringWriter stringWriter = new StringWriter(); - e.printStackTrace(new PrintWriter(stringWriter)); - String errorMsg = stringWriter.toString(); - return errorMsg; - } - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/XxlJobRemotingUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/XxlJobRemotingUtil.java deleted file mode 100644 index 63fb3ccc..00000000 --- a/xxl-job-core/src/main/java/com/xxl/job/core/util/XxlJobRemotingUtil.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.xxl.job.core.util; - -import com.xxl.job.core.biz.model.ReturnT; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.*; -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Map; - -/** - * @author xuxueli 2018-11-25 00:55:31 - */ -public class XxlJobRemotingUtil { - private static Logger logger = LoggerFactory.getLogger(XxlJobRemotingUtil.class); - public static final String XXL_JOB_ACCESS_TOKEN = "XXL-JOB-ACCESS-TOKEN"; - - - // trust-https start - private static void trustAllHosts(HttpsURLConnection connection) { - try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - SSLSocketFactory newFactory = sc.getSocketFactory(); - - connection.setSSLSocketFactory(newFactory); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - connection.setHostnameVerifier(new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }); - } - private static final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { - @Override - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new java.security.cert.X509Certificate[]{}; - } - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - } - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - } - }}; - // trust-https end - - - /** - * post - * - * @param url - * @param accessToken - * @param timeout by second - * @param requestObj - * @param returnTargClassOfT - * @return - */ - public static ReturnT postBody(String url, String accessToken, int timeout, Object requestObj, Class returnTargClassOfT) { - HttpURLConnection connection = null; - BufferedReader bufferedReader = null; - try { - // connection - URL realUrl = new URL(url); - connection = (HttpURLConnection) realUrl.openConnection(); - - // trust-https - boolean useHttps = url.startsWith("https"); - if (useHttps) { - HttpsURLConnection https = (HttpsURLConnection) connection; - trustAllHosts(https); - } - - // connection setting - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - connection.setDoInput(true); - connection.setUseCaches(false); - connection.setReadTimeout(timeout * 1000); - connection.setConnectTimeout(timeout * 1000); - connection.setRequestProperty("connection", "Keep-Alive"); - connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); - connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8"); - - if(accessToken!=null && accessToken.trim().length()>0){ - connection.setRequestProperty(XXL_JOB_ACCESS_TOKEN, accessToken); - } - - // do connection - connection.connect(); - - // write requestBody - if (requestObj != null) { - String requestBody = GsonTool.toJson(requestObj); - - DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream()); - dataOutputStream.write(requestBody.getBytes("UTF-8")); - dataOutputStream.flush(); - dataOutputStream.close(); - } - - /*byte[] requestBodyBytes = requestBody.getBytes("UTF-8"); - connection.setRequestProperty("Content-Length", String.valueOf(requestBodyBytes.length)); - OutputStream outwritestream = connection.getOutputStream(); - outwritestream.write(requestBodyBytes); - outwritestream.flush(); - outwritestream.close();*/ - - // valid StatusCode - int statusCode = connection.getResponseCode(); - if (statusCode != 200) { - return new ReturnT(ReturnT.FAIL_CODE, "xxl-job remoting fail, StatusCode("+ statusCode +") invalid. for url : " + url); - } - - // result - bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); - StringBuilder result = new StringBuilder(); - String line; - while ((line = bufferedReader.readLine()) != null) { - result.append(line); - } - String resultJson = result.toString(); - - // parse returnT - try { - ReturnT returnT = GsonTool.fromJson(resultJson, ReturnT.class, returnTargClassOfT); - return returnT; - } catch (Exception e) { - logger.error("xxl-job remoting (url="+url+") response content invalid("+ resultJson +").", e); - return new ReturnT(ReturnT.FAIL_CODE, "xxl-job remoting (url="+url+") response content invalid("+ resultJson +")."); - } - - } catch (Exception e) { - logger.error(e.getMessage(), e); - return new ReturnT(ReturnT.FAIL_CODE, "xxl-job remoting error("+ e.getMessage() +"), for url : " + url); - } finally { - try { - if (bufferedReader != null) { - bufferedReader.close(); - } - if (connection != null) { - connection.disconnect(); - } - } catch (Exception e2) { - logger.error(e2.getMessage(), e2); - } - } - } - -} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/AdminBizClient.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/AdminBizClient.java new file mode 100644 index 00000000..99e4718e --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/AdminBizClient.java @@ -0,0 +1,54 @@ +//package com.xxl.job.core.openapi.client; +// +//import com.xxl.job.core.openapi.AdminBiz; +//import com.xxl.job.core.openapi.model.HandleCallbackRequest; +//import com.xxl.job.core.openapi.model.RegistryRequest; +//import com.xxl.job.core.util.XxlJobRemotingUtil; +//import com.xxl.tool.response.Response; +// +//import java.util.List; +// +///** +// * admin api test +// * +// * @author xuxueli 2017-07-28 22:14:52 +// */ +//public class AdminBizClient implements AdminBiz { +// +// public AdminBizClient() { +// } +// public AdminBizClient(String addressUrl, String accessToken, int timeout) { +// this.addressUrl = addressUrl; +// this.accessToken = accessToken; +// this.timeout = timeout; +// +// // valid +// if (!this.addressUrl.endsWith("/")) { +// this.addressUrl = this.addressUrl + "/"; +// } +// if (!(this.timeout >=1 && this.timeout <= 10)) { +// this.timeout = 3; +// } +// } +// +// private String addressUrl ; +// private String accessToken; +// private int timeout; +// +// +// @Override +// public Response callback(List handleCallbackRequestList) { +// return XxlJobRemotingUtil.postBody(addressUrl+"api/callback", accessToken, timeout, handleCallbackRequestList, String.class); +// } +// +// @Override +// public Response registry(RegistryRequest registryRequest) { +// return XxlJobRemotingUtil.postBody(addressUrl + "api/registry", accessToken, timeout, registryRequest, String.class); +// } +// +// @Override +// public Response registryRemove(RegistryRequest registryRequest) { +// return XxlJobRemotingUtil.postBody(addressUrl + "api/registryRemove", accessToken, timeout, registryRequest, String.class); +// } +// +//} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/DateUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/DateUtil.java new file mode 100644 index 00000000..6b740cd6 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/DateUtil.java @@ -0,0 +1,156 @@ +//package com.xxl.job.core.util; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.text.DateFormat; +//import java.text.ParseException; +//import java.text.SimpleDateFormat; +//import java.util.Calendar; +//import java.util.Date; +//import java.util.HashMap; +//import java.util.Map; +// +///** +// * date util +// * +// * @author xuxueli 2018-08-19 01:24:11 +// */ +//public class DateUtil { +// +// // ---------------------- format parse ---------------------- +// private static Logger logger = LoggerFactory.getLogger(DateUtil.class); +// +// private static final String DATE_FORMAT = "yyyy-MM-dd"; +// private static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; +// +// private static final ThreadLocal> dateFormatThreadLocal = new ThreadLocal>(); +// private static DateFormat getDateFormat(String pattern) { +// if (pattern==null || pattern.trim().length()==0) { +// throw new IllegalArgumentException("pattern cannot be empty."); +// } +// +// Map dateFormatMap = dateFormatThreadLocal.get(); +// if(dateFormatMap!=null && dateFormatMap.containsKey(pattern)){ +// return dateFormatMap.get(pattern); +// } +// +// synchronized (dateFormatThreadLocal) { +// if (dateFormatMap == null) { +// dateFormatMap = new HashMap(); +// } +// dateFormatMap.put(pattern, new SimpleDateFormat(pattern)); +// dateFormatThreadLocal.set(dateFormatMap); +// } +// +// return dateFormatMap.get(pattern); +// } +// +// /** +// * format datetime. like "yyyy-MM-dd" +// * +// * @param date +// * @return +// * @throws ParseException +// */ +// public static String formatDate(Date date) { +// return format(date, DATE_FORMAT); +// } +// +// /** +// * format date. like "yyyy-MM-dd HH:mm:ss" +// * +// * @param date +// * @return +// * @throws ParseException +// */ +// public static String formatDateTime(Date date) { +// return format(date, DATETIME_FORMAT); +// } +// +// /** +// * format date +// * +// * @param date +// * @param patten +// * @return +// * @throws ParseException +// */ +// public static String format(Date date, String patten) { +// return getDateFormat(patten).format(date); +// } +// +// /** +// * parse date string, like "yyyy-MM-dd HH:mm:s" +// * +// * @param dateString +// * @return +// * @throws ParseException +// */ +// public static Date parseDate(String dateString){ +// return parse(dateString, DATE_FORMAT); +// } +// +// /** +// * parse datetime string, like "yyyy-MM-dd HH:mm:ss" +// * +// * @param dateString +// * @return +// * @throws ParseException +// */ +// public static Date parseDateTime(String dateString) { +// return parse(dateString, DATETIME_FORMAT); +// } +// +// /** +// * parse date +// * +// * @param dateString +// * @param pattern +// * @return +// * @throws ParseException +// */ +// public static Date parse(String dateString, String pattern) { +// try { +// Date date = getDateFormat(pattern).parse(dateString); +// return date; +// } catch (Exception e) { +// logger.warn("parse date error, dateString = {}, pattern={}; errorMsg = {}", dateString, pattern, e.getMessage()); +// return null; +// } +// } +// +// +// // ---------------------- add date ---------------------- +// +// public static Date addYears(final Date date, final int amount) { +// return add(date, Calendar.YEAR, amount); +// } +// +// public static Date addMonths(final Date date, final int amount) { +// return add(date, Calendar.MONTH, amount); +// } +// +// public static Date addDays(final Date date, final int amount) { +// return add(date, Calendar.DAY_OF_MONTH, amount); +// } +// +// public static Date addHours(final Date date, final int amount) { +// return add(date, Calendar.HOUR_OF_DAY, amount); +// } +// +// public static Date addMinutes(final Date date, final int amount) { +// return add(date, Calendar.MINUTE, amount); +// } +// +// private static Date add(final Date date, final int calendarField, final int amount) { +// if (date == null) { +// return null; +// } +// final Calendar c = Calendar.getInstance(); +// c.setTime(date); +// c.add(calendarField, amount); +// return c.getTime(); +// } +// +//} \ No newline at end of file diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ExecutorBizClient.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ExecutorBizClient.java new file mode 100644 index 00000000..86259497 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ExecutorBizClient.java @@ -0,0 +1,61 @@ +//package com.xxl.job.core.openapi.client; +// +//import com.xxl.job.core.openapi.ExecutorBiz; +//import com.xxl.job.core.openapi.model.*; +//import com.xxl.job.core.util.XxlJobRemotingUtil; +//import com.xxl.tool.response.Response; +// +///** +// * admin api test +// * +// * @author xuxueli 2017-07-28 22:14:52 +// */ +//public class ExecutorBizClient implements ExecutorBiz { +// +// public ExecutorBizClient() { +// } +// public ExecutorBizClient(String addressUrl, String accessToken, int timeout) { +// this.addressUrl = addressUrl; +// this.accessToken = accessToken; +// this.timeout = timeout; +// +// // valid +// if (!this.addressUrl.endsWith("/")) { +// this.addressUrl = this.addressUrl + "/"; +// } +// if (!(this.timeout >=1 && this.timeout <= 10)) { +// this.timeout = 3; +// } +// } +// +// private String addressUrl ; +// private String accessToken; +// private int timeout; +// +// +// @Override +// public Response beat() { +// return XxlJobRemotingUtil.postBody(addressUrl+"beat", accessToken, timeout, "", String.class); +// } +// +// @Override +// public Response idleBeat(IdleBeatRequest idleBeatRequest){ +// return XxlJobRemotingUtil.postBody(addressUrl+"idleBeat", accessToken, timeout, idleBeatRequest, String.class); +// } +// +// @Override +// public Response run(TriggerRequest triggerRequest) { +// return XxlJobRemotingUtil.postBody(addressUrl + "run", accessToken, timeout, triggerRequest, String.class); +// } +// +// @Override +// public Response kill(KillRequest killRequest) { +// return XxlJobRemotingUtil.postBody(addressUrl + "kill", accessToken, timeout, killRequest, String.class); +// } +// +// @Override +// public Response log(LogRequest logRequest) { +// return XxlJobRemotingUtil.postBody(addressUrl + "log", accessToken, timeout, logRequest, LogResult.class); +// } +// +//} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/FileUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/FileUtil.java new file mode 100644 index 00000000..d66f3028 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/FileUtil.java @@ -0,0 +1,181 @@ +//package com.xxl.job.core.util; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.File; +//import java.io.FileInputStream; +//import java.io.FileOutputStream; +//import java.io.IOException; +// +///** +// * file tool +// * +// * @author xuxueli 2017-12-29 17:56:48 +// */ +//public class FileUtil { +// private static Logger logger = LoggerFactory.getLogger(FileUtil.class); +// +// +// /** +// * delete recursively +// * +// * @param root +// * @return +// */ +// public static boolean deleteRecursively(File root) { +// if (root != null && root.exists()) { +// if (root.isDirectory()) { +// File[] children = root.listFiles(); +// if (children != null) { +// for (File child : children) { +// deleteRecursively(child); +// } +// } +// } +// return root.delete(); +// } +// return false; +// } +// +// +// public static void deleteFile(String fileName) { +// // file +// File file = new File(fileName); +// if (file.exists()) { +// file.delete(); +// } +// } +// +// +// public static void writeFileContent(File file, byte[] data) { +// +// // file +// if (!file.exists()) { +// file.getParentFile().mkdirs(); +// } +// +// // append file content +// FileOutputStream fos = null; +// try { +// fos = new FileOutputStream(file); +// fos.write(data); +// fos.flush(); +// } catch (Exception e) { +// logger.error(e.getMessage(), e); +// } finally { +// if (fos != null) { +// try { +// fos.close(); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } +// } +// } +// +// } +// +// public static byte[] readFileContent(File file) { +// Long fileLength = file.length(); +// byte[] fileContent = new byte[fileLength.intValue()]; +// +// FileInputStream in = null; +// try { +// in = new FileInputStream(file); +// in.read(fileContent); +// in.close(); +// +// return fileContent; +// } catch (Exception e) { +// logger.error(e.getMessage(), e); +// return null; +// } finally { +// if (in != null) { +// try { +// in.close(); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } +// } +// } +// } +// +// +// /*public static void appendFileLine(String fileName, String content) { +// +// // file +// File file = new File(fileName); +// if (!file.exists()) { +// try { +// file.createNewFile(); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// return; +// } +// } +// +// // content +// if (content == null) { +// content = ""; +// } +// content += "\r\n"; +// +// // append file content +// FileOutputStream fos = null; +// try { +// fos = new FileOutputStream(file, true); +// fos.write(content.getBytes("utf-8")); +// fos.flush(); +// } catch (Exception e) { +// logger.error(e.getMessage(), e); +// } finally { +// if (fos != null) { +// try { +// fos.close(); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } +// } +// } +// +// } +// +// public static List loadFileLines(String fileName){ +// +// List result = new ArrayList<>(); +// +// // valid log file +// File file = new File(fileName); +// if (!file.exists()) { +// return result; +// } +// +// // read file +// StringBuffer logContentBuffer = new StringBuffer(); +// int toLineNum = 0; +// LineNumberReader reader = null; +// try { +// //reader = new LineNumberReader(new FileReader(logFile)); +// reader = new LineNumberReader(new InputStreamReader(new FileInputStream(file), "utf-8")); +// String line = null; +// while ((line = reader.readLine())!=null) { +// if (line!=null && line.trim().length()>0) { +// result.add(line); +// } +// } +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } finally { +// if (reader != null) { +// try { +// reader.close(); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } +// } +// } +// +// return result; +// }*/ +// +//} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/GsonTool.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/GsonTool.java new file mode 100644 index 00000000..91164e2c --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/GsonTool.java @@ -0,0 +1,202 @@ +//package com.xxl.job.core.util; +// +//import com.google.gson.Gson; +//import com.google.gson.GsonBuilder; +//import com.google.gson.JsonElement; +//import com.google.gson.reflect.TypeToken; +// +//import java.lang.reflect.Type; +//import java.util.ArrayList; +//import java.util.HashMap; +// +///** +// * gson tool (From https://github.com/xuxueli/xxl-tool ) +// * +// * @author xuxueli 2020-04-11 20:56:31 +// */ +//public class GsonTool { +// +// private static Gson gson = null; +// static { +// gson= new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").disableHtmlEscaping().create(); +// } +// +// /** +// * Object 转成 json +// * +// *

      +//     *     String json = GsonTool.toJson(new Demo());
      +//     * 
      +// * +// * @param src +// * @return String +// */ +// public static String toJson(Object src) { +// return gson.toJson(src); +// } +// +// /** +// * json 转成 特定的cls的Object +// * +// *
      +//     *     Demo demo = GsonTool.fromJson(json, Demo.class);
      +//     * 
      +// * +// * @param json +// * @param classOfT +// * @return +// */ +// public static T fromJson(String json, Class classOfT) { +// return gson.fromJson(json, classOfT); +// } +// +// /** +// * json 转成 特定的 rawClass 的Object +// * +// *
      +//     *     Response response = GsonTool.fromJson(json, Response.class, Demo.class);
      +//     * 
      +// * +// * @param json +// * @param classOfT +// * @param argClassOfT +// * @return +// */ +// /*public static T fromJson(String json, Class classOfT, Class argClassOfT) { +// Type type = new ParameterizedType4ReturnT(classOfT, new Class[]{argClassOfT}); +// return gson.fromJson(json, type); +// } +// public static class ParameterizedType4ReturnT implements ParameterizedType { +// private final Class raw; +// private final Type[] args; +// public ParameterizedType4ReturnT(Class raw, Type[] args) { +// this.raw = raw; +// this.args = args != null ? args : new Type[0]; +// } +// @Override +// public Type[] getActualTypeArguments() { +// return args; +// } +// @Override +// public Type getRawType() { +// return raw; +// } +// @Override +// public Type getOwnerType() {return null;} +// }*/ +// +// /** +// * json 转成 特定的 Type 的Object +// * +// * @param json +// * @param typeOfT +// * @return +// * @param +// */ +// public static T fromJson(String json, Type typeOfT) { +// return gson.fromJson(json, typeOfT); +// } +// +// /** +// * json 转成 特定的 Type 的Object +// * +// *
      +//     *     Response response = GsonTool.fromJson(json, Response.class, Demo.class);
      +//     * 
      +// * +// * @param json +// * @param rawType +// * @param typeArguments +// * @return +// */ +// public static T fromJson(String json, Type rawType, Type... typeArguments) { +// Type type = TypeToken.getParameterized(rawType, typeArguments).getType(); +// return gson.fromJson(json, type); +// } +// +// /** +// * json 转成 特定的cls的 ArrayList +// * +// *
      +//     *     List demoList = GsonTool.fromJsonList(json, Demo.class);
      +//     * 
      +// * +// * @param json +// * @param classOfT +// * @return +// */ +// public static ArrayList fromJsonList(String json, Class classOfT) { +// Type type = TypeToken.getParameterized(ArrayList.class, classOfT).getType(); +// return gson.fromJson(json, type); +// } +// +// /** +// * json 转成 特定的cls的 HashMap +// * +// *
      +//     *     HashMap map = GsonTool.fromJsonMap(json, String.class, Demo.class);
      +//     * 
      +// * +// * @param json +// * @param keyClass +// * @param valueClass +// * @return +// * @param +// * @param +// */ +// public static HashMap fromJsonMap(String json, Class keyClass, Class valueClass) { +// Type type = TypeToken.getParameterized(HashMap.class, keyClass, valueClass).getType(); +// return gson.fromJson(json, type); +// } +// +// // --------------------------------- +// +// /** +// * Object 转成 JsonElement +// * +// * @param src +// * @return +// */ +// public static JsonElement toJsonElement(Object src) { +// return gson.toJsonTree(src); +// } +// +// /** +// * JsonElement 转成 特定的cls的Object +// * +// * @param json +// * @param classOfT +// * @return +// * @param +// */ +// public static T fromJsonElement(JsonElement json, Class classOfT) { +// return gson.fromJson(json, classOfT); +// } +// +// /** +// * JsonElement 转成 特定的 rawClass 的Object +// * +// * @param json +// * @param typeOfT +// * @return +// * @param +// */ +// public static T fromJsonElement(JsonElement json, Type typeOfT) { +// return gson.fromJson(json, typeOfT); +// } +// +// /** +// * JsonElement 转成 特定的 Type 的 Object +// * +// * @param json +// * @param rawType +// * @param typeArguments +// * @return +// * @param +// */ +// public static T fromJsonElement(JsonElement json, Type rawType, Type... typeArguments) { +// Type typeOfT = TypeToken.getParameterized(rawType, typeArguments).getType(); +// return gson.fromJson(json, typeOfT); +// } +// +//} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/IpUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/IpUtil.java new file mode 100644 index 00000000..95fb3c93 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/IpUtil.java @@ -0,0 +1,206 @@ +//package com.xxl.job.core.util; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.IOException; +//import java.net.Inet6Address; +//import java.net.InetAddress; +//import java.net.NetworkInterface; +//import java.net.UnknownHostException; +//import java.util.Enumeration; +//import java.util.regex.Pattern; +// +///** +// * ip tool +// * +// * @author xuxueli 2016-5-22 11:38:05 +// */ +//public class IpUtil { +// private static final Logger logger = LoggerFactory.getLogger(IpUtil.class); +// +// private static final String ANYHOST_VALUE = "0.0.0.0"; +// private static final String LOCALHOST_VALUE = "127.0.0.1"; +// private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$"); +// +// +// +// private static volatile InetAddress LOCAL_ADDRESS = null; +// +// // ---------------------- valid ---------------------- +// +// private static InetAddress toValidAddress(InetAddress address) { +// if (address instanceof Inet6Address) { +// Inet6Address v6Address = (Inet6Address) address; +// if (isPreferIPV6Address()) { +// return normalizeV6Address(v6Address); +// } +// } +// if (isValidV4Address(address)) { +// return address; +// } +// return null; +// } +// +// private static boolean isPreferIPV6Address() { +// return Boolean.getBoolean("java.net.preferIPv6Addresses"); +// } +// +// /** +// * valid Inet4Address +// * +// * @param address +// * @return +// */ +// private static boolean isValidV4Address(InetAddress address) { +// if (address == null || address.isLoopbackAddress()) { +// return false; +// } +// String name = address.getHostAddress(); +// boolean result = (name != null +// && IP_PATTERN.matcher(name).matches() +// && !ANYHOST_VALUE.equals(name) +// && !LOCALHOST_VALUE.equals(name)); +// return result; +// } +// +// +// /** +// * normalize the ipv6 Address, convert scope name to scope id. +// * e.g. +// * convert +// * fe80:0:0:0:894:aeec:f37d:23e1%en0 +// * to +// * fe80:0:0:0:894:aeec:f37d:23e1%5 +// *

      +// * The %5 after ipv6 address is called scope id. +// * see java doc of {@link Inet6Address} for more details. +// * +// * @param address the input address +// * @return the normalized address, with scope id converted to int +// */ +// private static InetAddress normalizeV6Address(Inet6Address address) { +// String addr = address.getHostAddress(); +// int i = addr.lastIndexOf('%'); +// if (i > 0) { +// try { +// return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId()); +// } catch (UnknownHostException e) { +// // ignore +// logger.debug("Unknown IPV6 address: ", e); +// } +// } +// return address; +// } +// +// // ---------------------- find ip ---------------------- +// +// +// private static InetAddress getLocalAddress0() { +// InetAddress localAddress = null; +// // 1、prefer filter NetworkInterface +// try { +// Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); +// if (null == interfaces) { +// return localAddress; +// } +// while (interfaces.hasMoreElements()) { +// try { +// NetworkInterface network = interfaces.nextElement(); +// if (network.isLoopback() || network.isVirtual() || !network.isUp()) { +// continue; +// } +// Enumeration addresses = network.getInetAddresses(); +// while (addresses.hasMoreElements()) { +// try { +// InetAddress addressItem = toValidAddress(addresses.nextElement()); +// if (addressItem != null) { +// try { +// if(addressItem.isReachable(100)){ +// return addressItem; +// } +// } catch (IOException e) { +// // ignore +// } +// } +// } catch (Throwable e) { +// logger.error(e.getMessage(), e); +// } +// } +// } catch (Throwable e) { +// logger.error(e.getMessage(), e); +// } +// } +// } catch (Throwable e) { +// logger.error(e.getMessage(), e); +// } +// +// // 2、getLocalAddress +// try { +// localAddress = InetAddress.getLocalHost(); +// InetAddress addressItem = toValidAddress(localAddress); +// if (addressItem != null) { +// return addressItem; +// } +// } catch (Throwable e) { +// logger.error(e.getMessage(), e); +// } +// +// return localAddress; +// } +// +// +// // ---------------------- tool ---------------------- +// +// /** +// * Find first valid IP from local network card +// * +// * @return first valid local IP +// */ +// public static InetAddress getLocalAddress() { +// if (LOCAL_ADDRESS != null) { +// return LOCAL_ADDRESS; +// } +// InetAddress localAddress = getLocalAddress0(); +// LOCAL_ADDRESS = localAddress; +// return localAddress; +// } +// +// /** +// * get ip address +// * +// * @return String +// */ +// public static String getIp(){ +// return getLocalAddress().getHostAddress(); +// } +// +// /** +// * get ip:port +// * +// * @param port +// * @return String +// */ +// public static String getIpPort(int port){ +// String ip = getIp(); +// return getIpPort(ip, port); +// } +// +// public static String getIpPort(String ip, int port){ +// if (ip==null) { +// return null; +// } +// return ip.concat(":").concat(String.valueOf(port)); +// } +// +// public static Object[] parseIpPort(String address){ +// String[] array = address.split(":"); +// +// String host = array[0]; +// int port = Integer.parseInt(array[1]); +// +// return new Object[]{host, port}; +// } +// +// +//} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/JdkSerializeTool.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/JdkSerializeTool.java new file mode 100644 index 00000000..cf5df957 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/JdkSerializeTool.java @@ -0,0 +1,75 @@ +//package com.xxl.job.core.util; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.*; +// +///** +// * @author xuxueli 2020-04-12 0:14:00 +// */ +//public class JdkSerializeTool { +// private static Logger logger = LoggerFactory.getLogger(JdkSerializeTool.class); +// +// +// // ------------------------ serialize and unserialize ------------------------ +// +// /** +// * 将对象-->byte[] (由于jedis中不支持直接存储object所以转换成byte[]存入) +// * +// * @param object +// * @return +// */ +// public static byte[] serialize(Object object) { +// ObjectOutputStream oos = null; +// ByteArrayOutputStream baos = null; +// try { +// // 序列化 +// baos = new ByteArrayOutputStream(); +// oos = new ObjectOutputStream(baos); +// oos.writeObject(object); +// byte[] bytes = baos.toByteArray(); +// return bytes; +// } catch (Exception e) { +// logger.error(e.getMessage(), e); +// } finally { +// try { +// oos.close(); +// baos.close(); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } +// } +// return null; +// } +// +// +// /** +// * 将byte[] -->Object +// * +// * @param bytes +// * @return +// */ +// public static Object deserialize(byte[] bytes, Class clazz) { +// ObjectInputStream ois = null; +// ByteArrayInputStream bais = null; +// try { +// // 反序列化 +// bais = new ByteArrayInputStream(bytes); +// ois = new ObjectInputStream(bais); +// return ois.readObject(); +// } catch (Exception e) { +// logger.error(e.getMessage(), e); +// } finally { +// try { +// ois.close(); +// bais.close(); +// } catch (IOException e) { +// logger.error(e.getMessage(), e); +// } +// } +// return null; +// } +// +// +//} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/NetUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/NetUtil.java new file mode 100644 index 00000000..f983e49c --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/NetUtil.java @@ -0,0 +1,70 @@ +//package com.xxl.job.core.util; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.IOException; +//import java.net.ServerSocket; +// +///** +// * net util +// * +// * @author xuxueli 2017-11-29 17:00:25 +// */ +//public class NetUtil { +// private static Logger logger = LoggerFactory.getLogger(NetUtil.class); +// +// /** +// * find available port +// * +// * @param defaultPort +// * @return +// */ +// public static int findAvailablePort(int defaultPort) { +// int portTmp = defaultPort; +// while (portTmp < 65535) { +// if (!isPortUsed(portTmp)) { +// return portTmp; +// } else { +// portTmp++; +// } +// } +// portTmp = defaultPort - 1; +// while (portTmp > 0) { +// if (!isPortUsed(portTmp)) { +// return portTmp; +// } else { +// portTmp--; +// } +// } +// throw new RuntimeException("no available port."); +// } +// +// /** +// * check port used +// * +// * @param port +// * @return +// */ +// public static boolean isPortUsed(int port) { +// boolean used = false; +// ServerSocket serverSocket = null; +// try { +// serverSocket = new ServerSocket(port); +// used = false; +// } catch (IOException e) { +// logger.info(">>>>>>>>>>> xxl-job, port[{}] is in use.", port); +// used = true; +// } finally { +// if (serverSocket != null) { +// try { +// serverSocket.close(); +// } catch (IOException e) { +// logger.info(""); +// } +// } +// } +// return used; +// } +// +//} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ReturnT.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ReturnT.java new file mode 100644 index 00000000..e7a07cd0 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ReturnT.java @@ -0,0 +1,103 @@ +//package com.xxl.job.core.openapi.model; +// +//import java.io.Serializable; +// +///** +// * common return +// * +// * @author xuxueli 2015-12-4 16:32:31 +// * @param +// */ +//public class ReturnT implements Serializable { +// public static final long serialVersionUID = 42L; +// +// private int code; +// +// private String msg; +// +// private T content; +// +// public ReturnT(){} +// +// public ReturnT(int code, String msg) { +// this.code = code; +// this.msg = msg; +// } +// +// public ReturnT(int code, String msg, T content) { +// this.code = code; +// this.msg = msg; +// this.content = content; +// } +// +// public int getCode() { +// return code; +// } +// +// public void setCode(int code) { +// this.code = code; +// } +// +// public String getMsg() { +// return msg; +// } +// +// public void setMsg(String msg) { +// this.msg = msg; +// } +// +// public T getContent() { +// return content; +// } +// +// public void setContent(T content) { +// this.content = content; +// } +// +// @Override +// public String toString() { +// return "ReturnT{" + +// "code=" + code + +// ", msg='" + msg + '\'' + +// ", content=" + content + +// '}'; +// } +// +// +// // --------------------------- tool --------------------------- +// +// public static final int SUCCESS_CODE = 200; +// public static final int FAIL_CODE = 500; +// +// /** +// * is success +// * +// * @return +// */ +// public boolean isSuccess() { +// return code == SUCCESS_CODE; +// } +// +// public static ReturnT of(int code, String msg, T data) { +// return new ReturnT(code, msg, data); +// } +// public static ReturnT of(int code, String msg) { +// return new ReturnT(code, msg, null); +// } +// +// public static ReturnT ofSuccess(T data) { +// return new ReturnT(SUCCESS_CODE, "Success", data); +// } +// +// public static ReturnT ofSuccess() { +// return new ReturnT(SUCCESS_CODE, "Success", null); +// } +// +// public static ReturnT ofFail(String msg) { +// return new ReturnT(FAIL_CODE, msg, null); +// } +// public static ReturnT ofFail() { +// return new ReturnT(FAIL_CODE, "Fail", null); +// } +// +//} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/ShardingUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ShardingUtil.java similarity index 100% rename from xxl-job-core/src/main/java/com/xxl/job/core/util/ShardingUtil.java rename to xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ShardingUtil.java diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ThrowableUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ThrowableUtil.java new file mode 100644 index 00000000..33d5d8a6 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/ThrowableUtil.java @@ -0,0 +1,24 @@ +//package com.xxl.job.core.util; +// +//import java.io.PrintWriter; +//import java.io.StringWriter; +// +///** +// * @author xuxueli 2018-10-20 20:07:26 +// */ +//public class ThrowableUtil { +// +// /** +// * parse error to string +// * +// * @param e +// * @return +// */ +// public static String toString(Throwable e) { +// StringWriter stringWriter = new StringWriter(); +// e.printStackTrace(new PrintWriter(stringWriter)); +// String errorMsg = stringWriter.toString(); +// return errorMsg; +// } +// +//} diff --git a/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/XxlJobRemotingUtil.java b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/XxlJobRemotingUtil.java new file mode 100644 index 00000000..35a6a0f9 --- /dev/null +++ b/xxl-job-core/src/main/java/com/xxl/job/core/util/deprecated/XxlJobRemotingUtil.java @@ -0,0 +1,163 @@ +//package com.xxl.job.core.util; +// +//import com.xxl.tool.gson.GsonTool; +//import com.xxl.tool.response.Response; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import javax.net.ssl.*; +//import java.io.BufferedReader; +//import java.io.DataOutputStream; +//import java.io.InputStreamReader; +//import java.net.HttpURLConnection; +//import java.net.URL; +//import java.security.cert.CertificateException; +//import java.security.cert.X509Certificate; +// +///** +// * @author xuxueli 2018-11-25 00:55:31 +// */ +//public class XxlJobRemotingUtil { +// private static Logger logger = LoggerFactory.getLogger(XxlJobRemotingUtil.class); +// public static final String XXL_JOB_ACCESS_TOKEN = "XXL-JOB-ACCESS-TOKEN"; +// +// +// // trust-https start +// private static void trustAllHosts(HttpsURLConnection connection) { +// try { +// SSLContext sc = SSLContext.getInstance("TLS"); +// sc.init(null, trustAllCerts, new java.security.SecureRandom()); +// SSLSocketFactory newFactory = sc.getSocketFactory(); +// +// connection.setSSLSocketFactory(newFactory); +// } catch (Exception e) { +// logger.error(e.getMessage(), e); +// } +// connection.setHostnameVerifier(new HostnameVerifier() { +// @Override +// public boolean verify(String hostname, SSLSession session) { +// return true; +// } +// }); +// } +// private static final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { +// @Override +// public java.security.cert.X509Certificate[] getAcceptedIssuers() { +// return new java.security.cert.X509Certificate[]{}; +// } +// @Override +// public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { +// } +// @Override +// public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { +// } +// }}; +// // trust-https end +// +// +// /** +// * post +// * +// * @param url +// * @param accessToken +// * @param timeout by second +// * @param requestObj +// * @param returnTargClassOfT +// * @return +// */ +// public static Response postBody(String url, String accessToken, int timeout, Object requestObj, Class returnTargClassOfT) { +// HttpURLConnection connection = null; +// BufferedReader bufferedReader = null; +// DataOutputStream dataOutputStream = null; +// try { +// // connection +// URL realUrl = new URL(url); +// connection = (HttpURLConnection) realUrl.openConnection(); +// +// // trust-https +// boolean useHttps = url.startsWith("https"); +// if (useHttps) { +// HttpsURLConnection https = (HttpsURLConnection) connection; +// trustAllHosts(https); +// } +// +// // connection setting +// connection.setRequestMethod("POST"); +// connection.setDoOutput(true); +// connection.setDoInput(true); +// connection.setUseCaches(false); +// connection.setReadTimeout(timeout * 1000); +// connection.setConnectTimeout(timeout * 1000); +// connection.setRequestProperty("connection", "Keep-Alive"); +// connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); +// connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8"); +// +// if(accessToken!=null && !accessToken.trim().isEmpty()){ +// connection.setRequestProperty(XXL_JOB_ACCESS_TOKEN, accessToken); +// } +// +// // do connection +// connection.connect(); +// +// // write requestBody +// if (requestObj != null) { +// String requestBody = GsonTool.toJson(requestObj); +// +// dataOutputStream = new DataOutputStream(connection.getOutputStream()); +// dataOutputStream.write(requestBody.getBytes("UTF-8")); +// dataOutputStream.flush(); +// dataOutputStream.close(); +// } +// +// /*byte[] requestBodyBytes = requestBody.getBytes("UTF-8"); +// connection.setRequestProperty("Content-Length", String.valueOf(requestBodyBytes.length)); +// OutputStream outwritestream = connection.getOutputStream(); +// outwritestream.write(requestBodyBytes); +// outwritestream.flush(); +// outwritestream.close();*/ +// +// // valid StatusCode +// int statusCode = connection.getResponseCode(); +// if (statusCode != 200) { +// return Response.ofFail("xxl-job remoting fail, StatusCode("+ statusCode +") invalid. for url : " + url); +// } +// +// // result +// bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); +// StringBuilder result = new StringBuilder(); +// String line; +// while ((line = bufferedReader.readLine()) != null) { +// result.append(line); +// } +// String resultJson = result.toString(); +// +// // parse returnT +// try { +// Response returnT = GsonTool.fromJson(resultJson, Response.class, returnTargClassOfT); +// return returnT; +// } catch (Exception e) { +// logger.error("xxl-job remoting (url="+url+") response content invalid("+ resultJson +").", e); +// return Response.ofFail("xxl-job remoting (url="+url+") response content invalid("+ resultJson +")."); +// } +// +// } catch (Exception e) { +// logger.error(e.getMessage(), e); +// return Response.ofFail("xxl-job remoting error("+ e.getMessage() +"), for url : " + url); +// } finally { +// try { +// if (dataOutputStream != null) { +// dataOutputStream.close(); +// } +// if (bufferedReader != null) { +// bufferedReader.close(); +// } +// if (connection != null) { +// connection.disconnect(); +// } +// } catch (Exception e2) { +// logger.error(e2.getMessage(), e2); +// } +// } +// } +// +//} diff --git a/xxl-job-executor-samples/pom.xml b/xxl-job-executor-samples/pom.xml index f9cd9caa..3ae3289a 100644 --- a/xxl-job-executor-samples/pom.xml +++ b/xxl-job-executor-samples/pom.xml @@ -4,7 +4,7 @@ com.xuxueli xxl-job - 2.5.0 + 3.4.0 xxl-job-executor-samples pom @@ -12,9 +12,11 @@ xxl-job-executor-sample-frameless xxl-job-executor-sample-springboot + xxl-job-executor-sample-springboot-ai + true true diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/pom.xml b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/pom.xml index de0c6650..2bc4a588 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/pom.xml +++ b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/pom.xml @@ -6,7 +6,7 @@ com.xuxueli xxl-job-executor-samples - 2.5.0 + 3.4.0 xxl-job-executor-sample-frameless jar @@ -22,13 +22,11 @@ org.slf4j slf4j-reload4j - ${slf4j-api.version} org.junit.jupiter junit-jupiter-engine - ${junit-jupiter.version} test @@ -36,7 +34,6 @@ com.xuxueli xxl-job-core - ${project.parent.version} diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/FramelessApplication.java b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/XxlJobFramelessApplication.java similarity index 86% rename from xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/FramelessApplication.java rename to xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/XxlJobFramelessApplication.java index 1e7cb7dd..eee00845 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/FramelessApplication.java +++ b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/XxlJobFramelessApplication.java @@ -9,8 +9,8 @@ import java.util.concurrent.TimeUnit; /** * @author xuxueli 2018-10-31 19:05:43 */ -public class FramelessApplication { - private static Logger logger = LoggerFactory.getLogger(FramelessApplication.class); +public class XxlJobFramelessApplication { + private static final Logger logger = LoggerFactory.getLogger(XxlJobFramelessApplication.class); public static void main(String[] args) { diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/config/FrameLessXxlJobConfig.java b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/config/FrameLessXxlJobConfig.java index 36349496..facbb5c7 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/config/FrameLessXxlJobConfig.java +++ b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/config/FrameLessXxlJobConfig.java @@ -2,11 +2,10 @@ package com.xxl.job.executor.sample.frameless.config; import com.xxl.job.executor.sample.frameless.jobhandler.SampleXxlJob; import com.xxl.job.core.executor.impl.XxlJobSimpleExecutor; +import com.xxl.tool.core.PropTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.InputStreamReader; import java.util.Arrays; import java.util.Properties; @@ -14,10 +13,10 @@ import java.util.Properties; * @author xuxueli 2018-10-31 19:05:43 */ public class FrameLessXxlJobConfig { - private static Logger logger = LoggerFactory.getLogger(FrameLessXxlJobConfig.class); + private static final Logger logger = LoggerFactory.getLogger(FrameLessXxlJobConfig.class); - private static FrameLessXxlJobConfig instance = new FrameLessXxlJobConfig(); + private static final FrameLessXxlJobConfig instance = new FrameLessXxlJobConfig(); public static FrameLessXxlJobConfig getInstance() { return instance; } @@ -31,19 +30,20 @@ public class FrameLessXxlJobConfig { public void initXxlJobExecutor() { // load executor prop - Properties xxlJobProp = loadProperties("xxl-job-executor.properties"); + Properties xxlJobProp = PropTool.loadProp("xxl-job-executor.properties"); // init executor xxlJobExecutor = new XxlJobSimpleExecutor(); xxlJobExecutor.setAdminAddresses(xxlJobProp.getProperty("xxl.job.admin.addresses")); xxlJobExecutor.setAccessToken(xxlJobProp.getProperty("xxl.job.admin.accessToken")); xxlJobExecutor.setTimeout(Integer.valueOf(xxlJobProp.getProperty("xxl.job.admin.timeout"))); + xxlJobExecutor.setEnabled(Boolean.valueOf(xxlJobProp.getProperty("xxl.job.executor.enabled"))); xxlJobExecutor.setAppname(xxlJobProp.getProperty("xxl.job.executor.appname")); xxlJobExecutor.setAddress(xxlJobProp.getProperty("xxl.job.executor.address")); xxlJobExecutor.setIp(xxlJobProp.getProperty("xxl.job.executor.ip")); - xxlJobExecutor.setPort(Integer.valueOf(xxlJobProp.getProperty("xxl.job.executor.port"))); + xxlJobExecutor.setPort(Integer.parseInt(xxlJobProp.getProperty("xxl.job.executor.port"))); xxlJobExecutor.setLogPath(xxlJobProp.getProperty("xxl.job.executor.logpath")); - xxlJobExecutor.setLogRetentionDays(Integer.valueOf(xxlJobProp.getProperty("xxl.job.executor.logretentiondays"))); + xxlJobExecutor.setLogRetentionDays(Integer.parseInt(xxlJobProp.getProperty("xxl.job.executor.logretentiondays"))); // registry job bean xxlJobExecutor.setXxlJobBeanList(Arrays.asList(new SampleXxlJob())); @@ -65,30 +65,4 @@ public class FrameLessXxlJobConfig { } } - - public static Properties loadProperties(String propertyFileName) { - InputStreamReader in = null; - try { - ClassLoader loder = Thread.currentThread().getContextClassLoader(); - - in = new InputStreamReader(loder.getResourceAsStream(propertyFileName), "UTF-8");; - if (in != null) { - Properties prop = new Properties(); - prop.load(in); - return prop; - } - } catch (IOException e) { - logger.error("load {} error!", propertyFileName); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - logger.error("close {} error!", propertyFileName); - } - } - } - return null; - } - } diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/jobhandler/SampleXxlJob.java b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/jobhandler/SampleXxlJob.java index a4eefd14..d695c151 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/jobhandler/SampleXxlJob.java +++ b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/java/com/xxl/job/executor/sample/frameless/jobhandler/SampleXxlJob.java @@ -2,16 +2,20 @@ package com.xxl.job.executor.sample.frameless.jobhandler; import com.xxl.job.core.context.XxlJobHelper; import com.xxl.job.core.handler.annotation.XxlJob; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.json.GsonTool; +import com.xxl.tool.http.HttpTool; +import com.xxl.tool.http.http.HttpResponse; +import com.xxl.tool.http.http.enums.ContentType; +import com.xxl.tool.http.http.enums.Method; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedInputStream; import java.io.BufferedReader; -import java.io.DataOutputStream; import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Arrays; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -26,7 +30,7 @@ import java.util.concurrent.TimeUnit; * @author xuxueli 2019-12-11 21:52:51 */ public class SampleXxlJob { - private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class); + private static final Logger logger = LoggerFactory.getLogger(SampleXxlJob.class); /** @@ -70,6 +74,8 @@ public class SampleXxlJob { /** * 3、命令行任务 + * + * 参数示例:"ls -a" 或者 "pwd" */ @XxlJob("commandJobHandler") public void commandJobHandler() throws Exception { @@ -78,9 +84,18 @@ public class SampleXxlJob { BufferedReader bufferedReader = null; try { + // valid + if (command==null || command.trim().isEmpty()) { + XxlJobHelper.handleFail("command empty."); + return; + } + + // command split + String[] commandArray = command.split(" "); + // command process ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command(command); + processBuilder.command(commandArray); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); @@ -117,122 +132,244 @@ public class SampleXxlJob { /** * 4、跨平台Http任务 + * * 参数示例: - * "url: http://www.baidu.com\n" + - * "method: get\n" + - * "data: content\n"; + *

      +     *      // 1、简单示例:
      +     *      {
      +     *          "url": "http://www.baidu.com",
      +     *          "method": "get",
      +     *          "data": "hello world"
      +     *      }
      +     *
      +     *      // 2、完整参数示例:
      +     *      {
      +     *          "url": "http://www.baidu.com",
      +     *          "method": "POST",
      +     *          "contentType": "application/json",
      +     *          "headers": {
      +     *              "header01": "value01"
      +     *          },
      +     *          "cookies": {
      +     *              "cookie01": "value01"
      +     *          },
      +     *          "timeout": 3000,
      +     *          "data": "request body data",
      +     *          "form": {
      +     *              "key01": "value01"
      +     *          },
      +     *          "auth": "auth data"
      +     *      }
      +     *  
      */ @XxlJob("httpJobHandler") public void httpJobHandler() throws Exception { - // param parse + // param data String param = XxlJobHelper.getJobParam(); - if (param==null || param.trim().length()==0) { + if (param==null || param.trim().isEmpty()) { XxlJobHelper.log("param["+ param +"] invalid."); XxlJobHelper.handleFail(); return; } - String[] httpParams = param.split("\n"); - String url = null; - String method = null; - String data = null; - for (String httpParam: httpParams) { - if (httpParam.startsWith("url:")) { - url = httpParam.substring(httpParam.indexOf("url:") + 4).trim(); - } - if (httpParam.startsWith("method:")) { - method = httpParam.substring(httpParam.indexOf("method:") + 7).trim().toUpperCase(); - } - if (httpParam.startsWith("data:")) { - data = httpParam.substring(httpParam.indexOf("data:") + 5).trim(); - } + // param parse + HttpJobParam httpJobParam = null; + try { + httpJobParam = GsonTool.fromJson(param, HttpJobParam.class); + } catch (Exception e) { + XxlJobHelper.log(new RuntimeException("HttpJobParam parse error", e)); + XxlJobHelper.handleFail(); + return; } // param valid - if (url==null || url.trim().length()==0) { - XxlJobHelper.log("url["+ url +"] invalid."); - + if (httpJobParam == null) { + XxlJobHelper.log("param parse fail."); XxlJobHelper.handleFail(); return; } - if (method==null || !Arrays.asList("GET", "POST").contains(method)) { - XxlJobHelper.log("method["+ method +"] invalid."); - + if (StringTool.isBlank(httpJobParam.getUrl())) { + XxlJobHelper.log("url["+ httpJobParam.getUrl() +"] invalid."); + XxlJobHelper.handleFail(); + return; + } + if (!isValidDomain(httpJobParam.getUrl())) { + XxlJobHelper.log("url["+ httpJobParam.getUrl() +"] not allowed."); XxlJobHelper.handleFail(); return; } - boolean isPostMethod = method.equals("POST"); + Method method = Method.POST; + if (StringTool.isNotBlank(httpJobParam.getMethod())) { + Method methodParam = Method.valueOf(httpJobParam.getMethod().toUpperCase()); + if (methodParam == null) { + XxlJobHelper.log("method["+ httpJobParam.getMethod() +"] invalid."); + XxlJobHelper.handleFail(); + return; + } + method = methodParam; + } + ContentType contentType = ContentType.JSON; + if (StringTool.isNotBlank(httpJobParam.getContentType())) { + for (ContentType contentTypeParam : ContentType.values()) { + if (contentTypeParam.getValue().equals(httpJobParam.getContentType())) { + contentType = contentTypeParam; + break; + } + } + } + if (httpJobParam.getTimeout() <= 0) { + httpJobParam.setTimeout(3000); + } - // request - HttpURLConnection connection = null; - BufferedReader bufferedReader = null; + // do request try { - // connection - URL realUrl = new URL(url); - connection = (HttpURLConnection) realUrl.openConnection(); - - // connection setting - connection.setRequestMethod(method); - connection.setDoOutput(isPostMethod); - connection.setDoInput(true); - connection.setUseCaches(false); - connection.setReadTimeout(5 * 1000); - connection.setConnectTimeout(3 * 1000); - connection.setRequestProperty("connection", "Keep-Alive"); - connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); - connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8"); - - // do connection - connection.connect(); - - // data - if (isPostMethod && data!=null && data.trim().length()>0) { - DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream()); - dataOutputStream.write(data.getBytes("UTF-8")); - dataOutputStream.flush(); - dataOutputStream.close(); - } + HttpResponse httpResponse = HttpTool.createRequest() + .url(httpJobParam.getUrl()) + .method(method) + .contentType(contentType) + .header(httpJobParam.getHeaders()) + .cookie(httpJobParam.getCookies()) + .body(httpJobParam.getData()) + .form(httpJobParam.getForm()) + .auth(httpJobParam.getAuth()) + .execute(); + + XxlJobHelper.log("StatusCode: " + httpResponse.statusCode()); + XxlJobHelper.log("Response:
      " + httpResponse.response()); + } catch (Exception e) { + XxlJobHelper.log(e); + XxlJobHelper.handleFail(); + } + } - // valid StatusCode - int statusCode = connection.getResponseCode(); - if (statusCode != 200) { - throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid."); - } + /** + * domain white-list, for httpJobHandler + */ + private static Set DOMAIN_WHITE_LIST = Set.of( + "http://www.baidu.com", + "http://cn.bing.com" + ); - // result - bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); - StringBuilder result = new StringBuilder(); - String line; - while ((line = bufferedReader.readLine()) != null) { - result.append(line); + /** + * valid if domain is in white-list + */ + private boolean isValidDomain(String url) { + if (url == null || DOMAIN_WHITE_LIST.isEmpty()) { + return false; + } + for (String prefix : DOMAIN_WHITE_LIST) { + if (url.startsWith(prefix)) { + return true; } - String responseMsg = result.toString(); + } + return false; + } - XxlJobHelper.log(responseMsg); + /*public static void main(String[] args) { + HttpJobParam httpJobParam = new HttpJobParam(); + httpJobParam.setUrl("http://www.baidu.com"); + httpJobParam.setMethod(Method.POST.name()); + httpJobParam.setContentType(ContentType.JSON.getValue()); + httpJobParam.setHeaders(Map.of("header01", "value01")); + httpJobParam.setCookies(Map.of("cookie01", "value01")); + httpJobParam.setTimeout(3000); + httpJobParam.setData("request body data"); + httpJobParam.setForm(Map.of("form01", "value01")); + httpJobParam.setAuth("auth data"); + + logger.info(GsonTool.toJson(httpJobParam)); + }*/ - return; - } catch (Exception e) { - XxlJobHelper.log(e); + /** + * http job param + */ + private static class HttpJobParam{ + private String url; // 请求 Url + private String method; // Method + private String contentType; // Content-Type + private Map headers; // 存储请求头 + private Map cookies; // Cookie(需要格式转换) + private int timeout; // 请求超时时间 + private String data; // 存储请求体 + private Map form; // 存储表单数据 + private String auth; // 鉴权信息 + + public String getUrl() { + return url; + } - XxlJobHelper.handleFail(); - return; - } finally { - try { - if (bufferedReader != null) { - bufferedReader.close(); - } - if (connection != null) { - connection.disconnect(); - } - } catch (Exception e2) { - XxlJobHelper.log(e2); - } + public void setUrl(String url) { + this.url = url; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Map getCookies() { + return cookies; + } + + public void setCookies(Map cookies) { + this.cookies = cookies; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public String getData() { + return data; } + public void setData(String data) { + this.data = data; + } + + public Map getForm() { + return form; + } + + public void setForm(Map form) { + this.form = form; + } + + public String getAuth() { + return auth; + } + + public void setAuth(String auth) { + this.auth = auth; + } } + /** * 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑; */ diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/resources/xxl-job-executor.properties b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/resources/xxl-job-executor.properties index 5c66d2c8..5d987e2e 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/resources/xxl-job-executor.properties +++ b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/main/resources/xxl-job-executor.properties @@ -1,10 +1,12 @@ ### xxl-job admin address list, such as "http://address" or "http://address01,http://address02" xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin -### xxl-job access-token +### xxl-job access token xxl.job.admin.accessToken=default_token ### xxl-job timeout by second, default 3s xxl.job.admin.timeout=3 +### xxl-job executor enable, default true +xxl.job.executor.enabled=true ### xxl-job executor appname xxl.job.executor.appname=xxl-job-executor-sample ### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/test/java/com/xxl/job/executor/sample/frameless/test/FramelessApplicationTest.java b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/test/java/com/xxl/job/executor/sample/frameless/test/FramelessApplicationTest.java index 1f9be9a9..1446c51c 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/test/java/com/xxl/job/executor/sample/frameless/test/FramelessApplicationTest.java +++ b/xxl-job-executor-samples/xxl-job-executor-sample-frameless/src/test/java/com/xxl/job/executor/sample/frameless/test/FramelessApplicationTest.java @@ -1,12 +1,21 @@ package com.xxl.job.executor.sample.frameless.test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.platform.commons.annotation.Testable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +@Testable public class FramelessApplicationTest { + private static final Logger logger = LoggerFactory.getLogger(FramelessApplicationTest.class); @Test - public void test(){ - System.out.println("111"); + @DisplayName("test1") + public void test1(){ + logger.info("111"); + Assertions.assertNull( null); } } diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/Dockerfile b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/Dockerfile new file mode 100644 index 00000000..f64dff67 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/Dockerfile @@ -0,0 +1,21 @@ +# base image +FROM eclipse-temurin:17-jre + +# maintainer +MAINTAINER xuxueli + +# set params +ENV PARAMS="" + +# set timezone +ENV TZ=PRC +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# copy jar +ADD target/xxl-job-executor-sample-springboot-ai*.jar /app.jar + +# command +# log home: -e LOG_HOME=/data/applogs +# jvm options: -e JAVA_OPTS="-Xms128m -Xmx128m" +# app params: -e PARAMS="--server.port=8080" +ENTRYPOINT ["sh","-c","java ${LOG_HOME:+-DLOG_HOME=$LOG_HOME} -jar $JAVA_OPTS /app.jar $PARAMS"] \ No newline at end of file diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/pom.xml b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/pom.xml new file mode 100644 index 00000000..df5f01ce --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + com.xuxueli + xxl-job-executor-samples + 3.4.0 + + xxl-job-executor-sample-springboot-ai + jar + + ${project.artifactId} + Example executor project for spring boot. + https://www.xuxueli.com/ + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.xuxueli + xxl-job-core + + + + + org.springframework.ai + spring-ai-starter-model-ollama + + + + org.springframework.ai + spring-ai-starter-model-openai + + + + io.github.imfangs + dify-java-client + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + + + \ No newline at end of file diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/XxlJobAIExecutorApplication.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/XxlJobAIExecutorApplication.java new file mode 100644 index 00000000..aebc58d2 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/XxlJobAIExecutorApplication.java @@ -0,0 +1,16 @@ +package com.xxl.job.executor; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author xuxueli 2018-10-28 00:38:13 + */ +@SpringBootApplication +public class XxlJobAIExecutorApplication { + + public static void main(String[] args) { + SpringApplication.run(XxlJobAIExecutorApplication.class, args); + } + +} \ No newline at end of file diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/config/XxlJobConfig.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/config/XxlJobConfig.java new file mode 100644 index 00000000..cf836f0e --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/config/XxlJobConfig.java @@ -0,0 +1,72 @@ +package com.xxl.job.executor.config; + +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * xxl-job config + * + * @author xuxueli 2017-04-28 + */ +@Configuration +public class XxlJobConfig { + private static final Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); + + @Value("${xxl.job.admin.addresses}") + private String adminAddresses; + + @Value("${xxl.job.admin.accessToken}") + private String accessToken; + + @Value("${xxl.job.admin.timeout}") + private int timeout; + + @Value("${xxl.job.executor.enabled}") + private Boolean enabled; + + @Value("${xxl.job.executor.appname}") + private String appname; + + @Value("${xxl.job.executor.address}") + private String address; + + @Value("${xxl.job.executor.ip}") + private String ip; + + @Value("${xxl.job.executor.port}") + private int port; + + @Value("${xxl.job.executor.logpath}") + private String logPath; + + @Value("${xxl.job.executor.logretentiondays}") + private int logRetentionDays; + + @Value("${xxl.job.executor.excludedpackage}") + private String excludedPackage; + + + @Bean + public XxlJobSpringExecutor xxlJobExecutor() { + logger.info(">>>>>>>>>>> xxl-job config init."); + XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); + xxlJobSpringExecutor.setAdminAddresses(adminAddresses); + xxlJobSpringExecutor.setAccessToken(accessToken); + xxlJobSpringExecutor.setTimeout(timeout); + xxlJobSpringExecutor.setEnabled(enabled); + xxlJobSpringExecutor.setAppname(appname); + xxlJobSpringExecutor.setAddress(address); + xxlJobSpringExecutor.setIp(ip); + xxlJobSpringExecutor.setPort(port); + xxlJobSpringExecutor.setLogPath(logPath); + xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); + xxlJobSpringExecutor.setExcludedPackage(excludedPackage); + + return xxlJobSpringExecutor; + } + +} \ No newline at end of file diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/controller/IndexController.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/controller/IndexController.java new file mode 100644 index 00000000..0b5f2358 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/controller/IndexController.java @@ -0,0 +1,202 @@ +package com.xxl.job.executor.controller;//package com.xxl.job.executor.mvc.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.imfangs.dify.client.DifyClientFactory; +import io.github.imfangs.dify.client.DifyWorkflowClient; +import io.github.imfangs.dify.client.callback.WorkflowStreamCallback; +import io.github.imfangs.dify.client.enums.ResponseMode; +import io.github.imfangs.dify.client.event.*; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunRequest; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunResponse; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; +import org.springframework.ai.ollama.OllamaChatModel; +import org.springframework.ai.ollama.api.OllamaChatOptions; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +@Controller +@EnableAutoConfiguration +public class IndexController { + private static Logger logger = LoggerFactory.getLogger(IndexController.class); + + + @RequestMapping("/") + @ResponseBody + String index() { + return "xxl job ai executor running."; + } + + + // --------------------------------- ollama chat --------------------------------- + + @Resource + private OllamaChatModel ollamaChatModel; + private String prompt = "你好,你是一个研发工程师,擅长解决技术类问题。"; + private String modle = "qwen3.5:2b"; + + /** + * ChatClient 简单调用 + */ + @GetMapping("/chat/simple") + @ResponseBody + public String simpleChat(@RequestParam(value = "input", required = false, defaultValue = "介绍你自己") String input) { + + // build chat-client + ChatClient ollamaChatClient = ChatClient + .builder(ollamaChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) // add memory + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) // add logger + .defaultOptions(OllamaChatOptions.builder().model(modle).build()) // assign model + .build(); + + // call ollama + String response = ollamaChatClient + .prompt(prompt) + .user(input) + .call() + .content(); + + logger.info("result: " + response); + return response; + } + + /** + * ChatClient 流式调用 + */ + @GetMapping("/chat/stream") + public Flux streamChat(HttpServletResponse response, @RequestParam(value = "input", required = false, defaultValue = "介绍你自己") String input) { + response.setCharacterEncoding("UTF-8"); + + // build chat-client + ChatClient ollamaChatClient = ChatClient + .builder(ollamaChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .defaultOptions(OllamaChatOptions.builder().model(modle).build()) + .build(); + + // call ollama + return ollamaChatClient + .prompt(prompt) + .user(input) + .stream() + .content(); + } + + + // --------------------------------- dify workflow --------------------------------- + + // dify config sample + private final String apiKey = "app-46gHBiqUb5jqAHl9TDWwnRZ8"; + private final String baseUrl = "http://localhost/v1"; + + @GetMapping("/dify/simple") + @ResponseBody + public String difySimple(@RequestParam(required = false, value = "input") String input) throws Exception { + + Map inputs = new HashMap<>(); + inputs.put("input", input); + + // request + WorkflowRunRequest request = WorkflowRunRequest.builder() + .inputs(inputs) + .responseMode(ResponseMode.BLOCKING) + .user("user-123") + .build(); + + // invoke + DifyWorkflowClient workflowClient = DifyClientFactory.createWorkflowClient(baseUrl, apiKey); + WorkflowRunResponse response = workflowClient.runWorkflow(request); + + // response + return write2Json(response.getData().getOutputs()); + } + + private String write2Json(Object obj) { + if (obj == null) { + return "null"; + } + try { + return new ObjectMapper().writeValueAsString(obj); + } catch (JsonProcessingException e) { + return obj.toString(); + } + } + + @GetMapping( "/dify/stream") + public Flux difyStream(@RequestParam(required = false, value = "input") String input) { + + Map inputs = new HashMap<>(); + inputs.put("input", input); + + // request + WorkflowRunRequest request = WorkflowRunRequest.builder() + .inputs(inputs) + .responseMode(ResponseMode.STREAMING) + .user("user-123") + .build(); + + // invoke + DifyWorkflowClient workflowClient = DifyClientFactory.createWorkflowClient(baseUrl, apiKey); + return Flux.create(new Consumer>() { + @Override + public void accept(FluxSink sink) { + try { + workflowClient.runWorkflowStream(request, new WorkflowStreamCallback() { + @Override + public void onWorkflowStarted(WorkflowStartedEvent event) { + sink.next("工作流开始: " + write2Json(event.getData())); + } + + @Override + public void onNodeStarted(NodeStartedEvent event) { + sink.next("节点开始: " + write2Json(event.getData())); + } + + @Override + public void onNodeFinished(NodeFinishedEvent event) { + sink.next("节点完成: " + write2Json(event.getData().getOutputs())); + } + + @Override + public void onWorkflowFinished(WorkflowFinishedEvent event) { + sink.next("工作流完成: " + write2Json(event.getData().getOutputs())); + sink.complete(); + } + + @Override + public void onError(ErrorEvent event) { + sink.error(new RuntimeException(event.getMessage())); + } + + @Override + public void onException(Throwable throwable) { + sink.error(throwable); + } + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + +} \ No newline at end of file diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/jobhandler/AIXxlJob.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/jobhandler/AIXxlJob.java new file mode 100644 index 00000000..06c39f66 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/jobhandler/AIXxlJob.java @@ -0,0 +1,345 @@ +package com.xxl.job.executor.jobhandler; + +import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.job.core.handler.annotation.XxlJob; +import com.xxl.tool.json.GsonTool; +import io.github.imfangs.dify.client.DifyClientFactory; +import io.github.imfangs.dify.client.DifyWorkflowClient; +import io.github.imfangs.dify.client.enums.ResponseMode; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunRequest; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunResponse; +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; +import org.springframework.ai.ollama.OllamaChatModel; +import org.springframework.ai.ollama.api.OllamaChatOptions; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * AI 任务开发示例 + * + * @author xuxueli 2025-04-06 + */ +@Component +public class AIXxlJob { + + // --------------------------------- ollama chat --------------------------------- + + @Resource + private OllamaChatModel ollamaChatModel; + @Resource + private OpenAiChatModel openAiChatModel; + + /** + * 1、ollama Chat任务 + * + * 参数示例:格式见 OllamaParam + *
      +     *      {
      +     *          "input": "{输入信息,必填信息}",
      +     *          "prompt": "{模型prompt,可选信息}"
      +     *      }
      +     *  
      + */ + @XxlJob("ollamaJobHandler") + public void ollamaJobHandler() { + + // param + String param = XxlJobHelper.getJobParam(); + if (param==null || param.trim().isEmpty()) { + XxlJobHelper.log("param is empty."); + + XxlJobHelper.handleFail(); + return; + } + + // ollama param + OllamaParam ollamaParam = null; + try { + ollamaParam = GsonTool.fromJson(param, OllamaParam.class); + if (ollamaParam.getPrompt()==null || ollamaParam.getPrompt().isBlank()) { + ollamaParam.setPrompt("你是一个研发工程师,擅长解决技术类问题。"); + } + if (ollamaParam.getInput() == null || ollamaParam.getInput().isBlank()) { + XxlJobHelper.log("input is empty."); + + XxlJobHelper.handleFail(); + return; + } + if (ollamaParam.getModel()==null || ollamaParam.getModel().isBlank()) { + ollamaParam.setModel("qwen3.5:2b"); + } + } catch (Exception e) { + XxlJobHelper.log(new RuntimeException("OllamaParam parse error", e)); + XxlJobHelper.handleFail(); + return; + } + + // input + XxlJobHelper.log("

      【Input】: " + ollamaParam.getInput()+ "

      "); + + // build chat-client + ChatClient ollamaChatClient = ChatClient + .builder(ollamaChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .defaultOptions(OllamaChatOptions.builder().model(ollamaParam.getModel()).build()) + .build(); + + // call ollama + String response = ollamaChatClient + .prompt(ollamaParam.getPrompt()) + .user(ollamaParam.getInput()) + .call() + .content(); + + XxlJobHelper.log("

      【Output】: " + response + "

      "); + } + + private static class OllamaParam{ + private String input; + private String prompt; + private String model; + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getPrompt() { + return prompt; + } + + public void setPrompt(String prompt) { + this.prompt = prompt; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + } + + + // --------------------------------- dify workflow --------------------------------- + + /** + * 2、dify Workflow任务 + * + * 参数示例:格式见 DifyParam + *
      +     *      {
      +     *          "inputs":{                      // inputs 为dify工作流任务参数;参数不固定,结合各自 workflow 自行定义。
      +     *              "input":"{用户输入信息}"      // 该参数为示例变量,需要 workflow 的“开始”节点 自定义参数 “input”,可自行调整或删除。
      +     *          },
      +     *          "user": "{用户标识,选填}"
      +     *      }
      +     *  
      + */ + @XxlJob("difyWorkflowJobHandler") + public void difyWorkflowJobHandler() throws Exception { + + // param + String param = XxlJobHelper.getJobParam(); + if (param==null || param.trim().isEmpty()) { + XxlJobHelper.log("param is empty."); + XxlJobHelper.handleFail(); + return; + } + + // param parse + DifyParam difyParam; + try { + difyParam =GsonTool.fromJson(param, DifyParam.class); + if (difyParam.getInputs() == null) { + difyParam.setInputs(new HashMap<>()); + } + if (difyParam.getUser() == null) { + difyParam.setUser("xxl-job"); + } + if (difyParam.getBaseUrl()==null || difyParam.getApiKey()==null) { + XxlJobHelper.log("baseUrl or apiKey invalid."); + XxlJobHelper.handleFail(); + return; + } + } catch (Exception e) { + XxlJobHelper.log(new RuntimeException("DifyParam parse error", e)); + XxlJobHelper.handleFail(); + return; + } + + + // dify param + XxlJobHelper.log("

      【inputs】: " + difyParam.getInputs() + "

      "); + + // dify request + WorkflowRunRequest request = WorkflowRunRequest.builder() + .inputs(difyParam.getInputs()) + .responseMode(ResponseMode.BLOCKING) + .user(difyParam.getUser()) + .build(); + + // dify invoke + DifyWorkflowClient workflowClient = DifyClientFactory.createWorkflowClient(difyParam.getBaseUrl(), difyParam.getApiKey()); + WorkflowRunResponse response = workflowClient.runWorkflow(request); + + // response + XxlJobHelper.log("

      【Output】: " + response.getData().getOutputs()+ "

      "); + } + + private static class DifyParam{ + + /** + * dify input, 允许传入 Dify App 定义的各变量值 + */ + private Map inputs; + + /** + * dify user + */ + private String user; + + /** + * dify baseUrl + */ + private String baseUrl; + + /** + * dify apiKey + */ + private String apiKey; + + public Map getInputs() { + return inputs; + } + + public void setInputs(Map inputs) { + this.inputs = inputs; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + } + + // --------------------------------- openclaw --------------------------------- + + /** + * 3、openclaw 任务 + * + * 参数示例:格式见 OpenclawParam + *
      +     *      {
      +     *          "input": "{输入信息,必填信息}",
      +     *          "prompt": "{模型prompt,可选信息}"
      +     *      }
      +     *  
      + */ + @XxlJob("openClawJobHandler") + public void openClawJobHandler() { + + // param + String param = XxlJobHelper.getJobParam(); + if (param == null || param.trim().isEmpty()) { + XxlJobHelper.log("param is empty."); + + XxlJobHelper.handleFail(); + return; + } + + // openclaw param + OpenClawParam openClawParam = null; + try { + openClawParam = GsonTool.fromJson(param, OpenClawParam.class); + if (openClawParam.getPrompt()==null || openClawParam.getPrompt().isBlank()) { + openClawParam.setPrompt("你是一个出游助手,擅长做旅游规划"); + } + if (openClawParam.getInput() == null || openClawParam.getInput().isBlank()) { + XxlJobHelper.log("input is empty."); + + XxlJobHelper.handleFail(); + return; + } + } catch (Exception e) { + XxlJobHelper.log(new RuntimeException("OpenclawParam parse error", e)); + XxlJobHelper.handleFail(); + return; + } + + // input + XxlJobHelper.log("

      【Input】: " + openClawParam.getInput()+ "

      "); + + // build chat-client + ChatClient openclawChatClient = ChatClient + .builder(openAiChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .build(); + + // call opencalw + String response = openclawChatClient + .prompt(openClawParam.getPrompt()) + .user(openClawParam.getInput()) + .call() + .content(); + + XxlJobHelper.log("

      【Output】: " + response + "

      "); + + } + + private static class OpenClawParam { + private String input; + private String prompt; + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getPrompt() { + return prompt; + } + + public void setPrompt(String prompt) { + this.prompt = prompt; + } + } + + +} diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/application.properties b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/application.properties new file mode 100644 index 00000000..a55c963e --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/application.properties @@ -0,0 +1,48 @@ +# web port +server.port=8082 + +# encoding +spring.servlet.encoding.force=true +spring.servlet.encoding.charset=UTF-8 + +# log config +logging.config=classpath:logback.xml + + +### xxl-job admin address list, such as "http://address" or "http://address01,http://address02" +xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin +### xxl-job access token +xxl.job.admin.accessToken=default_token +### xxl-job timeout by second, default 3s +xxl.job.admin.timeout=3 + +### xxl-job executor enable, default true +xxl.job.executor.enabled=true +### xxl-job executor appname +xxl.job.executor.appname=xxl-job-executor-sample-ai +### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null +xxl.job.executor.address= +### xxl-job executor server-info +xxl.job.executor.ip= +xxl.job.executor.port=9997 +### xxl-job executor log-path +xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler +### xxl-job executor log-retention-days +xxl.job.executor.logretentiondays=30 +### xxl-job executor excluded package, will skip scan job. such as "org.package01" or "org.package01,org.package02" +xxl.job.executor.excludedpackage= + +### http timeout +spring.http.clients.connect-timeout=5000 +spring.http.clients.read-timeout=10000 + +### spring-ai retry +spring.ai.retry.max-attempts=2 + +### ollama +spring.ai.ollama.base-url=http://localhost:11434 + +### openai (for openclaw) +spring.ai.openai.base-url=http://127.0.0.1:18789 +spring.ai.openai.api-key=xxxxxx + diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/logback.xml b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/logback.xml new file mode 100644 index 00000000..ca0f8b41 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/logback.xml @@ -0,0 +1,27 @@ + + + + logback + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + ${log.path} + + ${log.path}.%d{yyyy-MM-dd}.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + \ No newline at end of file diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/BaseTests.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/BaseTests.java new file mode 100644 index 00000000..1fdd0b84 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/BaseTests.java @@ -0,0 +1,14 @@ +package com.xxl.job.executor.test; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class BaseTests { + + @Test + public void test() { + System.out.println(11); + } + +} \ No newline at end of file diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/dify/DifyTest.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/dify/DifyTest.java new file mode 100644 index 00000000..5ca98124 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/dify/DifyTest.java @@ -0,0 +1,51 @@ +package com.xxl.job.executor.test.dify; + +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import io.github.imfangs.dify.client.DifyClientFactory; +import io.github.imfangs.dify.client.DifyWorkflowClient; +import io.github.imfangs.dify.client.enums.ResponseMode; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunRequest; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunResponse; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.util.Map; + +@SpringBootTest +public class DifyTest { + private static final Logger logger = LoggerFactory.getLogger(DifyTest.class); + + // ignore + @MockitoBean + private XxlJobSpringExecutor xxlJobSpringExecutor; + + @Test + public void test() throws Exception { + + String baseUrl = "https://xx.ai"; + String apiKey = "xx"; + String user = "zhangsan"; + Map inputs = Map.of( + "input", "请写一个java程序,实现一个方法,输入一个字符串,返回字符串的长度。" + ); + + // dify request + WorkflowRunRequest request = WorkflowRunRequest.builder() + .inputs(inputs) + .responseMode(ResponseMode.BLOCKING) + .user(user) + .build(); + + // dify invoke + DifyWorkflowClient workflowClient = DifyClientFactory.createWorkflowClient(baseUrl, apiKey); + WorkflowRunResponse response = workflowClient.runWorkflow(request); + + // response + logger.info("input: " + inputs); + logger.info("output: " + response.getData().getOutputs()); + } + +} diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/ollama/OllamaTest.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/ollama/OllamaTest.java new file mode 100644 index 00000000..3caf5fde --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/ollama/OllamaTest.java @@ -0,0 +1,94 @@ +package com.xxl.job.executor.test.ollama; + +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; +import org.springframework.ai.ollama.OllamaChatModel; +import org.springframework.ai.ollama.api.OllamaChatOptions; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import reactor.core.publisher.Flux; + +import java.util.concurrent.TimeUnit; + +@SpringBootTest +public class OllamaTest { + private static final Logger logger = LoggerFactory.getLogger(OllamaTest.class); + + // ignore + @MockitoBean + private XxlJobSpringExecutor xxlJobSpringExecutor; + + + @Resource + private OllamaChatModel ollamaChatModel; + + @Test + public void chatTest() { + + String model = "qwen3.5:2b"; + String prompt = "背景说明:你是一个研发工程师,擅长解决技术类问题。"; + String input = "请写一个java程序,实现一个方法,输入一个字符串,返回字符串的长度。"; + + + // build chat-client + ChatClient ollamaChatClient = ChatClient + .builder(ollamaChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .defaultOptions(OllamaChatOptions.builder().model(model).build()) + .build(); + + // call ollama + String response = ollamaChatClient + .prompt(prompt) + .user(input) + .call() + .content(); + + logger.info("input: {}", input); + logger.info("response: {}", response); + } + + @Test + public void chatStreamTest() throws InterruptedException { + + String model = "qwen3.5:2b"; + String prompt = "背景说明:你是一个研发工程师,擅长解决技术类问题。"; + String input = "请写一个java程序,实现一个方法,输入一个字符串,返回字符串的长度。"; + + + // build chat-client + ChatClient ollamaChatClient = ChatClient + .builder(ollamaChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .defaultOptions(OllamaChatOptions.builder().model(model).build()) + .build(); + + // call ollama + logger.info("input: {}", input); + Flux flux = ollamaChatClient + .prompt(prompt) + .user(input) + .stream() + .content(); + + flux.subscribe( + data -> System.out.println("Received: " + data), // onNext 处理 + error -> System.err.println("Error: " + error), // onError 处理 + () -> System.out.println("Completed") // onComplete 处理 + ); + + TimeUnit.SECONDS.sleep(10); + + } + + +} diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/openclaw/OpenClawTest.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/openclaw/OpenClawTest.java new file mode 100644 index 00000000..e031ba82 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/openclaw/OpenClawTest.java @@ -0,0 +1,95 @@ +package com.xxl.job.executor.test.openclaw; + +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.boot.test.context.SpringBootTest; +import reactor.core.publisher.Flux; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@SpringBootTest +public class OpenClawTest { + private static final Logger logger = LoggerFactory.getLogger(OpenClawTest.class); + + @Resource + private OpenAiChatModel openAiChatModel; + + /*ChatModel chatModel = OpenAiChatModel + .builder() + .openAiApi(OpenAiApi + .builder() + .baseUrl(baseUrl) + .apiKey( token) + .webClientBuilder(WebClient.builder().clientConnector( + new ReactorClientHttpConnector( + HttpClient.create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30 * 1000) + .responseTimeout(Duration.ofMillis(30 * 1000)) + ) + )) + .build()) + .build();*/ + + @Test + public void test() throws Exception { + + String prompt = "你是一个出游助手,擅长做旅游规划"; + String input = "查看下上海今天得天气,给出出游建议"; + + // ChatClient + ChatClient chatClient = ChatClient + .builder(openAiChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .build(); + + // Call LLM: 同步输出 + String response = chatClient + .prompt(prompt) + .user(input) + .call() + .content(); + + logger.info("Input: {}", input); + logger.info("Output: {}", response); + } + + @Test + public void test2() throws Exception { + + String prompt = "你是一个出游助手,擅长做旅游规划"; + String input = "查看下上海今天得天气,给出出游建议"; + + // ChatClient + ChatClient chatClient = ChatClient + .builder(openAiChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .build(); + + // Call LLM: 流式输出 + Flux flux = chatClient + .prompt(prompt) + .user(input) + .user(user -> user.text(input).params(Map.of("stream", true))) + .stream() + .content(); + + flux.subscribe( + data -> System.out.println("Received: " + data), // onNext 处理 + error -> System.err.println("Error: " + error), // onError 处理 + () -> System.out.println("Completed") // onComplete 处理 + ); + + TimeUnit.SECONDS.sleep(30); + } + +} diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/Dockerfile b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/Dockerfile index 8648d945..324af24f 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/Dockerfile +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/Dockerfile @@ -1,11 +1,21 @@ -FROM openjdk:8-jre-slim +# base image +FROM eclipse-temurin:17-jre + +# maintainer MAINTAINER xuxueli +# set params ENV PARAMS="" +# set timezone ENV TZ=PRC RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone +# copy jar ADD target/xxl-job-executor-sample-springboot-*.jar /app.jar -ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /app.jar $PARAMS"] \ No newline at end of file +# command +# log home: -e LOG_HOME=/data/applogs +# jvm options: -e JAVA_OPTS="-Xms128m -Xmx128m" +# app params: -e PARAMS="--server.port=8080" +ENTRYPOINT ["sh","-c","java ${LOG_HOME:+-DLOG_HOME=$LOG_HOME} -jar $JAVA_OPTS /app.jar $PARAMS"] \ No newline at end of file diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/pom.xml b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/pom.xml index 58947b6c..6a904637 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/pom.xml +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/pom.xml @@ -6,7 +6,7 @@ com.xuxueli xxl-job-executor-samples - 2.5.0 + 3.4.0 xxl-job-executor-sample-springboot jar @@ -18,19 +18,6 @@ - - - - - org.springframework.boot - spring-boot-starter-parent - ${spring-boot.version} - pom - import - - - - @@ -47,14 +34,12 @@ com.xuxueli xxl-job-core - ${project.parent.version} - org.springframework.boot spring-boot-maven-plugin @@ -66,9 +51,6 @@ - - true - diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/config/XxlJobConfig.java similarity index 85% rename from xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java rename to xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/config/XxlJobConfig.java index b60c3dee..e2381b19 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/config/XxlJobConfig.java @@ -1,4 +1,4 @@ -package com.xxl.job.executor.core.config; +package com.xxl.job.executor.config; import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; import org.slf4j.Logger; @@ -14,7 +14,7 @@ import org.springframework.context.annotation.Configuration; */ @Configuration public class XxlJobConfig { - private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); + private static final Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); @Value("${xxl.job.admin.addresses}") private String adminAddresses; @@ -25,6 +25,9 @@ public class XxlJobConfig { @Value("${xxl.job.admin.timeout}") private int timeout; + @Value("${xxl.job.executor.enabled}") + private Boolean enabled; + @Value("${xxl.job.executor.appname}") private String appname; @@ -43,20 +46,25 @@ public class XxlJobConfig { @Value("${xxl.job.executor.logretentiondays}") private int logRetentionDays; + @Value("${xxl.job.executor.excludedpackage}") + private String excludedPackage; + @Bean public XxlJobSpringExecutor xxlJobExecutor() { logger.info(">>>>>>>>>>> xxl-job config init."); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(adminAddresses); + xxlJobSpringExecutor.setAccessToken(accessToken); + xxlJobSpringExecutor.setTimeout(timeout); + xxlJobSpringExecutor.setEnabled(enabled); xxlJobSpringExecutor.setAppname(appname); xxlJobSpringExecutor.setAddress(address); xxlJobSpringExecutor.setIp(ip); xxlJobSpringExecutor.setPort(port); - xxlJobSpringExecutor.setAccessToken(accessToken); - xxlJobSpringExecutor.setTimeout(timeout); xxlJobSpringExecutor.setLogPath(logPath); xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); + xxlJobSpringExecutor.setExcludedPackage(excludedPackage); return xxlJobSpringExecutor; } diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/mvc/controller/IndexController.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/controller/IndexController.java similarity index 100% rename from xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/mvc/controller/IndexController.java rename to xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/controller/IndexController.java diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/jobhandler/SampleXxlJob.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/jobhandler/SampleXxlJob.java new file mode 100644 index 00000000..971af8c0 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/jobhandler/SampleXxlJob.java @@ -0,0 +1,389 @@ +package com.xxl.job.executor.jobhandler; + +import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.job.core.handler.annotation.XxlJob; +import com.xxl.tool.core.StringTool; +import com.xxl.tool.json.GsonTool; +import com.xxl.tool.http.HttpTool; +import com.xxl.tool.http.http.HttpResponse; +import com.xxl.tool.http.http.enums.ContentType; +import com.xxl.tool.http.http.enums.Method; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * XxlJob开发示例(Bean模式) + * + * 开发步骤: + * 1、任务开发:在Spring Bean实例中,开发Job方法; + * 2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。 + * 3、执行日志:需要通过 "XxlJobHelper.log" 打印执行日志; + * 4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果; + * + * @author xuxueli 2019-12-11 21:52:51 + */ +@Component +public class SampleXxlJob { + private static final Logger logger = LoggerFactory.getLogger(SampleXxlJob.class); + + + /** + * 1、简单任务示例(Bean模式) + */ + @XxlJob("demoJobHandler") + public void demoJobHandler() throws Exception { + XxlJobHelper.log("XXL-JOB, Hello World."); + + for (int i = 0; i < 5; i++) { + XxlJobHelper.log("beat at:" + i); + TimeUnit.SECONDS.sleep(2); + } + // default success + } + + + /** + * 2、分片广播任务 + */ + @XxlJob("shardingJobHandler") + public void shardingJobHandler() throws Exception { + + // 分片参数 + int shardIndex = XxlJobHelper.getShardIndex(); + int shardTotal = XxlJobHelper.getShardTotal(); + + XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal); + + // 业务逻辑 + for (int i = 0; i < shardTotal; i++) { + if (i == shardIndex) { + XxlJobHelper.log("第 {} 片, 命中分片开始处理", i); + } else { + XxlJobHelper.log("第 {} 片, 忽略", i); + } + } + + } + + + /** + * 3、命令行任务 + * + * 参数示例:"ls -a" 或者 "pwd" + */ + @XxlJob("commandJobHandler") + public void commandJobHandler() throws Exception { + String command = XxlJobHelper.getJobParam(); + int exitValue = -1; + + BufferedReader bufferedReader = null; + try { + // valid + if (command==null || command.trim().length()==0) { + XxlJobHelper.handleFail("command empty."); + return; + } + + // command split + String[] commandArray = command.split(" "); + + // command process + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.command(commandArray); + processBuilder.redirectErrorStream(true); + + Process process = processBuilder.start(); + //Process process = Runtime.getRuntime().exec(command); + + BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream()); + bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream)); + + // command log + String line; + while ((line = bufferedReader.readLine()) != null) { + XxlJobHelper.log(line); + } + + // command exit + process.waitFor(); + exitValue = process.exitValue(); + } catch (Exception e) { + XxlJobHelper.log(e); + } finally { + if (bufferedReader != null) { + bufferedReader.close(); + } + } + + if (exitValue == 0) { + // default success + } else { + XxlJobHelper.handleFail("command exit value("+exitValue+") is failed"); + } + + } + + + /** + * 4、跨平台Http任务 + * + * 参数示例: + *
      +     *      // 1、简单示例:
      +     *      {
      +     *          "url": "http://www.baidu.com",
      +     *          "method": "get",
      +     *          "data": "hello world"
      +     *      }
      +     *
      +     *      // 2、完整参数示例:
      +     *      {
      +     *          "url": "http://www.baidu.com",
      +     *          "method": "POST",
      +     *          "contentType": "application/json",
      +     *          "headers": {
      +     *              "header01": "value01"
      +     *          },
      +     *          "cookies": {
      +     *              "cookie01": "value01"
      +     *          },
      +     *          "timeout": 3000,
      +     *          "data": "request body data",
      +     *          "form": {
      +     *              "key01": "value01"
      +     *          },
      +     *          "auth": "auth data"
      +     *      }
      +     *  
      + */ + @XxlJob("httpJobHandler") + public void httpJobHandler() throws Exception { + + // param data + String param = XxlJobHelper.getJobParam(); + if (param==null || param.trim().isEmpty()) { + XxlJobHelper.log("param["+ param +"] invalid."); + + XxlJobHelper.handleFail(); + return; + } + + // param parse + HttpJobParam httpJobParam = null; + try { + httpJobParam = GsonTool.fromJson(param, HttpJobParam.class); + } catch (Exception e) { + XxlJobHelper.log(new RuntimeException("HttpJobParam parse error", e)); + XxlJobHelper.handleFail(); + return; + } + + // param valid + if (httpJobParam == null) { + XxlJobHelper.log("param parse fail."); + XxlJobHelper.handleFail(); + return; + } + if (StringTool.isBlank(httpJobParam.getUrl())) { + XxlJobHelper.log("url["+ httpJobParam.getUrl() +"] invalid."); + XxlJobHelper.handleFail(); + return; + } + if (!isValidDomain(httpJobParam.getUrl())) { + XxlJobHelper.log("url["+ httpJobParam.getUrl() +"] not allowed."); + XxlJobHelper.handleFail(); + return; + } + Method method = Method.POST; + if (StringTool.isNotBlank(httpJobParam.getMethod())) { + Method methodParam = Method.valueOf(httpJobParam.getMethod().toUpperCase()); + if (methodParam == null) { + XxlJobHelper.log("method["+ httpJobParam.getMethod() +"] invalid."); + XxlJobHelper.handleFail(); + return; + } + method = methodParam; + } + ContentType contentType = ContentType.JSON; + if (StringTool.isNotBlank(httpJobParam.getContentType())) { + for (ContentType contentTypeParam : ContentType.values()) { + if (contentTypeParam.getValue().equals(httpJobParam.getContentType())) { + contentType = contentTypeParam; + break; + } + } + } + if (httpJobParam.getTimeout() <= 0) { + httpJobParam.setTimeout(3000); + } + + // do request + try { + HttpResponse httpResponse = HttpTool.createRequest() + .url(httpJobParam.getUrl()) + .method(method) + .contentType(contentType) + .header(httpJobParam.getHeaders()) + .cookie(httpJobParam.getCookies()) + .body(httpJobParam.getData()) + .form(httpJobParam.getForm()) + .auth(httpJobParam.getAuth()) + .execute(); + + XxlJobHelper.log("StatusCode: " + httpResponse.statusCode()); + XxlJobHelper.log("Response:
      " + httpResponse.response()); + } catch (Exception e) { + XxlJobHelper.log(e); + XxlJobHelper.handleFail(); + } + } + + /** + * domain white-list, for httpJobHandler + */ + private static Set DOMAIN_WHITE_LIST = Set.of( + "http://www.baidu.com", + "http://cn.bing.com" + ); + + /** + * valid if domain is in white-list + */ + private boolean isValidDomain(String url) { + if (url == null || DOMAIN_WHITE_LIST.isEmpty()) { + return false; + } + for (String prefix : DOMAIN_WHITE_LIST) { + if (url.startsWith(prefix)) { + return true; + } + } + return false; + } + + /*public static void main(String[] args) { + HttpJobParam httpJobParam = new HttpJobParam(); + httpJobParam.setUrl("http://www.baidu.com"); + httpJobParam.setMethod(Method.POST.name()); + httpJobParam.setContentType(ContentType.JSON.getValue()); + httpJobParam.setHeaders(Map.of("header01", "value01")); + httpJobParam.setCookies(Map.of("cookie01", "value01")); + httpJobParam.setTimeout(3000); + httpJobParam.setData("request body data"); + httpJobParam.setForm(Map.of("form01", "value01")); + httpJobParam.setAuth("auth data"); + + logger.info(GsonTool.toJson(httpJobParam)); + }*/ + + /** + * http job param + */ + private static class HttpJobParam{ + private String url; // 请求 Url + private String method; // Method + private String contentType; // Content-Type + private Map headers; // 存储请求头 + private Map cookies; // Cookie(需要格式转换) + private int timeout; // 请求超时时间 + private String data; // 存储请求体 + private Map form; // 存储表单数据 + private String auth; // 鉴权信息 + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Map getCookies() { + return cookies; + } + + public void setCookies(Map cookies) { + this.cookies = cookies; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Map getForm() { + return form; + } + + public void setForm(Map form) { + this.form = form; + } + + public String getAuth() { + return auth; + } + + public void setAuth(String auth) { + this.auth = auth; + } + } + + /** + * 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑; + */ + @XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy") + public void demoJobHandler2() throws Exception { + XxlJobHelper.log("XXL-JOB, Hello World."); + } + public void init(){ + logger.info("init"); + } + public void destroy(){ + logger.info("destroy"); + } + + +} diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/service/jobhandler/SampleXxlJob.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/service/jobhandler/SampleXxlJob.java deleted file mode 100644 index 759d6625..00000000 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/service/jobhandler/SampleXxlJob.java +++ /dev/null @@ -1,253 +0,0 @@ -package com.xxl.job.executor.service.jobhandler; - -import com.xxl.job.core.context.XxlJobHelper; -import com.xxl.job.core.handler.annotation.XxlJob; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Arrays; -import java.util.concurrent.TimeUnit; - -/** - * XxlJob开发示例(Bean模式) - * - * 开发步骤: - * 1、任务开发:在Spring Bean实例中,开发Job方法; - * 2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。 - * 3、执行日志:需要通过 "XxlJobHelper.log" 打印执行日志; - * 4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果; - * - * @author xuxueli 2019-12-11 21:52:51 - */ -@Component -public class SampleXxlJob { - private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class); - - - /** - * 1、简单任务示例(Bean模式) - */ - @XxlJob("demoJobHandler") - public void demoJobHandler() throws Exception { - XxlJobHelper.log("XXL-JOB, Hello World."); - - for (int i = 0; i < 5; i++) { - XxlJobHelper.log("beat at:" + i); - TimeUnit.SECONDS.sleep(2); - } - // default success - } - - - /** - * 2、分片广播任务 - */ - @XxlJob("shardingJobHandler") - public void shardingJobHandler() throws Exception { - - // 分片参数 - int shardIndex = XxlJobHelper.getShardIndex(); - int shardTotal = XxlJobHelper.getShardTotal(); - - XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal); - - // 业务逻辑 - for (int i = 0; i < shardTotal; i++) { - if (i == shardIndex) { - XxlJobHelper.log("第 {} 片, 命中分片开始处理", i); - } else { - XxlJobHelper.log("第 {} 片, 忽略", i); - } - } - - } - - - /** - * 3、命令行任务 - */ - @XxlJob("commandJobHandler") - public void commandJobHandler() throws Exception { - String command = XxlJobHelper.getJobParam(); - int exitValue = -1; - - BufferedReader bufferedReader = null; - try { - // command process - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command(command); - processBuilder.redirectErrorStream(true); - - Process process = processBuilder.start(); - //Process process = Runtime.getRuntime().exec(command); - - BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream()); - bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream)); - - // command log - String line; - while ((line = bufferedReader.readLine()) != null) { - XxlJobHelper.log(line); - } - - // command exit - process.waitFor(); - exitValue = process.exitValue(); - } catch (Exception e) { - XxlJobHelper.log(e); - } finally { - if (bufferedReader != null) { - bufferedReader.close(); - } - } - - if (exitValue == 0) { - // default success - } else { - XxlJobHelper.handleFail("command exit value("+exitValue+") is failed"); - } - - } - - - /** - * 4、跨平台Http任务 - * 参数示例: - * "url: http://www.baidu.com\n" + - * "method: get\n" + - * "data: content\n"; - */ - @XxlJob("httpJobHandler") - public void httpJobHandler() throws Exception { - - // param parse - String param = XxlJobHelper.getJobParam(); - if (param==null || param.trim().length()==0) { - XxlJobHelper.log("param["+ param +"] invalid."); - - XxlJobHelper.handleFail(); - return; - } - - String[] httpParams = param.split("\n"); - String url = null; - String method = null; - String data = null; - for (String httpParam: httpParams) { - if (httpParam.startsWith("url:")) { - url = httpParam.substring(httpParam.indexOf("url:") + 4).trim(); - } - if (httpParam.startsWith("method:")) { - method = httpParam.substring(httpParam.indexOf("method:") + 7).trim().toUpperCase(); - } - if (httpParam.startsWith("data:")) { - data = httpParam.substring(httpParam.indexOf("data:") + 5).trim(); - } - } - - // param valid - if (url==null || url.trim().length()==0) { - XxlJobHelper.log("url["+ url +"] invalid."); - - XxlJobHelper.handleFail(); - return; - } - if (method==null || !Arrays.asList("GET", "POST").contains(method)) { - XxlJobHelper.log("method["+ method +"] invalid."); - - XxlJobHelper.handleFail(); - return; - } - boolean isPostMethod = method.equals("POST"); - - // request - HttpURLConnection connection = null; - BufferedReader bufferedReader = null; - try { - // connection - URL realUrl = new URL(url); - connection = (HttpURLConnection) realUrl.openConnection(); - - // connection setting - connection.setRequestMethod(method); - connection.setDoOutput(isPostMethod); - connection.setDoInput(true); - connection.setUseCaches(false); - connection.setReadTimeout(5 * 1000); - connection.setConnectTimeout(3 * 1000); - connection.setRequestProperty("connection", "Keep-Alive"); - connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); - connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8"); - - // do connection - connection.connect(); - - // data - if (isPostMethod && data!=null && data.trim().length()>0) { - DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream()); - dataOutputStream.write(data.getBytes("UTF-8")); - dataOutputStream.flush(); - dataOutputStream.close(); - } - - // valid StatusCode - int statusCode = connection.getResponseCode(); - if (statusCode != 200) { - throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid."); - } - - // result - bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); - StringBuilder result = new StringBuilder(); - String line; - while ((line = bufferedReader.readLine()) != null) { - result.append(line); - } - String responseMsg = result.toString(); - - XxlJobHelper.log(responseMsg); - - return; - } catch (Exception e) { - XxlJobHelper.log(e); - - XxlJobHelper.handleFail(); - return; - } finally { - try { - if (bufferedReader != null) { - bufferedReader.close(); - } - if (connection != null) { - connection.disconnect(); - } - } catch (Exception e2) { - XxlJobHelper.log(e2); - } - } - - } - - /** - * 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑; - */ - @XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy") - public void demoJobHandler2() throws Exception { - XxlJobHelper.log("XXL-JOB, Hello World."); - } - public void init(){ - logger.info("init"); - } - public void destroy(){ - logger.info("destroy"); - } - - -} diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties index a97ab628..3067097c 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties @@ -9,11 +9,13 @@ logging.config=classpath:logback.xml ### xxl-job admin address list, such as "http://address" or "http://address01,http://address02" xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin -### xxl-job, access token +### xxl-job access token xxl.job.admin.accessToken=default_token ### xxl-job timeout by second, default 3s xxl.job.admin.timeout=3 +### xxl-job executor enable, default true +xxl.job.executor.enabled=true ### xxl-job executor appname xxl.job.executor.appname=xxl-job-executor-sample ### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null @@ -25,3 +27,5 @@ xxl.job.executor.port=9999 xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler ### xxl-job executor log-retention-days xxl.job.executor.logretentiondays=30 +### xxl-job executor excluded package, will skip scan job. such as "org.package01" or "org.package01,org.package02" +xxl.job.executor.excludedpackage= diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/logback.xml b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/logback.xml index d5a0d2ca..00e03d5e 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/logback.xml +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/logback.xml @@ -2,28 +2,26 @@ logback - - + - %d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ${log.path} - ${log.path}.%d{yyyy-MM-dd}.zip + ${log.path}.%d{yyyy-MM-dd}.log - %date %level [%thread] %logger{36} [%file : %line] %msg%n - + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + \ No newline at end of file