5
0
mirror of https://github.com/scxwhite/hera.git synced 2025-05-02 15:09:30 +08:00
This commit is contained in:
苏承祥 2020-10-19 18:16:57 +08:00
commit db368c4836
1632 changed files with 273197 additions and 0 deletions

60
.all-contributorsrc Executable file
View File

@ -0,0 +1,60 @@
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "scxwhite",
"name": "苏承祥",
"avatar_url": "https://avatars2.githubusercontent.com/u/23207189?v=4",
"profile": "https://blog.csdn.net/su20145104009/",
"contributions": [
"design"
]
},
{
"login": "jiangeyu",
"name": "凌霄",
"avatar_url": "https://avatars2.githubusercontent.com/u/34758993?v=4",
"profile": "https://blog.csdn.net/Pengjx2014",
"contributions": [
"design"
]
},
{
"login": "jet2007",
"name": "jet2007",
"avatar_url": "https://avatars3.githubusercontent.com/u/3906077?v=4",
"profile": "https://github.com/jet2007",
"contributions": [
"design"
]
},
{
"login": "akong0115",
"name": "akong0115",
"avatar_url": "https://avatars3.githubusercontent.com/u/26199270?v=4",
"profile": "https://github.com/akong0115",
"contributions": [
"design"
]
},
{
"login": "Deegue",
"name": "Yizhong Zhang",
"avatar_url": "https://avatars3.githubusercontent.com/u/25916266?v=4",
"profile": "https://github.com/Deegue",
"contributions": [
"design"
]
}
],
"contributorsPerLine": 7,
"projectName": "hera",
"projectOwner": "scxwhite",
"repoType": "github",
"repoHost": "https://github.com"
}

3
.gitattributes vendored Executable file
View File

@ -0,0 +1,3 @@
*.js linguist-language=Java
*.css linguist-language=Java
*.html linguist-language=Java

17
.github/workflows/maven.yml vendored Executable file
View File

@ -0,0 +1,17 @@
name: Java CI
on: [push]
jobs:
build:
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

34
.gitignore vendored Executable file
View File

@ -0,0 +1,34 @@
#maven ignore
target/
*.tar.gz
*.jar
*.war
*.ear
#idea ignore
.idea/
*.iml
*.ipr
*.iws
#eclipse ignore
.settings/
.project
.classpath
#temp ignore
*.log
*.cache
*.diff
*.patch
*.tmp
#system ignore
.DS_Store
Thumbs.db
.idea
# other edit
*.vscode

6
.travis.yml Executable file
View File

@ -0,0 +1,6 @@
language: java
jdk:
- openjdk8
install: mvn install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true
#script: mvn test
script: mvn clean package -Pdev

339
LICENSE Executable file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

282
README.md Executable file
View File

@ -0,0 +1,282 @@
github地址https://github.com/scxwhite/hera
# 数据平台打造的任务调度系统(HERA)
[![Build Status](https://travis-ci.org/scxwhite/hera.svg?branch=open-source)](https://travis-ci.org/scxwhite/hera)
[![](https://www.jitpack.io/v/scxwhite/hera.svg)](https://www.jitpack.io/#scxwhite/hera)
目前接入hera的公司[点我接入](https://github.com/scxwhite/hera/issues/24)
- 杭州二维火科技有限公司
- 杭州涂鸦科技有限公司
- 北京高因科技(居理新房)有限公司
- 盈亚科技有限公司
- 北京智融时代信息技术有限公司
- 卓尔智联集团(02098·HK)
- 北京果敢时代科技有限公司大V店
- 中通天鸿-中国领先的云计算呼叫中心平台及人工智能科技公司
- 杭州-呆萝卜
- 微神马科技(大连)有限公司
- 上海骅天技术服务有限公司
- 浙江格家网络技术有限公司
- 紫梧桐(北京)资产管理有限公司 (蛋壳公寓)
- 海拍客
- 摩比神奇(北京)信息技术有限公司
- 蛋壳公寓
- 杭州富聊科技有限公司
- 持续更新中。。欢迎大家自荐
# 交流群
个人微信(已满99人需要我拉你进去)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190102190821351.png)
# 赞助
![开源不易,感谢支持](https://img-blog.csdnimg.cn/20191114120009558.png)
开源不易,感谢支持
# 介绍文章
[操作文档](https://github.com/scxwhite/hera/blob/hera-master/hera-admin/src/main/resources/static/help/help.md)
[赫拉(hera)分布式任务调度系统之操作文档](https://scx-white.blog.csdn.net/article/details/102571798)
[赫拉(hera)分布式任务调度系统之架构,基本功能(一)](https://blog.csdn.net/su20145104009/article/details/85124746)
[赫拉(hera)分布式任务调度系统之项目启动(二)](https://blog.csdn.net/su20145104009/article/details/85161711)
[赫拉(hera)分布式任务调度系统之开发中心(三)](https://blog.csdn.net/su20145104009/article/details/85336364)
[赫拉(hera)分布式任务调度系统之版本(四)](https://blog.csdn.net/su20145104009/article/details/85778303)
[赫拉(hera)分布式任务调度系统之Q&A(五)](https://blog.csdn.net/su20145104009/article/details/86076137)
## 前言
在大数据平台随着业务发展每天承载着成千上万的ETL任务调度这些任务集中在hive,shell脚本调度。怎么样让大量的ETL任务准确的完成调度而不出现问题甚至在任务调度执行中出现错误的情况下任务能够完成自我恢复甚至执行错误告警与完整的日志查询。`hera`任务调度系统就是在这种背景下衍生的一款分布式调度系统。随着hera集群动态扩展可以承载成千上万的任务调度。它是一款原生的分布式任务调度可以快速的添加部署`wokrer`节点,动态扩展集群规模。支持`shell,hive,spark`脚本调度,可以动态的扩展支持`python`等服务器端脚本调度。
>hera分布式任务调度系统是根据前阿里开源调度系统(`zeus`)进行的二次开发其中zeus大概在2014年开源开源后却并未进行维护。我公司(二维火)2015年引进了zeus任务调度系统一直使用至今年11月份在我们部门乃至整个公司发挥着不可替代的作用。在我使用zeus的这一年多不得不承认它的强大只要集群规模于配置适度他可以承担数万乃至十万甚至更高的数量级的任务调度。但是由于zeus代码是未维护的前端更是使用GWT技术难于在`zeus`上面进行维护。我与另外一个小伙伴(花名:凌霄,现在阿里淘宝部门)于今年三月份开始重写`zeus`,改名赫拉(hera)
```***项目地址git@github.com:scxwhite/hera.git ***```
## 架构
`hera`系统只是负责调度以及辅助的系统,具体的计算还是要落在`hadoop、hive、yarn、spark`等集群中去。所以此时又一个硬性要求,如果要执行`hadoophivespark`等任务,我们的`hera`系统的`worker`一定要部署在这些集群某些机器之上。如果仅仅是`shell`,那么也至少需要`linux`系统。对于`windows`系统,可以把自己作为`master`进行调试。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191213100911982.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
`hera`系统本身严格的遵从主从架构模式,由主节点充当着任务调度触发与任务分发器,从节点作为具体的任务执行器.架构图如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191213100937780.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
`hera``2.4` 版本以上也支持了`emr` 集群,即允许任务执行在阿里云、亚马逊的 `emr` 机器之上,架构图如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191114114902720.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
## 功能
![具体功能](https://img-blog.csdnimg.cn/20200922110012179.png)
- 支持任务的定时调度、依赖调度、手动调度、手动恢复、超级恢复、重跑历史
- 支持丰富的任务类型:`shell,hive,python,spark-sql,java`
- 可视化的任务`DAG`图展示,任务的执行严格按照任务的依赖关系执行
- 某个任务的上、下游执行状况查看,通过任务依赖图可以清楚的判断当前任务为何还未执行,删除该任务会影响那些任务。
- 支持上传文件到`hdfs`,支持使用`hdfs`文件资源
- 支持日志的实时滚动
- 支持任务失败自动恢复
- 实现集群HA机器宕机环境实现机器断线重连与心跳恢复与`hera`集群`HA`,节点单点故障环境下任务自动恢复,`master`断开,`worker`抢占`master`
- 支持对`master/work` 负载,内存,进程,`cpu`信息的可视化查看
- 支持正在等待执行的任务,每个`worker`上正在执行的任务信息的可视化查看
- 支持实时运行的任务,失败任务,成功任务,任务耗时`top10`的可视化查看
- 支持历史执行任务信息的折线图查看 具体到某天的总运行次数,总失败次数,总成功次数,总任务数,总失败任务数,总成功任务数
- 支持关注自己的任务,自动调度执行失败时会向负责人发送邮件
- 对外提供`API`开放系统任务调度触发接口便于对接其它需要使用hera的系统
- 组下任务总览、组下任务失败、组下任务正在运行
- 支持`map-reduce`任务和`yarn`任务的实时取消。
- 支持任务超时提醒
- 支持用户与组的概念
- 支持任务操作历史记录查看与恢复
- 支持任务告警定位到个人
- 告警类型支持邮箱以及自定义的钉钉、企业微信、短信、电话等
- 支持任务各种条件的模糊搜索
- 支持阿里云emr的自动创建、销毁
- 支持亚马逊emr的自动创建、销毁、弹性伸缩
- (还有更多,等待大家探索)
# 安装部署与启动
## 创建表
当使用`git`把`hera`克隆到本地之后,首先在`hera/hera-admin/resources`目录下找到`hera.sql`文件在自己的数据库中新建这些必要的表并插入初始化的数据如果你目前使用的是低版本的hera那么你可以到 [update](https://github.com/scxwhite/hera/tree/hera-master/update/sql) 目录查看是否有你的 `hera` 版本升级的 `ddl` ,如果有请根据你的版本依次执行 `ddl` 语句)
此时可以在`hera/hera-admin/resources`目录下找到`application.yml`文件。在文件里修改数据源`hera`的数据源(修改`druid.datasource`下的配置)即可进行下面的操作。
```yml
## 省略部分
druid:
datasource:
username: root #数据库用户名
password: moye #数据库密码
driver-class-name: com.mysql.jdbc.Driver #数据库驱动
url: jdbc:mysql://localhost:3306/hera?characterEncoding=utf-8&amp;zeroDateTimeBehavior=convertToNull&amp;autoReconnect=true&allowMultiQueries=true
## 省略部分
```
## 打包部署
### 2.4.1及以上版本部署方案
**[注2.4.1及以上版本已经集成启动和关闭的sh]**
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019111411090872.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
如果你的 `hera` 使用的是 `2.4.1` 版本以上的使用maven执行 mvn clean package -Dmaven.test.skip=true -Pdev
打包后在根目录会出现如图所示的压缩包
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191114111031525.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
你可以通过 `ssh` 把该包上传到服务器解压该tar.gz包。然后修改 `config` 目录下的`application.yml` 配置文件,在 `bin` 目录里执行 `start.sh` 脚本即可成功启动`hera`。
### 2.4.1以下版本部署方案
```
mvn clean package -Dmaven.test.skip=true -Pdev
```
打包后可以进入`hera-admin/target`目录下查看打包后的`hera-dev.jar` 。此时可以简单使用`java -server -Xms4G -Xmx4G -Xmn2G -jar hera.jar `启动项目,此时即可在浏览器中输入
```
localhost:8080/hera/login/admin
```
即进入登录界面,账号为`hera` 密码为`biadmin`,点击登录即进入系统。
><font color='red'>目前hera有用户账户和组账户之分默认跳转的登录地址为用户账户需要用户注册用户需要归属于一个组账户然后hera组账户在用户管理里页面审核通过后即可登录用户账户。</font>
顺便附上我的启动脚本
```shell
#!/bin/sh
JAVA_OPTS="-server -Xms4G -Xmx4G -Xmn2G -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:CMSFullGCsBeforeCompaction=5 -XX:+CMSParallelInitialMarkEnabled -XX:CMSInitiatingOccupancyFraction=80 -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/opt/logs/spring-boot/gc.log -XX:MetaspaceSize=128m -XX:+UseCMSCompactAtFullCollection -XX:MaxMetaspaceSize=128m -XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/logs/spring-boot/dump"
log_dir="/opt/logs/spring-boot"
log_file="/opt/logs/spring-boot/all.log"
jar_file="/opt/app/spring-boot/hera.jar"
#日志文件夹不存在,则创建
if [ ! -d "${log_dir}" ]; then
echo "创建日志目录:${log_dir}"
mkdir -p "${log_dir}"
echo "创建日志目录完成:${log_dir}"
fi
#父目录下jar文件存在
if [ -f "${jar_file}" ]; then
#启动jar包 错误输出的error 标准输出的log
nohup java $JAVA_OPTS -jar ${jar_file} 1>"${log_file}" 2>"${log_dir}"/error.log &
echo "启动完成"
exit 0
else
echo -e "\033[31m${jar_file}文件不存在!\033[0m"
exit 1
fi
```
关闭的脚本
```bash
#!/bin/bash
pid=`ps aux| grep java | grep hera | awk '{print $2}'`
[ ! $pid ] && echo "找不到hera的进程,请确认hera已经启动" && exit 0
res=`kill -9 $pid`
echo 关闭hera成功pid:$pid
```
## 测试
默认登陆地址为:http://localhost:8080/hera 下面需要做的是在`worker`管理这里添加执行任务的机器`IP`,然后选择一个机器组(组的概念:对于不同的`worker`而言环境可能不同,可能有的用来执行`spark`任务,有的用来执行`hadoop`任务,有的只是开发等等。当创建任务的时候根据任务类型选择一个组,要执行任务的时候会发送到相应的组的机器上执行任务)。
对于执行`work`的机器`ip`调试时可以是`master`,生产环境建议不要让`master`执行任务。如果要执行`map-reduce`或者`spark`任务,要保证你的`work`具有这些集群的客户端。
那么我们就在`work`管理页面增加要执行的`work`地址以及机器组。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20181225102855978.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1MjAxNDUxMDQwMDk=,size_16,color_FFFFFF,t_70)
此时有30分钟的缓冲时间`master` 才会检测到该 `work` 加入。为了测试,此时我们可以通过重启 `master` 来立刻使该 work` 加入执行组(后面会增加一键刷新 `work` 信息)。
此时要注意,我们的 work 也一定也要安装 hera 应用并启动。
重启后我们可以进入调度中心 ,在搜索栏里搜索 `1` ,然后按回车
![在这里插入图片描述](https://img-blog.csdnimg.cn/20181225103951766.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1MjAxNDUxMDQwMDk=,size_16,color_FFFFFF,t_70)
会发现一个 `echoTest` 任务 ,此时我们还不能执行任务,因为我们的所有任务的执行者登录用户。比如此刻我使用 `hera` 登录的,那么此时一定要保证你的 `work` 机器上有 `hera` 这个用户。
否则执行任务会出现 `sudo: unknown user: hera` 异常。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20181225104307920.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1MjAxNDUxMDQwMDk=,size_16,color_FFFFFF,t_70)
此时可以向我们填写的 `work` 机器上增加 `hera` 用户。
useradd hera
如果是 `mac` 系统 那么可以使用以下命令创建 `hera` 用户
sudo dscl . -create /Users/hera
sudo dscl . -create /Users/hera UserShell /bin/bash
sudo dscl . -create /Users/hera RealName "hera分布式任务调度"
sudo dscl . -create /Users/hera UniqueID "1024"
sudo dscl . -create /Users/hera PrimaryGroupID 80
sudo dscl . -create /Users/hera NFSHomeDirectory /Users/hera
>此时点击手动执行->选择版本->执行。此时该任务会运行,点击右上角的查看日志,可以看到任务的执行记录。
此时如果任务执行失败,`error` 日志内容为
```
sudo: no tty present and no askpass program specified
```
那么此时要使你启动` hera` 项目的用户具有 `sudo -u hera` 的权限(无须输入`root`密码,即可执行 `sudo -u hera echo 1` ,具体可以在 `sudo visudo` 中配置)。
比如我启动 `hera` 应用的用户是 `wyr`
那么首先在终端执行 `sudo visudo`命令,此时会进入文本编辑
然后在后面追加一行
wyr ALL=(ALL) NOPASSWD:ALL
如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20181228103306280.png)
这样就会在切换用户的时候无须输入密码。当然如果你使用的是`root`用户启动,即可跳过这段。
由于在 `hera` 中还用到了 `dos2unix` ,需要在执行任务的`work`上安装 `dos2unix` 工具。
```
yum install dos2unix
```
如果一切配置完成,那么即可看到输出任务执行成功的日志。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20181228103625553.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1MjAxNDUxMDQwMDk=,size_16,color_FFFFFF,t_70)
至此 已经完成了 任务的手动执行。
## TIPS
当然在部署的时候可能会出现各种状况。
比如:`work` 无法连接到 `master`,连接时抛出
```
java.net.NoRouteToHostException: 没有到主机的路由
```
这个时候请注意我们的master使用的端口是`9887`。需要在每台 `hera` 机器上的防火墙开启此端口(最好关闭防火墙 `sudo service iptables stop` )。
还有一种情况: `work` 可以连接上 `master` ,但是在`master`日志中发现 `work` 总是一段时间后断开。原因是:`hera` 各个机器的时间不一致,修改一下
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore -->
<table>
<tr>
<td align="center"><a href="https://blog.csdn.net/su20145104009/"><img src="https://avatars2.githubusercontent.com/u/23207189?v=4" width="100px;" alt="苏承祥"/><br /><sub><b>苏承祥</b></sub></a><br /><a href="#design-scxwhite" title="Design">🎨</a></td>
<td align="center"><a href="https://blog.csdn.net/Pengjx2014"><img src="https://avatars2.githubusercontent.com/u/34758993?v=4" width="100px;" alt="凌霄"/><br /><sub><b>凌霄</b></sub></a><br /><a href="#design-jiangeyu" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/akong0115"><img src="https://avatars3.githubusercontent.com/u/26199270?v=4" width="100px;" alt="akong0115"/><br /><sub><b>akong0115</b></sub></a><br /><a href="#design-akong0115" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/Deegue"><img src="https://avatars3.githubusercontent.com/u/25916266?v=4" width="100px;" alt="Yizhong Zhang"/><br /><sub><b>Yizhong Zhang</b></sub></a><br /><a href="#design-Deegue" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/jet2007"><img src="https://avatars3.githubusercontent.com/u/3906077?v=4" width="100px;" alt="jet2007"/><br /><sub><b>jet2007</b></sub></a><br /><a href="#design-jet2007" title="Design">🎨</a></td>
</tr>
</table>
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

16
bin/restart.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
workDir=$(cd "$(dirname $0)";pwd)
if [[ ! -f "$workDir/stop.sh" ]];then
echo "找不到关闭hera的脚本stop.sh请确保${workDir}目录下有stop.sh脚本"
exit 1
fi
if [[ ! -f "$workDir/start.sh" ]];then
echo "找不到启动hera的脚本start.sh请确保${workDir}目录下有start.sh脚本"
exit 1
fi
sh $workDir/stop.sh
sh $workDir/start.sh
echo "---------重启hera成功----------"

28
bin/start.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
workDir=$(cd "$(dirname $0)";cd ..;pwd)
JAVA_OPTS="-server -Xms4G -Xmx4G -Xmn2G -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:CMSFullGCsBeforeCompaction=5 -XX:+CMSParallelInitialMarkEnabled -XX:CMSInitiatingOccupancyFraction=80 -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/opt/logs/spring-boot/gc.log -XX:MetaspaceSize=128m -XX:+UseCMSCompactAtFullCollection -XX:MaxMetaspaceSize=128m -XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${workDir}/dump"
log_dir="${workDir}/logs"
log_file="${log_dir}/all.log"
#日志文件夹不存在,则创建
if [[ ! -d "${log_dir}" ]]; then
echo "创建日志目录:${log_dir}"
mkdir -p "${log_dir}"
echo "创建日志目录完成:${log_dir}"
fi
jar_file=`find ${workDir} -maxdepth 1 -name "hera-*.jar"`
echo ${jar_file}
#父目录下jar文件存在
if [[ -f "${jar_file}" ]]; then
#启动jar包 错误输出的error 标准输出的log
nohup java ${JAVA_OPTS} -jar ${jar_file} 1>"${log_file}" 2>"${log_dir}"/error.log &
echo "启动完成,日志路径:${log_dir}"
exit 0
else
echo -e "\033[31m启动失败无法在${workDir}目录找不到hera启动jar文件\033[0m"
exit 1
fi

8
bin/stop.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
pid=`ps aux | grep java | grep hera | awk '{print $2}'`
[ ! $pid ] && echo "找不到hera的进程,请确认hera已经启动" && exit 0
res=`kill -9 $pid`
echo 关闭hera成功pid:$pid

View File

@ -0,0 +1,8 @@
本分支新增功能:
- feature-job-biz-ver引入批次号+业务标签,弱化版本号概念(调度周期,基准日期间隔,业务标签)
- feature-job-display任务详情模块布局及功能调整
- feature-job-copy调度中心页面-复制任务
- feature-job-log与feature-job-log-info1、在worder机器生成明细所有日志文件2、web端日志信息使用新页面展示
- feature-dag-display依赖关系图展示任务名称
- feture-web-session-expireweb会话有效期可配置

View File

@ -0,0 +1 @@
主要改进点:依赖图(任务管理与调度中心模块下)的节点显示节点名称

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@ -0,0 +1,103 @@
[TOC]
## 说明
引入批次号+业务标签,弱化版本号概念
作业:调度周期,基准日期间隔,业务标签;
作业实例:批次号;
如jobA定义的调度周期=day,入参基准日期间隔=-1时则版本号(id)=20191203010203xxxx的批次号=2019-01-02
附:对于业务来说,版本号太技术了,不容易理解。
## 功能
一、调度中心-作业-页面(查看与编辑)
1. 标签:作业的业务标签,多个标签逗号分隔
2. 调度周期year,month,day,hour,minute,second,other(other当前等价于second)
3. 参数基准时间间隔: 数字值,如-1
4. 如jobA定义的调度周期=day,入参基准日期间隔=-1时则版本号(id)=20191203010203xxxx的批次号=2019-01-02
![作业查看页面](job-base-info.jpg)
![作业编辑页面](job-base-edit.jpg)
二、任务详细-作业页面
1. 突出的标签的显示
2. 单个作业实例中突出显示批次号隐藏了版本号ID信息
![](job-inst-01.jpg)
![](job-inst-02.jpg)
### hera meta 改动
一、表hera_job增加3个字段----不改变原hera的调用逻辑只是会结合版本号得到作业实例的批次号信息。
```
cron_period varchar(50) DEFAULT 'other' COMMENT '调度周期(year,month,day,hour,minute,second,other)',
cron_interval int DEFAULT 0 COMMENT '调度间隔,业务定义的日期与调度日期的间隔',
biz_label varchar(500) not null DEFAULT '' COMMENT '业务标签,逗号分隔'
```
二、表hera_action增加1个字段批次号
如jobA定义的cron_period=day,cron_interval=-1时则版本号(id)=20191203010203xxxx的批次号=20190102
```
batch_id varchar(50) DEFAULT NULL COMMENT '批次号',
```
三、hera_action_history表
```
batch_id varchar(50) DEFAULT NULL COMMENT '批次号',
biz_label varchar(500) DEFAULT NULL COMMENT '标签',
```
### hera脚本改动
HeraJob的实体与实体vo
jobAction的实体与实体vo
jobActionHistroy增加setBizLabelsetBatchId
```
1.com.dfire.controller.ScheduleCenterController
public JsonResponse execute方法 的actionHistory set两个setBizLabelsetBatchId
2.com.dfire.core.event.handler.JobHandler
startNewJob方法--build()前的两个setBizLabelsetBatchId
3. com.dfire.core.netty.master.Master
private void startNewJob方法--build()前的两个setBizLabelsetBatchId
4.com.dfire.core.netty.master.MasterRunJob
private void runScheduleJobContext方法--build()前的两个setBizLabelsetBatchId
```
JobHistoryVo
jobDetail.js

View File

@ -0,0 +1,2 @@
- 调度中心页面-复制任务
- 复制任务=同目录下任务名_copy , 状态=失效

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,48 @@
[TOC]
## 任务详情模块
### 任务详情-主页面
- 任务组与任务的展开子页面内容不一样
- 筛选条件,增加开始日期与结束日期
![](job-main-list.jpg)
### 任务详情-组子页面
- 组下的任务执行历史情况(点击主页面的任务组,按开始时间升序)
![](job-group-list.jpg)
### 任务详情-任务子页面
- 同一个任务执行历史情况(点击主页面的任务,按开始时间降序)
![](job-job-list.jpg)
### 子页面的功能说明
#### 时间轴
- 当前任务的执行的时间区间
#### 操作
说明:对于当前任务的手动可操作的一系列动作。
- 重做:包含【重做当前】和【重做后续】,相当于之前版本的【手动执行】与【手动恢复】
- 取消:取消正在执行的任务
- 强制:直接将任务的状态值,强制设置为【失败】、【成功】、【等待】
备注3个选项的可见性较为简单未细分可进一步优化(如若当前状=成功,则强制成功为不可见,目前就未实现)
![](job-operate.jpg)

View File

@ -0,0 +1,6 @@
### 日志页面功能
#### 任务实例的日志信息
- 配置项application.xml中增加配置项webLogHeadCount,webLogTailCount分别可查看日志头尾多少条信息默认值为100005000+5000
- 全新的日志页面:任务详情页--操作--日志,使用新标签页打开日志信息( 原来为第1列在当前页面中展开并查看明细日志

View File

@ -0,0 +1,80 @@
[TOC]
## 日志详情功能
### 日志详情
- 之前网页端只保留1000行左右的日志若超过就不展示
- 本Feature增加功能在worker机器的脚本运行目录上--生成日志文件
- 日志文件清单
```shell
#示例
/opt/logs/spring-boot/2019-12-29/manual-4564
-rwxrwxrwx 1 hera hera 105 Dec 29 15:25 1577604332970.sh
-rw-r--r-- 1 hera hera 8917 Dec 29 15:25 3912efed-c508-498a-9610-ee545a0c9c87.log
-rwxrwxrwx 1 hera hera 517 Dec 29 15:25 tmp.sh
```
- tmp.sh
```shell
curDir=$(cd `dirname $0`; pwd)
scriptName=`basename $0`
cd ${curDir}
log_file=78e32f90-16ca-481d-b55f-a6c82f0e63a7.log
echo "调度作业的日志文件:[${curDir}/${log_file}]"
runtime=`date '+%Y-%m-%d %H:%M:%S'`
echo "作业执行开始,时间[$runtime]"
bash /opt/logs/spring-boot/2019-12-30/manual-4657/1577673901459.sh 2>&1|tee -a ${log_file}
if [ ${PIPESTATUS[0]} != 0 ]
then
runtime=`date '+%Y-%m-%d %H:%M:%S'`
echo "作业执行失败,时间[$runtime]"
exit -1
else
runtime=`date '+%Y-%m-%d %H:%M:%S'`
#'此处可设置web或FTP服务,如上传日志文件,以达到网页端可查看完成日志功能'
echo "作业执行成功,时间[$runtime]"
fi
```
- 网页端的日志示例
```shell
HERA# 本地执行任务
2019-12-30 10:45:11 开始运行
HERA# ==================开始输出脚本内容==================
sleep 234
HERA# ==================结束输出脚本内容==================
HERA# 开始执行前置处理单元DownLoadJob
HERA# 前置处理单元DownLoadJob处理完毕
HERA# 开始执行核心job
HERA# dos2unix file:/opt/logs/spring-boot/2019-12-30/manual-4658/1577673912031.sh
CONSOLE# dos2unix: converting file /opt/logs/spring-boot/2019-12-30/manual-4658/1577673912031.sh to Unix format ...
CONSOLE# 调度作业的日志文件:[/opt/logs/spring-boot/2019-12-30/manual-4658/343ac3e9-7243-407e-bfd3-aa0c666f9fa6.log]
CONSOLE# 作业执行开始,时间[2019-12-30 10:45:12]
HERA# 核心job处理完毕
HERA# exitCode = 0
```
### 下一步计划
- 在网页端提供一个“下载”完整日志

View File

@ -0,0 +1,2 @@
- feture-web-session-expire
- 网页sesseion会话的有效期可配置详见application.xml的webSessionExpire

385
docs/help.md Executable file
View File

@ -0,0 +1,385 @@
# 前言
>最近发现我总是站在我的角度来使用hera,每个功能都很清楚但是对于使用者他们是不清楚的所以提供一篇hera操作文档。有问题可以在下面回复
[开源地址,请点个start,谢谢](https://github.com/scxwhite/hera)
# 操作文档
## 登录和注册
`hera`上登录和注册其实分为两个部分,即用户和用户组(如果使用的是`hera2.4`版本以下的没这个功能)
### 用户
用户的登录url地址为 `/login`,页面效果如图
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191015191930463.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
请注意看提示,用户名为你注册的邮箱的前缀。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191015192026639.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
- 邮箱:任务失败(手动恢复或者自动调度触发并且达到任务重试次数)后发送邮件
- 手机号:任务失败(手动恢复或者自动调度触发并且达到任务重试次数)后打电话
- 工号:任务失败(手动恢复或者自动调度触发并且达到任务重试次数)后发送钉钉/企业微信消息
- 部门: 这里的部门,即是用户组,每一个用户一定属于一个用户组。用户注册后可以联系管理员修改组
***Tip:这里的工号在数据库中只设置了长度为5的字符如果需要更长请联系管理员修改***
### 用户组
用户组对应的 `url` 为 /`login/admin`,页面效果如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191015192824629.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
用户组也可以登录,使用你注册的用户名和密码登录即可。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191015192847295.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
- 账号: 注意,这里的用户组账号对应到 `linux` 机器上的用户。在每个任务执行的时候都会切换到该用户组来执行任务,来实现 `linux` 多租户的功能
- 邮箱:所属于该用户组的用户所创建的任务失败(手动恢复或者自动调度触发并且达到任务重试次数)后发送邮件
- 手机:暂时无用
- 账号描述:部门描述等额外信息
### 总结
用户组和用户的含义在公司里解释:
>用户组对应我们公司的各个部门,每个部门的 `leader` 持有该账户。
用户对应部门下的员工,每个员工持有该账户
用户组和用户的含义在 `hera` 里的解释:
>用户组表示一大堆任务的所有者,`hera` 里面任务的权限粒度只到用户组级别,也就是说,所属于同一个用户组的用户可以访问彼此的任务。
## 首页
目前首页放的是一系列的任务信息,简单介绍以下
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191015194314337.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019101519442779.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
- 今日总任务数: 从今天凌晨到此刻,在 `hera` 上执行的总的任务数
- 失败任务数:从今天凌晨到此刻,在 `hear` 上执行的失败的任务数
- 实时任务状态:此刻,在 `hera` 集群实时运行的任务数量及状态统计
- 任务执行状态最近7填任务的数量的折线图
- 任务时长`TOP10`:昨日和今日任务的执行时长对比柱状图(排序方式为:今天任务的执行时长耗时)
总结
>通过今日总任务数清楚集群执行了多少任务
>通过失败任务数来查看任务的失败,以便处理失败任务。
>通过实时任务状态来判断集群是否有任务在执行。
>通过任务的执行状态来大致查看任务最近几天的执行状态
>通过任务的时长 `TOP10` 来优化那些耗时长的任务
## 机器组监控
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191015195912940.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191030164012243.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
主要是查看机器的相关信息,没什么好说的
## 系统管理
系统管理页面只有赫拉的**超级管理员**才能查看,主要包含用户、用户组的审核及编辑,机器组的新建与删除、机器组中机器的创建与删除,下面一一介绍
### 用户管理
#### 用户组
用户组即是在 `/login/admin` 地址中注册的用户,注册后会展示在这里等待管理员审核
![在这里插入图片描述](https://img-blog.csdnimg.cn/201910152000319.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
用户组的操作有审核通过、审核拒绝以及在编辑中修改用户组的邮箱和手机号信息。
#### 用户
用户组即是在 `/login` 地址中注册的用户,注册后会展示在这里等待管理员审核
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191015200112107.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
用户操作主要是对用户组所属的用户进行审核通过、审核拒绝以及对用户的工号、邮箱、电话、所属用户组的修改
#### 总结
该页面主要是 `hera` 超级管理员的操作,默认为 `hera` 组的所有成员,用来审核用户以及修改用户信息
### 监控管理
监控管理页面的效果图如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191017192355322.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
监控管理页面的功能主要是:
- 添加任务监控人
- 移除任务监控人
>解释一下什么是监控人,每一个任务都有一个或多个监控人(关注人),一旦该任务执行失败(手动恢复/自动调度)就会通过短信、电话、邮件(如果发现无告警,联系管理员自行扩展 `AlarmCenter.class`的电话和微信告警)的方式通知该任务的所有监控人
### 机器组管理&worker管理
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191017193106889.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
该页面是为 `hera` 的所有 `work` 进行分组,假设 `hera` 有4台 `work` 分别为`A,B,C,D`。其中 `A、B` 两台机器执行spark任务`C、D` 两台机器执行默认任务。我们就可以在机器组页面创建两个机器组分别为默认组和 `spark` 组。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024192314877.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
然后在 `worker `管理页面分别添加 `A、B` 机器的 `ip``spark` 组,`C、D` 两台机器到默认组。
然后就可以在添加任务的时候选择所需要执行的机器组了。
<font color='red'>
注意:
1.机器组管理在`emr`集群上目前是不支持分组的,也就是说你的分组无效。因为 `emr``ssh` 到远程集群,并不是在本地执行。
2.在 `worker` 页面添加机器 `ip` 后不是实时生效的,需要等待半个小时或者手动重启 `master `
</font>
## 任务管理
任务管理主要是对调度中心任务的一些信息检索及查看,主要包含:任务详情、任务依赖、任务搜索、日志记录等
### 任务详情
界面展示如图
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019102419512526.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
该界面展示所有任务的执行的状态,包括执行次数,执行时长,执行人,执行机器等信息。可以通过右上角的状态进行勾选想要查看的状态
- 状态 选项有全部、成功、失败、运行中、等待等任务的几种状态
- 日期 默认选择今天,可供使用者自己勾选
同时,每一条记录都是支持点击查看运行记录详情,点击后的展开图如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024194800115.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
该详情其实与调度中心的运行日志界面基本一致,主要是展示运行的运行记录,运行结果,运行时长等基本信息
### 任务依赖
任务依赖,顾名思义就是任务的上下游关系,该界面主要是为了查看某个任务的上下游任务状态。
任务状态由不同的颜色来表示
>绿色:执行成功
黄色:正在运行
红色:失败或未执行
灰色:关闭
具体操作是:先填入任务 `ID`,之后点击上游任务链或者下游任务链按钮,此时展示全部会变为不可点击状态,等待展示全部变为点击状态时,点击一下,任务的依赖关系图就会绘制完成
#### 上游任务
上游任务一般用来排查当前任务为什么没执行?任务在上游执行到哪里了?上游哪个任务失败了需要我手动恢复?等等
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191030171030964.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
#### 下游任务
下游任务一般看当前任务的执行进度,以及哪些任务依赖当前任务。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191030170506873.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
tips:
- 任务依赖图也可以在调度中心直接点击,无需填写任务 `ID`
- 依赖图展示完毕后可以把鼠标放在某个任务上面,此时右侧的任务状态信息就会变化
- 任务依赖图中的任务支持点击查看
- `0` 号任务没有实际意义,仅仅为了标识依赖图的开始节点。即:如果你点击的是上有任务链,那么 `0` 号任务在最下方,如果你点击的是下游任务链,那么 `0` 号任务在最上方
### 任务搜索
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024195520190.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
该界面主要根据脚本内容、任务名称、描述内容、变量内容、任务类型、任务是否开启等任务条件来模糊搜索该平台的所有任务,很简单,就不再介绍。
### 日志记录
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024195718621.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
该界面主要展示任务的所有操作记录,点击记录右侧的查看可以看到操作的详细内容
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024195920373.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
主要是为了方便对比或者找回曾经修改的脚本
>有一点需要注意,超级管理员可以看所有用户组中用户的操作记录。如果是普通用户只能看该组内的用户操作记录
## 开发中心
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024200116228.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
开发中心,顾名思义,是我们日常开发任务的地方。
在任务栏最外层有两个文件夹,分别为个人文档和共享文档,区别如下:
- 个人文档: 属于同一个组的用户共同使用个人文档内创建的任务
- 共享文档: 任意组的人都能查看在共享文档内创建的任务
### 新建任务/新建文件夹
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019102420103166.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
在任务栏处可以通过把鼠标放在某个文件夹上然后右键来调出任务操作。主要包含增加文件夹、新建 `Hive` 任务、新建 `Shell` 任务、新建 `Spark` 任务、重命名、删除等操作。
### 执行任务
大家可能发现脚本区的上方有几个按钮,其中执行和执行选中的代码,就是用来执行脚本内容的。
当在文件夹中新建任务后,就可以写脚本执行任务了。通过鼠标点击左侧的任务可以进行编辑任务,比如我点击左侧的 `echo.sh` 任务然后代码内容写为 `echo "hello world"` ,再然后点击工具栏的执行按钮,此时脚本下方会实时输出任务的执行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024202030984.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
如果脚本内容分很多段,我只想执行某一部分怎么办?
此时可以通过鼠标选中你要执行的代码,然后点击上方的执行选中的代码按钮即可
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024202239582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
### 日志查看
在开发中心,日志查看有两种方式
1.第一种就是上面那种,通过任务执行,直接在下方查看
2.点击最下方的历史日志
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024202414844.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024202506563.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
然后就能看详细的历史日志了
### 上传资源
点击脚本上方工具栏的上传资源按钮会弹出上传文件的弹框,通过选择自己要上传的文件,点击上传按钮即可完成上传
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191024202724571.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
注意
>1.上传的文件目前仅支持`'py','jar','sql','hive','sh','js','txt','png','jpg','gif'` 等扩展名,如果需要其它文件,请联系管理员修改
>2.上传成功后会返回一个路径请copy下你上传后的文件地址以便后面使用。目前还未有界面来维护上传的资源在计划开发中
### 同步任务
该功能开发中,后续会自动关联调度中心,关联调度中心某个任务的配置
### 保存脚本
脚本会自动保存,如果不放心的话,就点一下吧
## 调度中心
调度中心,可谓是赫拉的核心操作的地方。在这里主要用来开发任务,控制任务的开启/关闭状态,设置任务的定时时间,任务的依赖,手动执行、手动恢复任务、查看运行日志,配置告警,查看组下任务状态等等。下面一一来介绍。
### 总览
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191030172436777.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
调度中心主要分三部分,红框标注的为任务树,绿框标注的为任务的基本信息,蓝框标注的为任务的操作按钮(任务的操作按钮和任务组的操作按钮不一致,此处截取的是任务的操作按钮)
### 任务树
大家可以通过上面截图中的红框内容发现,最上方有两个按钮,分别为:我的调度任务和全部调度任务。
- 我的调度任务:我所属的用户组所创建的所有任务
- 全部调度任务:所有用户组用户所创建的所有任务
任务树中的图标主要有三种:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191030173550856.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
1.大目录
>大目录下只能创建大目录或者小目录
2.小目录
>小目录下面只能创建文件(任务)
3.文件
>文件的内容就是任务的脚本
<font color='red'>提示上图中可以看到点击98号任务后右侧显示编辑和删除的按钮这两个按钮均为临时重命名和删除刷新后还会存在。需要知道的是调度中心所有的操作按钮都在最右侧即总览所附图中蓝色框标注的部分</font>
### 任务基本信息
当你在任务树中点击某个任务后,任务的基本信息就会改变
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191030174046849.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
包含的内容比较多,我会把主要部分信息介绍一下
- 任务`id` :系统自动生成,任务的唯一标识
- 名称:新建任务时用户填写,最好见名知意,尽可能短
- 任务类型:编辑任务时可选`shell、hive、spark`三种类型
- 自动调度: 自动调度是否开启,可以通过右侧的开启/关闭按钮来操作
- 任务优先级:编辑任务时可选`low,medium,high`,当队列中任务很多时会根据该优先级进行分发任务执行
- 描述:必填项,任务信息的描述
- 调度类型:编辑任务时可选定时调度、依赖调度,定时调度时需要填写`cron` 表达式,依赖调度时需要选择所依赖的任务 `ID`
- 定时表达式:调度类型为定时调度时所填写的 `cron` 表达式
- 依赖任务:调度类型为依赖调度时所选择的依赖任务 `ID` 的列表
- 重试次数:任务自动调度/手动恢复失败时重试的次数
- 重试间隔:任务自动调度/手动恢复失败后需要等待多久进行重试
- 预计时长:整数,`0`表示无限大。当任务执行超出预计时长时会进行告警
- 报警类型:任务任务自动调度/手动恢复失败,并且重试次数已经用完进行邮件、企业微信、电话告警,告警级别依次升高。当选择电话告警时,三种告警都会触发
- 所有人:任务创建者所属的用户组
- 关注人员:任务失败后告警的人员,默认是任务的创建者。其它用户也可以在这里点击关注或者管理员在监控管理界面添加
- 管理员:该任务还允许哪些用户组的用户操作,可以通过操作按钮的配置管理员进行添加
- 重复执行:是否运行同一时间允许两个或两个以上实例执行。比如:每个小时触发的任务,任务的执行时间超出了一个小时,如果设置为否,则下个小时的任务不会执行,直到漏跑检测到才会执行
- 机器组:该任务需要在哪个机器组执行。机器组的创建请参考上面的机器组管理
- 区域:任务需要在哪个区域执行。默认是 `all` ,如果你们的 `hera` 数据库做了多区同步,那么可以选择需要执行的区域,在不是所选择的区域执行该任务时会直接设置为成功。如果需要支持,可以联系我加入赫拉开源群
### 任务配置项以及脚本内容
上面简单表述了一下任务的基本信息,下面需要说明任务的配置信息、脚本等
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019103119563747.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
从上往下依次是:任务配置项、脚本内容、继承的配置项
> - 任务的配置项:是专属于该任务的配置,以 `key=value` 对的形式存在,换行分割。
>- 脚本内容:供开发者编写的脚本内容,脚本内容要根据任务的类型来写,如果是 `spark` 任务就写 `spark-sql`,如果是 `hive` 任务就写 `hive-sql`,如果是 `shell` 任务就写 `shell` 脚本
> - 继承的配置项:在 `hera` 中允许配置项的继承,凡是任务都能够使用其所有父级的目录,所以 `hera` 可以把一些公共的配置配置到目录上。如果任务的配置项和继承配置项有重复 `key` 的,那么以`最近原则`为准,取任务的配置而忽略继承的配置。
---
有一点需要说明的是,`hera` 本身支持区域代码前缀的变量,那么该变量就会在指定区域使用
我们公司共有五个区域
- AY中国
- EU欧洲
- US美国
- IND印度
- UE美东
我的配置项里有两个配置:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191031201249164.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
然后我选择的执行区域为美国和欧洲,配置项里只有欧洲和美国开头的`name` 变量,然后我在不同区域执行该任务时:
- 中国区域
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191031201649194.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
由于执行区域只有欧洲(`EU`)和美国(`US`),所以在中国(`AY`)执行直接设置为成功,然后通知下游任务执行
- 欧洲区域
![在这里插入图片描述](https://img-blog.csdnimg.cn/201910312018432.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
由于我们设置的配置项的 `EU.name=欧洲` 所以欧洲区域读取的变量内容为欧洲
- 美国区域
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191031202053759.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
美国区域读取的就是 `US.name=美国` 的变量内容
---
脚本内容
脚本内容其实不想说的,不过又担心大家不懂,还是举几个例子
- shell任务
其实 `shell` 任务可以执行任务任务,只要是使用 `shell` 脚本可以操作的,这里都可以操作,就举个 `shell` 实现 `spark-submit` 的任务
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191031203056883.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
- hive任务
`hive` 任务就是使用 `hive sql` 写的脚本,也举个例子
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191031203430689.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
- spark任务
`spark` 任务就是使用 `spark sql` 写的任务。`spark` 任务和 `hive` 任务差不多,不过对于 `spark` 任务通常我们会配置 `--executor-memory --num-executors` 等参数。`hera` 默认已经配置了这些参数,当你觉得资源不够时,可以通过在配置项增加 `hera.spark.conf` 变量覆盖默认参数
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191031203904878.png)
### 操作按钮
操作按钮包含了调度中心的所有点击操作,任务操作按钮和组操作按钮有些许不同,下面一一介绍
#### 组操作按钮
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191031210133265.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
基本信息模块与任务模块差不多,不再介绍
- 任务总览
任务总览可以查看该组下的所有任务执行状态信息
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191031210342473.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
通过该功能我们可以知道那些任务执行了,那些任务没执行的原因是上游哪个任务失败了,右侧按钮可以进行再次过滤
- 正在运行
与任务总览类似,只不过只看正在运行的任务
- 失败记录
与任务总览类似,只不过只看失败的任务
- 添加组
该功能在鼠标放在大目录上时显示,能够在该组下面创建新的组
- 编辑
编辑该组配置信息和基本信息
- 添加任务
该功能在放在小目录上时显示,能够在该组下面创建新的任务
- 删除
字面意思
- 配置管理员
当其它用户组需要在该组下添加任务时,需要有该组的权限。通过配置管理员的方式可以实现
#### 任务操作按钮
- 运行日志
顾名思义,运行日志查看任务的执行日志信息,当点击该任务后会弹出一个模态框
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191031204357319.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
该模态框包含了该任务所有的历史执行记录主要包含开始时间、结束时间、执行的机器ip、执行人、执行时长等信息。左侧的`+`号按钮可以展开查看详细的执行日志。
- 操作记录
操作记录主要记录了该任务的历史所有操作记录
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019103120483137.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
主要包含操作类型、操作人、操作时间等信息,左侧的 `+` 号同样支持展开
查看详细操作
比如,我展开更新脚本内容
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019103120485064.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9zY3gtd2hpdGUuYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70)
左侧为历史的脚本内容,右侧为最新的脚本内容。如果想查找历史脚本可以从这里恢复
- 版本生成
在赫拉中任务的执行需要版本一说所以每个任务如果要执行都需要版本。赫拉本身会在每个小时的整点附近自动生成版本如果某人想立刻生成版本需要点击一下版本生成按钮目前2.4及以下版本不支持依赖任务版本生成,仅支持定时任务,如果需要的话,可以先把任务设置为定时任务,生成版本后再切换回去)。
- 依赖图
依赖图与上面介绍的一样,不再叙述
- 编辑
默认情况下 **基本信息****脚本配置项** 与**脚本**是只读的状态,只有点击编辑后才能够进行编辑这些信息
- 手动执行
执行一次该任务,执行成功不会通知下游依赖任务。手动执行的时候需要选择一个版本,如果没有版本就要等待版本生成或者手动生成
- 手动恢复
执行一次该任务,执行成功通知下游依赖任务。其它与手动执行一致
- 开启/关闭
任务是否开启自动调度,默认是关闭状态。开启状态的任务失败会进行告警。
- 失效
可以理解为关闭。
- 删除
字面含义
- 配置管理员
该功能一般在该任务需要交接给其它部门的人时使用,凡是在配置管理员内的用户组的用户都能操作该任务
- 关注该任务
当前登录用户关注该任务的失败告警通知

26
docs/index.html Executable file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>帮助文档</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: '',
repo: '',
basePath: '/',
homepage: './help.md'
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
</body>
</html>

212
hera-admin/pom.xml Executable file
View File

@ -0,0 +1,212 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dfire.hera</groupId>
<artifactId>hera</artifactId>
<version>2.4.2</version>
</parent>
<artifactId>hera-admin</artifactId>
<version>${hera.admin.version}</version>
<packaging>jar</packaging>
<name>hera-admin</name>
<description>hera任务调度系统</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<main-class>com.dfire.AdminBootstrap</main-class>
<final-name>hera-${env}</final-name>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
<!--
使用后会影响dubbo的初始化
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>com.dfire.hera</groupId>
<artifactId>hera-core</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>17.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>${final-name}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<!--<exclude>./static/adminlte/bootstrap/fonts/**</exclude>-->
<exclude>**/adminlte/plugins/font-awesome-4.5.0/fonts/**</exclude>
<exclude>**/adminlte/bootstrap/fonts/**</exclude>
<exclude>**/plugins/layui/font/**</exclude>
</excludes>
</resource>
<!-- fonts file cannot use filter as the data structure of byte file will be changed via filter -->
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<!--<include>./static/adminlte/bootstrap/fonts/**</include>-->
<include>**/adminlte/plugins/font-awesome-4.5.0/fonts/**</include>
<include>**/adminlte/bootstrap/fonts/**</include>
<include>**/plugins/layui/font/**</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifest>
<mainClass>${main-class}</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
<manifestEntries>
<Class-Path>./</Class-Path>
</manifestEntries>
</archive>
<excludes>
<exclude>config/*.yml</exclude>
<exclude>config/*.xml</exclude>
<exclude>config/*.yaml</exclude>
<exclude>config/*.properties</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<configuration>
<descriptor>src/main/assembly/assembly.xml</descriptor>
<finalName>${final-name}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<outputDirectory>../</outputDirectory>
<tarLongFileMode>posix</tarLongFileMode>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,73 @@
<!--
- Copyright 1999-2011 Alibaba Group.
-
- 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.
-->
<assembly>
<id>assembly</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>${assembly.include.base.directory}</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${basedir}/../bin</directory>
<includes>
<include>*.sh</include>
</includes>
<fileMode>700</fileMode>
<outputDirectory>/bin</outputDirectory>
</fileSet>
<fileSet>
<directory>${basedir}/../plugins</directory>
<includes>
<include>*.rpm</include>
<include>*.txt</include>
</includes>
<fileMode>700</fileMode>
<outputDirectory>/plugins</outputDirectory>
</fileSet>
<!-- 文体文体jar包 -->
<fileSet>
<directory>${basedir}/${project.build.directory}</directory>
<includes>
<include>${project.build.finalName}.${project.packaging}</include>
</includes>
<outputDirectory>/</outputDirectory>
</fileSet>
<!-- resources -->
<fileSet>
<directory>${basedir}/${project.build.directory}/classes/config</directory>
<includes>
<include>*.yaml</include>
<include>*.yml</include>
<include>*.xml</include>
<include>*.properties</include>
</includes>
<fileMode>600</fileMode>
<outputDirectory>/config</outputDirectory>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<scope>runtime</scope>
<excludes>
<exclude>${groupId}:${artifactId}</exclude>
</excludes>
<outputDirectory>/lib</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>

View File

@ -0,0 +1,24 @@
package com.dfire;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @author: <a href="mailto:lingxiao@2dfire.com">凌霄</a>
* @time: Created in 11:59 2018/1/1
* 启动入口
*/
@MapperScan(basePackages = "com.dfire.*.mapper")
@SpringBootApplication(scanBasePackages = "com.dfire")
@ServletComponentScan(value = "com.dfire.config")
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
public class AdminBootstrap {
public static void main(String[] args) {
SpringApplication.run(AdminBootstrap.class, args);
}
}

View File

@ -0,0 +1,12 @@
package com.dfire.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AdminCheck {
}

View File

@ -0,0 +1,50 @@
package com.dfire.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
*
* @author xiaosuda
* @date 2018/7/26
*/
@Configuration
public class DruidConfig {
@Bean
@Primary
@ConfigurationProperties("druid.datasource")
public DataSource druidDateSource() {
return new DruidDataSource();
}
@Bean
@Primary
public ServletRegistrationBean druidStatViewServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
servletRegistrationBean.addInitParameter("loginUsername","hera");
servletRegistrationBean.addInitParameter("loginPassword","admin");
servletRegistrationBean.addInitParameter("resetEnable","false");
return servletRegistrationBean;
}
@Bean
@Primary
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}

View File

@ -0,0 +1,217 @@
package com.dfire.config;
import com.dfire.common.constants.Constants;
import com.dfire.common.entity.HeraGroup;
import com.dfire.common.entity.HeraJob;
import com.dfire.common.entity.HeraPermission;
import com.dfire.common.entity.vo.HeraGroupVo;
import com.dfire.common.entity.vo.HeraJobVo;
import com.dfire.common.enums.RunAuthType;
import com.dfire.common.exception.NoPermissionException;
import com.dfire.common.service.HeraGroupService;
import com.dfire.common.service.HeraJobService;
import com.dfire.common.service.HeraPermissionService;
import com.dfire.common.util.StringUtil;
import com.dfire.core.util.JwtUtils;
import com.dfire.logs.ErrorLog;
import com.dfire.logs.HeraLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @author xiaosuda
* @date 2018/8/1
*/
@Aspect
@Component
public class HeraAspect {
@Autowired
@Qualifier("heraJobMemoryService")
private HeraJobService heraJobService;
@Autowired
private HeraPermissionService heraPermissionService;
@Autowired
private HeraGroupService heraGroupService;
@Pointcut("execution(* com.dfire.controller..*(..)) || @annotation(RunAuth)")
private void auth() {
}
@Around("auth()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = getMethod(joinPoint);
if (method != null) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (method.isAnnotationPresent(AdminCheck.class)) {
checkAdmin(request);
}
if (method.isAnnotationPresent(RunAuth.class)) {
checkRunAuth(method, request, joinPoint);
}
}
Long start = System.currentTimeMillis();
Object res;
res = joinPoint.proceed();
Long end = System.currentTimeMillis();
if (start - end >= 10 * 1000L) {
HeraLog.warn("方法名:{},参数:{},耗时:{}ms", joinPoint.getSignature().getName(), Arrays.asList(joinPoint.getArgs()), end - start);
}
return res;
}
/**
* 解析方法上的@RunAuth注解参数
*
* @param method 包含RunAuth注解的方法
* @param request request
* @param joinPoint 切面
* @throws NoPermissionException 无权限
* @throws IllegalArgumentException 参数错误
*/
private void checkRunAuth(Method method, HttpServletRequest request, ProceedingJoinPoint joinPoint) throws NoPermissionException, IllegalArgumentException {
if (!method.isAnnotationPresent(RunAuth.class)) {
throw new IllegalArgumentException("方法" + method.getName() + "不含@RunAuth注解");
}
RunAuth runAuth = method.getAnnotation(RunAuth.class);
Integer runId = null;
RunAuthType runAuthType;
if (runAuth.typeIndex() != -1) {
runAuthType = (RunAuthType) getRunId(joinPoint, runAuth.typeIndex());
} else {
runAuthType = runAuth.authType();
}
// -1表示参数是个vo,并且在首位0
if (runAuth.idIndex() == -1) {
Object param = getRunId(joinPoint, 0);
if (runAuthType == RunAuthType.JOB) {
if (param instanceof HeraJobVo) {
runId = ((HeraJobVo) param).getId();
} else if (param instanceof HeraJob) {
runId = ((HeraJob) param).getId();
}
} else {
if (param instanceof HeraGroup) {
runId = ((HeraGroup) param).getId();
} else if (param instanceof HeraGroupVo) {
runId = ((HeraGroupVo) param).getId();
}
}
} else {
Object param = getRunId(joinPoint, runAuth.idIndex());
if (param == null) {
throw new IllegalArgumentException("参数格式错误");
}
if (runAuthType == RunAuthType.GROUP && param instanceof String) {
runId = StringUtil.getGroupId((String) param);
} else {
if (param instanceof Integer) {
runId = (Integer) param;
} else {
runId = Integer.parseInt(String.valueOf(param));
}
}
}
if (runId == null) {
throw new IllegalArgumentException("参数格式错误");
}
checkPermission(getOwner(request), runId, runAuthType);
}
private void checkPermission(String owner, Integer id, RunAuthType runAuthType) throws NoPermissionException {
String errorMsg = "抱歉,您没有权限进行此操作";
if (owner == null || id == null) {
if (owner == null) {
ErrorLog.warn("owner为null无权限执行");
} else {
ErrorLog.warn("id为null,无权限执行");
}
throw new NoPermissionException(errorMsg);
}
if (isAdmin(owner)) {
return;
}
if (runAuthType == null) {
throw new IllegalArgumentException("RunAuthType 参数有误");
}
if (RunAuthType.JOB == runAuthType) {
HeraJob job = heraJobService.findMemById(id);
if (job != null && !job.getOwner().equals(owner)) {
HeraPermission permission = heraPermissionService.findByCond(id, owner, runAuthType.getName());
if (permission == null) {
ErrorLog.warn(owner + "无权限操作任务:" + id);
throw new NoPermissionException(errorMsg);
}
}
} else if (RunAuthType.GROUP == runAuthType) {
HeraGroup group = heraGroupService.findById(id);
if (group != null && !owner.equals(group.getOwner())) {
if (heraPermissionService.findByCond(id, owner, runAuthType.getName()) == null) {
ErrorLog.warn(owner + "无权限操作组:" + id);
throw new NoPermissionException(errorMsg);
}
}
}
}
private Object getRunId(ProceedingJoinPoint joinPoint, int argIndex) {
if (joinPoint.getArgs() == null || joinPoint.getArgs().length < argIndex) {
throw new IllegalArgumentException(joinPoint.getSignature().toShortString() + ":@RunAuth参数下标越界");
}
return joinPoint.getArgs()[argIndex];
}
/**
* 检测用户是否为超级用户
*
* @param request request
*/
private void checkAdmin(HttpServletRequest request) throws NoPermissionException {
if (!isAdmin(getOwner(request))) {
throw new NoPermissionException("操作无权限,请使用超级用户执行该操作");
}
}
private boolean isAdmin(String owner) {
return owner.equals(HeraGlobalEnv.getAdmin());
}
private String getOwner(HttpServletRequest request) {
return JwtUtils.getObjectFromToken(Constants.TOKEN_NAME, request, Constants.SESSION_USERNAME);
}
/**
* 根据切面获得当前执行的方法
*
* @param jp 切面
* @return 方法
*/
private Method getMethod(JoinPoint jp) {
Method proxyMethod = ((MethodSignature) jp.getSignature()).getMethod();
try {
return jp.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
} catch (NoSuchMethodException | SecurityException ignored) {
}
return null;
}
}

View File

@ -0,0 +1,95 @@
package com.dfire.config;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.entity.model.TableResponse;
import com.dfire.common.exception.NoPermissionException;
import com.dfire.logs.ErrorLog;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
/**
* desc:
*
* @author scx
* @create 2019/06/18
*/
@ControllerAdvice
public class HeraExceptionHandler {
@ExceptionHandler(Exception.class)
public Object handlerException(HttpServletRequest request, Exception ex) {
ErrorLog.error("请求" + request.getRequestURI() + "异常:", ex);
return getReturn(getReturnType(request), HttpStatus.INTERNAL_SERVER_ERROR, "请求异常,请联系管理员", "当您看到这个页面,表示该应用出错,请联系管理员进行解决");
}
@ExceptionHandler(MissingServletRequestParameterException.class)
public Object handlerMissParameterException(HttpServletRequest request) {
return getReturn(getReturnType(request), HttpStatus.BAD_GATEWAY, "参数不完整,请核查", "当你看到这个页面,说明你请求的参数不正确,请核查");
}
@ExceptionHandler(NoPermissionException.class)
public ModelAndView handlerNoPermissionException(HttpServletRequest request) {
return getReturn(getReturnType(request), HttpStatus.FORBIDDEN, "操作无权限", "当你看到这个页面,说明你访问了你无权限访问的页面或者请求,请向管理员申请相关权限");
}
@ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView handlerNotFoundException(HttpServletRequest request) {
ErrorLog.error("请求" + request.getRequestURI() + "404异常");
return getReturn(NoHandlerFoundException.class, HttpStatus.NOT_FOUND, "page not found", "当您看到这个页面,表示您的访问出错,这个错误是您打开的页面不存在,请确认您输入的地址是正确的,如果是在本站点击后出现这个页面,请联系管理员进行处理感谢您的支持!");
}
private ModelAndView getReturn(Class<?> returnType, HttpStatus internalServerError, String errorMsg, String content) {
if (returnType == JsonResponse.class) {
ModelAndView json = new ModelAndView(new MappingJackson2JsonView());
json.addAllObjects(new JsonResponse(false, errorMsg).toMap());
return json;
} else if (returnType == TableResponse.class) {
ModelAndView json = new ModelAndView(new MappingJackson2JsonView());
json.addAllObjects(new TableResponse(-1, errorMsg).toMap());
return json;
} else { //String ModelAndView都返回错误页面
ModelAndView modelAndView = new ModelAndView("/error.index");
modelAndView.addObject("msg", errorMsg);
modelAndView.addObject("code", internalServerError.value());
modelAndView.addObject("content", content);
return modelAndView;
}
}
private Class<?> getReturnType(HttpServletRequest request) {
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(request);
Map<String, HandlerMapping> mappingMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
HandlerExecutionChain chain;
for (HandlerMapping handlerMapping : mappingMap.values()) {
try {
if (handlerMapping instanceof RequestMappingHandlerMapping && (chain = handlerMapping.getHandler(request)) != null) {
Method method = ((HandlerMethod) chain.getHandler()).getMethod();
return method.getReturnType();
}
} catch (Exception e) {
ErrorLog.error("获取返回类型失败", e);
}
}
return null;
}
}

View File

@ -0,0 +1,106 @@
package com.dfire.config;
import com.dfire.common.util.ActionUtil;
import com.dfire.logs.MonitorLog;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.Date;
/**
* desc:
* hera启动监听
*
* @author scx
* @create 2019/03/25
*/
public class HeraRunListener implements SpringApplicationRunListener {
private Date startTime;
private SpringApplication application;
private String[] args;
public HeraRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
}
@Override
public void starting() {
startTime = new Date();
MonitorLog.info("spring starting: " + ActionUtil.getDefaultFormatterDate(startTime));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
MonitorLog.info("spring environmentPrepared");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
MonitorLog.info("spring contextPrepared");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
MonitorLog.info("spring contextLoaded");
}
@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
Date endTime = new Date();
int serverPort = Integer.parseInt(context.getEnvironment().getProperty("server.port"));
MonitorLog.info("Tomcat started on port(s):" + serverPort);
MonitorLog.info("固定集群ip为:" + HeraGlobalEnv.getEmrFixedHost());
MonitorLog.info("==========启动完成了: " + ActionUtil.getDefaultFormatterDate(endTime) + "; 共花费: " + DateBetween(startTime, endTime));
}
private String DateBetween(Date start, Date end) {
if (start.compareTo(end) > 0) {
Date tmp;
tmp = start;
start = end;
end = tmp;
}
long cost = (end.getTime() - start.getTime()) / 1000;
long hours, minute, seconds;
hours = cost / 60 / 60;
cost = cost % (60 * 60);
minute = cost / 60;
cost = cost % 60;
seconds = cost;
StringBuilder res = new StringBuilder();
if (hours > 9) {
res.append(hours);
} else {
res.append("0").append(hours);
}
res.append(":");
if (minute > 9) {
res.append(minute);
} else {
res.append("0").append(minute);
}
res.append(":");
if (seconds > 9) {
res.append(seconds);
} else {
res.append("0").append(seconds);
}
return res.toString();
}
}

View File

@ -0,0 +1,38 @@
package com.dfire.config;
import com.dfire.common.enums.RunAuthType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author scx
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RunAuth {
/**
* 需要赋权的类型
*
* @return RunAuthType
*/
RunAuthType authType() default RunAuthType.JOB;
/**
* id 的下标 -1表示第一个参数是vo
*
* @return int
*/
int idIndex() default 0;
/**
* 赋权类型的下标会覆盖authType
*
* @return
*/
int typeIndex() default -1;
}

View File

@ -0,0 +1,24 @@
package com.dfire.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import springfox.documentation.spring.web.SpringfoxWebMvcConfiguration;
/**
* desc:
*
* @author scx
* @create 2020/09/17
*/
//@ConditionalOnClass(SpringfoxWebMvcConfiguration.class)
@Configuration
public class SwaggerBootstrapUiDemoApplication extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}

View File

@ -0,0 +1,42 @@
package com.dfire.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* desc:
*
* @author scx
* @create 2020/09/17
*/
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.dfire.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("swagger-bootstrap-ui RESTful APIs")
.description("swagger-bootstrap-ui")
.termsOfServiceUrl("http://localhost:8121/")
.contact("developer@mail.com")
.version("1.0")
.build();
}
}

View File

@ -0,0 +1,15 @@
package com.dfire.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by xiaosuda on 2018/7/24.
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UnCheckLogin {
}

View File

@ -0,0 +1,71 @@
package com.dfire.config;
import com.dfire.common.constants.Constants;
import com.dfire.controller.BaseHeraController;
import com.dfire.core.util.JwtUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author: <a href="mailto:lingxiao@2dfire.com">凌霄</a>
* @time: Created in 上午11:09 2018/5/22
* @desc
*/
@Configuration
public class WebSecurityConfig extends WebMvcConfigurerAdapter {
@Bean
public SecurityInterceptor getSecurityInterceptor() {
return new SecurityInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
InterceptorRegistration addRegistry = interceptorRegistry.addInterceptor(getSecurityInterceptor());
addRegistry.excludePathPatterns("/error").excludePathPatterns("/login/**");
}
private class SecurityInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod method = (HandlerMethod) handler;
UnCheckLogin methodAnnotation = method.getMethodAnnotation(UnCheckLogin.class);
if (methodAnnotation != null) {
return true;
}
UnCheckLogin declaredAnnotation = method.getBeanType().getDeclaredAnnotation(UnCheckLogin.class);
if (declaredAnnotation != null) {
return true;
}
String heraToken = JwtUtils.getValFromCookies(Constants.TOKEN_NAME, request);
if (StringUtils.isNotBlank(heraToken) && JwtUtils.verifyToken(heraToken)) {
return true;
}
request.getRequestDispatcher("/login").forward(request, response);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
BaseHeraController.remove();
super.postHandle(request, response, handler, modelAndView);
}
}
}

View File

@ -0,0 +1,170 @@
package com.dfire.controller;
import com.dfire.common.constants.Constants;
import com.dfire.common.entity.HeraRecord;
import com.dfire.common.enums.LogTypeEnum;
import com.dfire.common.enums.RecordTypeEnum;
import com.dfire.common.enums.RunAuthType;
import com.dfire.common.service.HeraRecordService;
import com.dfire.common.util.NamedThreadFactory;
import com.dfire.config.HeraGlobalEnv;
import com.dfire.core.util.JwtUtils;
import com.dfire.logs.ErrorLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ModelAttribute;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author xiaosuda
* @date 2018/7/19
*/
public abstract class BaseHeraController {
private static ThreadPoolExecutor poolExecutor;
private static ThreadLocal<HttpServletRequest> requestThread = new ThreadLocal<>();
@Autowired
protected HeraRecordService recordService;
@Autowired
ScheduleCenterController scheduleCenterController;
{
poolExecutor = new ThreadPoolExecutor(
1, Runtime.getRuntime().availableProcessors() * 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory("updateJobThread"), new ThreadPoolExecutor.AbortPolicy());
poolExecutor.allowCoreThreadTimeOut(true);
}
public static void remove() {
requestThread.remove();
}
public static void set(HttpServletRequest request) {
if(requestThread.get() == null) {
requestThread.set(request);
}
}
protected String getIp() {
String unKnow = "unknown";
HttpServletRequest request = requestThread.get();
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || unKnow.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || unKnow.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || unKnow.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || unKnow.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || unKnow.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
@ModelAttribute
protected void setRequest(HttpServletRequest request) {
requestThread.set(request);
}
protected String getOwner() {
return JwtUtils.getObjectFromToken(Constants.TOKEN_NAME, requestThread.get(), Constants.SESSION_USERNAME);
}
protected String getSsoName() {
return JwtUtils.getObjectFromToken(Constants.TOKEN_NAME, requestThread.get(), Constants.SESSION_SSO_NAME);
}
protected String getOwnerId() {
return JwtUtils.getObjectFromToken(Constants.TOKEN_NAME, requestThread.get(), Constants.SESSION_USER_ID);
}
protected String getSsoId() {
return JwtUtils.getObjectFromToken(Constants.TOKEN_NAME, requestThread.get(), Constants.SESSION_SSO_ID);
}
protected void addRecord(LogTypeEnum jobType, Integer jobId, String content, RecordTypeEnum typeEnum, String owner, String ownerId) {
recordService.add(HeraRecord.builder()
.logId(jobId)
.logType(jobType.getName())
.content(content)
.sso(owner)
.gid(Integer.parseInt(ownerId))
.type(typeEnum.getId())
.build());
}
protected void addJobRecord(Integer jobId, String content, RecordTypeEnum typeEnum, String owner, String ownerId) {
recordService.add(HeraRecord.builder()
.logId(jobId)
.logType(LogTypeEnum.JOB.getName())
.content(content)
.sso(owner)
.gid(Integer.parseInt(ownerId))
.type(typeEnum.getId())
.build());
}
protected void addGroupRecord(Integer jobId, String content, RecordTypeEnum typeEnum, String owner, String ownerId) {
recordService.add(HeraRecord.builder()
.logId(jobId)
.logType(LogTypeEnum.GROUP.getName())
.content(content)
.sso(owner)
.gid(Integer.parseInt(ownerId))
.type(typeEnum.getId())
.build());
}
protected void addDebugRecord(Integer jobId, String content, RecordTypeEnum typeEnum, String ownerName, String ownerId) {
recordService.add(HeraRecord.builder()
.logId(jobId)
.logType(LogTypeEnum.DEBUG.getName())
.content(content)
.sso(ownerName)
.gid(Integer.parseInt(ownerId))
.type(typeEnum.getId())
.build());
}
protected void addUserRecord(Integer userId, String content, RecordTypeEnum typeEnum, String ssoName, String ownerId) {
recordService.add(HeraRecord.builder()
.logId(userId)
.logType(LogTypeEnum.USER.getName())
.content(content)
.sso(ssoName)
.gid(Integer.parseInt(ownerId))
.type(typeEnum.getId())
.build());
}
protected void checkPermission(Integer jobId, RunAuthType type) {
scheduleCenterController.doAspectAuth(jobId, type);
}
protected void doAsync(Runnable runnable) {
poolExecutor.execute(() -> {
try {
runnable.run();
} catch (Exception e) {
ErrorLog.error("异步执行异常", e);
}
});
}
protected boolean isAdmin() {
return HeraGlobalEnv.getAdmin().equals(getOwner());
}
}

View File

@ -0,0 +1,274 @@
package com.dfire.controller;
import com.dfire.common.constants.Constants;
import com.dfire.common.entity.HeraDebugHistory;
import com.dfire.common.entity.HeraFile;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.enums.RecordTypeEnum;
import com.dfire.common.service.HeraDebugHistoryService;
import com.dfire.common.service.HeraFileService;
import com.dfire.config.HeraGlobalEnv;
import com.dfire.core.netty.worker.WorkClient;
import com.dfire.logs.MonitorLog;
import com.dfire.protocol.JobExecuteKind;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
/**
* @author: <a href="mailto:lingxiao@2dfire.com">凌霄</a>
* @time: Created in 16:34 2018/1/13
* @desc 开发中心
*/
@Controller
@RequestMapping("/developCenter")
@Api("开发中心接口")
public class DevelopCenterController extends BaseHeraController {
@Autowired
@Qualifier("heraFileMemoryService")
private HeraFileService heraFileService;
@Autowired
private HeraDebugHistoryService debugHistoryService;
@Autowired
private WorkClient workClient;
@RequestMapping(method = RequestMethod.GET)
@ApiOperation("页面跳转")
public String dev() {
return "developCenter/developCenter.index";
}
@RequestMapping(value = "/init", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("获取开发中心个人文档和所有文档")
public JsonResponse initFileTree() {
return new JsonResponse(true, heraFileService.buildFileTree(getOwner()));
}
@RequestMapping(value = "/addFile", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("添加新的脚本")
public JsonResponse addFileAndFolder(@ApiParam(value = "file对象", required = true) HeraFile heraFile) {
Integer parent = heraFile.getParent();
HeraFile parentFile = heraFileService.findById(parent);
if (Constants.FILE_ALL_NAME.equals(parentFile.getOwner())) {
heraFile.setOwner(Constants.FILE_ALL_NAME);
} else {
heraFile.setOwner(getOwner());
}
Integer id = heraFileService.insert(heraFile);
addDebugRecord(id, heraFile.getName(), RecordTypeEnum.Add, getSsoName(), getOwnerId());
return new JsonResponse(true, id);
}
@RequestMapping(value = "/find", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("查询脚本内容")
public JsonResponse getHeraFile(@ApiParam(value = "file对象", required = true) HeraFile heraFile) {
return new JsonResponse(true, heraFileService.findById(heraFile.getId()));
}
@RequestMapping(value = "/delete", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("删除脚本")
public JsonResponse delete(@ApiParam(value = "file对象", required = true) HeraFile heraFile) {
HeraFile file = heraFileService.findById(heraFile.getId());
if (Constants.FILE_SELF.equals(file.getName())) {
return new JsonResponse(false, "无法删除个人文档");
} else if (Constants.FILE_ALL.equals(file.getName())) {
return new JsonResponse(false, "无法删除共享文档");
}
boolean res = heraFileService.delete(heraFile.getId()) > 0;
addDebugRecord(heraFile.getId(), heraFile.getName(), RecordTypeEnum.DELETE, getSsoName(), getOwnerId());
return new JsonResponse(res, res ? "删除成功" : "删除失败");
}
@RequestMapping(value = "/rename", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("脚本重命名")
public JsonResponse rename(@ApiParam(value = "file对象", required = true) HeraFile heraFile) {
return new JsonResponse(true, heraFileService.updateFileName(heraFile) > 0 ? "更新成功" : "更新失败");
}
/**
* 手动执行脚本
*
* @param heraFile
* @return
*/
@RequestMapping(value = "/debug", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("执行整个脚本")
public JsonResponse debug(@RequestBody @ApiParam(value = "file对象", required = true) HeraFile heraFile) throws ExecutionException, InterruptedException, TimeoutException {
String owner = getOwner();
HeraFile file = heraFileService.findById(heraFile.getId());
if (file == null) {
return new JsonResponse(false, "脚本已被删除");
}
String name = file.getName();
file.setContent(heraFile.getContent());
heraFileService.updateContent(heraFile);
HeraDebugHistory history = HeraDebugHistory.builder()
.fileId(file.getId())
.script(heraFile.getContent())
.startTime(new Date())
.owner(Constants.FILE_ALL_NAME.equals(file.getOwner()) ? owner : file.getOwner())
.hostGroupId(file.getHostGroupId() == 0 ? HeraGlobalEnv.defaultWorkerGroup : file.getHostGroupId())
.build();
return executeJob(name, history, getSsoName());
}
/**
* 手动执行选中的代码
*
* @param heraFile
* @return
*/
@RequestMapping(value = "/debugSelectCode", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("执行选中脚本")
public JsonResponse debugSelectCode(@RequestBody @ApiParam(value = "file对象", required = true) HeraFile heraFile) throws ExecutionException, InterruptedException, TimeoutException {
String owner = getOwner();
HeraFile file = heraFileService.findById(heraFile.getId());
file.setContent(heraFile.getContent());
String name = file.getName();
HeraDebugHistory history = HeraDebugHistory.builder()
.jobId(file.getJobId())
.fileId(file.getId())
.script(heraFile.getContent())
.startTime(new Date())
.owner(owner)
.hostGroupId(file.getHostGroupId() == 0 ? HeraGlobalEnv.defaultWorkerGroup : file.getHostGroupId())
.build();
return executeJob(name, history, getSsoName());
}
private JsonResponse executeJob(String name, HeraDebugHistory history, String ssoName) throws ExecutionException, InterruptedException, TimeoutException {
int suffixIndex = name.lastIndexOf(Constants.POINT);
if (suffixIndex == -1) {
return new JsonResponse(false, "无后缀名,请设置支持的后缀名[.sh .hive .spark]");
}
String suffix = name.substring(suffixIndex);
String runType;
if ((Constants.HIVE_SUFFIX).equalsIgnoreCase(suffix)) {
runType = Constants.HIVE_FILE;
} else if ((Constants.SHELL_SUFFIX).equalsIgnoreCase(suffix)) {
runType = Constants.SHELL_FILE;
} else if ((Constants.SPARK_SUFFIX).equalsIgnoreCase(suffix)) {
runType = Constants.SPARK_FILE;
} else {
return new JsonResponse(false, "暂未支持的后缀名[" + suffix + "],请设置支持的后缀名[.sh .hive .spark]");
}
history.setRunType(runType);
Long newId = debugHistoryService.insert(history);
String ownerId = getOwnerId();
doAsync(() -> addDebugRecord(history.getFileId(), String.valueOf(history.getId()), RecordTypeEnum.Execute, ssoName, ownerId));
workClient.executeJobFromWeb(JobExecuteKind.ExecuteKind.DebugKind, newId);
Map<String, Object> res = new HashMap<>(2);
res.put("fileId", history.getFileId());
res.put("debugId", newId);
return new JsonResponse(true, "执行成功", res);
}
/**
* 获取脚本执行历史
*
* @param fileId
* @return
*/
@RequestMapping(value = "findDebugHistory", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("查看执行历史记录")
public JsonResponse findDebugHistory(@ApiParam(value = "fileid", required = true) Integer fileId) {
return new JsonResponse(true, debugHistoryService.findByFileId(fileId));
}
/**
* 取消执行脚本
*
* @param id
* @return
*/
@RequestMapping(value = "/cancelJob", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("取消任务")
public JsonResponse cancelJob(@ApiParam(value = "fileid", required = true) Long id) throws ExecutionException, InterruptedException, TimeoutException {
return new JsonResponse(true, workClient.cancelJobFromWeb(JobExecuteKind.ExecuteKind.DebugKind, id));
}
@RequestMapping(value = "/getLog", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("获取日志")
public JsonResponse getJobLog(@ApiParam(value = "fileid", required = true) Integer id) {
return new JsonResponse(true, debugHistoryService.findLogById(id));
}
@RequestMapping(value = "/check", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("权限检测")
public JsonResponse check(@ApiParam(value = "fileid", required = true) Integer id) {
if (checkPermission(id)) {
return new JsonResponse(true, "查询成功", true);
} else {
return new JsonResponse(true, "无权限", false);
}
}
@RequestMapping(value = "/moveNode", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("节点移动")
public JsonResponse moveNode(@ApiParam(value = "fileid", required = true) Integer id
, @ApiParam(value = "file移动前父目录id", required = true) Integer parent
, @ApiParam(value = "file移动后父目录id", required = true) Integer lastParent) {
if (!checkPermission(id)) {
return new JsonResponse(false, "无权限,移动失败");
}
boolean res = heraFileService.updateParentById(id, parent);
if (res) {
addDebugRecord(id, lastParent + "=>" + parent, RecordTypeEnum.MOVE, getSsoName(), getOwnerId());
MonitorLog.info("开发中心任务{}【移动】:{} ----> {}", id, lastParent, parent);
return new JsonResponse(true, "移动成功");
} else {
return new JsonResponse(false, "移动失败,请联系管理员");
}
}
private boolean checkPermission(Integer id) {
if (isAdmin()) {
return true;
}
HeraFile heraFile = heraFileService.findById(id);
return heraFile != null && heraFile.getOwner().equals(getOwner());
}
@RequestMapping(value = "saveScript", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("保存脚本")
public JsonResponse saveScript(@RequestBody @ApiParam(value = "file对象", required = true) HeraFile heraFile) {
boolean result = heraFileService.updateContent(heraFile) > 0;
return new JsonResponse(result, result ? "保存成功" : "保存失败");
}
}

View File

@ -0,0 +1,25 @@
package com.dfire.controller;
import com.dfire.config.UnCheckLogin;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* desc:
*
* @author scx
* @create 2019/11/05
*/
@Controller
@RequestMapping("/help")
public class HelpController {
@RequestMapping
@UnCheckLogin
public String index() {
return "help";
}
}

View File

@ -0,0 +1,48 @@
package com.dfire.controller;
import com.dfire.common.entity.HeraAdvice;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.service.HeraAdviceService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* @author xiaosuda
* @date 2018/12/5
*/
@Api("建议/留言接口")
@Controller
@RequestMapping("/adviceController")
public class HeraAdviceController extends BaseHeraController {
@Autowired
private HeraAdviceService heraAdviceService;
@RequestMapping(method = RequestMethod.GET)
public ModelAndView toPage() {
ModelAndView mv = new ModelAndView("/bugReport");
mv.addObject("allMsg", heraAdviceService.getAll());
return mv;
}
@RequestMapping(value = "/add", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("添加留言接口")
public JsonResponse addAdvice(@ApiParam(value = "建议对象", required = true) HeraAdvice heraAdvice) {
if (heraAdvice.getMsg().contains("<") || heraAdvice.getMsg().contains(">")) {
return new JsonResponse(false, "不允许输入特殊符号");
}
boolean res = heraAdviceService.addAdvice(heraAdvice);
return new JsonResponse(res, res ? "添加成功" : "添加失败");
}
}

View File

@ -0,0 +1,203 @@
package com.dfire.controller;
import com.alibaba.fastjson.JSONObject;
import com.dfire.common.constants.Constants;
import com.dfire.common.entity.HeraRerun;
import com.dfire.common.entity.form.HeraRerunForm;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.entity.model.TablePageForm;
import com.dfire.common.entity.model.TableResponse;
import com.dfire.common.entity.vo.HeraRerunVo;
import com.dfire.common.service.HeraJobHistoryService;
import com.dfire.common.service.HeraJobService;
import com.dfire.common.service.HeraRerunService;
import com.dfire.common.util.ActionUtil;
import com.dfire.common.util.Pair;
import com.dfire.config.RunAuth;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* desc:
*
* @author scx
* @create 2019/11/25
*/
@RequestMapping("/rerun/")
@Controller
@Api("重跑任务接口")
public class HeraRerunController extends BaseHeraController {
@Autowired
private HeraRerunService heraRerunService;
@Autowired
@Qualifier("heraJobMemoryService")
private HeraJobService heraJobService;
@Autowired
private HeraJobHistoryService heraJobHistoryService;
@RequestMapping(value = "/list", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("任务重跑列表")
public TableResponse list(@ApiParam(value = "分页参数", required = true) TablePageForm pageForm,
@ApiParam(value = "状态:-1所有0开启1结束", required = true) Integer status) {
Pair<Integer, List<HeraRerunVo>> res = heraRerunService.findByPage(pageForm, status);
return new TableResponse(res.fst(), 0, res.snd());
}
@RequestMapping(value = "/failed", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("重跑失败列表查询")
public TableResponse failedList(@ApiParam(value = "分页参数", required = true) TablePageForm pageForm, @ApiParam(value = "重跑id", required = true) Integer rerunId) {
HeraRerunVo heraRerun = heraRerunService.findVoById(rerunId);
Pair<Integer, List<JSONObject>> res = heraJobHistoryService.findRerunFailed(heraRerun.getJobId()
, String.valueOf(rerunId)
, ActionUtil.getActionByDateStr(heraRerun.getStartTime()) - 1, pageForm);
return new TableResponse(res.fst(), 0, res.snd());
}
@RequestMapping(value = "/failed", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("重跑所有失败记录")
public JsonResponse rerunFailed(@ApiParam(value = "重跑id", required = true) Integer rerunId) {
HeraRerunVo lastRerun = heraRerunService.findVoById(rerunId);
String rerunFailedCount = lastRerun.getExtra().getOrDefault(Constants.ACTION_FAILED_NUM, "0");
if ("0".equals(rerunFailedCount)) {
return new JsonResponse(false, "无失败记录,无需重跑");
}
Integer realFailedCount = heraJobHistoryService.findRerunFailedCount(lastRerun.getJobId(), String.valueOf(lastRerun.getId()), ActionUtil.getActionByDateStr(lastRerun.getStartTime()) - 1);
if (realFailedCount != Integer.parseInt(rerunFailedCount)) {
return new JsonResponse(false, "执行记录可能被删除,无法重跑失败");
}
HeraRerunVo newRerun = new HeraRerunVo();
newRerun.setStartTime(lastRerun.getStartTime());
newRerun.setEndTime(lastRerun.getEndTime());
newRerun.setGmtCreate(ActionUtil.getTodayString());
newRerun.setName("重跑失败-" + lastRerun.getName());
newRerun.setJobId(lastRerun.getJobId());
newRerun.setSsoName(lastRerun.getSsoName());
Map<String, String> extra = new HashMap<>(2);
extra.put(Constants.RERUN_THREAD, lastRerun.getExtra().getOrDefault(Constants.RERUN_THREAD, "1"));
extra.put(Constants.RERUN_FAILED, Boolean.TRUE.toString());
extra.put(Constants.ACTION_DONE, Boolean.TRUE.toString());
extra.put(Constants.ACTION_PROCESS_NUM, "0");
extra.put(Constants.ACTION_ALL_NUM, rerunFailedCount);
extra.put(Constants.LAST_RERUN_ID, String.valueOf(lastRerun.getId()));
newRerun.setExtra(extra);
heraRerunService.add(newRerun);
return new JsonResponse(true, "添加重跑任务成功");
}
@RequestMapping(value = "/status", method = RequestMethod.PUT)
@ResponseBody
@ApiOperation("重跑状态更改")
public JsonResponse update(@ApiParam(value = "重跑id", required = true) Integer id,
@ApiParam(value = "状态0开启1结束", required = true) Integer isEnd) {
if (isEnd == 0) {
HeraRerunVo heraRerun = heraRerunService.findVoById(id);
if (Integer.parseInt(heraRerun.getExtra().getOrDefault(Constants.ACTION_ALL_NUM, String.valueOf(Integer.MAX_VALUE))) <= Integer.parseInt(heraRerun.getExtra().getOrDefault(Constants.ACTION_PROCESS_NUM, "0"))) {
return new JsonResponse(false, "重跑任务已经结束,无法开启");
}
Integer cntByJob = heraRerunService.findCntByJob(heraRerun.getJobId(), 0);
if (cntByJob > 0) {
return new JsonResponse(false, "存在正在执行的重跑任务任务ID[" + heraRerun.getJobId() + "],请先关闭之前的重跑任务");
}
if (!isAdmin() && !heraRerun.getSsoName().trim().equals(getSsoName())) {
return new JsonResponse(false, "无权限关闭该重跑任务,请联系[" + heraRerun.getSsoName() + "]关闭");
}
}
boolean res = heraRerunService.updateById(HeraRerun.builder()
.id(id)
.isEnd(isEnd)
.build());
return new JsonResponse(res, res ? (isEnd == 0 ? "开启成功" : "关闭成功") : (isEnd == 0 ? "开启失败" : "关闭失败"));
}
@RequestMapping(value = "/add", method = RequestMethod.POST)
@ResponseBody
@RunAuth
@Transactional(rollbackFor = Exception.class)
@ApiOperation("重跑任务添加")
public JsonResponse add(@ApiParam(value = "任务id", required = false) Integer jobId
, @ApiParam(value = "重跑json对象seeHeraRerunForm", required = true) String rerunJson) {
JSONObject rerunObj = JSONObject.parseObject(rerunJson);
HeraRerunForm rerunForm = new HeraRerunForm();
rerunForm.setName(rerunObj.getString("name"));
rerunForm.setJobId(rerunObj.getInteger("jobId"));
rerunForm.setThreads(rerunObj.getString("threads"));
if (StringUtils.isBlank(rerunForm.getName()) || rerunForm.getName().length() < 4) {
return new JsonResponse(false, "重跑名称不可少且最短4个字");
}
if (rerunForm.getJobId() == null || heraJobService.findMemById(rerunForm.getJobId()) == null) {
return new JsonResponse(false, "任务ID不存在");
}
Map<String, String> timeMap = new HashMap<>(rerunObj.size());
for (Map.Entry<String, Object> entry : rerunObj.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (key.startsWith(Constants.RERUN_START_TIME) || key.startsWith(Constants.RERUN_END_TIME)) {
if (value == null || StringUtils.isBlank(entry.getValue().toString())) {
return new JsonResponse(false, "重跑时间不允许为空");
}
timeMap.put(key, String.valueOf(value));
}
}
int dateSize = timeMap.size() / 2;
for (int i = 0; i < dateSize; i++) {
String startTime = timeMap.get(Constants.RERUN_START_TIME + i);
String endTime = timeMap.get(Constants.RERUN_END_TIME + i);
heraRerunService.add(HeraRerunVo.builder()
.jobId(rerunForm.getJobId())
.name(rerunForm.getName())
.startTime(startTime)
.endTime(endTime)
.gmtCreate(ActionUtil.getTodayString())
.ssoName(getSsoName())
.extra(Collections.singletonMap(Constants.RERUN_THREAD, rerunForm.getThreads()))
.build());
}
return new JsonResponse(true, "添加成功");
}
}

View File

@ -0,0 +1,78 @@
package com.dfire.controller;
import com.dfire.common.entity.HeraHostGroup;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.entity.model.TableResponse;
import com.dfire.common.service.HeraHostGroupService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.List;
/**
* @author xiaosuda
* @date 2018/4/20
*/
@Controller
@RequestMapping("/hostGroup/")
@Api("机器组相关接口")
public class HostGroupController {
private final HeraHostGroupService heraHostGroupService;
public HostGroupController(HeraHostGroupService heraHostGroupService) {
this.heraHostGroupService = heraHostGroupService;
}
@RequestMapping(value = "list", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("获取所有机器组列表")
public TableResponse getAll() {
List<HeraHostGroup> groupList = heraHostGroupService.getAll();
if (groupList == null) {
return new TableResponse(-1, "查询失败");
}
return new TableResponse(groupList.size(), 0, groupList);
}
@RequestMapping(value = "add", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("添加机器组")
public JsonResponse add(@ApiParam(value = "机器组", required = true) HeraHostGroup heraHostGroup) {
boolean res = heraHostGroupService.insert(heraHostGroup) > 0;
return new JsonResponse(res, res ? "新增成功" : "新增失败");
}
@RequestMapping(value = "update", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("更新机器组")
public JsonResponse update(@ApiParam(value = "机器组", required = true) HeraHostGroup heraHostGroup) {
boolean update = heraHostGroupService.update(heraHostGroup) > 0;
return new JsonResponse(update, update ? "更新成功" : "更新失败");
}
@RequestMapping(value = "del", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("删除机器组")
public JsonResponse del(@ApiParam(value = "机器组id", required = true) Integer id) {
int res = heraHostGroupService.delete(id);
return new JsonResponse(res > 0, res > 0 ? "删除成功" : "删除失败");
}
}

View File

@ -0,0 +1,169 @@
package com.dfire.controller;
import com.alibaba.fastjson.JSONObject;
import com.dfire.common.entity.HeraJob;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.service.HeraJobService;
import com.dfire.common.util.StringUtil;
import com.dfire.form.JobSearchForm;
import com.dfire.monitor.service.JobManageService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* desc:
*
* @author scx
* @create 2019/07/10
*/
@Controller
@RequestMapping("job/")
public class JobManageController extends BaseHeraController {
@Autowired
private JobManageService jobManageService;
@Autowired
@Qualifier("heraJobMemoryService")
private HeraJobService heraJobService;
/**
* 今日任务详情
*
* @param status 任务状态
* @return 历史结果
*/
@RequestMapping(value = "history", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("任务执行详情")
public JsonResponse findJobHistoryByStatus(@RequestParam("status") @ApiParam(value = "任务状态", required = true) String status
, @RequestParam("begindt") @ApiParam(value = "开始日期", required = true) String begindt
, @RequestParam("enddt") @ApiParam(value = "结束日志", required = true) String enddt) {
return jobManageService.findJobHistoryByStatus(status, begindt, enddt);
}
@RequestMapping(value = "search", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("任务搜索")
public JsonResponse jobSearch(@ApiParam(value = "任务搜索对象", required = true) JobSearchForm searchForm) {
List<HeraJob> jobList = heraJobService.getAll();
//全部小写处理
searchForm.setName(getLowerCase(searchForm.getName()));
searchForm.setScript(getLowerCase(searchForm.getScript()));
searchForm.setDescription(getLowerCase(searchForm.getDescription()));
searchForm.setConfig(getLowerCase(searchForm.getConfig()));
searchForm.setRunType(getLowerCase(searchForm.getRunType()));
return new JsonResponse(true, jobList.parallelStream()
.filter(job -> Optional.ofNullable(searchForm.getAuto())
.map(auto -> job.getAuto().equals(auto)).orElse(true)
&& Optional.ofNullable(searchForm.getRunType())
.map(runTypeEnum -> runTypeEnum.equals(job.getRunType())).orElse(true)
&& Optional.ofNullable(searchForm.getName())
.map(name -> job.getName().toLowerCase().contains(name))
.orElse(true)
&& Optional.ofNullable(searchForm.getDescription())
.map(desc -> StringUtils.isNotBlank(job.getDescription())
&& job.getDescription().toLowerCase().contains(searchForm.getDescription()))
.orElse(true)
&& Optional.ofNullable(searchForm.getConfig())
.map(confName -> StringUtils.isNotBlank(job.getConfigs()) &&
job.getConfigs().toLowerCase().contains(confName))
.orElse(true)
&& Optional.ofNullable(searchForm.getScript())
.map(script -> StringUtils.isNotBlank(job.getScript())
&& job.getScript().toLowerCase().contains(script)).orElse(true))
.map(job -> {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", job.getId());
jsonObject.put("name", markAsRed(job.getName(), searchForm.getName()));
Map<String, String> configMap = StringUtil.convertStringToMap(markAsRed(job.getConfigs(), searchForm.getConfig()));
jsonObject.put("config", mapToString(configMap));
jsonObject.put("configLine", configMap.size());
if (StringUtils.isBlank(job.getScript())) {
jsonObject.put("script", "");
} else {
Pair<String, Integer> pair = replaceLine(markAsRed(job.getScript(), searchForm.getScript()));
jsonObject.put("script", pair.getLeft());
jsonObject.put("scriptLine", pair.getRight());
}
jsonObject.put("runType", job.getRunType());
jsonObject.put("description", markAsRed(job.getDescription(), searchForm.getDescription()));
return jsonObject;
})
.collect(Collectors.toList()));
}
private String mapToString(Map<String, String> map) {
StringBuilder builder = new StringBuilder();
map.forEach((key, value) -> {
if (value.toLowerCase().contains("password")) {
value = "******";
}
builder.append(key).append("=").append(value).append("<br>");
});
return builder.toString();
}
private Pair<String, Integer> replaceLine(String script) {
Matcher pattern = Pattern.compile("\n+").matcher(script);
StringBuilder builder = new StringBuilder();
int start = 0, end = script.length(), line = 1;
while (pattern.find()) {
builder.append(script, start, pattern.start());
builder.append("<br>");
start = pattern.end();
line++;
}
builder.append(script, start, end);
return new MutablePair<>(builder.toString(), line);
}
private String markAsRed(String source, String word) {
if (StringUtils.isBlank(word)) {
return source;
}
Matcher matcher = Pattern.compile(word, Pattern.CASE_INSENSITIVE).matcher(source);
StringBuilder builder = new StringBuilder();
int start = 0, end = source.length();
while (matcher.find()) {
builder.append(source, start, matcher.start());
builder.append("<font color='").append("red'>").append(matcher.group()).append("</font>");
start = matcher.end();
}
builder.append(source, start, end);
return builder.toString();
}
private String getLowerCase(String val) {
if (StringUtils.isBlank(val)) {
return null;
}
return val.toLowerCase();
}
}

View File

@ -0,0 +1,187 @@
package com.dfire.controller;
import com.alibaba.fastjson.JSONObject;
import com.dfire.common.constants.Constants;
import com.dfire.common.entity.HeraSso;
import com.dfire.common.entity.HeraUser;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.enums.RecordTypeEnum;
import com.dfire.common.service.HeraSsoService;
import com.dfire.common.service.HeraUserService;
import com.dfire.common.util.StringUtil;
import com.dfire.config.UnCheckLogin;
import com.dfire.core.util.JwtUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
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.bind.annotation.ResponseStatus;
import springfox.documentation.annotations.ApiIgnore;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author: 凌霄
* @time: Created in 16:34 2018/1/1
* @desc 登陆控制器
*/
@Api("登陆控制器")
@Controller
public class LoginController extends BaseHeraController {
@Autowired
private HeraUserService heraUserService;
@Autowired
private HeraSsoService heraSsoService;
@RequestMapping(value = "/",method = RequestMethod.GET)
public String toLogin() {
return "redirect:/login";
}
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String login() {
return "/login";
}
@RequestMapping(value = "/login/admin",method = RequestMethod.GET)
public String admin() {
return "/admin";
}
@RequestMapping(value = "/home",method = RequestMethod.GET)
public String index() {
return "home";
}
@UnCheckLogin
@ResponseBody
@RequestMapping(value = "admin/login", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
@ApiOperation("用户组登陆接口")
public JsonResponse adminLogin(@ApiParam(value = "sso对象",required = true)String userName
, @ApiParam(value = "密码32为md5",required = true)String password
, @ApiIgnore HttpServletResponse response) {
HeraUser user = heraUserService.findByName(userName.split("@")[0]);
if (user == null) {
return new JsonResponse(false, "用户不存在");
}
String pwd = user.getPassword();
if (!StringUtils.isEmpty(password)) {
password = StringUtil.EncoderByMd5(password);
if (pwd.equals(password)) {
if (user.getIsEffective() == 0) {
return new JsonResponse(false, "审核未通过,请联系管理员");
}
Cookie cookie = new Cookie(Constants.TOKEN_NAME, JwtUtils.createToken(userName, String.valueOf(user.getId()), Constants.DEFAULT_ID, userName));
cookie.setMaxAge(Constants.LOGIN_TIME_OUT);
cookie.setPath("/");
response.addCookie(cookie);
addUserRecord(user.getId(), "组账户", RecordTypeEnum.LOGIN, userName, String.valueOf(user.getId()));
return new JsonResponse(true, "登录成功");
} else {
return new JsonResponse(false, "密码错误,请重新输入");
}
}
return new JsonResponse(false, "请输入密码");
}
@ResponseBody
@UnCheckLogin
@ResponseStatus(HttpStatus.CREATED)
@RequestMapping(value = "admin/register", method = RequestMethod.POST)
@ApiOperation("用户组注册接口")
public JsonResponse adminRegister(@ApiParam(value = "用户组对象",required = true)HeraUser user) {
HeraUser check = heraUserService.findByName(user.getName());
if (check != null) {
return new JsonResponse(false, "用户名已存在,请更换用户名");
}
int res = heraUserService.insert(user);
return new JsonResponse(res > 0, res > 0 ? "注册成功,等待管理员审核" : "注册失败,请联系管理员");
}
@ResponseBody
@UnCheckLogin
@ResponseStatus(HttpStatus.OK)
@RequestMapping(value = "sso/login", method = RequestMethod.POST)
@ApiOperation("用户登陆接口")
public JsonResponse ssoLogin(@ApiParam(value = "sso对象",required = true) String userName
,@ApiParam(value = "密码32为md5",required = true) String password
,@ApiIgnore HttpServletResponse response) {
HeraSso heraSso = heraSsoService.findSsoByName(userName);
if (heraSso == null) {
return new JsonResponse(false, "用户不存在");
}
String pwd = heraSso.getPassword();
if (!StringUtils.isEmpty(password)) {
password = StringUtil.EncoderByMd5(password);
if (pwd.equals(password)) {
if (heraSso.getIsValid() == 0) {
return new JsonResponse(false, "审核未通过,请联系管理员");
}
String owner = Optional.of(heraUserService.findById(heraSso.getGid())).orElse(HeraUser.builder().name("read_only").build()).getName();
Cookie cookie = new Cookie(Constants.TOKEN_NAME, JwtUtils.createToken(owner
, String.valueOf(heraSso.getGid())
, String.valueOf(heraSso.getId())
, userName));
cookie.setMaxAge(Constants.LOGIN_TIME_OUT);
cookie.setPath("/");
response.addCookie(cookie);
addUserRecord(heraSso.getId(), "sso账户", RecordTypeEnum.LOGIN, userName, String.valueOf(heraSso.getGid()));
return new JsonResponse(true, "登录成功");
} else {
return new JsonResponse(false, "密码错误,请重新输入");
}
}
return new JsonResponse(false, "请输入密码");
}
@ResponseBody
@UnCheckLogin
@ResponseStatus(HttpStatus.CREATED)
@RequestMapping(value = "sso/register", method = RequestMethod.POST)
@ApiOperation("用户注册接口")
public JsonResponse ssoRegister(@ApiParam(value = "sso对象",required = true) HeraSso heraSso) {
heraSso.setName(heraSso.getEmail().substring(0, heraSso.getEmail().indexOf("@")));
boolean exist = heraSsoService.checkByName(heraSso.getName());
if (exist) {
return new JsonResponse(false, "用户名已存在,请更换用户名");
}
boolean res = heraSsoService.addSso(heraSso);
return new JsonResponse(res, res ? "注册成功,等待管理员审核" : "注册失败,请联系管理员");
}
@ResponseBody
@UnCheckLogin
@ResponseStatus(HttpStatus.OK)
@RequestMapping(value = "sso/groups", method = RequestMethod.GET)
@ApiOperation("查询用户组列表")
public JsonResponse ssoGroups() {
List<HeraUser> users = heraUserService.getAll();
if (users == null || users.size() == 0) {
return new JsonResponse(false, "找不到部门,请联系管理员");
}
List<JSONObject> groups = users.stream().filter(user -> user.getIsEffective() == 1).map(x -> {
JSONObject group = new JSONObject();
group.put("id", x.getId());
group.put("name", x.getName());
return group;
}).collect(Collectors.toList());
return new JsonResponse(true, "查询成功", groups);
}
}

View File

@ -0,0 +1,162 @@
package com.dfire.controller;
import com.alibaba.fastjson.JSONObject;
import com.dfire.common.entity.HeraJob;
import com.dfire.common.entity.HeraRecord;
import com.dfire.common.entity.HeraUser;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.entity.model.TablePageForm;
import com.dfire.common.entity.model.TableResponse;
import com.dfire.common.entity.vo.HeraRecordVo;
import com.dfire.common.entity.vo.PageHelper;
import com.dfire.common.enums.LogTypeEnum;
import com.dfire.common.enums.RecordTypeEnum;
import com.dfire.common.service.HeraJobService;
import com.dfire.common.service.HeraUserService;
import com.dfire.common.util.ActionUtil;
import com.dfire.common.util.Pair;
import com.dfire.config.HeraGlobalEnv;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.*;
import java.util.stream.Collectors;
/**
* desc:
*
* @author scx
* @create 2019/07/17
*/
@Controller
@Api(value = "历史记录查看接口")
@RequestMapping("/record")
public class RecordController extends BaseHeraController {
@Autowired
private HeraUserService heraUserService;
@Autowired
private HeraJobService heraJobService;
@RequestMapping
public String record() {
return "/jobManage/record.index";
}
@RequestMapping(value = "/list", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("所有历史查看")
public TableResponse listRecord(@ApiParam(value = "分页", required = true) TablePageForm pageForm) {
String ownerName;
boolean isAdmin = HeraGlobalEnv.getAdmin().equals(ownerName = getOwner());
Pair<Integer, List<HeraRecord>> pair;
Map<Integer, String> cacheOwner = new HashMap<>();
if (isAdmin) {
pair = recordService.selectByPage(pageForm);
} else {
pair = recordService.selectByPage(pageForm, Integer.parseInt(getOwnerId()));
}
List<HeraRecordVo> voList = pair.snd().stream().map(record -> {
HeraRecordVo recordVo = new HeraRecordVo();
BeanUtils.copyProperties(record, recordVo);
recordVo.setCreateTime(ActionUtil.getDefaultFormatterDate(new Date(record.getGmtCreate())));
recordVo.setType(RecordTypeEnum.parseById(record.getType()).getType());
if (isAdmin) {
if (!cacheOwner.containsKey(record.getGid())) {
cacheOwner.put(record.getGid(), Optional.of(heraUserService.findById(record.getGid())).map(HeraUser::getName).orElse("none"));
}
recordVo.setGName(cacheOwner.get(record.getGid()));
} else {
recordVo.setGName(ownerName);
}
return recordVo;
}).collect(Collectors.toList());
return new TableResponse(pair.fst(), 0, voList);
}
@RequestMapping(value = "/find", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("根据类型查询历史记录")
@SuppressWarnings("unchecked")
public JsonResponse getRecord(@ApiParam(value = "分页", required = true) PageHelper pageHelper
) {
Map<Integer, String> cacheOwner = new HashMap<>(2);
Map<String, Object> res = recordService.findPageByLogId(pageHelper);
String rows = "rows";
List<HeraRecordVo> vos = ((List<HeraRecord>) res.get(rows)).stream().map(record -> {
HeraRecordVo recordVo = new HeraRecordVo();
BeanUtils.copyProperties(record, recordVo);
recordVo.setType(RecordTypeEnum.parseById(record.getType()).getType());
recordVo.setCreateTime(ActionUtil.getDefaultFormatterDate(new Date(record.getGmtCreate())));
if (!cacheOwner.containsKey(record.getGid())) {
cacheOwner.put(record.getGid(), Optional.ofNullable(heraUserService.findById(record.getGid())).map(HeraUser::getName).orElse(""));
}
recordVo.setGName(cacheOwner.get(record.getGid()));
return recordVo;
}).collect(Collectors.toList());
res.put(rows, vos);
return new JsonResponse(true, res);
}
@RequestMapping("/now")
@ResponseBody
@ApiOperation("查询记录的最新状态")
public JsonResponse findNow(@ApiParam(value = "记录id", required = true) Integer logId
, @ApiParam(value = "记录类型:job,group,debug,upload,user", required = true) String logType
, @ApiParam(value = "操作类型:RecordTypeEnum", required = true) String type) {
LogTypeEnum typeEnum = LogTypeEnum.parseByName(logType);
JSONObject resData = new JSONObject();
String content = "";
String runType = "Shell";
switch (typeEnum) {
case JOB:
HeraJob heraJob = heraJobService.findById(logId);
runType = heraJob.getRunType();
switch (RecordTypeEnum.parseByName(type)) {
case SCRIPT:
case DELETE:
content = heraJob.getScript();
break;
case RUN_TYPE:
content = heraJob.getRunType();
break;
case CRON:
content = heraJob.getCronExpression();
break;
case CONFIG:
content = heraJob.getConfigs();
break;
case AREA:
content = heraJob.getAreaId();
break;
case SWITCH:
content = String.valueOf(heraJob.getAuto());
break;
default:
break;
}
break;
default:
break;
}
resData.put("content", content);
resData.put("runType", runType);
return new JsonResponse(true, resData);
}
}

View File

@ -0,0 +1,510 @@
package com.dfire.controller;
import com.dfire.common.constants.Constants;
import com.dfire.common.entity.*;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.entity.model.TablePageForm;
import com.dfire.common.entity.model.TableResponse;
import com.dfire.common.entity.vo.HeraActionVo;
import com.dfire.common.entity.vo.HeraGroupVo;
import com.dfire.common.entity.vo.HeraJobVo;
import com.dfire.common.entity.vo.PageHelperTimeRange;
import com.dfire.common.enums.*;
import com.dfire.common.exception.NoPermissionException;
import com.dfire.common.service.*;
import com.dfire.common.util.ActionUtil;
import com.dfire.common.util.BeanConvertUtils;
import com.dfire.common.util.PasswordUtils;
import com.dfire.common.util.StringUtil;
import com.dfire.common.vo.GroupTaskVo;
import com.dfire.config.HeraGlobalEnv;
import com.dfire.config.RunAuth;
import com.dfire.config.UnCheckLogin;
import com.dfire.core.netty.worker.WorkClient;
import com.dfire.logs.MonitorLog;
import com.dfire.protocol.JobExecuteKind;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
* @author: <a href="mailto:lingxiao@2dfire.com">凌霄</a>
* @time: Created in 16:50 2018/1/13
* @desc 调度中心视图管理器
*/
@Controller
@Api(value = "调度中心")
@RequestMapping("/scheduleCenter")
public class ScheduleCenterController extends BaseHeraController {
private final HeraJobService heraJobService;
private final HeraJobActionService heraJobActionService;
private final HeraGroupService heraGroupService;
private final HeraJobHistoryService heraJobHistoryService;
private final HeraJobMonitorService heraJobMonitorService;
private final HeraUserService heraUserService;
private final HeraPermissionService heraPermissionService;
private final WorkClient workClient;
private final HeraHostGroupService heraHostGroupService;
private final HeraAreaService heraAreaService;
private final HeraSsoService heraSsoService;
public ScheduleCenterController(HeraJobMonitorService heraJobMonitorService, @Qualifier("heraJobMemoryService") HeraJobService heraJobService, HeraJobActionService heraJobActionService, @Qualifier("heraGroupMemoryService") HeraGroupService heraGroupService, HeraJobHistoryService heraJobHistoryService, HeraUserService heraUserService, HeraPermissionService heraPermissionService, WorkClient workClient, HeraHostGroupService heraHostGroupService, HeraAreaService heraAreaService, HeraSsoService heraSsoService) {
this.heraJobMonitorService = heraJobMonitorService;
this.heraJobService = heraJobService;
this.heraJobActionService = heraJobActionService;
this.heraGroupService = heraGroupService;
this.heraJobHistoryService = heraJobHistoryService;
this.heraUserService = heraUserService;
this.heraPermissionService = heraPermissionService;
this.workClient = workClient;
this.heraHostGroupService = heraHostGroupService;
this.heraAreaService = heraAreaService;
this.heraSsoService = heraSsoService;
}
@RequestMapping(method = RequestMethod.GET)
@ApiOperation(value = "调度中心页面跳转")
public String login() {
return "scheduleCenter/scheduleCenter.index";
}
@RequestMapping(value = "/init", method = RequestMethod.POST)
@ResponseBody
@ApiOperation(value = "获取任务树,包含我的任务和全部任务两个集合")
public JsonResponse initJobTree() {
return new JsonResponse(true, Optional.of(heraJobService.buildJobTree(getOwner(), Integer.parseInt(getSsoId()))).get());
}
@RequestMapping(value = "/getJobMessage", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "获取任务信息")
public JsonResponse getJobMessage(@ApiParam(value = "任务ID", required = true) Integer jobId) {
HeraJob job = heraJobService.findById(jobId);
HeraJobVo heraJobVo = BeanConvertUtils.convert(job);
heraJobVo.setInheritConfig(getInheritConfig(job.getGroupId()));
HeraJobMonitor monitor = heraJobMonitorService.findByJobId(jobId);
StringBuilder focusUsers = new StringBuilder("[ ");
Optional.ofNullable(monitor).ifPresent(m -> {
if (StringUtils.isNotBlank(m.getUserIds())) {
String ssoId = getSsoId();
Arrays.stream(monitor.getUserIds().split(Constants.COMMA)).filter(StringUtils::isNotBlank).distinct().forEach(id -> {
if (ssoId.equals(id)) {
heraJobVo.setFocus(true);
focusUsers.append(Constants.BLANK_SPACE).append(getSsoName());
} else {
Optional.ofNullable(heraSsoService.findSsoById(Integer.parseInt(id)))
.ifPresent(sso -> focusUsers.append(Constants.BLANK_SPACE).append(sso.getName()));
}
});
}
});
focusUsers.append(" ]");
Optional.ofNullable(heraHostGroupService.findById(job.getHostGroupId()))
.ifPresent(group -> heraJobVo.setHostGroupName(group.getName()));
heraJobVo.setUIdS(getuIds(jobId, RunAuthType.JOB));
heraJobVo.setFocusUser(focusUsers.toString());
heraJobVo.setAlarmLevel(AlarmLevel.getName(job.getOffset()));
heraJobVo.setCycle(CycleEnum.parse(job.getCycle()).getDesc());
configDecry(heraJobVo.getConfigs());
configDecry(heraJobVo.getInheritConfig());
//如果无权限进行变量加密
if (heraJobVo.getConfigs().keySet().stream().anyMatch(key -> key.toLowerCase().contains(Constants.PASSWORD_WORD))
|| heraJobVo.getInheritConfig().keySet().stream().anyMatch(key -> key.toLowerCase().contains(Constants.PASSWORD_WORD))) {
try {
checkPermission(jobId, RunAuthType.JOB);
} catch (NoPermissionException e) {
encryption(heraJobVo.getConfigs());
encryption(heraJobVo.getInheritConfig());
}
}
return new JsonResponse(true, heraJobVo);
}
@RunAuth(typeIndex = 1)
@GetMapping("/checkPermission")
@ResponseBody
@ApiOperation(value = "权限检测接口")
public JsonResponse doAspectAuth(@ApiParam(value = "任务ID", required = true) Integer jobId
, @ApiParam(value = "检测类型", required = true) RunAuthType type) {
return new JsonResponse(true, true);
}
private void configDecry(Map<String, String> config) {
Optional.ofNullable(config)
.ifPresent(cxf -> cxf.entrySet()
.stream()
.filter(pair -> pair.getKey().toLowerCase().contains(Constants.SECRET_PREFIX))
.forEach(entry -> entry.setValue(PasswordUtils.aesDecrypt(entry.getValue()))));
}
private void encryption(Map<String, String> config) {
Optional.ofNullable(config)
.ifPresent(cxf -> cxf.entrySet()
.stream()
.filter(pair -> pair.getKey().toLowerCase().contains(Constants.PASSWORD_WORD))
.forEach(entry -> entry.setValue("******")));
}
/**
* 组下搜索任务
*
* @param groupId groupId
* @param status all:全部;
* @param pageForm layui table分页参数
* @return 结果
*/
@RequestMapping(value = "/getGroupTask", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "某组下搜索任务")
public TableResponse getGroupTask(@ApiParam(value = "组Id", required = true) String groupId,
@ApiParam(value = "任务状态") String status,
@ApiParam(value = "日期", required = true) String dt,
@ApiParam(value = "分页参数", required = true) TablePageForm pageForm) {
List<HeraGroup> group = heraGroupService.findDownStreamGroup(StringUtil.getGroupId(groupId));
Set<Integer> groupSet = group.stream().map(HeraGroup::getId).collect(Collectors.toSet());
List<HeraJob> jobList = heraJobService.getAll();
Set<Integer> jobIdSet = jobList.stream().filter(job -> groupSet.contains(job.getGroupId())).map(HeraJob::getId).collect(Collectors.toSet());
SimpleDateFormat format = new SimpleDateFormat("yyMMdd");
Calendar calendar = Calendar.getInstance();
String startDate;
Date start;
if (StringUtils.isBlank(dt)) {
start = new Date();
} else {
try {
start = format.parse(dt);
} catch (ParseException e) {
start = new Date();
}
}
calendar.setTime(start);
startDate = ActionUtil.getFormatterDate("yyyyMMdd", calendar.getTime());
calendar.add(Calendar.DAY_OF_YEAR, +1);
String endDate = ActionUtil.getFormatterDate("yyyyMMdd", calendar.getTime());
List<GroupTaskVo> taskVos = heraJobActionService.findByJobIds(new ArrayList<>(jobIdSet), startDate, endDate, pageForm, status);
return new TableResponse(pageForm.getCount(), 0, taskVos);
}
@RequestMapping(value = "/getGroupMessage", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "获取组信息")
public JsonResponse getGroupMessage(@ApiParam(value = "组ID", required = true) String groupId) {
Integer id = StringUtil.getGroupId(groupId);
HeraGroup group = heraGroupService.findById(id);
HeraGroupVo groupVo = BeanConvertUtils.convert(group);
groupVo.setInheritConfig(getInheritConfig(groupVo.getParent()));
groupVo.setUIdS(getuIds(id, RunAuthType.GROUP));
configDecry(groupVo.getConfigs());
configDecry(groupVo.getInheritConfig());
if (groupVo.getConfigs().keySet().stream().anyMatch(key -> key.toLowerCase().contains(Constants.PASSWORD_WORD))
|| groupVo.getInheritConfig().keySet().stream().anyMatch(key -> key.toLowerCase().contains(Constants.PASSWORD_WORD))) {
try {
checkPermission(id, RunAuthType.GROUP);
} catch (NoPermissionException e) {
encryption(groupVo.getConfigs());
encryption(groupVo.getInheritConfig());
}
}
return new JsonResponse(true, groupVo);
}
@RequestMapping(value = "/getJobOperator", method = RequestMethod.GET)
@ResponseBody
@RunAuth(typeIndex = 1)
@ApiOperation("获取任务管理员admin表示目前有权限操作的用户")
public JsonResponse getJobOperator(@ApiParam(value = "任务/组ID", required = true) String jobId,
@ApiParam(value = "任务类型", required = true) RunAuthType type) {
Integer groupId = StringUtil.getGroupId(jobId);
List<HeraPermission> permissions = heraPermissionService.findByTargetId(groupId, type.getName(), 1);
List<HeraUser> all = heraUserService.findAllName();
if (all == null || permissions == null) {
return new JsonResponse(false, "发生错误,请联系管理员");
}
Map<String, Object> res = new HashMap<>(2);
res.put("allUser", all);
res.put("admin", permissions);
return new JsonResponse(true, "查询成功", res);
}
@RequestMapping(value = "/getJobVersion", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("获取任务的所有版本")
public JsonResponse getJobVersion(@ApiParam(value = "任务ID", required = true) Long jobId) {
return new JsonResponse(true, heraJobActionService.getActionVersionByJobId(jobId)
.stream()
.map(id -> HeraActionVo.builder().id(id).build())
.collect(Collectors.toList()));
}
@RequestMapping(value = "/addJob", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("新增任务")
@RunAuth(authType = RunAuthType.GROUP, idIndex = 1)
public JsonResponse addJob(@ApiParam(value = "任务信息", required = true) HeraJob heraJob,
@ApiParam(value = "所在组目录", required = true) String parentId) {
heraJob.setGroupId(StringUtil.getGroupId(parentId));
heraJob.setHostGroupId(HeraGlobalEnv.defaultWorkerGroup);
heraJob.setOwner(getOwner());
heraJob.setScheduleType(JobScheduleTypeEnum.Independent.getType());
int insert = heraJobService.insert(heraJob);
if (insert > 0) {
addJobRecord(heraJob);
return new JsonResponse(true, String.valueOf(heraJob.getId()));
} else {
return new JsonResponse(false, "新增失败");
}
}
@PostMapping(value = "/copyJob")
@ResponseBody
@RunAuth(authType = RunAuthType.JOB)
@ApiOperation("复制任务接口")
public JsonResponse copyJob(
@ApiParam(value = "复制的任务ID", required = true) int jobId) {
HeraJob copyJob = heraJobService.findById(jobId);
copyJob.setName(copyJob.getName() + "_copy");
copyJob.setOwner(getOwner());
copyJob.setScheduleType(JobScheduleTypeEnum.Independent.getType());
copyJob.setId(0);
copyJob.setIsValid(0);
int insert = heraJobService.insert(copyJob);
if (insert > 0) {
addJobRecord(copyJob);
return new JsonResponse(true, String.valueOf(copyJob.getId()));
} else {
return new JsonResponse(false, "新增失败");
}
}
private void addJobRecord(HeraJob heraJob) {
String ssoName = getSsoName();
String ownerId = getOwnerId();
MonitorLog.info("{}[{}]【添加】任务{}成功", heraJob.getOwner(), ssoName, heraJob.getId());
updateMonitor(heraJob.getId());
doAsync(() -> addJobRecord(heraJob.getId(), heraJob.getName(), RecordTypeEnum.Add, ssoName, ownerId));
}
@RequestMapping(value = "/addMonitor", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("任务添加监控")
public JsonResponse updateMonitor(@ApiParam(value = "任务ID", required = true) Integer id) {
String ssoId = getSsoId();
if (Constants.DEFAULT_ID.equals(ssoId)) {
return new JsonResponse(false, "组账户不支持监控任务");
}
boolean res = heraJobMonitorService.addMonitor(ssoId, id);
if (res) {
MonitorLog.info("{}【关注】任务{}成功", getSsoName(), id);
return new JsonResponse(true, "关注成功");
} else {
return new JsonResponse(false, "系统异常,请联系管理员");
}
}
@RequestMapping(value = "/delMonitor", method = RequestMethod.POST)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@ApiOperation("删除任务关注人")
public JsonResponse deleteMonitor(@ApiParam(value = "任务ID", required = true) Integer id) {
String ssoId = getSsoId();
HeraJobMonitor monitor = heraJobMonitorService.findByJobId(id);
if (monitor != null) {
if (monitor.getUserIds().split(Constants.COMMA).length == 1) {
return new JsonResponse(false, "至少有一个监控人");
}
}
boolean res = heraJobMonitorService.removeMonitor(ssoId, id);
if (res) {
MonitorLog.info("{}【取关】任务{}成功", getSsoName(), id);
return new JsonResponse(true, "取关成功");
} else {
return new JsonResponse(false, "系统异常,请联系管理员");
}
}
@RequestMapping(value = "/addGroup", method = RequestMethod.POST)
@ResponseBody
@RunAuth(authType = RunAuthType.GROUP, idIndex = 1)
@ApiOperation("添加组")
public JsonResponse addJob(@ApiParam(value = "组信息", required = true) HeraGroup heraGroup,
@ApiParam(value = "所在组id", required = true) String parentId) {
heraGroup.setParent(StringUtil.getGroupId(parentId));
heraGroup.setOwner(getOwner());
heraGroup.setExisted(1);
int insert = heraGroupService.insert(heraGroup);
if (insert > 0) {
MonitorLog.info("{}【添加】组{}成功", getSsoName(), heraGroup.getId());
return new JsonResponse(true, Constants.GROUP_PREFIX + heraGroup.getId());
} else {
return new JsonResponse(false, String.valueOf(-1));
}
}
@RequestMapping(value = "/generateVersion", method = RequestMethod.POST)
@ResponseBody
@RunAuth
@ApiOperation("全量版本生成/单个版本生成")
public JsonResponse generateVersion(@ApiParam("任务ID") Long jobId) throws ExecutionException, InterruptedException, TimeoutException {
return new JsonResponse(true, workClient.generateActionFromWeb(JobExecuteKind.ExecuteKind.ManualKind, jobId));
}
/**
* 获取任务历史版本
*
* @param pageHelper
* @return
*/
@RequestMapping(value = "/getJobHistory", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("任务历史记录")
public JsonResponse getJobHistory(@ApiParam(value = "分页", required = true) PageHelperTimeRange pageHelper) {
return new JsonResponse(true, heraJobHistoryService.findLogByPage(pageHelper));
}
@RequestMapping(value = "/getHostGroupIds", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("获取机器组Id")
public JsonResponse getHostGroupIds() {
return new JsonResponse(true, heraHostGroupService.getAll());
}
@RequestMapping(value = "getLog", method = RequestMethod.GET)
@ResponseBody
@RunAuth()
@ApiOperation("获取任务日志接口")
public JsonResponse getJobLog(@ApiParam(value = "任务ID", required = true) Integer id) {
return new JsonResponse(true, heraJobHistoryService.findLogById(id));
}
@RequestMapping(value = "/status/{jobId}", method = RequestMethod.GET)
@ResponseBody
@UnCheckLogin
@ApiOperation("开放接口,查询任务状态")
public JsonResponse getStatus(@PathVariable("jobId") @ApiParam(value = "任务ID", required = true) Integer jobId
, @RequestParam("time") @ApiParam(value = "时间戳,只查询该时间戳之后的记录", required = true) long time) {
HeraJobHistory history = heraJobHistoryService.findNewest(jobId);
if (history == null) {
return new JsonResponse(false, "无执行记录");
}
//此时可能正在创建动态集群 或者发送netty消息的路上
if (history.getStartTime() == null && history.getGmtCreate().getTime() >= time) {
return new JsonResponse(true, StatusEnum.RUNNING.toString());
}
if (history.getStartTime() != null && history.getStartTime().getTime() < time) {
return new JsonResponse(false, "无执行记录");
}
return new JsonResponse(true, Optional.ofNullable(history.getStatus()).orElse(StatusEnum.RUNNING.toString()));
}
private Map<String, String> getInheritConfig(Integer groupId) {
HeraGroup group = heraGroupService.findConfigById(groupId);
Map<String, String> configMap = new TreeMap<>();
while (group != null && groupId != null && groupId != 0) {
Map<String, String> map = StringUtil.convertStringToMap(group.getConfigs());
// 多重继承相同变量以第一个的为准
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
if (!configMap.containsKey(key)) {
configMap.put(key, entry.getValue());
}
}
groupId = group.getParent();
group = heraGroupService.findConfigById(groupId);
}
return configMap;
}
private String getuIds(Integer id, RunAuthType type) {
List<HeraPermission> permissions = heraPermissionService.findByTargetId(id, type.getName(), 1);
StringBuilder uids = new StringBuilder("[ ");
if (permissions != null && permissions.size() > 0) {
permissions.forEach(x -> uids.append(x.getUid()).append(" "));
}
uids.append("]");
return uids.toString();
}
@RequestMapping(value = "/getJobImpactOrProgress", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("查询任务依赖关系接口")
public JsonResponse getJobImpactOrProgress(@ApiParam(value = "任务ID", required = true) Integer jobId
, @ApiParam(value = "0:上游/1:下游", required = true) Integer type) {
Map<String, Object> graph = heraJobService.findCurrentJobGraph(jobId, type);
if (graph == null) {
return new JsonResponse(false, "当前任务不存在");
}
return new JsonResponse(true, "成功", graph);
}
@RequestMapping(value = "/getAllArea", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("获取所有区域")
public JsonResponse getAllArea() {
return new JsonResponse(true, "成功", Optional.of(heraAreaService.findAll()).get());
}
/**
* @param jobHisId
* @param status
* @return
*/
@RequestMapping(value = "/forceJobHisStatus", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("强制设置任务状态")
public JsonResponse forceJobHisStatus(@ApiParam(value = "执行记录id", required = true) Long jobHisId
, @ApiParam(value = "设置的状态", required = true) String status) {
String info = "";
if (status.equals(StatusEnum.WAIT.toString())) {
info = "手动强制等待状态";
} else if (status.equals(StatusEnum.FAILED.toString())) {
info = "手动强制失败状态";
} else if (status.equals(StatusEnum.SUCCESS.toString())) {
info = "手动强制成功状态";
} else if (status.equals(StatusEnum.RUNNING.toString())) {
info = "手动强制运行中状态";
}
String illustrate = heraJobHistoryService.findById(jobHisId).getIllustrate();
if (StringUtils.isNotBlank(illustrate)) {
illustrate += ";" + info;
} else {
illustrate = info;
}
heraJobHistoryService.updateStatusAndIllustrate(jobHisId, status, illustrate, new Date());
return new JsonResponse(true, "修改状态成功");
}
}

View File

@ -0,0 +1,664 @@
package com.dfire.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.dfire.common.constants.Constants;
import com.dfire.common.entity.*;
import com.dfire.common.entity.model.HeraJobBean;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.entity.vo.HeraGroupVo;
import com.dfire.common.entity.vo.HeraJobVo;
import com.dfire.common.enums.RecordTypeEnum;
import com.dfire.common.enums.RunAuthType;
import com.dfire.common.enums.StatusEnum;
import com.dfire.common.enums.TriggerTypeEnum;
import com.dfire.common.exception.HeraException;
import com.dfire.common.exception.NoPermissionException;
import com.dfire.common.service.*;
import com.dfire.common.util.*;
import com.dfire.config.HeraGlobalEnv;
import com.dfire.config.RunAuth;
import com.dfire.config.UnCheckLogin;
import com.dfire.core.netty.worker.WorkClient;
import com.dfire.core.util.JobUtils;
import com.dfire.logs.ErrorLog;
import com.dfire.logs.MonitorLog;
import com.dfire.protocol.JobExecuteKind;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang3.StringUtils;
import org.quartz.CronExpression;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.WebAsyncTask;
import springfox.documentation.annotations.ApiIgnore;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
* desc:
*
* @author scx
* @create 2020/06/19
*/
@Controller
@Api("调度中心操作")
@RequestMapping("/scheduleCenter")
public class ScheduleOperatorController extends BaseHeraController {
private final HeraJobService heraJobService;
private final HeraJobActionService heraJobActionService;
private final HeraGroupService heraGroupService;
private final HeraJobHistoryService heraJobHistoryService;
private final HeraUserService heraUserService;
private final HeraPermissionService heraPermissionService;
private final WorkClient workClient;
private final HeraHostGroupService heraHostGroupService;
private final HeraSsoService heraSsoService;
private final Set<Long> cancelSet = new HashSet<>();
private final HeraJobMonitorService jobMonitorService;
public ScheduleOperatorController(HeraJobMonitorService jobMonitorService, HeraJobActionService heraJobActionService, @Qualifier("heraJobMemoryService") HeraJobService heraJobService, @Qualifier("heraGroupMemoryService") HeraGroupService heraGroupService, HeraJobHistoryService heraJobHistoryService, HeraUserService heraUserService, HeraPermissionService heraPermissionService, WorkClient workClient, HeraHostGroupService heraHostGroupService, HeraSsoService heraSsoService) {
this.heraJobActionService = heraJobActionService;
this.heraJobService = heraJobService;
this.heraGroupService = heraGroupService;
this.heraJobHistoryService = heraJobHistoryService;
this.heraUserService = heraUserService;
this.heraPermissionService = heraPermissionService;
this.workClient = workClient;
this.heraHostGroupService = heraHostGroupService;
this.heraSsoService = heraSsoService;
this.jobMonitorService = jobMonitorService;
}
@PostMapping("/moveNodes")
@ResponseBody
@ApiOperation("任务批量移动接口")
public JsonResponse moveNodes(@ApiParam(value = "任务id集合用,分割",required = true)String ids
,@ApiParam(value = "之前的所在组目录",required = true) String oldParent
,@ApiParam(value = "新的所在组目录",required = true) String newParent) {
if (ids != null) {
for (String id : ids.split(Constants.COMMA)) {
moveNode(id, newParent, oldParent);
}
}
return new JsonResponse(true, "成功");
}
@RequestMapping(value = "/moveNode", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("任务单个移动接口")
public JsonResponse moveNode(@ApiParam(value = "任务id", required = true) String id
, @ApiParam(value = "新的所在组目录", required = true) String parent
, @ApiParam(value = "之前的所在组目录", required = true) String lastParent) throws NoPermissionException {
Integer newParent = StringUtil.getGroupId(parent);
Integer newId;
String ssoName = getSsoName();
String ownerId = getOwnerId();
if (id.startsWith(Constants.GROUP_PREFIX)) {
newId = StringUtil.getGroupId(id);
checkPermission(newId, RunAuthType.GROUP);
boolean result = heraGroupService.changeParent(newId, newParent);
doAsync(() -> addGroupRecord(newId, lastParent + "=>" + newParent, RecordTypeEnum.MOVE, ssoName, ownerId));
MonitorLog.info("组{}:发生移动 {} ---> {}", newId, lastParent, newParent);
return new JsonResponse(result, result ? "处理成功" : "移动失败");
} else {
newId = Integer.parseInt(id);
checkPermission(newId, RunAuthType.JOB);
boolean result = heraJobService.changeParent(newId, newParent);
doAsync(() -> addJobRecord(newId, lastParent + "=>" + newParent, RecordTypeEnum.MOVE, ssoName, ownerId));
MonitorLog.info("任务{}:发生移动{} ---> {}", newId, lastParent, newParent);
return new JsonResponse(result, result ? "处理成功" : "移动失败");
}
}
@RequestMapping(value = "/updatePermission", method = RequestMethod.POST)
@ResponseBody
@Transactional(rollbackFor = Exception.class)
@RunAuth(typeIndex = 1)
@ApiOperation("权限更新接口")
public JsonResponse updatePermission(@RequestParam("id") @ApiParam(value = "任务id", required = true) String id,
@RequestParam("type") @ApiParam(value = "类型job,group", required = true) RunAuthType type,
@RequestParam("uIdS") @ApiParam(value = "ssoid集合", required = true) String names) {
Integer newId = StringUtil.getGroupId(id);
JSONArray parseId = JSONArray.parseArray(names);
Set<String> uIdS;
if (parseId == null) {
uIdS = new HashSet<>(0);
} else {
uIdS = parseId.stream().map(uid -> (String) uid).collect(Collectors.toSet());
}
String typeStr = type.getName();
Optional.ofNullable(heraPermissionService.findByTargetId(newId, typeStr, 1)).ifPresent(perms -> perms.forEach(perm -> {
if (!uIdS.contains(perm.getUid())) {
heraPermissionService.updateByUid(newId, typeStr, 0, perm.getUid());
} else {
uIdS.remove(perm.getUid());
}
}));
//经过第一轮的筛选后如果还剩下继续处理
if (uIdS.size() > 0) {
List<HeraPermission> perms = heraPermissionService.findByTargetId(newId, typeStr, 0);
//把以前设置为无效的这次加入管理的重新设置为有效
if (perms != null) {
perms.stream().filter(perm -> uIdS.contains(perm.getUid())).forEach(perm -> {
uIdS.remove(perm.getUid());
heraPermissionService.updateByUid(newId, typeStr, 1, perm.getUid());
});
}
if (uIdS.size() > 0) {
//余下的都是需要新增的
Long targetId = Long.parseLong(String.valueOf(newId));
uIdS.forEach(uid -> heraPermissionService.insert(HeraPermission
.builder()
.type(typeStr)
.targetId(targetId)
.uid(uid)
.build())
);
}
}
return new JsonResponse(true, "修改成功");
}
@RequestMapping(value = "/check", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("权限检测接口")
public JsonResponse check(@ApiParam(value = "任务id", required = true) String id) {
if (id == null) {
return new JsonResponse(true, "查询成功", false);
}
boolean auth = true;
if (id.startsWith(Constants.GROUP_PREFIX)) {
try {
checkPermission(StringUtil.getGroupId(id), RunAuthType.GROUP);
} catch (NoPermissionException e) {
auth = false;
}
return new JsonResponse(true, "查询成功", auth);
} else {
try {
checkPermission(Integer.parseInt(id), RunAuthType.JOB);
} catch (NoPermissionException e) {
auth = false;
}
return new JsonResponse(true, "查询成功", auth);
}
}
/**
* 一键开启/关闭/失效 某job 的上游/下游的所有任务
*
* @param jobId jobId
* @param type 0:上游 1:下游
* @param auto 0:关闭 1:开启 2:失效
* @return
*/
@RequestMapping(value = "/switchAll", method = RequestMethod.GET)
@ResponseBody
@ApiIgnore
public JsonResponse getJobImpact(Integer jobId, Integer type, Integer auto) throws NoPermissionException, HeraException {
List<Integer> jobList = heraJobService.findJobImpact(jobId, type);
if (jobList == null) {
return new JsonResponse(false, "当前任务不存在");
}
int size = jobList.size();
JsonResponse response;
if ((type == 0 && auto == 1) || (type == 1 && auto != 1)) {
for (int i = size - 1; i >= 0; i--) {
response = updateSwitch(jobList.get(i), auto);
if (!response.isSuccess()) {
return response;
}
}
} else if ((type == 1 && auto == 1) || (type == 0 && auto != 1)) {
for (int i = 0; i < size; i++) {
response = updateSwitch(jobList.get(i), auto);
if (!response.isSuccess()) {
return response;
}
}
} else {
return new JsonResponse(false, "未知的type:" + type);
}
return new JsonResponse(true, "全部处理成功", jobList);
}
/**
* 取消正在执行的任务
*
* @param jobId
* @param historyId
* @return
*/
@RequestMapping(value = "/cancelJob", method = RequestMethod.GET)
@ResponseBody
@RunAuth(idIndex = 1)
@ApiOperation("取消任务")
public WebAsyncTask<JsonResponse> cancelJob(@ApiParam(value = "执行记录id", required = true) Long historyId, @ApiParam(value = "任务id", required = true) String jobId) {
MonitorLog.info("{}取消任务{}", getOwner(), jobId);
if (cancelSet.contains(historyId)) {
return new WebAsyncTask<>(() -> new JsonResponse(true, "任务正在取消中,请稍后"));
}
String ssoName = getSsoName();
String ownerId = getOwnerId();
WebAsyncTask<JsonResponse> response = new WebAsyncTask<>(() -> {
String res = null;
try {
cancelSet.add(historyId);
addJobRecord(Integer.parseInt(jobId), "", RecordTypeEnum.CANCEL, ssoName, ownerId);
try {
res = workClient.cancelJobFromWeb(JobExecuteKind.ExecuteKind.ScheduleKind, historyId);
} catch (ExecutionException | InterruptedException e) {
ErrorLog.error("取消任务异常", e);
}
return new JsonResponse(true, res);
} finally {
cancelSet.remove(historyId);
MonitorLog.info("取消任务{}结果为:{}", jobId, res);
}
});
response.onTimeout(() -> new JsonResponse(false, "任务正在取消,请稍后"));
return response;
}
@RequestMapping(value = "/updateSwitch", method = RequestMethod.POST)
@ResponseBody
@RunAuth
@ApiOperation("开启/关闭任务")
public JsonResponse updateSwitch(@ApiParam(value = "任务id", required = true) Integer id, @ApiParam(value = "切换状态0关闭1开启", required = true) Integer status) throws HeraException {
HeraJob heraJob = heraJobService.findById(id);
if (status.equals(heraJob.getAuto())) {
return new JsonResponse(true, "操作成功");
}
//TODO 上下游任务检测时需要优化 任务链路复杂时 导致关闭/开启耗时较久
//关闭动作 上游关闭时需要判断下游是否有开启任务如果有则不允许关闭
if (status != 1) {
String errorMsg;
if ((errorMsg = getJobFromAuto(heraJobService.findDownStreamJob(id), 1)) != null) {
return new JsonResponse(false, id + "下游存在开启状态任务:" + errorMsg);
}
} else { //开启动作 如果有上游任务上游任务不能为关闭状态
String errorMsg;
if ((errorMsg = getJobFromAuto(heraJobService.findUpStreamJob(id), 0)) != null) {
return new JsonResponse(false, id + "上游存在关闭状态任务:" + errorMsg);
}
}
boolean result = heraJobService.changeSwitch(id, status);
if (result) {
MonitorLog.info("{}【切换】任务{}状态{}成功", getSsoName(), status == 1 ? Constants.OPEN_STATUS : status == 0 ? "关闭" : "失效");
}
String ssoName = getSsoName();
String ownerId = getOwnerId();
doAsync(() -> addJobRecord(id, String.valueOf(status), RecordTypeEnum.SWITCH, ssoName, ownerId));
if (status == 1) {
updateJobToMaster(result, id);
return new JsonResponse(result, result ? "开启成功" : "开启失败");
} else if (status == 0) {
return new JsonResponse(result, result ? "关闭成功" : "关闭失败");
} else {
return new JsonResponse(result, result ? "成功设置为失效状态" : "设置状态失败");
}
}
private void updateJobToMaster(boolean result, Integer id) {
if (result) {
doAsync(() -> {
try {
workClient.updateJobFromWeb(String.valueOf(id));
} catch (ExecutionException | InterruptedException | TimeoutException e) {
ErrorLog.error("更新异常", e);
}
});
}
}
@RequestMapping(value = "/execute", method = RequestMethod.GET)
@ResponseBody
@UnCheckLogin
@ApiOperation("http外部调用执行任务")
public JsonResponse publicExecute(@RequestParam @ApiParam(value = "map参数类型替换任务的配置信息", required = true) Map<String, String> params) throws ExecutionException, InterruptedException, NoPermissionException, HeraException, TimeoutException {
String secret = params.get("secret");
String decrypt = PasswordUtils.aesDecrypt(secret);
if (decrypt == null) {
return new JsonResponse(false, "解密失败,请询问管理员");
}
String[] split = decrypt.split(";");
if (split.length != 2) {
return new JsonResponse(false, "解密失败,请询问管理员");
}
HeraAction action = heraJobActionService.findLatestByJobId(Long.parseLong((split[0])));
if (action == null) {
return new JsonResponse(false, "找不到版本");
}
addJobRecord(Integer.parseInt(split[0]), "远程执行任务", RecordTypeEnum.REMOTE, getIp() + ":" + split[1], String.valueOf(heraUserService.findByName(split[1]).getId()));
MonitorLog.info("远程调用:{}", JSONObject.toJSONString(params));
HeraJob heraJob = heraJobService.findById(Integer.parseInt(split[0]));
Map<String, String> configs = StringUtil.convertStringToMap(heraJob.getConfigs());
configs.putAll(params);
heraJob.setConfigs(StringUtil.convertMapToString(configs));
heraJobService.update(heraJob);
return execute(action.getId(), 2, split[1]);
}
/**
* 手动执行任务
*
* @param actionId
* @return
*/
@RequestMapping(value = "/manual", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("手动执行接口")
public JsonResponse execute(@JsonSerialize(using = ToStringSerializer.class) @ApiParam(value = "版本id", required = true) Long actionId
, @ApiParam(value = "触发类型2手动执行3手动恢复6超级恢复", required = true) Integer triggerType,
@RequestParam(required = false) @ApiParam(value = "任务执行组", required = false) String execUser) throws InterruptedException, ExecutionException, HeraException, TimeoutException {
if (actionId == null) {
return new JsonResponse(false, "请先生成版本再执行");
}
if (execUser == null) {
checkPermission(ActionUtil.getJobId(actionId), RunAuthType.JOB);
}
TriggerTypeEnum triggerTypeEnum = TriggerTypeEnum.parser(triggerType);
if (triggerTypeEnum == null) {
return new JsonResponse(false, " 无法识别的触发类型,请联系管理员");
}
HeraAction heraAction = heraJobActionService.findById(actionId);
HeraJob heraJob = heraJobService.findById(heraAction.getJobId());
if (execUser == null) {
execUser = super.getSsoName();
}
if (execUser == null) {
return new JsonResponse(false, "任务执行人为空");
}
String configs = heraJob.getConfigs();
HeraJobHistory actionHistory = HeraJobHistory.builder().build();
actionHistory.setJobId(heraAction.getJobId());
actionHistory.setActionId(heraAction.getId());
actionHistory.setTriggerType(triggerTypeEnum.getId());
actionHistory.setOperator(heraJob.getOwner());
actionHistory.setIllustrate(execUser);
actionHistory.setStatus(StatusEnum.RUNNING.toString());
actionHistory.setStatisticEndTime(heraAction.getStatisticEndTime());
actionHistory.setHostGroupId(heraAction.getHostGroupId());
heraJobHistoryService.insert(actionHistory);
heraAction.setScript(heraJob.getScript());
heraAction.setHistoryId(actionHistory.getId());
heraAction.setConfigs(configs);
heraAction.setAuto(heraJob.getAuto());
heraAction.setHostGroupId(heraJob.getHostGroupId());
heraJobActionService.update(heraAction);
workClient.executeJobFromWeb(JobExecuteKind.ExecuteKind.ManualKind, actionHistory.getId());
String ownerId = getOwnerId();
if (ownerId == null) {
ownerId = "0";
}
addJobRecord(heraJob.getId(), String.valueOf(actionId), RecordTypeEnum.Execute, execUser, ownerId);
return new JsonResponse(true, String.valueOf(actionId));
}
@RequestMapping(value = "/updateJobMessage", method = RequestMethod.POST)
@ResponseBody
@RunAuth(idIndex = -1)
@ApiOperation("更新任务信息")
public JsonResponse updateJobMessage(@ApiParam(value = "任务vo对象", required = true) HeraJobVo heraJobVo) {
if (StringUtils.isBlank(heraJobVo.getDescription())) {
return new JsonResponse(false, "描述不能为空");
}
try {
new CronExpression(heraJobVo.getCronExpression());
} catch (ParseException e) {
return new JsonResponse(false, "定时表达式不准确,请核实后再保存");
}
HeraHostGroup hostGroup = heraHostGroupService.findById(heraJobVo.getHostGroupId());
if (hostGroup == null) {
return new JsonResponse(false, "机器组不存在,请选择一个机器组");
}
if (StringUtils.isBlank(heraJobVo.getAreaId())) {
return new JsonResponse(false, "至少选择一个任务所在区域");
}
//如果是依赖任务
if (heraJobVo.getScheduleType() == 1) {
String dependencies = heraJobVo.getDependencies();
if (StringUtils.isNotBlank(dependencies)) {
String[] jobs = dependencies.split(Constants.COMMA);
HeraJob heraJob;
boolean jobAuto = true;
StringBuilder sb = null;
for (String job : jobs) {
heraJob = heraJobService.findById(Integer.parseInt(job));
if (heraJob == null) {
return new JsonResponse(false, "任务:" + job + "为空");
}
if (heraJob.getAuto() != 1) {
if (jobAuto) {
jobAuto = false;
sb = new StringBuilder();
sb.append(job);
} else {
sb.append(",").append(job);
}
}
}
if (!jobAuto) {
return new JsonResponse(false, "不允许依赖关闭状态的任务:" + sb.toString());
}
} else {
return new JsonResponse(false, "请勾选你要依赖的任务");
}
} else if (heraJobVo.getScheduleType() == 0) {
heraJobVo.setDependencies("");
} else {
return new JsonResponse(false, "无法识别的调度类型");
}
HeraJob memJob = heraJobService.findById(heraJobVo.getId());
Map<String, String> configMap = StringUtil.configsToMap(heraJobVo.getSelfConfigs());
configEncry(configMap);
heraJobVo.setSelfConfigs(StringUtil.mapToConfigs(configMap));
HeraJob newJob = BeanConvertUtils.convertToHeraJob(heraJobVo);
int maxTimeOut = HeraGlobalEnv.getTaskTimeout() * 60;
if (newJob.getMustEndMinute() > maxTimeOut) {
return new JsonResponse(false, "超出最大超时限制,最大为:" + maxTimeOut);
} else if (newJob.getMustEndMinute() == 0) {
newJob.setMustEndMinute(60);
}
if (StringUtils.isNotBlank(newJob.getDependencies())) {
if (!newJob.getDependencies().equals(memJob.getDependencies())) {
List<HeraJob> relation = heraJobService.getAllJobDependencies();
DagLoopUtil dagLoopUtil = new DagLoopUtil(heraJobService.selectMaxId());
for (HeraJob job : relation) {
String dependencies;
if (job.getId() == newJob.getId()) {
dependencies = newJob.getDependencies();
} else {
dependencies = job.getDependencies();
}
if (StringUtils.isNotBlank(dependencies)) {
String[] split = dependencies.split(",");
for (String s : split) {
HeraJob memById = heraJobService.findMemById(Integer.parseInt(s));
if (memById == null) {
return new JsonResponse(false, "依赖任务:" + s + "不存在");
}
dagLoopUtil.addEdge(job.getId(), Integer.parseInt(s));
}
}
}
if (dagLoopUtil.isLoop()) {
return new JsonResponse(false, "出现环形依赖,请检测依赖关系:" + dagLoopUtil.getLoop());
}
}
}
newJob.setAuto(memJob.getAuto());
String ssoName = getSsoName();
String ownerId = getOwnerId();
Integer update = heraJobService.update(newJob);
if (update == null || update == 0) {
return new JsonResponse(false, "更新失败");
}
doAsync(() -> {
//脚本更新
if (!newJob.getScript().equals(memJob.getScript())) {
addJobRecord(newJob.getId(), memJob.getScript(), RecordTypeEnum.SCRIPT, ssoName, ownerId);
}
//依赖任务更新
if (newJob.getDependencies() != null && !newJob.getDependencies().equals(memJob.getDependencies())) {
addJobRecord(newJob.getId(), memJob.getDependencies(), RecordTypeEnum.DEPEND, ssoName, ownerId);
}
//定时表达式更新
if (newJob.getCronExpression() != null && !newJob.getCronExpression().equals(memJob.getCronExpression())) {
addJobRecord(newJob.getId(), memJob.getCronExpression(), RecordTypeEnum.CRON, ssoName, ownerId);
}
//执行区域更新
if (newJob.getAreaId() != null && !newJob.getAreaId().equals(memJob.getAreaId())) {
addJobRecord(newJob.getId(), memJob.getAreaId(), RecordTypeEnum.AREA, ssoName, ownerId);
}
//脚本配置项变化
if (newJob.getConfigs() != null && !newJob.getConfigs().equals(memJob.getConfigs())) {
addJobRecord(newJob.getId(), memJob.getConfigs(), RecordTypeEnum.CONFIG, ssoName, ownerId);
}
if (newJob.getRunType() != null && !newJob.getRunType().equals(memJob.getRunType())) {
addJobRecord(newJob.getId(), memJob.getRunType(), RecordTypeEnum.RUN_TYPE, ssoName, ownerId);
}
});
return new JsonResponse(true, "更新成功");
}
@GetMapping(value = "previewJob")
@ResponseBody
@ApiOperation("预览任务接口")
public JsonResponse previewJobScript(@ApiParam(value = "任务版本id", required = true) Long actionId) throws HeraException {
return new JsonResponse("", true, getRenderScript(actionId));
}
private String getRenderScript(Long actionId) throws HeraException {
return this.getRenderScript(actionId, null);
}
private String getRenderScript(Long actionId, String script) throws HeraException {
Integer jobId = ActionUtil.getJobId(String.valueOf(actionId));
HeraJobBean jobBean = heraGroupService.getUpstreamJobBean(jobId);
if (script == null) {
script = jobBean.getHeraJob().getScript();
}
RenderHierarchyProperties renderHierarchyProperties = new RenderHierarchyProperties(jobBean.getHierarchyProperties());
script = JobUtils.previewScript(renderHierarchyProperties.getAllProperties(), script);
script = RenderHierarchyProperties.render(script, String.valueOf(actionId).substring(0, 12));
return script;
}
@RequestMapping(value = "/updateGroupMessage", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("更新组信息")
@RunAuth(authType = RunAuthType.GROUP, idIndex = 1)
public JsonResponse updateGroupMessage(@ApiParam(value = "组信息对象", required = true) HeraGroupVo groupVo, @ApiParam(value = "组id", required = true) String groupId) {
groupVo.setId(StringUtil.getGroupId(groupId));
Map<String, String> configMap = StringUtil.configsToMap(groupVo.getSelfConfigs());
configEncry(configMap);
groupVo.setSelfConfigs(StringUtil.mapToConfigs(configMap));
HeraGroup heraGroup = BeanConvertUtils.convert(groupVo);
String ownerId = getOwnerId();
String ssoName = getSsoName();
doAsync(() -> {
HeraGroup lastGroup = heraGroupService.findById(heraGroup.getId());
if (lastGroup.getConfigs() != null && !lastGroup.getConfigs().equals(heraGroup.getConfigs())) {
addGroupRecord(heraGroup.getId(), lastGroup.getConfigs(), RecordTypeEnum.CONFIG, ssoName, ownerId);
}
});
boolean res = heraGroupService.update(heraGroup) > 0;
return new JsonResponse(res, res ? "更新成功" : "系统异常,请联系管理员");
}
private void configEncry(Map<String, String> config) {
Optional.ofNullable(config)
.ifPresent(cxf -> cxf.entrySet()
.stream()
.filter(pair -> pair.getKey().toLowerCase().contains(Constants.SECRET_PREFIX))
.forEach(entry -> entry.setValue(PasswordUtils.aesEncryption(entry.getValue()))));
}
private String getJobFromAuto(List<HeraJob> streamJob, Integer auto) {
boolean has = false;
StringBuilder filterJob = null;
for (HeraJob job : streamJob) {
if (job.getAuto().equals(auto)) {
if (!has) {
has = true;
filterJob = new StringBuilder();
filterJob.append(job.getId());
} else {
filterJob.append(",").append(job.getId());
}
}
}
if (has) {
return filterJob.toString();
}
return null;
}
@RequestMapping(value = "/deleteJob", method = RequestMethod.POST)
@ResponseBody
@RunAuth(typeIndex = 1)
@ApiOperation("删除任务接口")
public JsonResponse deleteJob(@ApiParam(value = "任务id", required = true) String id,
@ApiParam(value = "类型job、group", required = true) RunAuthType type) throws NoPermissionException {
Integer xId = StringUtil.getGroupId(id);
boolean res;
String check = heraJobService.checkDependencies(xId, type);
if (StringUtils.isNotBlank(check)) {
return new JsonResponse(false, check);
}
String ssoName = getSsoName();
String ownerId = getOwnerId();
if (type == RunAuthType.GROUP) {
res = heraGroupService.delete(xId) > 0;
MonitorLog.info("{}【删除】组{}成功", ssoName, xId);
doAsync(() -> addGroupRecord(xId, null, RecordTypeEnum.DELETE, ssoName, ownerId));
return new JsonResponse(res, res ? "删除成功" : "系统异常,请联系管理员");
}
res = heraJobService.delete(xId) > 0;
MonitorLog.info("{}【删除】任务{}成功", getOwner(), xId);
updateJobToMaster(res, xId);
doAsync(() -> addJobRecord(xId, null, RecordTypeEnum.DELETE, ssoName, ownerId));
return new JsonResponse(res, res ? "删除成功" : "系统异常,请联系管理员");
}
}

View File

@ -0,0 +1,324 @@
package com.dfire.controller;
import com.dfire.common.constants.Constants;
import com.dfire.common.entity.HeraHostRelation;
import com.dfire.common.entity.HeraJobMonitor;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.entity.model.TableResponse;
import com.dfire.common.entity.vo.HeraActionVo;
import com.dfire.common.entity.vo.HeraJobMonitorVo;
import com.dfire.common.entity.vo.HeraSsoVo;
import com.dfire.common.exception.NoPermissionException;
import com.dfire.common.service.HeraHostRelationService;
import com.dfire.common.service.HeraJobActionService;
import com.dfire.common.service.HeraJobMonitorService;
import com.dfire.common.service.HeraSsoService;
import com.dfire.config.AdminCheck;
import com.dfire.core.netty.worker.WorkClient;
import com.dfire.monitor.service.JobManageService;
import com.dfire.protocol.JobExecuteKind;
import com.google.protobuf.InvalidProtocolBufferException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
/**
* @author: <a href="mailto:lingxiao@2dfire.com">凌霄</a>
* @time: Created in 16:52 2018/1/13
* @desc 系统管理
*/
@Controller
@Api("系统操作接口")
public class SystemManageController extends BaseHeraController {
@Autowired
private JobManageService jobManageService;
@Autowired
private HeraJobActionService heraJobActionService;
@Autowired
private HeraHostRelationService heraHostRelationService;
@Autowired
private WorkClient workClient;
@Autowired
private HeraJobMonitorService heraJobMonitorService;
@Autowired
private HeraSsoService heraSsoService;
@GetMapping("/userManage")
@AdminCheck
public String userManage() throws NoPermissionException {
return "systemManage/userManage.index";
}
@GetMapping("/basicManage")
@AdminCheck
public String basicManage() throws NoPermissionException {
return "systemManage/basicManage.index";
}
@RequestMapping("/workManage")
@GetMapping
public String workManage() throws NoPermissionException {
return "systemManage/workManage.index";
}
@GetMapping("/hostGroupManage")
@AdminCheck
public String hostGroupManage() throws NoPermissionException {
return "systemManage/hostGroupManage.index";
}
@GetMapping("/jobMonitor")
@AdminCheck
public String jobMonitor() throws NoPermissionException {
return "systemManage/jobMonitor.index";
}
@GetMapping("/jobDetail")
public String jobManage() {
return "jobManage/jobDetail.index";
}
@GetMapping("/rerun")
public String jobRerun() {
return "jobManage/rerun.index";
}
@GetMapping("/jobSearch")
public String jobSearch() {
return "jobManage/jobSearch.index";
}
@GetMapping("/jobDag")
public String jobDag() {
return "jobManage/jobDag.index";
}
@GetMapping("/machineInfo")
public String machineInfo() {
return "machineInfo";
}
@RequestMapping(value = "/workManage/list", method = RequestMethod.GET)
@ResponseBody
@AdminCheck
@ApiOperation("机器组关系列表查询")
public TableResponse workManageList() {
List<HeraHostRelation> hostRelations = heraHostRelationService.getAll();
if (hostRelations == null) {
return new TableResponse(-1, "查询失败");
}
return new TableResponse(hostRelations.size(), 0, hostRelations);
}
@RequestMapping(value = "/workManage/add", method = RequestMethod.POST)
@ResponseBody
@AdminCheck
@ApiOperation("机器组关系添加")
public JsonResponse workManageAdd(@ApiParam(value = "机器组管理对象",required = true) HeraHostRelation heraHostRelation) {
int insert = heraHostRelationService.insert(heraHostRelation);
if (insert > 0) {
return new JsonResponse(true, "插入成功");
}
return new JsonResponse(false, "插入失败");
}
@RequestMapping(value = "/workManage/del", method = RequestMethod.POST)
@ResponseBody
@AdminCheck
@ApiOperation("机器组关系删除")
public JsonResponse workManageDel(@ApiParam(value = "机器组关系id",required = true)Integer id) {
int delete = heraHostRelationService.delete(id);
if (delete > 0) {
return new JsonResponse(true, "删除成功");
}
return new JsonResponse(false, "删除失败");
}
@RequestMapping(value = "/workManage/update", method = RequestMethod.POST)
@ResponseBody
@AdminCheck
@ApiOperation("机器组关系更新")
public JsonResponse workManageUpdate(@ApiParam(value = "机器组管理对象",required = true)HeraHostRelation heraHostRelation) {
int update = heraHostRelationService.update(heraHostRelation);
if (update > 0) {
return new JsonResponse(true, "更新成功");
}
return new JsonResponse(false, "更新失败");
}
@GetMapping(value = "/jobMonitor/list")
@ResponseBody
@ApiOperation("任务监控列表查询")
public TableResponse jobMonitorList() {
List<HeraJobMonitorVo> monitors = heraJobMonitorService.findAllVo();
if (monitors == null || monitors.size() == 0) {
return new TableResponse(-1, "无监控任务");
}
Map<String, HeraSsoVo> cacheSso = new HashMap<>();
monitors.forEach(monitor -> {
if (!StringUtils.isBlank(monitor.getUserIds())) {
List<HeraSsoVo> ssoVos = new ArrayList<>();
Arrays.stream(monitor.getUserIds().split(Constants.COMMA)).filter(StringUtils::isNotBlank).distinct().forEach(id -> {
HeraSsoVo ssoVo = cacheSso.get(id);
if (ssoVo == null) {
ssoVo = heraSsoService.findSsoVoById(Integer.parseInt(id));
cacheSso.put(id, ssoVo);
}
ssoVos.add(ssoVo);
});
monitor.setMonitors(ssoVos);
} else {
monitor.setMonitors(new ArrayList<>(0));
}
Optional.ofNullable(monitor.getUserIds()).ifPresent(userIds -> {
if (userIds.endsWith(Constants.COMMA)) {
monitor.setUserIds(userIds.substring(0, userIds.length() - 1));
}
});
});
cacheSso.clear();
return new TableResponse(monitors.size(), 0, monitors);
}
@PostMapping(value = "/jobMonitor/add")
@ResponseBody
@AdminCheck
@ApiOperation("任务监控人添加接口")
public JsonResponse jobMonitorAdd(@ApiParam(value = "任务id",required = true)Integer jobId,
@ApiParam(value = "监控人hera_sso的id集合,多个,分割",required = true)String monitors) {
HeraJobMonitor monitor = heraJobMonitorService.findByJobId(jobId);
if (monitor != null) {
return new JsonResponse(false, "该监控任务已经存在,请直接编辑该任务");
}
boolean res = heraJobMonitorService.addMonitor(monitors, jobId);
return new JsonResponse(res, res ? "添加监控成功" : "添加监控失败");
}
@PostMapping(value = "/jobMonitor/update")
@ResponseBody
@AdminCheck
@ApiOperation("任务监控人更新接口")
public JsonResponse jobMonitorUpdate(@ApiParam(value = "任务id",required = true)Integer jobId,
@ApiParam(value = "监控人hera_sso的id集合,多个,分割",required = true)String monitors) {
boolean res = heraJobMonitorService.updateMonitor(monitors, jobId);
return new JsonResponse(res, res ? "添加监控成功" : "添加监控失败");
}
/**
* 首页任务运行top10
*
* @return
*/
@RequestMapping(value = "/homePage/findJobRunTimeTop10", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("首页任务运行top10")
public JsonResponse findJobRunTimeTop10() {
return jobManageService.findJobRunTimeTop10();
}
/**
* 今日所有任务状态初始化首页饼图
*
* @return
*/
@RequestMapping(value = "/homePage/findAllJobStatus", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("今日所有任务状态,初始化首页饼图")
public JsonResponse findAllJobStatus() {
return jobManageService.findAllJobStatus();
}
/**
* 今日所有任务状态明细线形图初始化
*
* @return
*/
@RequestMapping(value = "/homePage/findAllJobStatusDetail", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("今日任务详情明细,初始化曲线图")
public JsonResponse findAllJobStatusDetail() {
return jobManageService.findAllJobStatusDetail();
}
@RequestMapping(value = "/homePage/getJobQueueInfo", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("获取任务在work/master上的等待/执行队列及监控信息")
public JsonResponse getJobQueueInfo() throws InterruptedException, ExecutionException, InvalidProtocolBufferException, TimeoutException {
return new JsonResponse(true, workClient.getJobQueueInfoFromWeb());
}
@RequestMapping(value = "/homePage/getNotRunJob", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("查询未执行的任务")
public JsonResponse getNotRunJob() {
List<HeraActionVo> scheduleJob = heraJobActionService.getNotRunScheduleJob();
return new JsonResponse(true, "查询成功", scheduleJob);
}
@RequestMapping(value = "/homePage/getFailJob", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("查询最后一次调度失败的任务")
public JsonResponse getScheduleFailJob() {
List<HeraActionVo> failedJob = heraJobActionService.getFailedJob();
return new JsonResponse(true, "查询成功", failedJob);
}
@RequestMapping(value = "/homePage/getAllWorkInfo", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("查询所有的机器监控信息")
public JsonResponse getAllWorkInfo() throws InterruptedException, ExecutionException, InvalidProtocolBufferException, TimeoutException {
return new JsonResponse(true, workClient.getAllWorkInfo());
}
@RequestMapping(value = "/isAdmin", method = RequestMethod.GET)
@ResponseBody
@ApiOperation("是否为admin用户查询")
public JsonResponse checkAdmin() {
return new JsonResponse(true, isAdmin());
}
@RequestMapping(value = "/admin/generateAllVersion", method = RequestMethod.PUT)
@ResponseBody
@AdminCheck
@ApiOperation("全量版本生成接口")
public JsonResponse generateAllVersion() throws ExecutionException, InterruptedException, TimeoutException {
return new JsonResponse(true, workClient.generateActionFromWeb(JobExecuteKind.ExecuteKind.ManualKind, Constants.ALL_JOB_ID));
}
@RequestMapping(value = "admin/updateWork", method = RequestMethod.PUT)
@ResponseBody
@AdminCheck
@ApiOperation("更新work信息触发接口")
public JsonResponse updateWork() throws ExecutionException, InterruptedException, TimeoutException {
return new JsonResponse(true, workClient.updateWorkFromWeb());
}
}

View File

@ -0,0 +1,94 @@
package com.dfire.controller;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.enums.LogTypeEnum;
import com.dfire.common.enums.RecordTypeEnum;
import com.dfire.common.util.HierarchyProperties;
import com.dfire.config.HeraGlobalEnv;
import com.dfire.core.job.JobContext;
import com.dfire.core.job.ProcessJob;
import com.dfire.core.job.UploadEmrFileJob;
import com.dfire.core.job.UploadLocalFileJob;
import com.dfire.logs.ErrorLog;
import com.dfire.logs.HeraLog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
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.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import springfox.documentation.annotations.ApiIgnore;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
/**
* @author: <a href="mailto:lingxiao@2dfire.com">凌霄</a>
* @time: Created in 下午1:22 2018/7/21
* @desc
*/
@Controller
@RequestMapping("/uploadResource")
@Api("资源上传接口")
public class UploadResourceController extends BaseHeraController {
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
@Transactional(rollbackFor = Exception.class)
@ApiOperation("上传接口")
public JsonResponse uploadResource(@ApiIgnore MultipartHttpServletRequest request) {
Map<String, MultipartFile> fileMap = request.getFileMap();
String fileName = null;
String newFilePath;
File file;
JsonResponse response = new JsonResponse();
response.setSuccess(true);
StringBuilder resMsg = new StringBuilder();
try {
for (String key : fileMap.keySet()) {
MultipartFile multipartFile = fileMap.get(key);
fileName = multipartFile.getOriginalFilename();
int lastPoint = fileName.lastIndexOf(".");
fileName = fileName.substring(0, lastPoint) + "_" + System.nanoTime() + fileName.substring(lastPoint);
newFilePath = HeraGlobalEnv.getWorkDir() + File.separator + fileName;
file = new File(newFilePath);
multipartFile.transferTo(file);
JobContext jobContext = JobContext.builder().build();
jobContext.setProperties(new HierarchyProperties(new HashMap<>(16)));
jobContext.setWorkDir(HeraGlobalEnv.getWorkDir());
ProcessJob uploadJob;
int exitCode;
//如果是emr集群 scp 到emr固定机器上
if (HeraGlobalEnv.isEmrJob()) {
//默认都是hadoop用户
uploadJob = new UploadEmrFileJob(jobContext, file.getAbsolutePath(), fileName, HeraGlobalEnv.emrFixedHost);
exitCode = uploadJob.run();
} else {
uploadJob = new UploadLocalFileJob(jobContext, file.getAbsolutePath(), HeraGlobalEnv.getHdfsUploadPath());
exitCode = uploadJob.run();
}
HeraLog.info("controller upload file command {}", uploadJob.getCommandList().toString());
if (exitCode == 0) {
addRecord(LogTypeEnum.UPLOAD, 1, fileName, RecordTypeEnum.UPLOAD, getSsoName(), getOwnerId());
resMsg.append(fileName).append("[上传成功]: ").append(HeraGlobalEnv.getHdfsUploadPath()).append(fileName).append("<br>");
} else {
response.setSuccess(false);
addRecord(LogTypeEnum.UPLOAD, 1, fileName + "上传失败", RecordTypeEnum.UPLOAD, getSsoName(), getOwnerId());
resMsg.append(fileName).append("[上传失败]: ").append(HeraGlobalEnv.getHdfsUploadPath()).append(fileName).append("<br>");
}
//删除临时文件
file.delete();
}
} catch (Exception e) {
ErrorLog.error("上传文件异常:" + fileName, e);
return new JsonResponse(false, "上传文件异常,请联系管理员");
}
response.setMessage(resMsg.toString());
return response;
}
}

View File

@ -0,0 +1,282 @@
package com.dfire.controller;
import com.alibaba.fastjson.JSONObject;
import com.dfire.common.entity.HeraFile;
import com.dfire.common.entity.HeraSso;
import com.dfire.common.entity.HeraUser;
import com.dfire.common.entity.model.JsonResponse;
import com.dfire.common.entity.model.TableResponse;
import com.dfire.common.entity.vo.HeraSsoVo;
import com.dfire.common.entity.vo.HeraUserVo;
import com.dfire.common.service.HeraFileService;
import com.dfire.common.service.HeraSsoService;
import com.dfire.common.service.HeraUserService;
import com.dfire.common.util.ActionUtil;
import com.dfire.config.AdminCheck;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 java.util.*;
import java.util.stream.Collectors;
/**
* @author: <a href="mailto:lingxiao@2dfire.com">凌霄</a>
* @time: Created in 下午4:10 2018/6/14
* @desc
*/
@Api(value = "用户管理")
@Controller
@RequestMapping("/userManage/")
public class UserManageController {
@Autowired
private HeraUserService heraUserService;
@Autowired
private HeraSsoService heraSsoService;
@Autowired
@Qualifier("heraFileMemoryService")
private HeraFileService heraFileService;
@RequestMapping(value = "/initUser", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "获取用户组列表")
public TableResponse initUser() {
List<HeraUser> users = heraUserService.getAll();
List<HeraUserVo> res;
if (users != null) {
res = new ArrayList<>(users.size());
for (HeraUser user : users) {
HeraUserVo userVo = new HeraUserVo();
BeanUtils.copyProperties(user, userVo);
userVo.setCreateTime(ActionUtil.getDefaultFormatterDate(user.getGmtCreate()));
userVo.setOpTime(ActionUtil.getDefaultFormatterDate(user.getGmtModified()));
res.add(userVo);
}
} else {
res = new ArrayList<>(0);
}
res.sort((o1, o2) -> -(o1.getCreateTime().compareTo(o2.getCreateTime())));
return new TableResponse(res.size(), 0, res);
}
@RequestMapping(value = "/sso/update", method = RequestMethod.POST)
@ResponseBody
@AdminCheck
@ApiOperation(value = "更新用户")
public JsonResponse ssoUpdate(@ApiParam(value = "用户信息", required = true) HeraSso sso) {
boolean success = heraSsoService.updateHeraSsoById(sso);
return new JsonResponse(success, success ? "更新成功" : "更新失败");
}
@RequestMapping(value = "/user/update", method = RequestMethod.POST)
@ResponseBody
@AdminCheck
@ApiOperation(value = "更新用户组")
public JsonResponse userUpdate(@ApiParam(value = "用户组信息", required = true) HeraUser user) {
boolean success = heraUserService.update(user);
return new JsonResponse(success, success ? "更新成功" : "更新失败");
}
@RequestMapping(value = "/groups", method = RequestMethod.GET)
@ResponseBody
@AdminCheck
@ApiOperation(value = "获取所有用户组")
public JsonResponse ssoGroups() {
List<HeraUser> users = heraUserService.getGroups();
return new JsonResponse(true, users.stream()
.map(user -> {
JSONObject userVo = new JSONObject();
userVo.put("id", user.getId());
userVo.put("name", user.getName());
return userVo;
}).collect(Collectors.toList()));
}
@RequestMapping(value = "/initSso", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "获取所有用户列表")
public TableResponse initSso() {
List<HeraSso> ssoList = heraSsoService.getAll();
if (ssoList == null || ssoList.size() == 0) {
return new TableResponse(-1, "数据为空");
}
Map<Integer, String> userMap = Optional.ofNullable(heraUserService.getAll())
.orElse(new ArrayList<>(0))
.stream()
.filter(user -> user.getIsEffective() == 1)
.collect(Collectors.toMap(HeraUser::getId, HeraUser::getName));
List<HeraSsoVo> ssoVoList = ssoList.stream().map(sso -> {
HeraSsoVo ssoVo = new HeraSsoVo();
BeanUtils.copyProperties(sso, ssoVo);
ssoVo.setGName(userMap.getOrDefault(sso.getGid(), "佚名"));
return ssoVo;
}).collect(Collectors.toList());
return new TableResponse(ssoVoList.size(), 0, ssoVoList);
}
/**
* operateType: 1,执行删除操作2执行审核通过操作3执行审核拒绝操作
*
* @return
*/
@RequestMapping(value = "/operateUser", method = RequestMethod.POST)
@ResponseBody
@AdminCheck
@ApiOperation(value = "用户操作接口")
public JsonResponse operateUser(@ApiParam(value = "用户组信息", required = true) Integer id
, @ApiParam(value = "操作类型1删除2同意3拒绝", required = true) String operateType
, @ApiParam(value = "用户类型1用户0用户组", required = true) Integer userType) {
JsonResponse response = new JsonResponse(false, "更新失败");
switch (UserType.parse(userType)) {
case SSO:
switch (OperateTypeEnum.parse(operateType)) {
case Refuse:
if (heraSsoService.setValid(id, 0)) {
response.setSuccess(true);
response.setMessage("审核拒绝");
} else {
response.setSuccess(false);
response.setMessage("拒绝失败");
}
break;
case Delete:
if (heraSsoService.deleteSsoById(id)) {
response.setSuccess(true);
response.setMessage("删除成功");
} else {
response.setSuccess(false);
response.setMessage("删除失败");
}
break;
case Approve:
if (heraSsoService.setValid(id, 1)) {
response.setSuccess(true);
response.setMessage("审核通过");
} else {
response.setSuccess(false);
response.setMessage("审核通过失败");
}
break;
default:
response.setMessage("未知的操作类型");
response.setSuccess(false);
break;
}
break;
case ADMIN:
int result;
switch (OperateTypeEnum.parse(operateType)) {
case Refuse:
result = heraUserService.updateEffective(id, "0");
if (result > 0) {
response.setMessage("审核拒绝");
response.setSuccess(true);
}
break;
case Delete:
result = heraUserService.delete(id);
if (result > 0) {
response.setMessage("删除成功");
response.setSuccess(true);
}
break;
case Approve:
result = heraUserService.updateEffective(id, "1");
if (result > 0) {
HeraUser user = heraUserService.findById(id);
if (user != null) {
HeraFile file = heraFileService.findDocByOwner(user.getName());
if (file == null) {
Integer integer = heraFileService.insert(HeraFile.builder().name("个人文档").owner(user.getName()).type(1).build());
if (integer <= 0) {
return new JsonResponse(false, "新增文档失败,请联系管理员");
}
}
}
response.setMessage("审核通过");
response.setSuccess(true);
}
break;
default:
response.setMessage("未知的操作类型");
response.setSuccess(false);
break;
}
break;
default:
response.setSuccess(false);
response.setMessage("未知的用户类型");
break;
}
return response;
}
private enum OperateTypeEnum {
/**
* 删除操作
*/
Delete("1"),
/**
* 同意操作
*/
Approve("2"),
/**
* 拒绝操作
*/
Refuse("3");
private String operateType;
OperateTypeEnum(String type) {
this.operateType = type;
}
public static OperateTypeEnum parse(String operateType) {
Optional<OperateTypeEnum> typeEnum = Arrays.stream(OperateTypeEnum.values())
.filter(operate -> operate.toString().equals(operateType))
.findAny();
return typeEnum.orElse(OperateTypeEnum.Refuse);
}
@Override
public String toString() {
return operateType;
}
}
private enum UserType {
/**
* sso相关操作
*/
SSO(1),
/**
* 管理员相关
*/
ADMIN(0);
private Integer userType;
UserType(Integer type) {
this.userType = type;
}
public static UserType parse(Integer userType) {
Optional<UserType> typeEnum = Arrays.stream(UserType.values())
.filter(operate -> operate.userType.equals(userType))
.findAny();
return typeEnum.orElse(null);
}
}
}

View File

@ -0,0 +1,31 @@
package com.dfire.form;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* desc:
*
* @author scx
* @create 2019/07/10
*/
@Data
@ApiModel("任务模糊搜索表单")
public class JobSearchForm {
@ApiModelProperty("脚本内容")
private String script;
@ApiModelProperty("任务名称")
private String name;
@ApiModelProperty("任务描述")
private String description;
@ApiModelProperty("任务配置")
private String config;
@ApiModelProperty("开启状态")
private Integer auto;
@ApiModelProperty("执行类型")
private String runType;
}

View File

@ -0,0 +1 @@
org.springframework.boot.SpringApplicationRunListener=com.dfire.config.HeraRunListener

View File

@ -0,0 +1,11 @@
${AnsiColor.BRIGHT_CYAN}
_
| |__ ___ _ __ __ _
| '_ \ / _ \ '__/ _` |
| | | | __/ | | (_| |
|_| |_|\___|_| \__,_| create by: 凌霄 小苏打 生煎 火锅
${AnsiColor.BRIGHT_YELLOW}
::: Spring-Boot ${spring-boot.version} ::: Hera (version:${hera.version})
${AnsiColor.DEFAULT}

View File

@ -0,0 +1,346 @@
spring:
profiles:
active: @env@
http:
multipart:
max-file-size: 100Mb #允许上传文件的最大大小
max-request-size: 100Mb #允许上传文件的最大大小
encoding:
charset: utf-8
freemarker:
allow-request-override: true
cache: false
check-template-location: true
charset: utf-8
content-type: text/html
expose-request-attributes: false
expose-session-attributes: false
expose-spring-macro-helpers: false
suffix: .ftl
template-loader-path: classpath:/templates/
request-context-attribute: request
mvc:
throw-exception-if-no-handler-found: true
static-path-pattern: /static/**
druid:
datasource:
username: root #数据库用户名
password: moye #数据库密码
driver-class-name: com.mysql.jdbc.Driver #数据库驱动
url: jdbc:mysql://localhost:3306/hera2.4.2?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&autoReconnect=true&allowMultiQueries=true&useSSL=false
initial-size: 5 #初始化连接池数量
min-idle: 1 #最小生存连接数
max-active: 16 #最大连接池数量
max-wait: 5000 #获取连接时最大等待时间单位毫秒。配置了maxWait之后缺省启用公平锁并发效率会有所下降如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
time-between-connect-error-millis: 60000 # Destroy线程会检测连接的间隔时间如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接单位是毫秒
min-evictable-idle-time-millis: 300000 # 连接保持空闲而不被驱逐的最长时间,单位是毫秒
test-while-idle: true #申请连接的时候,如果检测到连接空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效
test-on-borrow: true #申请连接时执行validationQuery检测连接是否有效
test-on-return: false # 归还连接时执行validationQuery检测连接是否有效
connection-init-sqls: 'set names utf8mb4'
validation-query: select 1 #用来检测连接是否有效的sql要求是一个查询语句。如果validationQuery为nulltestOnBorrow、testOnReturn、testWhileIdle都不会其作用。
validation-query-timeout: 1 #单位检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
log-abandoned: true
stat-mergeSql: true
filters: stat,wall,log4j
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
server:
port: 8080
context-path: /hera
clean:
path: ${server.context-path}
#hera全局配置
hera:
defaultWorkerGroup: 1 #默认worker的host组id
preemptionMasterGroup: 1 #抢占master的host组id
excludeFile: jar;war
maxMemRate: 0.70 #已使用内存占总内存的最大比例,默认0.75
maxCpuLoadPerCore: 1.0 #cpu load per core等于最近1分钟系统的平均cpu负载÷cpu核心数量默认1.0
scanRate: 1000 #队列扫描频率(毫秒)
systemMemUsed: 4000 # 系统占用内存
requestTimeout: 10000 # 异步请求超时时间
channelTimeout: 1000 # netty请求超时时间
perTaskUseMem: 500 # 每个任务使用内存500M
warmUpCheck: 0 # 热身检测 默认10秒,0 表示关闭。在任务连续两次发送到同一台work时 会进行预热检测。#由于任务执行需要经历 发送指令-接受指令-初始化环境-提交任务-开始执行 等阶段, 如果任务分发频率较高时,容易宕机
jobCacheDay: 2 # <<该配置很重要>> 表示action最远可以重跑任务的日期默认2天。
loadBalance: roundrobin # 负载均衡策略,默认轮训 可选值有roundrobin轮训random(随机)
heartBeat: 2 # 心跳传递时间频率
workDir: /opt/logs/spring-boot # 工作路径 执行的任务文件/上传的文件都在这里
hdfsUploadPath: /hera/hdfs-upload-dir/ #此处必须是hdfs路径所有的上传附件都会存放在下面路径上.注意:必须保证启动hera项目的用户是此文件夹的所有者否则会导致上传错误
schedule-group: online
maxParallelNum: 2000 #master 允许的最大并行任务 当大于此数值 将会放在阻塞队列中
connectPort: 9887 #netty通信的端口
admin: hera # admin用户
taskTimeout: 12 #单个任务执行的最大时间 单位:小时
env: ${spring.profiles.active}
alarmEnv: daily,dev,pre,publish # 设置允许哪些环境开启告警,多个用,分开,默认全部环境
sudoUser: false #是否要使用sudo -u 切换账号(即启动多租户功能)
version: 2.4.2
kerberos:
keytabpath: #kerberos认证keytab文件,如果hadoop 集群无需kerberos授权则不填
principal: #kerberos认证principal,如果hadoop 集群无需kerberos授权则不填
job:
shell:
bin: bash
hive:
bin: hive
spark-sql:
bin: spark-sql
script-echo: true
emrJob: false #是否为emr集群 false 表示为本地hadoop集群true:amazon 亚马逊emr集群任务 true:aliYun 阿里云集群任务
emr_fixed_host: #emr固定集群地址
area: all
keyPath: /home/docker/conf/bigdata.pem #emr登录集群到私钥的绝对路径
monitorUsers: 00001 #(微信工号,只适用于企业微信告警 )多个用|分割
monitorEmails: monitor@qq.com #群发监控者的邮箱(任务任务失败都会通知到该列表) 多个用;分割
webSessionExpire: 4320 # web session失效时间单位为分钟3天*24*60=4320
webLogHeadCount: 5000 # web端的任务详情日志展示头部行数
webLogTailCount: 5000 # web端的任务详情日志展示尾部行数
emr_groups: #根据hera组来创建集群达到不同组任务执行集群隔离的效果。值为hera的组比如配置 hera.emr_groups: hera 则此时动态集群会创建两个一个是hera集群:只供hera组的任务使用一个是other集群:非hera组的任务执行
aws_emr_type: SPOT #emr 动态集群的类型,可选:SPOT,ON_DEMAND 默认使用竞价,最大按需
rerun:
maxParallelNum: 20 #任务重跑允许调度的最大上限。如果当前调度的任务超出此上限,将阻塞重跑任务调度,避免重跑影响了正常的调度
timeRange: 0-23 #重跑允许调度的时间范围
aliYun:
accessKey: xx #阿里云创建集群的ak
accessSecret: xx #阿里云创建集群的as
india:
accessKey: xx #印度amazon创建集群的ak
accessSecret: xx #印度amazon创建集群的as
logging:
config: classpath:config/logback-spring.xml
path: /opt/logs/spring-boot # 日志路径
level:
root: INFO
org.springframework: ERROR
com.dfire.common.mapper: ERROR
# 发送配置邮件的发送者
mail:
host: smtp.mxhichina.com
protocol: smtp
port: 465
user: xxx
password: xxx
mybatis:
configuration:
mapUnderscoreToCamelCase: true
#spark 配置
spark:
address: jdbc:hive2://localhost:10000 #ThriftServer地址
driver: org.apache.hive.jdbc.HiveDriver #jdbc driver
username: xxx #ThriftServer用户名
password: 123456 #ThriftServer密码
master: --master yarn
driver-memory: --driver-memory 1g
driver-cores: --driver-cores 1
executor-memory: --executor-memory 2g
executor-cores: --executor-cores 1
---
# 中国
spring:
profiles: dev
hera:
admin: hera
emrJob: false
area: CN
---
## 中国开发环境
spring:
profiles: dev_cn
druid:
datasource:
url: jdbc:mysql://localhost:3306/hera?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull&amp;autoReconnect=true&allowMultiQueries=true
username: root
password: root
hera:
area: CN
emrJob: true:aliYun
server:
port: 8121
context-path: /
---
## 中国日常环境
spring:
profiles: daily_cn
druid:
datasource:
url: xx
username: xx
password: xx
hera:
area: CN
emrJob: true:aliYun
maxParallelNum: 2
server:
port: 8121
context-path: /
---
## 中国预发环境
spring:
profiles: pre_cn
druid:
datasource:
url: xx
username: xx
password: xx
hera:
area: CN
emrJob: true:aliYun
emr_fixed_host: xx.xx.xx.xx
hdfsUploadPath: oss://xxx/pre/hera/
server:
port: 8121
context-path: /
---
## 中国线上环境
spring:
profiles: prod_cn
druid:
datasource:
url: xx
username: xx
password: xx
hera:
area: CN
emrJob: true:aliYun
emr_fixed_host: xx.xx.xx.xx
hdfsUploadPath: oss://xxx/hera/
server:
port: 8121
context-path: /
---
##############################################################################################################################
##############################################################################################################################
##############################################################################################################################
##############################################################################################################################
# 以下所有环境 如果想根据 maven -P参数指定请增加文件/Users/scx/work/git/person/hera/pom.xml 中profiles配置 #
# #
# 以下所有环境 如果想根据 maven -P参数指定请增加文件/Users/scx/work/git/person/hera/pom.xml 中profiles配置 #
# #
# 以下所有环境 如果想根据 maven -P参数指定请增加文件/Users/scx/work/git/person/hera/pom.xml 中profiles配置 #
# #
# 以下所有环境 如果想根据 maven -P参数指定请增加文件/Users/scx/work/git/person/hera/pom.xml 中profiles配置 #
# #
# 以下所有环境 如果想根据 maven -P参数指定请增加文件/Users/scx/work/git/person/hera/pom.xml 中profiles配置 #
##############################################################################################################################
##############################################################################################################################
##############################################################################################################################
##############################################################################################################################
## 印度预发环境
spring:
profiles: pre_ind
hera:
emrJob: true:amazon
area: IND
maxParallelNum: 40
server:
port: 8121
context-path: /
druid:
datasource:
username: xx
password: xx
url: xx
---
## 欧州预发环境
spring:
profiles: pre_eu
hera:
emrJob: true:amazon
area: EU
maxParallelNum: 40
emr_fixed_host: xx.xx.xx.xx
hdfsUploadPath: s3://xxx/pre/hera/
server:
port: 8121
context-path: /
druid:
datasource:
username: xx
password: xx
url: xx
---
## 欧州线上环境
spring:
profiles: prod_eu
hera:
emrJob: true:amazon
area: EU
maxParallelNum: 40
emr_fixed_host: xx.xx.xx
hdfsUploadPath: s3://xxx/hera/
server:
port: 8121
context-path: /
druid:
datasource:
username: xx
password: xx
url: xx
---
## 美国预发环境
spring:
profiles: pre_us
hera:
emrJob: true:amazon
area: US
maxParallelNum: 40
emr_fixed_host: xx.xx.xx.xx
hdfsUploadPath: s3://xxx/pre/hera/
server:
port: 8121
context-path: /
druid:
datasource:
username: xx
password: xx
url: xx
---
## 美国线上环境
spring:
profiles: prod_us
hera:
emrJob: true:amazon
area: US
maxParallelNum: 40
emr_fixed_host: xx.xx.xx.xx
hdfsUploadPath: s3://xxx/hera/
server:
port: 8121
context-path: /
druid:
datasource:
username: xx
password: xx
url: xx
---

View File

@ -0,0 +1,2 @@
# \u81EA\u5DF1\u8BBE\u7F6E\u670D\u52A1\u5668ip\uFF0C127.0.0.1 \u9ED8\u8BA4\u7CFB\u7EDF\u81EA\u5DF1\u67E5\u627E
server.ip=127.0.0.1

View File

@ -0,0 +1,242 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<jmxConfigurator/>
<property name="PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{32}.%M:%L %msg%n%xException%n"/>
<!--application.yml 传递参数不能使用logback 自带的<property>标签 -->
<springProperty scope="context" name="logPath" source="logging.path"/>
<springProperty scope="context" name="logLevel" source="logging.allLevel"/>
<springProperty scope="context" name="immediateFlush" source="logging.immediate.flush"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="scheduleLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/schedule.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/schedule.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="socketLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/socket.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/socket.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="heraLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/hera.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/hera.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="debugLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/debug.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/debug.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="all" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/info.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/info.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="masterLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/master.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/master.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="workerLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/worker.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/worker.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="heartLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/heart.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/heart.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>3</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="scanLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/scanLog.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/scan.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>3</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="taskLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/taskLog.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/task.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>3</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="errorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/errorLog.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/error.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>3</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<appender name="monitorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logPath}/monitorLog.log</file>
<Append>true</Append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/bak/monitor.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<!-- keep 10 days worth of history -->
<maxHistory>3</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${PATTERN}</pattern>
<immediateFlush>${immediateFlush}</immediateFlush>
</encoder>
</appender>
<!--控制台打印资源加载信息-->
<root>
<level value="${logLevel}"/>
<appender-ref ref="STDOUT"/>
<appender-ref ref="all"/>
</root>
<logger name="com.dfire.logs.TaskLog" additivity="true">
<level value="INFO"/>
<appender-ref ref="taskLog"/>
</logger>
<logger name="com.dfire.logs.MonitorLog" additivity="true">
<level value="INFO"/>
<appender-ref ref="monitorLog"/>
</logger>
<logger name="com.dfire.logs.HeartLog" additivity="true">
<level value="INFO"/>
<appender-ref ref="heartLog"/>
</logger>
<logger name="com.dfire.logs.MasterLog" additivity="true">
<level value="WARN"/>
<appender-ref ref="masterLog"/>
</logger>
<logger name="com.dfire.logs.WorkerLog" additivity="true">
<level value="WARN"/>
<appender-ref ref="workerLog"/>
</logger>
<logger name="com.dfire.logs.ScheduleLog" additivity="true">
<level value="INFO"/>
<appender-ref ref="scheduleLog"/>
</logger>
<logger name="com.dfire.logs.SocketLog" additivity="true">
<level value="INFO"/>
<appender-ref ref="socketLog"/>
</logger>
<logger name="com.dfire.logs.HeraLog" additivity="true">
<level value="INFO"/>
<appender-ref ref="heraLog"/>
</logger>
<logger name="com.dfire.logs.DebugLog" additivity="true">
<level value="INFO"/>
<appender-ref ref="debugLog"/>
</logger>
<logger name="com.dfire.logs.ScanLog" additivity="true">
<level value="INFO"/>
<appender-ref ref="scanLog"/>
</logger>
<logger name="com.dfire.logs.ErrorLog" additivity="true">
<level value="INFO"/>
<appender-ref ref="errorLog"/>
</logger>
</configuration>

View File

@ -0,0 +1,380 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
CREATE TABLE IF NOT EXISTS `hera_action`
(
`id` bigint(20) NOT NULL COMMENT '任务对应的唯一18位数字版本号',
`job_id` bigint(20) NOT NULL COMMENT '版本对应的任务id',
`auto` tinyint(2) DEFAULT NULL,
`configs` text COMMENT '任务的配置的变量',
`cron_expression` varchar(256) DEFAULT NULL COMMENT '当前版本对应的cron表达式',
`cycle` varchar(256) DEFAULT NULL COMMENT '是否为循环任务',
`dependencies` text COMMENT '依赖任务的版本号,逗号分隔',
`job_dependencies` varchar(2048) DEFAULT NULL COMMENT '依赖任务的id,逗号分隔',
`description` varchar(256) DEFAULT NULL COMMENT '版本描述',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`group_id` int(11) NOT NULL COMMENT '版本可运行分发的机器组',
`history_id` bigint(20) DEFAULT NULL COMMENT '当前版本运行的history id',
`host` varchar(32) DEFAULT NULL COMMENT '执行机器ip ',
`last_end_time` datetime DEFAULT NULL,
`last_result` varchar(256) DEFAULT NULL,
`name` varchar(256) NOT NULL DEFAULT '' COMMENT '任务描述',
`offset` tinyint(2) unsigned zerofill DEFAULT NULL,
`owner` varchar(32) NOT NULL COMMENT '任务的owner',
`post_processors` varchar(256) DEFAULT NULL,
`pre_processors` varchar(256) DEFAULT NULL,
`ready_dependency` text COMMENT '上游任务已完成的版本号',
`resources` text COMMENT '任务上传的资源配置',
`run_type` varchar(16) DEFAULT NULL COMMENT '任务触发类型(shell, hive)',
`schedule_type` tinyint(2) DEFAULT NULL COMMENT '任务调度类型(1,依赖调度2被依赖调度)',
`script` mediumtext COMMENT '任务对应的脚本',
`start_time` bigint(20) DEFAULT NULL,
`start_timestamp` bigint(20) DEFAULT NULL,
`statistic_end_time` datetime DEFAULT NULL,
`statistic_start_time` datetime DEFAULT NULL,
`status` varchar(16) DEFAULT NULL COMMENT '当前版本的运行状态job_history完成后会写更新此状态',
`timezone` varchar(32) DEFAULT NULL,
`host_group_id` tinyint(2) DEFAULT NULL COMMENT '任务可分配的执行服务器组',
`down_actions` varchar(16) DEFAULT NULL,
`batch_id` varchar(50) DEFAULT NULL COMMENT '批次号',
PRIMARY KEY (`id`),
KEY `ind_action_groupid` (`group_id`),
KEY `ind_actionjobid` (`job_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='job版本记录表';
CREATE TABLE IF NOT EXISTS `hera_action_history`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`job_id` bigint(20) DEFAULT NULL COMMENT 'hera任务id',
`action_id` bigint(20) DEFAULT NULL COMMENT '任务对应的版本号18位整数',
`cycle` varchar(16) DEFAULT NULL COMMENT '是否是循环任务',
`end_time` datetime DEFAULT NULL COMMENT '任务执行结束时间',
`execute_host` varchar(32) DEFAULT NULL COMMENT '当前版本任务执行的服务器',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`illustrate` varchar(256) DEFAULT NULL COMMENT '任务运行描述',
`log` longtext COMMENT '任务运行日志',
`operator` varchar(32) DEFAULT NULL COMMENT '任务运行操作人',
`properties` varchar(6144) DEFAULT NULL,
`start_time` datetime DEFAULT NULL COMMENT '任务开始执行的时间',
`statistic_end_time` datetime DEFAULT NULL COMMENT '版本生成结束时间',
`status` varchar(16) DEFAULT NULL COMMENT '当前版本的任务运行状态',
`timezone` varchar(32) DEFAULT NULL,
`trigger_type` tinyint(4) DEFAULT NULL COMMENT '任务触发类型(1,自动调度,2,手动触发,3,手动恢复)',
`host_group_id` int(11) DEFAULT NULL COMMENT '任务可分配的执行服务器组',
batch_id varchar(50) DEFAULT NULL COMMENT '批次号',
biz_label varchar(500) DEFAULT NULL COMMENT '标签',
PRIMARY KEY (`id`),
KEY `ind_acthisactionjobid` (`action_id`, `job_id`),
KEY `idx_job_id` (`job_id`),
KEY `ind_his_gmtcreate` (`gmt_create`),
KEY `ind_end_time` (`end_time`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='Job运行日志表';
CREATE TABLE IF NOT EXISTS `hera_advice`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`msg` varchar(256) DEFAULT NULL COMMENT '消息',
`address` varchar(256) DEFAULT NULL COMMENT 'ip地址',
`color` varchar(7) DEFAULT NULL COMMENT '颜色',
`create_time` varchar(19) DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='hera建议表';
CREATE TABLE IF NOT EXISTS `hera_area`
(
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '区域id',
`name` varchar(50) DEFAULT NULL COMMENT '区域名',
`timezone` varchar(25) DEFAULT NULL COMMENT '时区',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='hera任务区域表';
CREATE TABLE IF NOT EXISTS `hera_debug_history`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`end_time` datetime DEFAULT NULL COMMENT '运行结束时间',
`execute_host` varchar(255) DEFAULT NULL COMMENT '执行服务器',
`file_id` bigint(20) DEFAULT NULL COMMENT '脚本文件id',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '运行日志创建时间',
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '运行日志修改时间',
`log` longtext COMMENT '脚本运行日志',
`run_type` varchar(16) DEFAULT NULL COMMENT '运行类型hive,shell',
`script` longtext COMMENT '完整运行脚本,',
`start_time` datetime DEFAULT NULL COMMENT '统计开始时间',
`status` varchar(32) DEFAULT NULL COMMENT '脚本运行状态(runnin,success,failed,wait)',
`owner` varchar(32) DEFAULT NULL COMMENT '脚本owner',
`host_group_id` tinyint(4) DEFAULT NULL COMMENT '执行机器组id',
`job_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '关联调度任务id',
PRIMARY KEY (`id`),
KEY `idx_file_id` (`file_id`) USING BTREE
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
ROW_FORMAT = COMPACT COMMENT ='开发中心脚本运行日志表';
CREATE TABLE IF NOT EXISTS `hera_file`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`content` mediumtext COMMENT '脚本文件内容',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '脚本文件创建时间',
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`name` varchar(128) NOT NULL COMMENT '脚本名称',
`owner` varchar(32) NOT NULL COMMENT '脚本的owner',
`parent` int(20) DEFAULT NULL COMMENT '父目录id',
`type` tinyint(4) NOT NULL COMMENT '文件类型(1,目录,2,文件)',
`host_group_id` tinyint(2) DEFAULT NULL COMMENT '执行机器组id',
`job_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '关联调度任务id',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='开发中心脚本记录表';
CREATE TABLE IF NOT EXISTS `hera_group`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`configs` text,
`description` varchar(256) DEFAULT NULL,
`directory` int(11) NOT NULL,
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`name` varchar(255) NOT NULL,
`owner` varchar(255) NOT NULL,
`parent` int(11) DEFAULT NULL,
`resources` text,
`existed` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
KEY `ind_heragroupparent` (`parent`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `hera_host_group`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT NULL COMMENT '组描述',
`effective` tinyint(2) DEFAULT '0' COMMENT '是否有效1有效0无效',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`description` varchar(256) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='机器组记录表';
CREATE TABLE IF NOT EXISTS `hera_host_relation`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`host` varchar(32) DEFAULT NULL COMMENT '机器ip',
`host_group_id` int(11) DEFAULT NULL COMMENT '机器所在组id',
`domain` varchar(16) DEFAULT '' COMMENT '机器域名',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='机器与机器组关联表';
CREATE TABLE IF NOT EXISTS `hera_job`
(
`id` bigint(30) NOT NULL AUTO_INCREMENT COMMENT '任务id',
`auto` tinyint(2) DEFAULT '0' COMMENT '自动调度是否开启',
`configs` text COMMENT '配置的环境变量',
`cron_expression` varchar(32) DEFAULT NULL COMMENT 'cron表达式',
`cycle` varchar(16) DEFAULT NULL COMMENT '是否是循环任务',
`dependencies` varchar(2000) DEFAULT NULL COMMENT '依赖的任务id,逗号分隔',
`description` varchar(256) DEFAULT NULL COMMENT '任务描述',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`group_id` int(11) NOT NULL COMMENT '所在的目录 id',
`history_id` bigint(20) DEFAULT NULL COMMENT '运行历史id',
`host` varchar(32) DEFAULT NULL COMMENT '运行服务器ip',
`last_end_time` datetime DEFAULT NULL,
`last_result` varchar(16) DEFAULT NULL,
`name` varchar(256) NOT NULL COMMENT '任务名称',
`offset` int(11) DEFAULT NULL,
`owner` varchar(256) NOT NULL,
`post_processors` varchar(256) DEFAULT NULL COMMENT '任务运行所需的后置处理',
`pre_processors` varchar(256) DEFAULT NULL COMMENT '任务运行所需的前置处理',
`ready_dependency` varchar(16) DEFAULT NULL COMMENT '任务已完成的依赖',
`resources` text COMMENT '上传的资源文件配置',
`run_type` varchar(16) DEFAULT NULL COMMENT '运行的job类型(hive,shell)',
`schedule_type` tinyint(4) DEFAULT NULL COMMENT '任务调度类型',
`script` mediumtext COMMENT '脚本内容',
`start_time` datetime DEFAULT NULL,
`start_timestamp` bigint(20) DEFAULT NULL,
`statistic_end_time` datetime DEFAULT NULL,
`statistic_start_time` datetime DEFAULT NULL,
`status` varchar(16) DEFAULT NULL,
`timezone` varchar(32) DEFAULT NULL,
`host_group_id` tinyint(2) DEFAULT NULL COMMENT '分发的执行机器组id',
`must_end_minute` int(2) DEFAULT '0',
`area_id` varchar(50) DEFAULT '1' COMMENT '区域ID,多个用,分割',
`repeat_run` tinyint(2) DEFAULT '0' COMMENT '是否允许任务重复执行',
`is_valid` tinyint(1) DEFAULT '1' COMMENT '任务是否删除',
cron_period varchar(100) DEFAULT NULL,
cron_interval int(11) DEFAULT NULL,
biz_label varchar(500) DEFAULT '',
`estimated_end_hour` int(4) NOT NULL DEFAULT '0' COMMENT '预计结束结束时间',
PRIMARY KEY (`id`),
KEY `ind_zeusjobgroupid` (`group_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='hera的job 记录表';
CREATE TABLE IF NOT EXISTS `hera_job_monitor`
(
`job_id` bigint(20) NOT NULL,
`user_ids` varchar(100) NOT NULL,
PRIMARY KEY (`job_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `hera_lock`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`host` varchar(32) DEFAULT NULL COMMENT '机器对应ip',
`server_update` datetime DEFAULT NULL COMMENT '心跳更新时间',
`subgroup` varchar(32) DEFAULT NULL COMMENT '机器所在组,',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='分布式锁记录表';
CREATE TABLE IF NOT EXISTS `hera_permission`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`target_id` bigint(20) DEFAULT NULL COMMENT '授权的任务或者组id',
`type` varchar(32) DEFAULT NULL COMMENT '授权类型(job或者group)',
`uid` varchar(32) DEFAULT NULL COMMENT '被授权着名称',
`is_valid` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='任务授权记录表';
CREATE TABLE IF NOT EXISTS `hera_profile`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`hadoop_conf` varchar(2024) DEFAULT NULL COMMENT 'hadoop配置信息',
`uid` varchar(32) DEFAULT NULL COMMENT '用户名称',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='用户配置表';
CREATE TABLE IF NOT EXISTS `hera_user`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`name` varchar(255) DEFAULT NULL,
`phone` varchar(2000) DEFAULT NULL,
`uid` varchar(255) DEFAULT NULL,
`wangwang` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`user_type` int(11) DEFAULT '0',
`is_effective` int(11) DEFAULT '0',
`description` varchar(256) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `hera_sso`
(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(16) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(32) NOT NULL DEFAULT '' COMMENT '密码',
`gid` int(11) NOT NULL DEFAULT '0' COMMENT '组id对应hera_user的主键',
`phone` char(11) NOT NULL DEFAULT '' COMMENT '手机号',
`email` varchar(52) NOT NULL DEFAULT '' COMMENT '邮箱',
`job_number` char(5) NOT NULL DEFAULT '' COMMENT '工号',
`gmt_modified` bigint(20) NOT NULL DEFAULT '0' COMMENT '编辑时间',
`is_valid` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否有效0:无效1有效',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '新增时间',
PRIMARY KEY (`id`),
UNIQUE KEY `unq_name` (`name`) USING BTREE
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='sso用户表';
CREATE TABLE IF NOT EXISTS `hera_record`
(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '日志类型:比如编辑、更新',
`content` mediumtext COMMENT '脚本的变更前的内容',
`log_type` varchar(15) NOT NULL DEFAULT '-1' COMMENT '记录的日志的类型 比如:任务/组',
`log_id` int(11) NOT NULL DEFAULT '-1' COMMENT '任务id/组id',
`gmt_create` bigint(13) NOT NULL DEFAULT '1557814087800' COMMENT '创建时间',
`gmt_modified` bigint(13) NOT NULL DEFAULT '1557814087800' COMMENT '同步专用字段',
`sso` varchar(32) NOT NULL DEFAULT 'hera' COMMENT '用户名称',
`gId` int(11) NOT NULL DEFAULT '-1' COMMENT '组id',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 37
DEFAULT CHARSET = utf8mb4 COMMENT ='日志记录表';
CREATE TABLE `hera_rerun` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(256) NOT NULL DEFAULT '' COMMENT '重跑名称',
`start_millis` bigint(13) NOT NULL DEFAULT '0' COMMENT '其实跑的日期',
`end_millis` bigint(13) NOT NULL COMMENT '结束跑的日期',
`sso_name` varchar(16) NOT NULL COMMENT '创建人',
`extra` varchar(1000) NOT NULL DEFAULT '' COMMENT '其它配置',
`gmt_create` bigint(13) NOT NULL DEFAULT '0' COMMENT '创建时间',
`gmt_modified` bigint(13) NOT NULL DEFAULT '0' COMMENT '更新时间',
`job_id` int(11) NOT NULL DEFAULT '0' COMMENT '任务ID',
`is_end` tinyint(2) NOT NULL DEFAULT '0' COMMENT '是否结束',
`action_now` varchar(18) NOT NULL DEFAULT '' COMMENT '当前执行的版本号',
PRIMARY KEY (`id`),
KEY `idx_job_id` (`job_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='hera重跑任务表';
BEGIN;
## 添加默认用户组
insert into hera_user (email, name, uid, password, user_type, is_effective)
values ('1142819049@qq.com', 'hera', 'hera', 'd3886bd3bcba3d88e2ab14ba8c9326da', 0, 1);
## 添加默认用户
insert into hera_sso (name,password,gid,email,is_valid) values('hera','d3886bd3bcba3d88e2ab14ba8c9326da',1,'1142819049@qq.com',1);
## 添加默认区域
insert into hera_area (name)
values ('all');
## 初始化任务目录
INSERT INTO `hera_group`
VALUES ('1', '{\"name\":\"赫拉分布式任务调度系统\"}', '', '0', '2018-12-21 15:11:39', '2018-12-28 10:46:47', 'hera分布式调度系统', 'hera',
'0', '[]', '1'),
('2', '{\"qq\":\"1142819049\"}', '', '1', '2018-12-21 15:15:36', '2018-12-21 15:31:08', 'test', 'hera', '1',
'[]', '1');
## 添加初始化机器组
insert into hera_host_group (id, name, effective, description)
values (1, '默认组', 1, '机器默认组');
insert into hera_host_group (id, name, effective, description)
values (2, 'spark组', 1, '执行spark任务');
## 添加初始化任务
INSERT INTO `hera_job`
VALUES ('1', '0',
'{\"run.priority.level\":\"1\",\"roll.back.wait.time\":\"1\",\"roll.back.times\":\"0\",\"qqGroup\":\"965839395\"}',
'0 0 3 * * ?', null, '', '输出测试', '2018-12-22 11:14:55', '2019-01-04 11:14:09', '2', null, null, null, null,
'echoTest', null, 'hera', null, null, null, null, 'shell', '0',
'echo ${name}\n\necho \"当前时间戳\":${zdt.getTime()}\necho \" 明天\":${zdt.addDay(1).format(\"yyyy-MM-dd HH:mm:ss\")}\n\necho \"上个月的今天\": ${zdt.add(2,-1).format(\"yyyy-MM-dd HH:mm:ss\")}\n\necho \"真实的今天\":${zdt.getToday()}\n\n\necho \"如果需要更多时间查看HeraDateTool类,可以自定义时间\"\n\n\necho ${qqGroup}',
null, null, null, null, null, null, '1', null, '1', 0,1 ,'day', -1 ,'数据层,XXX业务','0');
## 初始化开发中心文档
INSERT INTO `hera_file`
VALUES ('1', null, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '个人文档', 'hera', null, '1', '0',0);
INSERT INTO `hera_file`
VALUES ('2', null, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '共享文档', 'all', null, '1', '0',0);
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1,418 @@
/*!
* Datetimepicker for Bootstrap
*
* Copyright 2012 Stefan Petre
* Improvements by Andrew Rowls
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
.datetimepicker {
padding: 4px;
margin-top: 1px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
direction: ltr;
}
.datetimepicker-inline {
width: 220px;
}
.datetimepicker.datetimepicker-rtl {
direction: rtl;
}
.datetimepicker.datetimepicker-rtl table tr td span {
float: right;
}
.datetimepicker-dropdown, .datetimepicker-dropdown-left {
top: 0;
left: 0;
}
[class*=" datetimepicker-dropdown"]:before {
content: '';
display: inline-block;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #cccccc;
border-bottom-color: rgba(0, 0, 0, 0.2);
position: absolute;
}
[class*=" datetimepicker-dropdown"]:after {
content: '';
display: inline-block;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #ffffff;
position: absolute;
}
[class*=" datetimepicker-dropdown-top"]:before {
content: '';
display: inline-block;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #cccccc;
border-top-color: rgba(0, 0, 0, 0.2);
border-bottom: 0;
}
[class*=" datetimepicker-dropdown-top"]:after {
content: '';
display: inline-block;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid #ffffff;
border-bottom: 0;
}
.datetimepicker-dropdown-bottom-left:before {
top: -7px;
right: 6px;
}
.datetimepicker-dropdown-bottom-left:after {
top: -6px;
right: 7px;
}
.datetimepicker-dropdown-bottom-right:before {
top: -7px;
left: 6px;
}
.datetimepicker-dropdown-bottom-right:after {
top: -6px;
left: 7px;
}
.datetimepicker-dropdown-top-left:before {
bottom: -7px;
right: 6px;
}
.datetimepicker-dropdown-top-left:after {
bottom: -6px;
right: 7px;
}
.datetimepicker-dropdown-top-right:before {
bottom: -7px;
left: 6px;
}
.datetimepicker-dropdown-top-right:after {
bottom: -6px;
left: 7px;
}
.datetimepicker > div {
display: none;
}
.datetimepicker.minutes div.datetimepicker-minutes {
display: block;
}
.datetimepicker.hours div.datetimepicker-hours {
display: block;
}
.datetimepicker.days div.datetimepicker-days {
display: block;
}
.datetimepicker.months div.datetimepicker-months {
display: block;
}
.datetimepicker.years div.datetimepicker-years {
display: block;
}
.datetimepicker table {
margin: 0;
}
.datetimepicker td,
.datetimepicker th {
text-align: center;
width: 20px;
height: 20px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
border: none;
}
.table-striped .datetimepicker table tr td,
.table-striped .datetimepicker table tr th {
background-color: transparent;
}
.datetimepicker table tr td.minute:hover {
background: #eeeeee;
cursor: pointer;
}
.datetimepicker table tr td.hour:hover {
background: #eeeeee;
cursor: pointer;
}
.datetimepicker table tr td.day:hover {
background: #eeeeee;
cursor: pointer;
}
.datetimepicker table tr td.old,
.datetimepicker table tr td.new {
color: #999999;
}
.datetimepicker table tr td.disabled,
.datetimepicker table tr td.disabled:hover {
background: none;
color: #999999;
cursor: default;
}
.datetimepicker table tr td.today,
.datetimepicker table tr td.today:hover,
.datetimepicker table tr td.today.disabled,
.datetimepicker table tr td.today.disabled:hover {
background-color: #fde19a;
background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
background-image: linear-gradient(to bottom, #fdd49a, #fdf59a);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
border-color: #fdf59a #fdf59a #fbed50;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
}
.datetimepicker table tr td.today:hover,
.datetimepicker table tr td.today:hover:hover,
.datetimepicker table tr td.today.disabled:hover,
.datetimepicker table tr td.today.disabled:hover:hover,
.datetimepicker table tr td.today:active,
.datetimepicker table tr td.today:hover:active,
.datetimepicker table tr td.today.disabled:active,
.datetimepicker table tr td.today.disabled:hover:active,
.datetimepicker table tr td.today.active,
.datetimepicker table tr td.today:hover.active,
.datetimepicker table tr td.today.disabled.active,
.datetimepicker table tr td.today.disabled:hover.active,
.datetimepicker table tr td.today.disabled,
.datetimepicker table tr td.today:hover.disabled,
.datetimepicker table tr td.today.disabled.disabled,
.datetimepicker table tr td.today.disabled:hover.disabled,
.datetimepicker table tr td.today[disabled],
.datetimepicker table tr td.today:hover[disabled],
.datetimepicker table tr td.today.disabled[disabled],
.datetimepicker table tr td.today.disabled:hover[disabled] {
background-color: #fdf59a;
}
.datetimepicker table tr td.today:active,
.datetimepicker table tr td.today:hover:active,
.datetimepicker table tr td.today.disabled:active,
.datetimepicker table tr td.today.disabled:hover:active,
.datetimepicker table tr td.today.active,
.datetimepicker table tr td.today:hover.active,
.datetimepicker table tr td.today.disabled.active,
.datetimepicker table tr td.today.disabled:hover.active {
background-color: #fbf069;
}
.datetimepicker table tr td.active,
.datetimepicker table tr td.active:hover,
.datetimepicker table tr td.active.disabled,
.datetimepicker table tr td.active.disabled:hover {
background-color: #006dcc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
border-color: #0044cc #0044cc #002a80;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datetimepicker table tr td.active:hover,
.datetimepicker table tr td.active:hover:hover,
.datetimepicker table tr td.active.disabled:hover,
.datetimepicker table tr td.active.disabled:hover:hover,
.datetimepicker table tr td.active:active,
.datetimepicker table tr td.active:hover:active,
.datetimepicker table tr td.active.disabled:active,
.datetimepicker table tr td.active.disabled:hover:active,
.datetimepicker table tr td.active.active,
.datetimepicker table tr td.active:hover.active,
.datetimepicker table tr td.active.disabled.active,
.datetimepicker table tr td.active.disabled:hover.active,
.datetimepicker table tr td.active.disabled,
.datetimepicker table tr td.active:hover.disabled,
.datetimepicker table tr td.active.disabled.disabled,
.datetimepicker table tr td.active.disabled:hover.disabled,
.datetimepicker table tr td.active[disabled],
.datetimepicker table tr td.active:hover[disabled],
.datetimepicker table tr td.active.disabled[disabled],
.datetimepicker table tr td.active.disabled:hover[disabled] {
background-color: #0044cc;
}
.datetimepicker table tr td.active:active,
.datetimepicker table tr td.active:hover:active,
.datetimepicker table tr td.active.disabled:active,
.datetimepicker table tr td.active.disabled:hover:active,
.datetimepicker table tr td.active.active,
.datetimepicker table tr td.active:hover.active,
.datetimepicker table tr td.active.disabled.active,
.datetimepicker table tr td.active.disabled:hover.active {
background-color: #003399;
}
.datetimepicker table tr td span {
display: block;
width: 23%;
height: 54px;
line-height: 54px;
float: left;
margin: 1%;
cursor: pointer;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.datetimepicker .datetimepicker-hours span {
height: 26px;
line-height: 26px;
}
.datetimepicker .datetimepicker-hours table tr td span.hour_am,
.datetimepicker .datetimepicker-hours table tr td span.hour_pm {
width: 14.6%;
}
.datetimepicker .datetimepicker-hours fieldset legend,
.datetimepicker .datetimepicker-minutes fieldset legend {
margin-bottom: inherit;
line-height: 30px;
}
.datetimepicker .datetimepicker-minutes span {
height: 26px;
line-height: 26px;
}
.datetimepicker table tr td span:hover {
background: #eeeeee;
}
.datetimepicker table tr td span.disabled,
.datetimepicker table tr td span.disabled:hover {
background: none;
color: #999999;
cursor: default;
}
.datetimepicker table tr td span.active,
.datetimepicker table tr td span.active:hover,
.datetimepicker table tr td span.active.disabled,
.datetimepicker table tr td span.active.disabled:hover {
background-color: #006dcc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(to bottom, #0088cc, #0044cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
border-color: #0044cc #0044cc #002a80;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datetimepicker table tr td span.active:hover,
.datetimepicker table tr td span.active:hover:hover,
.datetimepicker table tr td span.active.disabled:hover,
.datetimepicker table tr td span.active.disabled:hover:hover,
.datetimepicker table tr td span.active:active,
.datetimepicker table tr td span.active:hover:active,
.datetimepicker table tr td span.active.disabled:active,
.datetimepicker table tr td span.active.disabled:hover:active,
.datetimepicker table tr td span.active.active,
.datetimepicker table tr td span.active:hover.active,
.datetimepicker table tr td span.active.disabled.active,
.datetimepicker table tr td span.active.disabled:hover.active,
.datetimepicker table tr td span.active.disabled,
.datetimepicker table tr td span.active:hover.disabled,
.datetimepicker table tr td span.active.disabled.disabled,
.datetimepicker table tr td span.active.disabled:hover.disabled,
.datetimepicker table tr td span.active[disabled],
.datetimepicker table tr td span.active:hover[disabled],
.datetimepicker table tr td span.active.disabled[disabled],
.datetimepicker table tr td span.active.disabled:hover[disabled] {
background-color: #0044cc;
}
.datetimepicker table tr td span.active:active,
.datetimepicker table tr td span.active:hover:active,
.datetimepicker table tr td span.active.disabled:active,
.datetimepicker table tr td span.active.disabled:hover:active,
.datetimepicker table tr td span.active.active,
.datetimepicker table tr td span.active:hover.active,
.datetimepicker table tr td span.active.disabled.active,
.datetimepicker table tr td span.active.disabled:hover.active {
background-color: #003399;
}
.datetimepicker table tr td span.old {
color: #999999;
}
.datetimepicker th.switch {
width: 145px;
}
.datetimepicker th span.glyphicon {
pointer-events: none;
}
.datetimepicker thead tr:first-child th,
.datetimepicker tfoot th {
cursor: pointer;
}
.datetimepicker thead tr:first-child th:hover,
.datetimepicker tfoot th:hover {
background: #eeeeee;
}
.input-append.date .add-on i,
.input-prepend.date .add-on i,
.input-group.date .input-group-addon span {
cursor: pointer;
width: 14px;
height: 14px;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,59 @@
.nav-tabs li .closeable {
/* top: 65px; */
/* position: fixed; */
padding-left: 3px;
cursor: pointer;
}
/*修改tab高度样式*/
.nav-tabs li a{
line-height:1px;
}
.nav-tabs .active a{
border-top: solid 1px #3498db !important;
}
.nav-tabs{
background: #fafafa;
border-bottom: 1px #3498db solid;
}
.nav-tabs li a{
line-height:1;
border:1px #ddd solid;
margin-right: -1px;
color: #999;
border-radius: 0;
}
.nav-tabs li a .glyphicon-remove-sign:hover{
color:red;
cursor: pointer;
}
.nav-tabs li a i:first-child{
margin-right: 1px;
}
.nav-tabs .active a{
border-top: solid 1px #3498db !important;
background: #3498db !important;
color:#fff !important;
}
.nav-my-tab{
padding-left: 0px;
margin-bottom: 0px;
}
.nav-my-tab .middletab{
height: 36px;
overflow: hidden;
border-bottom: 3px #3498db solid;
position: relative;
background: #fafafa;
}
.nav-my-tab li{
list-style-type: none;
}
.nav-my-tab li a{
padding:5px 10px;
}

View File

@ -0,0 +1,587 @@
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
.btn-default.disabled,
.btn-primary.disabled,
.btn-success.disabled,
.btn-info.disabled,
.btn-warning.disabled,
.btn-danger.disabled,
.btn-default[disabled],
.btn-primary[disabled],
.btn-success[disabled],
.btn-info[disabled],
.btn-warning[disabled],
.btn-danger[disabled],
fieldset[disabled] .btn-default,
fieldset[disabled] .btn-primary,
fieldset[disabled] .btn-success,
fieldset[disabled] .btn-info,
fieldset[disabled] .btn-warning,
fieldset[disabled] .btn-danger {
-webkit-box-shadow: none;
box-shadow: none;
}
.btn-default .badge,
.btn-primary .badge,
.btn-success .badge,
.btn-info .badge,
.btn-warning .badge,
.btn-danger .badge {
text-shadow: none;
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-default.disabled,
.btn-default[disabled],
fieldset[disabled] .btn-default,
.btn-default.disabled:hover,
.btn-default[disabled]:hover,
fieldset[disabled] .btn-default:hover,
.btn-default.disabled:focus,
.btn-default[disabled]:focus,
fieldset[disabled] .btn-default:focus,
.btn-default.disabled.focus,
.btn-default[disabled].focus,
fieldset[disabled] .btn-default.focus,
.btn-default.disabled:active,
.btn-default[disabled]:active,
fieldset[disabled] .btn-default:active,
.btn-default.disabled.active,
.btn-default[disabled].active,
fieldset[disabled] .btn-default.active {
background-color: #e0e0e0;
background-image: none;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #245580;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #265a88;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #265a88;
border-color: #245580;
}
.btn-primary.disabled,
.btn-primary[disabled],
fieldset[disabled] .btn-primary,
.btn-primary.disabled:hover,
.btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary:hover,
.btn-primary.disabled:focus,
.btn-primary[disabled]:focus,
fieldset[disabled] .btn-primary:focus,
.btn-primary.disabled.focus,
.btn-primary[disabled].focus,
fieldset[disabled] .btn-primary.focus,
.btn-primary.disabled:active,
.btn-primary[disabled]:active,
fieldset[disabled] .btn-primary:active,
.btn-primary.disabled.active,
.btn-primary[disabled].active,
fieldset[disabled] .btn-primary.active {
background-color: #265a88;
background-image: none;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-success.disabled,
.btn-success[disabled],
fieldset[disabled] .btn-success,
.btn-success.disabled:hover,
.btn-success[disabled]:hover,
fieldset[disabled] .btn-success:hover,
.btn-success.disabled:focus,
.btn-success[disabled]:focus,
fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus,
.btn-success.disabled:active,
.btn-success[disabled]:active,
fieldset[disabled] .btn-success:active,
.btn-success.disabled.active,
.btn-success[disabled].active,
fieldset[disabled] .btn-success.active {
background-color: #419641;
background-image: none;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-info.disabled,
.btn-info[disabled],
fieldset[disabled] .btn-info,
.btn-info.disabled:hover,
.btn-info[disabled]:hover,
fieldset[disabled] .btn-info:hover,
.btn-info.disabled:focus,
.btn-info[disabled]:focus,
fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus,
.btn-info.disabled:active,
.btn-info[disabled]:active,
fieldset[disabled] .btn-info:active,
.btn-info.disabled.active,
.btn-info[disabled].active,
fieldset[disabled] .btn-info.active {
background-color: #2aabd2;
background-image: none;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-warning.disabled,
.btn-warning[disabled],
fieldset[disabled] .btn-warning,
.btn-warning.disabled:hover,
.btn-warning[disabled]:hover,
fieldset[disabled] .btn-warning:hover,
.btn-warning.disabled:focus,
.btn-warning[disabled]:focus,
fieldset[disabled] .btn-warning:focus,
.btn-warning.disabled.focus,
.btn-warning[disabled].focus,
fieldset[disabled] .btn-warning.focus,
.btn-warning.disabled:active,
.btn-warning[disabled]:active,
fieldset[disabled] .btn-warning:active,
.btn-warning.disabled.active,
.btn-warning[disabled].active,
fieldset[disabled] .btn-warning.active {
background-color: #eb9316;
background-image: none;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.btn-danger.disabled,
.btn-danger[disabled],
fieldset[disabled] .btn-danger,
.btn-danger.disabled:hover,
.btn-danger[disabled]:hover,
fieldset[disabled] .btn-danger:hover,
.btn-danger.disabled:focus,
.btn-danger[disabled]:focus,
fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus,
.btn-danger.disabled:active,
.btn-danger[disabled]:active,
fieldset[disabled] .btn-danger:active,
.btn-danger.disabled.active,
.btn-danger[disabled].active,
fieldset[disabled] .btn-danger.active {
background-color: #c12e2a;
background-image: none;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #e8e8e8;
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-color: #2e6da4;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.navbar-default {
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
}
.navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
}
.navbar-inverse .navbar-nav > .open > a,
.navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
@media (max-width: 767px) {
.navbar .navbar-nav .open .dropdown-menu > .active > a,
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
color: #fff;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #286090;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
background-repeat: repeat-x;
border-color: #2b669a;
}
.list-group-item.active .badge,
.list-group-item.active:hover .badge,
.list-group-item.active:focus .badge {
text-shadow: none;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
.panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
background-repeat: repeat-x;
}
.panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
background-repeat: repeat-x;
}
.panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
background-repeat: repeat-x;
}
.panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
background-repeat: repeat-x;
}
.well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
}
/*# sourceMappingURL=bootstrap-theme.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,288 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
<font-face units-per-em="1200" ascent="960" descent="-240" />
<missing-glyph horiz-adv-x="500" />
<glyph horiz-adv-x="0" />
<glyph horiz-adv-x="400" />
<glyph unicode=" " />
<glyph unicode="*" d="M600 1100q15 0 34 -1.5t30 -3.5l11 -1q10 -2 17.5 -10.5t7.5 -18.5v-224l158 158q7 7 18 8t19 -6l106 -106q7 -8 6 -19t-8 -18l-158 -158h224q10 0 18.5 -7.5t10.5 -17.5q6 -41 6 -75q0 -15 -1.5 -34t-3.5 -30l-1 -11q-2 -10 -10.5 -17.5t-18.5 -7.5h-224l158 -158 q7 -7 8 -18t-6 -19l-106 -106q-8 -7 -19 -6t-18 8l-158 158v-224q0 -10 -7.5 -18.5t-17.5 -10.5q-41 -6 -75 -6q-15 0 -34 1.5t-30 3.5l-11 1q-10 2 -17.5 10.5t-7.5 18.5v224l-158 -158q-7 -7 -18 -8t-19 6l-106 106q-7 8 -6 19t8 18l158 158h-224q-10 0 -18.5 7.5 t-10.5 17.5q-6 41 -6 75q0 15 1.5 34t3.5 30l1 11q2 10 10.5 17.5t18.5 7.5h224l-158 158q-7 7 -8 18t6 19l106 106q8 7 19 6t18 -8l158 -158v224q0 10 7.5 18.5t17.5 10.5q41 6 75 6z" />
<glyph unicode="+" d="M450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-350h350q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-350v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v350h-350q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5 h350v350q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xa0;" />
<glyph unicode="&#xa5;" d="M825 1100h250q10 0 12.5 -5t-5.5 -13l-364 -364q-6 -6 -11 -18h268q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-100h275q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-174q0 -11 -7.5 -18.5t-18.5 -7.5h-148q-11 0 -18.5 7.5t-7.5 18.5v174 h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h125v100h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h118q-5 12 -11 18l-364 364q-8 8 -5.5 13t12.5 5h250q25 0 43 -18l164 -164q8 -8 18 -8t18 8l164 164q18 18 43 18z" />
<glyph unicode="&#x2000;" horiz-adv-x="650" />
<glyph unicode="&#x2001;" horiz-adv-x="1300" />
<glyph unicode="&#x2002;" horiz-adv-x="650" />
<glyph unicode="&#x2003;" horiz-adv-x="1300" />
<glyph unicode="&#x2004;" horiz-adv-x="433" />
<glyph unicode="&#x2005;" horiz-adv-x="325" />
<glyph unicode="&#x2006;" horiz-adv-x="216" />
<glyph unicode="&#x2007;" horiz-adv-x="216" />
<glyph unicode="&#x2008;" horiz-adv-x="162" />
<glyph unicode="&#x2009;" horiz-adv-x="260" />
<glyph unicode="&#x200a;" horiz-adv-x="72" />
<glyph unicode="&#x202f;" horiz-adv-x="260" />
<glyph unicode="&#x205f;" horiz-adv-x="325" />
<glyph unicode="&#x20ac;" d="M744 1198q242 0 354 -189q60 -104 66 -209h-181q0 45 -17.5 82.5t-43.5 61.5t-58 40.5t-60.5 24t-51.5 7.5q-19 0 -40.5 -5.5t-49.5 -20.5t-53 -38t-49 -62.5t-39 -89.5h379l-100 -100h-300q-6 -50 -6 -100h406l-100 -100h-300q9 -74 33 -132t52.5 -91t61.5 -54.5t59 -29 t47 -7.5q22 0 50.5 7.5t60.5 24.5t58 41t43.5 61t17.5 80h174q-30 -171 -128 -278q-107 -117 -274 -117q-206 0 -324 158q-36 48 -69 133t-45 204h-217l100 100h112q1 47 6 100h-218l100 100h134q20 87 51 153.5t62 103.5q117 141 297 141z" />
<glyph unicode="&#x20bd;" d="M428 1200h350q67 0 120 -13t86 -31t57 -49.5t35 -56.5t17 -64.5t6.5 -60.5t0.5 -57v-16.5v-16.5q0 -36 -0.5 -57t-6.5 -61t-17 -65t-35 -57t-57 -50.5t-86 -31.5t-120 -13h-178l-2 -100h288q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-138v-175q0 -11 -5.5 -18 t-15.5 -7h-149q-10 0 -17.5 7.5t-7.5 17.5v175h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v100h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v475q0 10 7.5 17.5t17.5 7.5zM600 1000v-300h203q64 0 86.5 33t22.5 119q0 84 -22.5 116t-86.5 32h-203z" />
<glyph unicode="&#x2212;" d="M250 700h800q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#x231b;" d="M1000 1200v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-50v-100q0 -91 -49.5 -165.5t-130.5 -109.5q81 -35 130.5 -109.5t49.5 -165.5v-150h50q21 0 35.5 -14.5t14.5 -35.5v-150h-800v150q0 21 14.5 35.5t35.5 14.5h50v150q0 91 49.5 165.5t130.5 109.5q-81 35 -130.5 109.5 t-49.5 165.5v100h-50q-21 0 -35.5 14.5t-14.5 35.5v150h800zM400 1000v-100q0 -60 32.5 -109.5t87.5 -73.5q28 -12 44 -37t16 -55t-16 -55t-44 -37q-55 -24 -87.5 -73.5t-32.5 -109.5v-150h400v150q0 60 -32.5 109.5t-87.5 73.5q-28 12 -44 37t-16 55t16 55t44 37 q55 24 87.5 73.5t32.5 109.5v100h-400z" />
<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
<glyph unicode="&#x2601;" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -206.5q0 -121 -85 -207.5t-205 -86.5h-750q-79 0 -135.5 57t-56.5 137q0 69 42.5 122.5t108.5 67.5q-2 12 -2 37q0 153 108 260.5t260 107.5z" />
<glyph unicode="&#x26fa;" d="M774 1193.5q16 -9.5 20.5 -27t-5.5 -33.5l-136 -187l467 -746h30q20 0 35 -18.5t15 -39.5v-42h-1200v42q0 21 15 39.5t35 18.5h30l468 746l-135 183q-10 16 -5.5 34t20.5 28t34 5.5t28 -20.5l111 -148l112 150q9 16 27 20.5t34 -5zM600 200h377l-182 112l-195 534v-646z " />
<glyph unicode="&#x2709;" d="M25 1100h1150q10 0 12.5 -5t-5.5 -13l-564 -567q-8 -8 -18 -8t-18 8l-564 567q-8 8 -5.5 13t12.5 5zM18 882l264 -264q8 -8 8 -18t-8 -18l-264 -264q-8 -8 -13 -5.5t-5 12.5v550q0 10 5 12.5t13 -5.5zM918 618l264 264q8 8 13 5.5t5 -12.5v-550q0 -10 -5 -12.5t-13 5.5 l-264 264q-8 8 -8 18t8 18zM818 482l364 -364q8 -8 5.5 -13t-12.5 -5h-1150q-10 0 -12.5 5t5.5 13l364 364q8 8 18 8t18 -8l164 -164q8 -8 18 -8t18 8l164 164q8 8 18 8t18 -8z" />
<glyph unicode="&#x270f;" d="M1011 1210q19 0 33 -13l153 -153q13 -14 13 -33t-13 -33l-99 -92l-214 214l95 96q13 14 32 14zM1013 800l-615 -614l-214 214l614 614zM317 96l-333 -112l110 335z" />
<glyph unicode="&#xe001;" d="M700 650v-550h250q21 0 35.5 -14.5t14.5 -35.5v-50h-800v50q0 21 14.5 35.5t35.5 14.5h250v550l-500 550h1200z" />
<glyph unicode="&#xe002;" d="M368 1017l645 163q39 15 63 0t24 -49v-831q0 -55 -41.5 -95.5t-111.5 -63.5q-79 -25 -147 -4.5t-86 75t25.5 111.5t122.5 82q72 24 138 8v521l-600 -155v-606q0 -42 -44 -90t-109 -69q-79 -26 -147 -5.5t-86 75.5t25.5 111.5t122.5 82.5q72 24 138 7v639q0 38 14.5 59 t53.5 34z" />
<glyph unicode="&#xe003;" d="M500 1191q100 0 191 -39t156.5 -104.5t104.5 -156.5t39 -191l-1 -2l1 -5q0 -141 -78 -262l275 -274q23 -26 22.5 -44.5t-22.5 -42.5l-59 -58q-26 -20 -46.5 -20t-39.5 20l-275 274q-119 -77 -261 -77l-5 1l-2 -1q-100 0 -191 39t-156.5 104.5t-104.5 156.5t-39 191 t39 191t104.5 156.5t156.5 104.5t191 39zM500 1022q-88 0 -162 -43t-117 -117t-43 -162t43 -162t117 -117t162 -43t162 43t117 117t43 162t-43 162t-117 117t-162 43z" />
<glyph unicode="&#xe005;" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104z" />
<glyph unicode="&#xe006;" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429z" />
<glyph unicode="&#xe007;" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429zM477 700h-240l197 -142l-74 -226 l193 139l195 -140l-74 229l192 140h-234l-78 211z" />
<glyph unicode="&#xe008;" d="M600 1200q124 0 212 -88t88 -212v-250q0 -46 -31 -98t-69 -52v-75q0 -10 6 -21.5t15 -17.5l358 -230q9 -5 15 -16.5t6 -21.5v-93q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v93q0 10 6 21.5t15 16.5l358 230q9 6 15 17.5t6 21.5v75q-38 0 -69 52 t-31 98v250q0 124 88 212t212 88z" />
<glyph unicode="&#xe009;" d="M25 1100h1150q10 0 17.5 -7.5t7.5 -17.5v-1050q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v1050q0 10 7.5 17.5t17.5 7.5zM100 1000v-100h100v100h-100zM875 1000h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5t17.5 -7.5h550 q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM1000 1000v-100h100v100h-100zM100 800v-100h100v100h-100zM1000 800v-100h100v100h-100zM100 600v-100h100v100h-100zM1000 600v-100h100v100h-100zM875 500h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5 t17.5 -7.5h550q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM100 400v-100h100v100h-100zM1000 400v-100h100v100h-100zM100 200v-100h100v100h-100zM1000 200v-100h100v100h-100z" />
<glyph unicode="&#xe010;" d="M50 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM50 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe011;" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM850 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 700h200q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5 t35.5 14.5z" />
<glyph unicode="&#xe012;" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h700q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe013;" d="M465 477l571 571q8 8 18 8t17 -8l177 -177q8 -7 8 -17t-8 -18l-783 -784q-7 -8 -17.5 -8t-17.5 8l-384 384q-8 8 -8 18t8 17l177 177q7 8 17 8t18 -8l171 -171q7 -7 18 -7t18 7z" />
<glyph unicode="&#xe014;" d="M904 1083l178 -179q8 -8 8 -18.5t-8 -17.5l-267 -268l267 -268q8 -7 8 -17.5t-8 -18.5l-178 -178q-8 -8 -18.5 -8t-17.5 8l-268 267l-268 -267q-7 -8 -17.5 -8t-18.5 8l-178 178q-8 8 -8 18.5t8 17.5l267 268l-267 268q-8 7 -8 17.5t8 18.5l178 178q8 8 18.5 8t17.5 -8 l268 -267l268 268q7 7 17.5 7t18.5 -7z" />
<glyph unicode="&#xe015;" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM425 900h150q10 0 17.5 -7.5t7.5 -17.5v-75h75q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5 t-17.5 -7.5h-75v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-75q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v75q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe016;" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM325 800h350q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-350q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe017;" d="M550 1200h100q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM800 975v166q167 -62 272 -209.5t105 -331.5q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5 t-184.5 123t-123 184.5t-45.5 224q0 184 105 331.5t272 209.5v-166q-103 -55 -165 -155t-62 -220q0 -116 57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5q0 120 -62 220t-165 155z" />
<glyph unicode="&#xe018;" d="M1025 1200h150q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM725 800h150q10 0 17.5 -7.5t7.5 -17.5v-750q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v750 q0 10 7.5 17.5t17.5 7.5zM425 500h150q10 0 17.5 -7.5t7.5 -17.5v-450q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v450q0 10 7.5 17.5t17.5 7.5zM125 300h150q10 0 17.5 -7.5t7.5 -17.5v-250q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5 v250q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe019;" d="M600 1174q33 0 74 -5l38 -152l5 -1q49 -14 94 -39l5 -2l134 80q61 -48 104 -105l-80 -134l3 -5q25 -44 39 -93l1 -6l152 -38q5 -43 5 -73q0 -34 -5 -74l-152 -38l-1 -6q-15 -49 -39 -93l-3 -5l80 -134q-48 -61 -104 -105l-134 81l-5 -3q-44 -25 -94 -39l-5 -2l-38 -151 q-43 -5 -74 -5q-33 0 -74 5l-38 151l-5 2q-49 14 -94 39l-5 3l-134 -81q-60 48 -104 105l80 134l-3 5q-25 45 -38 93l-2 6l-151 38q-6 42 -6 74q0 33 6 73l151 38l2 6q13 48 38 93l3 5l-80 134q47 61 105 105l133 -80l5 2q45 25 94 39l5 1l38 152q43 5 74 5zM600 815 q-89 0 -152 -63t-63 -151.5t63 -151.5t152 -63t152 63t63 151.5t-63 151.5t-152 63z" />
<glyph unicode="&#xe020;" d="M500 1300h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-75h-1100v75q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5zM500 1200v-100h300v100h-300zM1100 900v-800q0 -41 -29.5 -70.5t-70.5 -29.5h-700q-41 0 -70.5 29.5t-29.5 70.5 v800h900zM300 800v-700h100v700h-100zM500 800v-700h100v700h-100zM700 800v-700h100v700h-100zM900 800v-700h100v700h-100z" />
<glyph unicode="&#xe021;" d="M18 618l620 608q8 7 18.5 7t17.5 -7l608 -608q8 -8 5.5 -13t-12.5 -5h-175v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v375h-300v-375q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v575h-175q-10 0 -12.5 5t5.5 13z" />
<glyph unicode="&#xe022;" d="M600 1200v-400q0 -41 29.5 -70.5t70.5 -29.5h300v-650q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5h450zM1000 800h-250q-21 0 -35.5 14.5t-14.5 35.5v250z" />
<glyph unicode="&#xe023;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h50q10 0 17.5 -7.5t7.5 -17.5v-275h175q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe024;" d="M1300 0h-538l-41 400h-242l-41 -400h-538l431 1200h209l-21 -300h162l-20 300h208zM515 800l-27 -300h224l-27 300h-170z" />
<glyph unicode="&#xe025;" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-450h191q20 0 25.5 -11.5t-7.5 -27.5l-327 -400q-13 -16 -32 -16t-32 16l-327 400q-13 16 -7.5 27.5t25.5 11.5h191v450q0 21 14.5 35.5t35.5 14.5zM1125 400h50q10 0 17.5 -7.5t7.5 -17.5v-350q0 -10 -7.5 -17.5t-17.5 -7.5 h-1050q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h50q10 0 17.5 -7.5t7.5 -17.5v-175h900v175q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe026;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -275q-13 -16 -32 -16t-32 16l-223 275q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z " />
<glyph unicode="&#xe027;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM632 914l223 -275q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5l223 275q13 16 32 16 t32 -16z" />
<glyph unicode="&#xe028;" d="M225 1200h750q10 0 19.5 -7t12.5 -17l186 -652q7 -24 7 -49v-425q0 -12 -4 -27t-9 -17q-12 -6 -37 -6h-1100q-12 0 -27 4t-17 8q-6 13 -6 38l1 425q0 25 7 49l185 652q3 10 12.5 17t19.5 7zM878 1000h-556q-10 0 -19 -7t-11 -18l-87 -450q-2 -11 4 -18t16 -7h150 q10 0 19.5 -7t11.5 -17l38 -152q2 -10 11.5 -17t19.5 -7h250q10 0 19.5 7t11.5 17l38 152q2 10 11.5 17t19.5 7h150q10 0 16 7t4 18l-87 450q-2 11 -11 18t-19 7z" />
<glyph unicode="&#xe029;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM540 820l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
<glyph unicode="&#xe030;" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-362q0 -10 -7.5 -17.5t-17.5 -7.5h-362q-11 0 -13 5.5t5 12.5l133 133q-109 76 -238 76q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5h150q0 -117 -45.5 -224 t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117z" />
<glyph unicode="&#xe031;" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-361q0 -11 -7.5 -18.5t-18.5 -7.5h-361q-11 0 -13 5.5t5 12.5l134 134q-110 75 -239 75q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5h-150q0 117 45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117zM1027 600h150 q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5q-192 0 -348 118l-134 -134q-7 -8 -12.5 -5.5t-5.5 12.5v360q0 11 7.5 18.5t18.5 7.5h360q10 0 12.5 -5.5t-5.5 -12.5l-133 -133q110 -76 240 -76q116 0 214.5 57t155.5 155.5t57 214.5z" />
<glyph unicode="&#xe032;" d="M125 1200h1050q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-1050q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM1075 1000h-850q-10 0 -17.5 -7.5t-7.5 -17.5v-850q0 -10 7.5 -17.5t17.5 -7.5h850q10 0 17.5 7.5t7.5 17.5v850 q0 10 -7.5 17.5t-17.5 7.5zM325 900h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 900h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 700h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 700h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 500h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 500h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 300h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 300h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe033;" d="M900 800v200q0 83 -58.5 141.5t-141.5 58.5h-300q-82 0 -141 -59t-59 -141v-200h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h900q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-100zM400 800v150q0 21 15 35.5t35 14.5h200 q20 0 35 -14.5t15 -35.5v-150h-300z" />
<glyph unicode="&#xe034;" d="M125 1100h50q10 0 17.5 -7.5t7.5 -17.5v-1075h-100v1075q0 10 7.5 17.5t17.5 7.5zM1075 1052q4 0 9 -2q16 -6 16 -23v-421q0 -6 -3 -12q-33 -59 -66.5 -99t-65.5 -58t-56.5 -24.5t-52.5 -6.5q-26 0 -57.5 6.5t-52.5 13.5t-60 21q-41 15 -63 22.5t-57.5 15t-65.5 7.5 q-85 0 -160 -57q-7 -5 -15 -5q-6 0 -11 3q-14 7 -14 22v438q22 55 82 98.5t119 46.5q23 2 43 0.5t43 -7t32.5 -8.5t38 -13t32.5 -11q41 -14 63.5 -21t57 -14t63.5 -7q103 0 183 87q7 8 18 8z" />
<glyph unicode="&#xe035;" d="M600 1175q116 0 227 -49.5t192.5 -131t131 -192.5t49.5 -227v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v300q0 127 -70.5 231.5t-184.5 161.5t-245 57t-245 -57t-184.5 -161.5t-70.5 -231.5v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50 q-10 0 -17.5 7.5t-7.5 17.5v300q0 116 49.5 227t131 192.5t192.5 131t227 49.5zM220 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6zM820 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460 q0 8 6 14t14 6z" />
<glyph unicode="&#xe036;" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM900 668l120 120q7 7 17 7t17 -7l34 -34q7 -7 7 -17t-7 -17l-120 -120l120 -120q7 -7 7 -17 t-7 -17l-34 -34q-7 -7 -17 -7t-17 7l-120 119l-120 -119q-7 -7 -17 -7t-17 7l-34 34q-7 7 -7 17t7 17l119 120l-119 120q-7 7 -7 17t7 17l34 34q7 8 17 8t17 -8z" />
<glyph unicode="&#xe037;" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6 l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238q-6 8 -4.5 18t9.5 17l29 22q7 5 15 5z" />
<glyph unicode="&#xe038;" d="M967 1004h3q11 -1 17 -10q135 -179 135 -396q0 -105 -34 -206.5t-98 -185.5q-7 -9 -17 -10h-3q-9 0 -16 6l-42 34q-8 6 -9 16t5 18q111 150 111 328q0 90 -29.5 176t-84.5 157q-6 9 -5 19t10 16l42 33q7 5 15 5zM321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5 t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238 q-6 8 -4.5 18.5t9.5 16.5l29 22q7 5 15 5z" />
<glyph unicode="&#xe039;" d="M500 900h100v-100h-100v-100h-400v-100h-100v600h500v-300zM1200 700h-200v-100h200v-200h-300v300h-200v300h-100v200h600v-500zM100 1100v-300h300v300h-300zM800 1100v-300h300v300h-300zM300 900h-100v100h100v-100zM1000 900h-100v100h100v-100zM300 500h200v-500 h-500v500h200v100h100v-100zM800 300h200v-100h-100v-100h-200v100h-100v100h100v200h-200v100h300v-300zM100 400v-300h300v300h-300zM300 200h-100v100h100v-100zM1200 200h-100v100h100v-100zM700 0h-100v100h100v-100zM1200 0h-300v100h300v-100z" />
<glyph unicode="&#xe040;" d="M100 200h-100v1000h100v-1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 200h-200v1000h200v-1000zM400 0h-300v100h300v-100zM600 0h-100v91h100v-91zM800 0h-100v91h100v-91zM1100 0h-200v91h200v-91z" />
<glyph unicode="&#xe041;" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
<glyph unicode="&#xe042;" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM800 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-56 56l424 426l-700 700h150zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5 t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
<glyph unicode="&#xe043;" d="M300 1200h825q75 0 75 -75v-900q0 -25 -18 -43l-64 -64q-8 -8 -13 -5.5t-5 12.5v950q0 10 -7.5 17.5t-17.5 7.5h-700q-25 0 -43 -18l-64 -64q-8 -8 -5.5 -13t12.5 -5h700q10 0 17.5 -7.5t7.5 -17.5v-950q0 -10 -7.5 -17.5t-17.5 -7.5h-850q-10 0 -17.5 7.5t-7.5 17.5v975 q0 25 18 43l139 139q18 18 43 18z" />
<glyph unicode="&#xe044;" d="M250 1200h800q21 0 35.5 -14.5t14.5 -35.5v-1150l-450 444l-450 -445v1151q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe045;" d="M822 1200h-444q-11 0 -19 -7.5t-9 -17.5l-78 -301q-7 -24 7 -45l57 -108q6 -9 17.5 -15t21.5 -6h450q10 0 21.5 6t17.5 15l62 108q14 21 7 45l-83 301q-1 10 -9 17.5t-19 7.5zM1175 800h-150q-10 0 -21 -6.5t-15 -15.5l-78 -156q-4 -9 -15 -15.5t-21 -6.5h-550 q-10 0 -21 6.5t-15 15.5l-78 156q-4 9 -15 15.5t-21 6.5h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-650q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h750q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5 t7.5 17.5v650q0 10 -7.5 17.5t-17.5 7.5zM850 200h-500q-10 0 -19.5 -7t-11.5 -17l-38 -152q-2 -10 3.5 -17t15.5 -7h600q10 0 15.5 7t3.5 17l-38 152q-2 10 -11.5 17t-19.5 7z" />
<glyph unicode="&#xe046;" d="M500 1100h200q56 0 102.5 -20.5t72.5 -50t44 -59t25 -50.5l6 -20h150q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h150q2 8 6.5 21.5t24 48t45 61t72 48t102.5 21.5zM900 800v-100 h100v100h-100zM600 730q-95 0 -162.5 -67.5t-67.5 -162.5t67.5 -162.5t162.5 -67.5t162.5 67.5t67.5 162.5t-67.5 162.5t-162.5 67.5zM600 603q43 0 73 -30t30 -73t-30 -73t-73 -30t-73 30t-30 73t30 73t73 30z" />
<glyph unicode="&#xe047;" d="M681 1199l385 -998q20 -50 60 -92q18 -19 36.5 -29.5t27.5 -11.5l10 -2v-66h-417v66q53 0 75 43.5t5 88.5l-82 222h-391q-58 -145 -92 -234q-11 -34 -6.5 -57t25.5 -37t46 -20t55 -6v-66h-365v66q56 24 84 52q12 12 25 30.5t20 31.5l7 13l399 1006h93zM416 521h340 l-162 457z" />
<glyph unicode="&#xe048;" d="M753 641q5 -1 14.5 -4.5t36 -15.5t50.5 -26.5t53.5 -40t50.5 -54.5t35.5 -70t14.5 -87q0 -67 -27.5 -125.5t-71.5 -97.5t-98.5 -66.5t-108.5 -40.5t-102 -13h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 24 -0.5 34t-3.5 24t-8.5 19.5t-17 13.5t-28 12.5t-42.5 11.5v71 l471 -1q57 0 115.5 -20.5t108 -57t80.5 -94t31 -124.5q0 -51 -15.5 -96.5t-38 -74.5t-45 -50.5t-38.5 -30.5zM400 700h139q78 0 130.5 48.5t52.5 122.5q0 41 -8.5 70.5t-29.5 55.5t-62.5 39.5t-103.5 13.5h-118v-350zM400 200h216q80 0 121 50.5t41 130.5q0 90 -62.5 154.5 t-156.5 64.5h-159v-400z" />
<glyph unicode="&#xe049;" d="M877 1200l2 -57q-83 -19 -116 -45.5t-40 -66.5l-132 -839q-9 -49 13 -69t96 -26v-97h-500v97q186 16 200 98l173 832q3 17 3 30t-1.5 22.5t-9 17.5t-13.5 12.5t-21.5 10t-26 8.5t-33.5 10q-13 3 -19 5v57h425z" />
<glyph unicode="&#xe050;" d="M1300 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM175 1000h-75v-800h75l-125 -167l-125 167h75v800h-75l125 167z" />
<glyph unicode="&#xe051;" d="M1100 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-650q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v650h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM1167 50l-167 -125v75h-800v-75l-167 125l167 125v-75h800v75z" />
<glyph unicode="&#xe052;" d="M50 1100h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe053;" d="M250 1100h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM250 500h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe054;" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000 q-21 0 -35.5 14.5t-14.5 35.5zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe055;" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe056;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 1100h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 800h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 500h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 500h800q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 200h800 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe057;" d="M400 0h-100v1100h100v-1100zM550 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM267 550l-167 -125v75h-200v100h200v75zM550 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe058;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM900 0h-100v1100h100v-1100zM50 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM1100 600h200v-100h-200v-75l-167 125l167 125v-75zM50 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe059;" d="M75 1000h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22zM1200 300l-300 300l300 300v-600z" />
<glyph unicode="&#xe060;" d="M44 1100h1112q18 0 31 -13t13 -31v-1012q0 -18 -13 -31t-31 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13zM100 1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500h-1000zM342 884q56 0 95 -39t39 -94.5t-39 -95t-95 -39.5t-95 39.5t-39 95t39 94.5 t95 39z" />
<glyph unicode="&#xe062;" d="M648 1169q117 0 216 -60t156.5 -161t57.5 -218q0 -115 -70 -258q-69 -109 -158 -225.5t-143 -179.5l-54 -62q-9 8 -25.5 24.5t-63.5 67.5t-91 103t-98.5 128t-95.5 148q-60 132 -60 249q0 88 34 169.5t91.5 142t137 96.5t166.5 36zM652.5 974q-91.5 0 -156.5 -65 t-65 -157t65 -156.5t156.5 -64.5t156.5 64.5t65 156.5t-65 157t-156.5 65z" />
<glyph unicode="&#xe063;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 173v854q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57z" />
<glyph unicode="&#xe064;" d="M554 1295q21 -72 57.5 -143.5t76 -130t83 -118t82.5 -117t70 -116t49.5 -126t18.5 -136.5q0 -71 -25.5 -135t-68.5 -111t-99 -82t-118.5 -54t-125.5 -23q-84 5 -161.5 34t-139.5 78.5t-99 125t-37 164.5q0 69 18 136.5t49.5 126.5t69.5 116.5t81.5 117.5t83.5 119 t76.5 131t58.5 143zM344 710q-23 -33 -43.5 -70.5t-40.5 -102.5t-17 -123q1 -37 14.5 -69.5t30 -52t41 -37t38.5 -24.5t33 -15q21 -7 32 -1t13 22l6 34q2 10 -2.5 22t-13.5 19q-5 4 -14 12t-29.5 40.5t-32.5 73.5q-26 89 6 271q2 11 -6 11q-8 1 -15 -10z" />
<glyph unicode="&#xe065;" d="M1000 1013l108 115q2 1 5 2t13 2t20.5 -1t25 -9.5t28.5 -21.5q22 -22 27 -43t0 -32l-6 -10l-108 -115zM350 1100h400q50 0 105 -13l-187 -187h-368q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v182l200 200v-332 q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM1009 803l-362 -362l-161 -50l55 170l355 355z" />
<glyph unicode="&#xe066;" d="M350 1100h361q-164 -146 -216 -200h-195q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-103q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M824 1073l339 -301q8 -7 8 -17.5t-8 -17.5l-340 -306q-7 -6 -12.5 -4t-6.5 11v203q-26 1 -54.5 0t-78.5 -7.5t-92 -17.5t-86 -35t-70 -57q10 59 33 108t51.5 81.5t65 58.5t68.5 40.5t67 24.5t56 13.5t40 4.5v210q1 10 6.5 12.5t13.5 -4.5z" />
<glyph unicode="&#xe067;" d="M350 1100h350q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-219q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M643 639l395 395q7 7 17.5 7t17.5 -7l101 -101q7 -7 7 -17.5t-7 -17.5l-531 -532q-7 -7 -17.5 -7t-17.5 7l-248 248q-7 7 -7 17.5t7 17.5l101 101q7 7 17.5 7t17.5 -7l111 -111q8 -7 18 -7t18 7z" />
<glyph unicode="&#xe068;" d="M318 918l264 264q8 8 18 8t18 -8l260 -264q7 -8 4.5 -13t-12.5 -5h-170v-200h200v173q0 10 5 12t13 -5l264 -260q8 -7 8 -17.5t-8 -17.5l-264 -265q-8 -7 -13 -5t-5 12v173h-200v-200h170q10 0 12.5 -5t-4.5 -13l-260 -264q-8 -8 -18 -8t-18 8l-264 264q-8 8 -5.5 13 t12.5 5h175v200h-200v-173q0 -10 -5 -12t-13 5l-264 265q-8 7 -8 17.5t8 17.5l264 260q8 7 13 5t5 -12v-173h200v200h-175q-10 0 -12.5 5t5.5 13z" />
<glyph unicode="&#xe069;" d="M250 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe070;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5 t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe071;" d="M1200 1050v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-492 480q-15 14 -15 35t15 35l492 480q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25z" />
<glyph unicode="&#xe072;" d="M243 1074l814 -498q18 -11 18 -26t-18 -26l-814 -498q-18 -11 -30.5 -4t-12.5 28v1000q0 21 12.5 28t30.5 -4z" />
<glyph unicode="&#xe073;" d="M250 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM650 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800 q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe074;" d="M1100 950v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5z" />
<glyph unicode="&#xe075;" d="M500 612v438q0 21 10.5 25t25.5 -10l492 -480q15 -14 15 -35t-15 -35l-492 -480q-15 -14 -25.5 -10t-10.5 25v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10z" />
<glyph unicode="&#xe076;" d="M1048 1102l100 1q20 0 35 -14.5t15 -35.5l5 -1000q0 -21 -14.5 -35.5t-35.5 -14.5l-100 -1q-21 0 -35.5 14.5t-14.5 35.5l-2 437l-463 -454q-14 -15 -24.5 -10.5t-10.5 25.5l-2 437l-462 -455q-15 -14 -25.5 -9.5t-10.5 24.5l-5 1000q0 21 10.5 25.5t25.5 -10.5l466 -450 l-2 438q0 20 10.5 24.5t25.5 -9.5l466 -451l-2 438q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe077;" d="M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10l464 -453v438q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe078;" d="M686 1081l501 -540q15 -15 10.5 -26t-26.5 -11h-1042q-22 0 -26.5 11t10.5 26l501 540q15 15 36 15t36 -15zM150 400h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe079;" d="M885 900l-352 -353l352 -353l-197 -198l-552 552l552 550z" />
<glyph unicode="&#xe080;" d="M1064 547l-551 -551l-198 198l353 353l-353 353l198 198z" />
<glyph unicode="&#xe081;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM650 900h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-150 q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5h150v-150q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v150h150q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-150v150q0 21 -14.5 35.5t-35.5 14.5z" />
<glyph unicode="&#xe082;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM850 700h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5 t35.5 -14.5h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5z" />
<glyph unicode="&#xe083;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM741.5 913q-12.5 0 -21.5 -9l-120 -120l-120 120q-9 9 -21.5 9 t-21.5 -9l-141 -141q-9 -9 -9 -21.5t9 -21.5l120 -120l-120 -120q-9 -9 -9 -21.5t9 -21.5l141 -141q9 -9 21.5 -9t21.5 9l120 120l120 -120q9 -9 21.5 -9t21.5 9l141 141q9 9 9 21.5t-9 21.5l-120 120l120 120q9 9 9 21.5t-9 21.5l-141 141q-9 9 -21.5 9z" />
<glyph unicode="&#xe084;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM546 623l-84 85q-7 7 -17.5 7t-18.5 -7l-139 -139q-7 -8 -7 -18t7 -18 l242 -241q7 -8 17.5 -8t17.5 8l375 375q7 7 7 17.5t-7 18.5l-139 139q-7 7 -17.5 7t-17.5 -7z" />
<glyph unicode="&#xe085;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM588 941q-29 0 -59 -5.5t-63 -20.5t-58 -38.5t-41.5 -63t-16.5 -89.5 q0 -25 20 -25h131q30 -5 35 11q6 20 20.5 28t45.5 8q20 0 31.5 -10.5t11.5 -28.5q0 -23 -7 -34t-26 -18q-1 0 -13.5 -4t-19.5 -7.5t-20 -10.5t-22 -17t-18.5 -24t-15.5 -35t-8 -46q-1 -8 5.5 -16.5t20.5 -8.5h173q7 0 22 8t35 28t37.5 48t29.5 74t12 100q0 47 -17 83 t-42.5 57t-59.5 34.5t-64 18t-59 4.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe086;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM675 1000h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5 t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5zM675 700h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h75v-200h-75q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h350q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5 t-17.5 7.5h-75v275q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe087;" d="M525 1200h150q10 0 17.5 -7.5t7.5 -17.5v-194q103 -27 178.5 -102.5t102.5 -178.5h194q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-194q-27 -103 -102.5 -178.5t-178.5 -102.5v-194q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v194 q-103 27 -178.5 102.5t-102.5 178.5h-194q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h194q27 103 102.5 178.5t178.5 102.5v194q0 10 7.5 17.5t17.5 7.5zM700 893v-168q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v168q-68 -23 -119 -74 t-74 -119h168q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-168q23 -68 74 -119t119 -74v168q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-168q68 23 119 74t74 119h-168q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h168 q-23 68 -74 119t-119 74z" />
<glyph unicode="&#xe088;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM759 823l64 -64q7 -7 7 -17.5t-7 -17.5l-124 -124l124 -124q7 -7 7 -17.5t-7 -17.5l-64 -64q-7 -7 -17.5 -7t-17.5 7l-124 124l-124 -124q-7 -7 -17.5 -7t-17.5 7l-64 64 q-7 7 -7 17.5t7 17.5l124 124l-124 124q-7 7 -7 17.5t7 17.5l64 64q7 7 17.5 7t17.5 -7l124 -124l124 124q7 7 17.5 7t17.5 -7z" />
<glyph unicode="&#xe089;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM782 788l106 -106q7 -7 7 -17.5t-7 -17.5l-320 -321q-8 -7 -18 -7t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l197 197q7 7 17.5 7t17.5 -7z" />
<glyph unicode="&#xe090;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5q0 -120 65 -225 l587 587q-105 65 -225 65zM965 819l-584 -584q104 -62 219 -62q116 0 214.5 57t155.5 155.5t57 214.5q0 115 -62 219z" />
<glyph unicode="&#xe091;" d="M39 582l522 427q16 13 27.5 8t11.5 -26v-291h550q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-550v-291q0 -21 -11.5 -26t-27.5 8l-522 427q-16 13 -16 32t16 32z" />
<glyph unicode="&#xe092;" d="M639 1009l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291h-550q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h550v291q0 21 11.5 26t27.5 -8z" />
<glyph unicode="&#xe093;" d="M682 1161l427 -522q13 -16 8 -27.5t-26 -11.5h-291v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v550h-291q-21 0 -26 11.5t8 27.5l427 522q13 16 32 16t32 -16z" />
<glyph unicode="&#xe094;" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-550h291q21 0 26 -11.5t-8 -27.5l-427 -522q-13 -16 -32 -16t-32 16l-427 522q-13 16 -8 27.5t26 11.5h291v550q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe095;" d="M639 1109l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291q-94 -2 -182 -20t-170.5 -52t-147 -92.5t-100.5 -135.5q5 105 27 193.5t67.5 167t113 135t167 91.5t225.5 42v262q0 21 11.5 26t27.5 -8z" />
<glyph unicode="&#xe096;" d="M850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5zM350 0h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249 q8 7 18 7t18 -7l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5z" />
<glyph unicode="&#xe097;" d="M1014 1120l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249q8 7 18 7t18 -7zM250 600h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5z" />
<glyph unicode="&#xe101;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM704 900h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5 t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe102;" d="M260 1200q9 0 19 -2t15 -4l5 -2q22 -10 44 -23l196 -118q21 -13 36 -24q29 -21 37 -12q11 13 49 35l196 118q22 13 45 23q17 7 38 7q23 0 47 -16.5t37 -33.5l13 -16q14 -21 18 -45l25 -123l8 -44q1 -9 8.5 -14.5t17.5 -5.5h61q10 0 17.5 -7.5t7.5 -17.5v-50 q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 -7.5t-7.5 -17.5v-175h-400v300h-200v-300h-400v175q0 10 -7.5 17.5t-17.5 7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5h61q11 0 18 3t7 8q0 4 9 52l25 128q5 25 19 45q2 3 5 7t13.5 15t21.5 19.5t26.5 15.5 t29.5 7zM915 1079l-166 -162q-7 -7 -5 -12t12 -5h219q10 0 15 7t2 17l-51 149q-3 10 -11 12t-15 -6zM463 917l-177 157q-8 7 -16 5t-11 -12l-51 -143q-3 -10 2 -17t15 -7h231q11 0 12.5 5t-5.5 12zM500 0h-375q-10 0 -17.5 7.5t-7.5 17.5v375h400v-400zM1100 400v-375 q0 -10 -7.5 -17.5t-17.5 -7.5h-375v400h400z" />
<glyph unicode="&#xe103;" d="M1165 1190q8 3 21 -6.5t13 -17.5q-2 -178 -24.5 -323.5t-55.5 -245.5t-87 -174.5t-102.5 -118.5t-118 -68.5t-118.5 -33t-120 -4.5t-105 9.5t-90 16.5q-61 12 -78 11q-4 1 -12.5 0t-34 -14.5t-52.5 -40.5l-153 -153q-26 -24 -37 -14.5t-11 43.5q0 64 42 102q8 8 50.5 45 t66.5 58q19 17 35 47t13 61q-9 55 -10 102.5t7 111t37 130t78 129.5q39 51 80 88t89.5 63.5t94.5 45t113.5 36t129 31t157.5 37t182 47.5zM1116 1098q-8 9 -22.5 -3t-45.5 -50q-38 -47 -119 -103.5t-142 -89.5l-62 -33q-56 -30 -102 -57t-104 -68t-102.5 -80.5t-85.5 -91 t-64 -104.5q-24 -56 -31 -86t2 -32t31.5 17.5t55.5 59.5q25 30 94 75.5t125.5 77.5t147.5 81q70 37 118.5 69t102 79.5t99 111t86.5 148.5q22 50 24 60t-6 19z" />
<glyph unicode="&#xe104;" d="M653 1231q-39 -67 -54.5 -131t-10.5 -114.5t24.5 -96.5t47.5 -80t63.5 -62.5t68.5 -46.5t65 -30q-4 7 -17.5 35t-18.5 39.5t-17 39.5t-17 43t-13 42t-9.5 44.5t-2 42t4 43t13.5 39t23 38.5q96 -42 165 -107.5t105 -138t52 -156t13 -159t-19 -149.5q-13 -55 -44 -106.5 t-68 -87t-78.5 -64.5t-72.5 -45t-53 -22q-72 -22 -127 -11q-31 6 -13 19q6 3 17 7q13 5 32.5 21t41 44t38.5 63.5t21.5 81.5t-6.5 94.5t-50 107t-104 115.5q10 -104 -0.5 -189t-37 -140.5t-65 -93t-84 -52t-93.5 -11t-95 24.5q-80 36 -131.5 114t-53.5 171q-2 23 0 49.5 t4.5 52.5t13.5 56t27.5 60t46 64.5t69.5 68.5q-8 -53 -5 -102.5t17.5 -90t34 -68.5t44.5 -39t49 -2q31 13 38.5 36t-4.5 55t-29 64.5t-36 75t-26 75.5q-15 85 2 161.5t53.5 128.5t85.5 92.5t93.5 61t81.5 25.5z" />
<glyph unicode="&#xe105;" d="M600 1094q82 0 160.5 -22.5t140 -59t116.5 -82.5t94.5 -95t68 -95t42.5 -82.5t14 -57.5t-14 -57.5t-43 -82.5t-68.5 -95t-94.5 -95t-116.5 -82.5t-140 -59t-159.5 -22.5t-159.5 22.5t-140 59t-116.5 82.5t-94.5 95t-68.5 95t-43 82.5t-14 57.5t14 57.5t42.5 82.5t68 95 t94.5 95t116.5 82.5t140 59t160.5 22.5zM888 829q-15 15 -18 12t5 -22q25 -57 25 -119q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 59 23 114q8 19 4.5 22t-17.5 -12q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q22 -36 47 -71t70 -82t92.5 -81t113 -58.5t133.5 -24.5 t133.5 24t113 58.5t92.5 81.5t70 81.5t47 70.5q11 18 9 42.5t-14 41.5q-90 117 -163 189zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l35 34q14 15 12.5 33.5t-16.5 33.5q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
<glyph unicode="&#xe106;" d="M592 0h-148l31 120q-91 20 -175.5 68.5t-143.5 106.5t-103.5 119t-66.5 110t-22 76q0 21 14 57.5t42.5 82.5t68 95t94.5 95t116.5 82.5t140 59t160.5 22.5q61 0 126 -15l32 121h148zM944 770l47 181q108 -85 176.5 -192t68.5 -159q0 -26 -19.5 -71t-59.5 -102t-93 -112 t-129 -104.5t-158 -75.5l46 173q77 49 136 117t97 131q11 18 9 42.5t-14 41.5q-54 70 -107 130zM310 824q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q18 -30 39 -60t57 -70.5t74 -73t90 -61t105 -41.5l41 154q-107 18 -178.5 101.5t-71.5 193.5q0 59 23 114q8 19 4.5 22 t-17.5 -12zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l12 11l22 86l-3 4q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
<glyph unicode="&#xe107;" d="M-90 100l642 1066q20 31 48 28.5t48 -35.5l642 -1056q21 -32 7.5 -67.5t-50.5 -35.5h-1294q-37 0 -50.5 34t7.5 66zM155 200h345v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h345l-445 723zM496 700h208q20 0 32 -14.5t8 -34.5l-58 -252 q-4 -20 -21.5 -34.5t-37.5 -14.5h-54q-20 0 -37.5 14.5t-21.5 34.5l-58 252q-4 20 8 34.5t32 14.5z" />
<glyph unicode="&#xe108;" d="M650 1200q62 0 106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -93 100 -113v-64q0 -21 -13 -29t-32 1l-205 128l-205 -128q-19 -9 -32 -1t-13 29v64q0 20 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41 q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44z" />
<glyph unicode="&#xe109;" d="M850 1200h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-150h-1100v150q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-50h500v50q0 21 14.5 35.5t35.5 14.5zM1100 800v-750q0 -21 -14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v750h1100zM100 600v-100h100v100h-100zM300 600v-100h100v100h-100zM500 600v-100h100v100h-100zM700 600v-100h100v100h-100zM900 600v-100h100v100h-100zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400 v-100h100v100h-100zM700 400v-100h100v100h-100zM900 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100zM500 200v-100h100v100h-100zM700 200v-100h100v100h-100zM900 200v-100h100v100h-100z" />
<glyph unicode="&#xe110;" d="M1135 1165l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-159l-600 -600h-291q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h209l600 600h241v150q0 21 10.5 25t24.5 -10zM522 819l-141 -141l-122 122h-209q-21 0 -35.5 14.5 t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h291zM1135 565l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-241l-181 181l141 141l122 -122h159v150q0 21 10.5 25t24.5 -10z" />
<glyph unicode="&#xe111;" d="M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z" />
<glyph unicode="&#xe112;" d="M150 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM850 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM1100 800v-300q0 -41 -3 -77.5t-15 -89.5t-32 -96t-58 -89t-89 -77t-129 -51t-174 -20t-174 20 t-129 51t-89 77t-58 89t-32 96t-15 89.5t-3 77.5v300h300v-250v-27v-42.5t1.5 -41t5 -38t10 -35t16.5 -30t25.5 -24.5t35 -19t46.5 -12t60 -4t60 4.5t46.5 12.5t35 19.5t25 25.5t17 30.5t10 35t5 38t2 40.5t-0.5 42v25v250h300z" />
<glyph unicode="&#xe113;" d="M1100 411l-198 -199l-353 353l-353 -353l-197 199l551 551z" />
<glyph unicode="&#xe114;" d="M1101 789l-550 -551l-551 551l198 199l353 -353l353 353z" />
<glyph unicode="&#xe115;" d="M404 1000h746q21 0 35.5 -14.5t14.5 -35.5v-551h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v401h-381zM135 984l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-400h385l215 -200h-750q-21 0 -35.5 14.5 t-14.5 35.5v550h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
<glyph unicode="&#xe116;" d="M56 1200h94q17 0 31 -11t18 -27l38 -162h896q24 0 39 -18.5t10 -42.5l-100 -475q-5 -21 -27 -42.5t-55 -21.5h-633l48 -200h535q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-50q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-300v-50 q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-31q-18 0 -32.5 10t-20.5 19l-5 10l-201 961h-54q-20 0 -35 14.5t-15 35.5t15 35.5t35 14.5z" />
<glyph unicode="&#xe117;" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" />
<glyph unicode="&#xe118;" d="M200 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q42 0 71 -29.5t29 -70.5h500v-200h-1000zM1500 700l-300 -700h-1200l300 700h1200z" />
<glyph unicode="&#xe119;" d="M635 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-601h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v601h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
<glyph unicode="&#xe120;" d="M936 864l249 -229q14 -15 14 -35.5t-14 -35.5l-249 -229q-15 -15 -25.5 -10.5t-10.5 24.5v151h-600v-151q0 -20 -10.5 -24.5t-25.5 10.5l-249 229q-14 15 -14 35.5t14 35.5l249 229q15 15 25.5 10.5t10.5 -25.5v-149h600v149q0 21 10.5 25.5t25.5 -10.5z" />
<glyph unicode="&#xe121;" d="M1169 400l-172 732q-5 23 -23 45.5t-38 22.5h-672q-20 0 -38 -20t-23 -41l-172 -739h1138zM1100 300h-1000q-41 0 -70.5 -29.5t-29.5 -70.5v-100q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v100q0 41 -29.5 70.5t-70.5 29.5zM800 100v100h100v-100h-100 zM1000 100v100h100v-100h-100z" />
<glyph unicode="&#xe122;" d="M1150 1100q21 0 35.5 -14.5t14.5 -35.5v-850q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v850q0 21 14.5 35.5t35.5 14.5zM1000 200l-675 200h-38l47 -276q3 -16 -5.5 -20t-29.5 -4h-7h-84q-20 0 -34.5 14t-18.5 35q-55 337 -55 351v250v6q0 16 1 23.5t6.5 14 t17.5 6.5h200l675 250v-850zM0 750v-250q-4 0 -11 0.5t-24 6t-30 15t-24 30t-11 48.5v50q0 26 10.5 46t25 30t29 16t25.5 7z" />
<glyph unicode="&#xe123;" d="M553 1200h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q19 0 33 -14.5t14 -35t-13 -40.5t-31 -27q-8 -4 -23 -9.5t-65 -19.5t-103 -25t-132.5 -20t-158.5 -9q-57 0 -115 5t-104 12t-88.5 15.5t-73.5 17.5t-54.5 16t-35.5 12l-11 4 q-18 8 -31 28t-13 40.5t14 35t33 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3.5 32t28.5 13zM498 110q50 -6 102 -6q53 0 102 6q-12 -49 -39.5 -79.5t-62.5 -30.5t-63 30.5t-39 79.5z" />
<glyph unicode="&#xe124;" d="M800 946l224 78l-78 -224l234 -45l-180 -155l180 -155l-234 -45l78 -224l-224 78l-45 -234l-155 180l-155 -180l-45 234l-224 -78l78 224l-234 45l180 155l-180 155l234 45l-78 224l224 -78l45 234l155 -180l155 180z" />
<glyph unicode="&#xe125;" d="M650 1200h50q40 0 70 -40.5t30 -84.5v-150l-28 -125h328q40 0 70 -40.5t30 -84.5v-100q0 -45 -29 -74l-238 -344q-16 -24 -38 -40.5t-45 -16.5h-250q-7 0 -42 25t-66 50l-31 25h-61q-45 0 -72.5 18t-27.5 57v400q0 36 20 63l145 196l96 198q13 28 37.5 48t51.5 20z M650 1100l-100 -212l-150 -213v-375h100l136 -100h214l250 375v125h-450l50 225v175h-50zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe126;" d="M600 1100h250q23 0 45 -16.5t38 -40.5l238 -344q29 -29 29 -74v-100q0 -44 -30 -84.5t-70 -40.5h-328q28 -118 28 -125v-150q0 -44 -30 -84.5t-70 -40.5h-50q-27 0 -51.5 20t-37.5 48l-96 198l-145 196q-20 27 -20 63v400q0 39 27.5 57t72.5 18h61q124 100 139 100z M50 1000h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM636 1000l-136 -100h-100v-375l150 -213l100 -212h50v175l-50 225h450v125l-250 375h-214z" />
<glyph unicode="&#xe127;" d="M356 873l363 230q31 16 53 -6l110 -112q13 -13 13.5 -32t-11.5 -34l-84 -121h302q84 0 138 -38t54 -110t-55 -111t-139 -39h-106l-131 -339q-6 -21 -19.5 -41t-28.5 -20h-342q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM400 792v-503l100 -89h293l131 339 q6 21 19.5 41t28.5 20h203q21 0 30.5 25t0.5 50t-31 25h-456h-7h-6h-5.5t-6 0.5t-5 1.5t-5 2t-4 2.5t-4 4t-2.5 4.5q-12 25 5 47l146 183l-86 83zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500 q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe128;" d="M475 1103l366 -230q2 -1 6 -3.5t14 -10.5t18 -16.5t14.5 -20t6.5 -22.5v-525q0 -13 -86 -94t-93 -81h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-85 0 -139.5 39t-54.5 111t54 110t138 38h302l-85 121q-11 15 -10.5 34t13.5 32l110 112q22 22 53 6zM370 945l146 -183 q17 -22 5 -47q-2 -2 -3.5 -4.5t-4 -4t-4 -2.5t-5 -2t-5 -1.5t-6 -0.5h-6h-6.5h-6h-475v-100h221q15 0 29 -20t20 -41l130 -339h294l106 89v503l-342 236zM1050 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5 v500q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe129;" d="M550 1294q72 0 111 -55t39 -139v-106l339 -131q21 -6 41 -19.5t20 -28.5v-342q0 -7 -81 -90t-94 -83h-525q-17 0 -35.5 14t-28.5 28l-9 14l-230 363q-16 31 6 53l112 110q13 13 32 13.5t34 -11.5l121 -84v302q0 84 38 138t110 54zM600 972v203q0 21 -25 30.5t-50 0.5 t-25 -31v-456v-7v-6v-5.5t-0.5 -6t-1.5 -5t-2 -5t-2.5 -4t-4 -4t-4.5 -2.5q-25 -12 -47 5l-183 146l-83 -86l236 -339h503l89 100v293l-339 131q-21 6 -41 19.5t-20 28.5zM450 200h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe130;" d="M350 1100h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5zM600 306v-106q0 -84 -39 -139t-111 -55t-110 54t-38 138v302l-121 -84q-15 -12 -34 -11.5t-32 13.5l-112 110 q-22 22 -6 53l230 363q1 2 3.5 6t10.5 13.5t16.5 17t20 13.5t22.5 6h525q13 0 94 -83t81 -90v-342q0 -15 -20 -28.5t-41 -19.5zM308 900l-236 -339l83 -86l183 146q22 17 47 5q2 -1 4.5 -2.5t4 -4t2.5 -4t2 -5t1.5 -5t0.5 -6v-5.5v-6v-7v-456q0 -22 25 -31t50 0.5t25 30.5 v203q0 15 20 28.5t41 19.5l339 131v293l-89 100h-503z" />
<glyph unicode="&#xe131;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM914 632l-275 223q-16 13 -27.5 8t-11.5 -26v-137h-275 q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h275v-137q0 -21 11.5 -26t27.5 8l275 223q16 13 16 32t-16 32z" />
<glyph unicode="&#xe132;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM561 855l-275 -223q-16 -13 -16 -32t16 -32l275 -223q16 -13 27.5 -8 t11.5 26v137h275q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5h-275v137q0 21 -11.5 26t-27.5 -8z" />
<glyph unicode="&#xe133;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM855 639l-223 275q-13 16 -32 16t-32 -16l-223 -275q-13 -16 -8 -27.5 t26 -11.5h137v-275q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v275h137q21 0 26 11.5t-8 27.5z" />
<glyph unicode="&#xe134;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM675 900h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-275h-137q-21 0 -26 -11.5 t8 -27.5l223 -275q13 -16 32 -16t32 16l223 275q13 16 8 27.5t-26 11.5h-137v275q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe135;" d="M600 1176q116 0 222.5 -46t184 -123.5t123.5 -184t46 -222.5t-46 -222.5t-123.5 -184t-184 -123.5t-222.5 -46t-222.5 46t-184 123.5t-123.5 184t-46 222.5t46 222.5t123.5 184t184 123.5t222.5 46zM627 1101q-15 -12 -36.5 -20.5t-35.5 -12t-43 -8t-39 -6.5 q-15 -3 -45.5 0t-45.5 -2q-20 -7 -51.5 -26.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79q-9 -34 5 -93t8 -87q0 -9 17 -44.5t16 -59.5q12 0 23 -5t23.5 -15t19.5 -14q16 -8 33 -15t40.5 -15t34.5 -12q21 -9 52.5 -32t60 -38t57.5 -11 q7 -15 -3 -34t-22.5 -40t-9.5 -38q13 -21 23 -34.5t27.5 -27.5t36.5 -18q0 -7 -3.5 -16t-3.5 -14t5 -17q104 -2 221 112q30 29 46.5 47t34.5 49t21 63q-13 8 -37 8.5t-36 7.5q-15 7 -49.5 15t-51.5 19q-18 0 -41 -0.5t-43 -1.5t-42 -6.5t-38 -16.5q-51 -35 -66 -12 q-4 1 -3.5 25.5t0.5 25.5q-6 13 -26.5 17.5t-24.5 6.5q1 15 -0.5 30.5t-7 28t-18.5 11.5t-31 -21q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q7 -12 18 -24t21.5 -20.5t20 -15t15.5 -10.5l5 -3q2 12 7.5 30.5t8 34.5t-0.5 32q-3 18 3.5 29 t18 22.5t15.5 24.5q6 14 10.5 35t8 31t15.5 22.5t34 22.5q-6 18 10 36q8 0 24 -1.5t24.5 -1.5t20 4.5t20.5 15.5q-10 23 -31 42.5t-37.5 29.5t-49 27t-43.5 23q0 1 2 8t3 11.5t1.5 10.5t-1 9.5t-4.5 4.5q31 -13 58.5 -14.5t38.5 2.5l12 5q5 28 -9.5 46t-36.5 24t-50 15 t-41 20q-18 -4 -37 0zM613 994q0 -17 8 -42t17 -45t9 -23q-8 1 -39.5 5.5t-52.5 10t-37 16.5q3 11 16 29.5t16 25.5q10 -10 19 -10t14 6t13.5 14.5t16.5 12.5z" />
<glyph unicode="&#xe136;" d="M756 1157q164 92 306 -9l-259 -138l145 -232l251 126q6 -89 -34 -156.5t-117 -110.5q-60 -34 -127 -39.5t-126 16.5l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-34 101 5.5 201.5t135.5 154.5z" />
<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " />
<glyph unicode="&#xe138;" d="M150 1200h900q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM700 500v-300l-200 -200v500l-350 500h900z" />
<glyph unicode="&#xe139;" d="M500 1200h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5zM500 1100v-100h200v100h-200zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" />
<glyph unicode="&#xe140;" d="M50 1200h300q21 0 25 -10.5t-10 -24.5l-94 -94l199 -199q7 -8 7 -18t-7 -18l-106 -106q-8 -7 -18 -7t-18 7l-199 199l-94 -94q-14 -14 -24.5 -10t-10.5 25v300q0 21 14.5 35.5t35.5 14.5zM850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-199 -199q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l199 199l-94 94q-14 14 -10 24.5t25 10.5zM364 470l106 -106q7 -8 7 -18t-7 -18l-199 -199l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l199 199 q8 7 18 7t18 -7zM1071 271l94 94q14 14 24.5 10t10.5 -25v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -25 10.5t10 24.5l94 94l-199 199q-7 8 -7 18t7 18l106 106q8 7 18 7t18 -7z" />
<glyph unicode="&#xe141;" d="M596 1192q121 0 231.5 -47.5t190 -127t127 -190t47.5 -231.5t-47.5 -231.5t-127 -190.5t-190 -127t-231.5 -47t-231.5 47t-190.5 127t-127 190.5t-47 231.5t47 231.5t127 190t190.5 127t231.5 47.5zM596 1010q-112 0 -207.5 -55.5t-151 -151t-55.5 -207.5t55.5 -207.5 t151 -151t207.5 -55.5t207.5 55.5t151 151t55.5 207.5t-55.5 207.5t-151 151t-207.5 55.5zM454.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38.5 -16.5t-38.5 16.5t-16 39t16 38.5t38.5 16zM754.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38 -16.5q-14 0 -29 10l-55 -145 q17 -23 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 23 16 39t38.5 16zM345.5 709q22.5 0 38.5 -16t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16zM854.5 709q22.5 0 38.5 -16 t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16z" />
<glyph unicode="&#xe142;" d="M546 173l469 470q91 91 99 192q7 98 -52 175.5t-154 94.5q-22 4 -47 4q-34 0 -66.5 -10t-56.5 -23t-55.5 -38t-48 -41.5t-48.5 -47.5q-376 -375 -391 -390q-30 -27 -45 -41.5t-37.5 -41t-32 -46.5t-16 -47.5t-1.5 -56.5q9 -62 53.5 -95t99.5 -33q74 0 125 51l548 548 q36 36 20 75q-7 16 -21.5 26t-32.5 10q-26 0 -50 -23q-13 -12 -39 -38l-341 -338q-15 -15 -35.5 -15.5t-34.5 13.5t-14 34.5t14 34.5q327 333 361 367q35 35 67.5 51.5t78.5 16.5q14 0 29 -1q44 -8 74.5 -35.5t43.5 -68.5q14 -47 2 -96.5t-47 -84.5q-12 -11 -32 -32 t-79.5 -81t-114.5 -115t-124.5 -123.5t-123 -119.5t-96.5 -89t-57 -45q-56 -27 -120 -27q-70 0 -129 32t-93 89q-48 78 -35 173t81 163l511 511q71 72 111 96q91 55 198 55q80 0 152 -33q78 -36 129.5 -103t66.5 -154q17 -93 -11 -183.5t-94 -156.5l-482 -476 q-15 -15 -36 -16t-37 14t-17.5 34t14.5 35z" />
<glyph unicode="&#xe143;" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104zM896 972q-33 0 -64.5 -19t-56.5 -46t-47.5 -53.5t-43.5 -45.5t-37.5 -19t-36 19t-40 45.5t-43 53.5t-54 46t-65.5 19q-67 0 -122.5 -55.5t-55.5 -132.5q0 -23 13.5 -51t46 -65t57.5 -63t76 -75l22 -22q15 -14 44 -44t50.5 -51t46 -44t41 -35t23 -12 t23.5 12t42.5 36t46 44t52.5 52t44 43q4 4 12 13q43 41 63.5 62t52 55t46 55t26 46t11.5 44q0 79 -53 133.5t-120 54.5z" />
<glyph unicode="&#xe144;" d="M776.5 1214q93.5 0 159.5 -66l141 -141q66 -66 66 -160q0 -42 -28 -95.5t-62 -87.5l-29 -29q-31 53 -77 99l-18 18l95 95l-247 248l-389 -389l212 -212l-105 -106l-19 18l-141 141q-66 66 -66 159t66 159l283 283q65 66 158.5 66zM600 706l105 105q10 -8 19 -17l141 -141 q66 -66 66 -159t-66 -159l-283 -283q-66 -66 -159 -66t-159 66l-141 141q-66 66 -66 159.5t66 159.5l55 55q29 -55 75 -102l18 -17l-95 -95l247 -248l389 389z" />
<glyph unicode="&#xe145;" d="M603 1200q85 0 162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5v953q0 21 30 46.5t81 48t129 37.5t163 15zM300 1000v-700h600v700h-600zM600 254q-43 0 -73.5 -30.5t-30.5 -73.5t30.5 -73.5t73.5 -30.5t73.5 30.5 t30.5 73.5t-30.5 73.5t-73.5 30.5z" />
<glyph unicode="&#xe146;" d="M902 1185l283 -282q15 -15 15 -36t-14.5 -35.5t-35.5 -14.5t-35 15l-36 35l-279 -267v-300l-212 210l-308 -307l-280 -203l203 280l307 308l-210 212h300l267 279l-35 36q-15 14 -15 35t14.5 35.5t35.5 14.5t35 -15z" />
<glyph unicode="&#xe148;" d="M700 1248v-78q38 -5 72.5 -14.5t75.5 -31.5t71 -53.5t52 -84t24 -118.5h-159q-4 36 -10.5 59t-21 45t-40 35.5t-64.5 20.5v-307l64 -13q34 -7 64 -16.5t70 -32t67.5 -52.5t47.5 -80t20 -112q0 -139 -89 -224t-244 -97v-77h-100v79q-150 16 -237 103q-40 40 -52.5 93.5 t-15.5 139.5h139q5 -77 48.5 -126t117.5 -65v335l-27 8q-46 14 -79 26.5t-72 36t-63 52t-40 72.5t-16 98q0 70 25 126t67.5 92t94.5 57t110 27v77h100zM600 754v274q-29 -4 -50 -11t-42 -21.5t-31.5 -41.5t-10.5 -65q0 -29 7 -50.5t16.5 -34t28.5 -22.5t31.5 -14t37.5 -10 q9 -3 13 -4zM700 547v-310q22 2 42.5 6.5t45 15.5t41.5 27t29 42t12 59.5t-12.5 59.5t-38 44.5t-53 31t-66.5 24.5z" />
<glyph unicode="&#xe149;" d="M561 1197q84 0 160.5 -40t123.5 -109.5t47 -147.5h-153q0 40 -19.5 71.5t-49.5 48.5t-59.5 26t-55.5 9q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -26 13.5 -63t26.5 -61t37 -66q6 -9 9 -14h241v-100h-197q8 -50 -2.5 -115t-31.5 -95q-45 -62 -99 -112 q34 10 83 17.5t71 7.5q32 1 102 -16t104 -17q83 0 136 30l50 -147q-31 -19 -58 -30.5t-55 -15.5t-42 -4.5t-46 -0.5q-23 0 -76 17t-111 32.5t-96 11.5q-39 -3 -82 -16t-67 -25l-23 -11l-55 145q4 3 16 11t15.5 10.5t13 9t15.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221v100h166q-23 47 -44 104q-7 20 -12 41.5t-6 55.5t6 66.5t29.5 70.5t58.5 71q97 88 263 88z" />
<glyph unicode="&#xe150;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM935 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-900h-200v900h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
<glyph unicode="&#xe151;" d="M1000 700h-100v100h-100v-100h-100v500h300v-500zM400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM801 1100v-200h100v200h-100zM1000 350l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150z " />
<glyph unicode="&#xe152;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 1050l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150zM1000 0h-100v100h-100v-100h-100v500h300v-500zM801 400v-200h100v200h-100z " />
<glyph unicode="&#xe153;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 700h-100v400h-100v100h200v-500zM1100 0h-100v100h-200v400h300v-500zM901 400v-200h100v200h-100z" />
<glyph unicode="&#xe154;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1100 700h-100v100h-200v400h300v-500zM901 1100v-200h100v200h-100zM1000 0h-100v400h-100v100h200v-500z" />
<glyph unicode="&#xe155;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" />
<glyph unicode="&#xe156;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" />
<glyph unicode="&#xe157;" d="M350 1100h400q162 0 256 -93.5t94 -256.5v-400q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5z" />
<glyph unicode="&#xe158;" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-163 0 -256.5 92.5t-93.5 257.5v400q0 163 94 256.5t256 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM440 770l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
<glyph unicode="&#xe159;" d="M350 1100h400q163 0 256.5 -94t93.5 -256v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 163 92.5 256.5t257.5 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM350 700h400q21 0 26.5 -12t-6.5 -28l-190 -253q-12 -17 -30 -17t-30 17l-190 253q-12 16 -6.5 28t26.5 12z" />
<glyph unicode="&#xe160;" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -163 -92.5 -256.5t-257.5 -93.5h-400q-163 0 -256.5 94t-93.5 256v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM580 693l190 -253q12 -16 6.5 -28t-26.5 -12h-400q-21 0 -26.5 12t6.5 28l190 253q12 17 30 17t30 -17z" />
<glyph unicode="&#xe161;" d="M550 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h450q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-450q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM338 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
<glyph unicode="&#xe162;" d="M793 1182l9 -9q8 -10 5 -27q-3 -11 -79 -225.5t-78 -221.5l300 1q24 0 32.5 -17.5t-5.5 -35.5q-1 0 -133.5 -155t-267 -312.5t-138.5 -162.5q-12 -15 -26 -15h-9l-9 8q-9 11 -4 32q2 9 42 123.5t79 224.5l39 110h-302q-23 0 -31 19q-10 21 6 41q75 86 209.5 237.5 t228 257t98.5 111.5q9 16 25 16h9z" />
<glyph unicode="&#xe163;" d="M350 1100h400q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-450q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h450q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400 q0 165 92.5 257.5t257.5 92.5zM938 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
<glyph unicode="&#xe164;" d="M750 1200h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -10.5 -25t-24.5 10l-109 109l-312 -312q-15 -15 -35.5 -15t-35.5 15l-141 141q-15 15 -15 35.5t15 35.5l312 312l-109 109q-14 14 -10 24.5t25 10.5zM456 900h-156q-41 0 -70.5 -29.5t-29.5 -70.5v-500 q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v148l200 200v-298q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5h300z" />
<glyph unicode="&#xe165;" d="M600 1186q119 0 227.5 -46.5t187 -125t125 -187t46.5 -227.5t-46.5 -227.5t-125 -187t-187 -125t-227.5 -46.5t-227.5 46.5t-187 125t-125 187t-46.5 227.5t46.5 227.5t125 187t187 125t227.5 46.5zM600 1022q-115 0 -212 -56.5t-153.5 -153.5t-56.5 -212t56.5 -212 t153.5 -153.5t212 -56.5t212 56.5t153.5 153.5t56.5 212t-56.5 212t-153.5 153.5t-212 56.5zM600 794q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" />
<glyph unicode="&#xe166;" d="M450 1200h200q21 0 35.5 -14.5t14.5 -35.5v-350h245q20 0 25 -11t-9 -26l-383 -426q-14 -15 -33.5 -15t-32.5 15l-379 426q-13 15 -8.5 26t25.5 11h250v350q0 21 14.5 35.5t35.5 14.5zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
<glyph unicode="&#xe167;" d="M583 1182l378 -435q14 -15 9 -31t-26 -16h-244v-250q0 -20 -17 -35t-39 -15h-200q-20 0 -32 14.5t-12 35.5v250h-250q-20 0 -25.5 16.5t8.5 31.5l383 431q14 16 33.5 17t33.5 -14zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
<glyph unicode="&#xe168;" d="M396 723l369 369q7 7 17.5 7t17.5 -7l139 -139q7 -8 7 -18.5t-7 -17.5l-525 -525q-7 -8 -17.5 -8t-17.5 8l-292 291q-7 8 -7 18t7 18l139 139q8 7 18.5 7t17.5 -7zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50 h-100z" />
<glyph unicode="&#xe169;" d="M135 1023l142 142q14 14 35 14t35 -14l77 -77l-212 -212l-77 76q-14 15 -14 36t14 35zM655 855l210 210q14 14 24.5 10t10.5 -25l-2 -599q-1 -20 -15.5 -35t-35.5 -15l-597 -1q-21 0 -25 10.5t10 24.5l208 208l-154 155l212 212zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5 v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
<glyph unicode="&#xe170;" d="M350 1200l599 -2q20 -1 35 -15.5t15 -35.5l1 -597q0 -21 -10.5 -25t-24.5 10l-208 208l-155 -154l-212 212l155 154l-210 210q-14 14 -10 24.5t25 10.5zM524 512l-76 -77q-15 -14 -36 -14t-35 14l-142 142q-14 14 -14 35t14 35l77 77zM50 300h1000q21 0 35.5 -14.5 t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
<glyph unicode="&#xe171;" d="M1200 103l-483 276l-314 -399v423h-399l1196 796v-1096zM483 424v-230l683 953z" />
<glyph unicode="&#xe172;" d="M1100 1000v-850q0 -21 -14.5 -35.5t-35.5 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200z" />
<glyph unicode="&#xe173;" d="M1100 1000l-2 -149l-299 -299l-95 95q-9 9 -21.5 9t-21.5 -9l-149 -147h-312v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1132 638l106 -106q7 -7 7 -17.5t-7 -17.5l-420 -421q-8 -7 -18 -7 t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l297 297q7 7 17.5 7t17.5 -7z" />
<glyph unicode="&#xe174;" d="M1100 1000v-269l-103 -103l-134 134q-15 15 -33.5 16.5t-34.5 -12.5l-266 -266h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1202 572l70 -70q15 -15 15 -35.5t-15 -35.5l-131 -131 l131 -131q15 -15 15 -35.5t-15 -35.5l-70 -70q-15 -15 -35.5 -15t-35.5 15l-131 131l-131 -131q-15 -15 -35.5 -15t-35.5 15l-70 70q-15 15 -15 35.5t15 35.5l131 131l-131 131q-15 15 -15 35.5t15 35.5l70 70q15 15 35.5 15t35.5 -15l131 -131l131 131q15 15 35.5 15 t35.5 -15z" />
<glyph unicode="&#xe175;" d="M1100 1000v-300h-350q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM850 600h100q21 0 35.5 -14.5t14.5 -35.5v-250h150q21 0 25 -10.5t-10 -24.5 l-230 -230q-14 -14 -35 -14t-35 14l-230 230q-14 14 -10 24.5t25 10.5h150v250q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe176;" d="M1100 1000v-400l-165 165q-14 15 -35 15t-35 -15l-263 -265h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM935 565l230 -229q14 -15 10 -25.5t-25 -10.5h-150v-250q0 -20 -14.5 -35 t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35v250h-150q-21 0 -25 10.5t10 25.5l230 229q14 15 35 15t35 -15z" />
<glyph unicode="&#xe177;" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-150h-1200v150q0 21 14.5 35.5t35.5 14.5zM1200 800v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v550h1200zM100 500v-200h400v200h-400z" />
<glyph unicode="&#xe178;" d="M935 1165l248 -230q14 -14 14 -35t-14 -35l-248 -230q-14 -14 -24.5 -10t-10.5 25v150h-400v200h400v150q0 21 10.5 25t24.5 -10zM200 800h-50q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v-200zM400 800h-100v200h100v-200zM18 435l247 230 q14 14 24.5 10t10.5 -25v-150h400v-200h-400v-150q0 -21 -10.5 -25t-24.5 10l-247 230q-15 14 -15 35t15 35zM900 300h-100v200h100v-200zM1000 500h51q20 0 34.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-34.5 -14.5h-51v200z" />
<glyph unicode="&#xe179;" d="M862 1073l276 116q25 18 43.5 8t18.5 -41v-1106q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v397q-4 1 -11 5t-24 17.5t-30 29t-24 42t-11 56.5v359q0 31 18.5 65t43.5 52zM550 1200q22 0 34.5 -12.5t14.5 -24.5l1 -13v-450q0 -28 -10.5 -59.5 t-25 -56t-29 -45t-25.5 -31.5l-10 -11v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447q-4 4 -11 11.5t-24 30.5t-30 46t-24 55t-11 60v450q0 2 0.5 5.5t4 12t8.5 15t14.5 12t22.5 5.5q20 0 32.5 -12.5t14.5 -24.5l3 -13v-350h100v350v5.5t2.5 12 t7 15t15 12t25.5 5.5q23 0 35.5 -12.5t13.5 -24.5l1 -13v-350h100v350q0 2 0.5 5.5t3 12t7 15t15 12t24.5 5.5z" />
<glyph unicode="&#xe180;" d="M1200 1100v-56q-4 0 -11 -0.5t-24 -3t-30 -7.5t-24 -15t-11 -24v-888q0 -22 25 -34.5t50 -13.5l25 -2v-56h-400v56q75 0 87.5 6.5t12.5 43.5v394h-500v-394q0 -37 12.5 -43.5t87.5 -6.5v-56h-400v56q4 0 11 0.5t24 3t30 7.5t24 15t11 24v888q0 22 -25 34.5t-50 13.5 l-25 2v56h400v-56q-75 0 -87.5 -6.5t-12.5 -43.5v-394h500v394q0 37 -12.5 43.5t-87.5 6.5v56h400z" />
<glyph unicode="&#xe181;" d="M675 1000h375q21 0 35.5 -14.5t14.5 -35.5v-150h-105l-295 -98v98l-200 200h-400l100 100h375zM100 900h300q41 0 70.5 -29.5t29.5 -70.5v-500q0 -41 -29.5 -70.5t-70.5 -29.5h-300q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5zM100 800v-200h300v200 h-300zM1100 535l-400 -133v163l400 133v-163zM100 500v-200h300v200h-300zM1100 398v-248q0 -21 -14.5 -35.5t-35.5 -14.5h-375l-100 -100h-375l-100 100h400l200 200h105z" />
<glyph unicode="&#xe182;" d="M17 1007l162 162q17 17 40 14t37 -22l139 -194q14 -20 11 -44.5t-20 -41.5l-119 -118q102 -142 228 -268t267 -227l119 118q17 17 42.5 19t44.5 -12l192 -136q19 -14 22.5 -37.5t-13.5 -40.5l-163 -162q-3 -1 -9.5 -1t-29.5 2t-47.5 6t-62.5 14.5t-77.5 26.5t-90 42.5 t-101.5 60t-111 83t-119 108.5q-74 74 -133.5 150.5t-94.5 138.5t-60 119.5t-34.5 100t-15 74.5t-4.5 48z" />
<glyph unicode="&#xe183;" d="M600 1100q92 0 175 -10.5t141.5 -27t108.5 -36.5t81.5 -40t53.5 -37t31 -27l9 -10v-200q0 -21 -14.5 -33t-34.5 -9l-202 34q-20 3 -34.5 20t-14.5 38v146q-141 24 -300 24t-300 -24v-146q0 -21 -14.5 -38t-34.5 -20l-202 -34q-20 -3 -34.5 9t-14.5 33v200q3 4 9.5 10.5 t31 26t54 37.5t80.5 39.5t109 37.5t141 26.5t175 10.5zM600 795q56 0 97 -9.5t60 -23.5t30 -28t12 -24l1 -10v-50l365 -303q14 -15 24.5 -40t10.5 -45v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45t24.5 40l365 303v50 q0 4 1 10.5t12 23t30 29t60 22.5t97 10z" />
<glyph unicode="&#xe184;" d="M1100 700l-200 -200h-600l-200 200v500h200v-200h200v200h200v-200h200v200h200v-500zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5 t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe185;" d="M700 1100h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-1000h300v1000q0 41 -29.5 70.5t-70.5 29.5zM1100 800h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-700h300v700q0 41 -29.5 70.5t-70.5 29.5zM400 0h-300v400q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-400z " />
<glyph unicode="&#xe186;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
<glyph unicode="&#xe187;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 300h-100v200h-100v-200h-100v500h100v-200h100v200h100v-500zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
<glyph unicode="&#xe188;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-300h200v-100h-300v500h300v-100zM900 700h-200v-300h200v-100h-300v500h300v-100z" />
<glyph unicode="&#xe189;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 400l-300 150l300 150v-300zM900 550l-300 -150v300z" />
<glyph unicode="&#xe190;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM900 300h-700v500h700v-500zM800 700h-130q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300zM300 700v-300 h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130z" />
<glyph unicode="&#xe191;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 300h-100v400h-100v100h200v-500z M700 300h-100v100h100v-100z" />
<glyph unicode="&#xe192;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM300 700h200v-400h-300v500h100v-100zM900 300h-100v400h-100v100h200v-500zM300 600v-200h100v200h-100z M700 300h-100v100h100v-100z" />
<glyph unicode="&#xe193;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 500l-199 -200h-100v50l199 200v150h-200v100h300v-300zM900 300h-100v400h-100v100h200v-500zM701 300h-100 v100h100v-100z" />
<glyph unicode="&#xe194;" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700h-300v-200h300v-100h-300l-100 100v200l100 100h300v-100z" />
<glyph unicode="&#xe195;" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700v-100l-50 -50l100 -100v-50h-100l-100 100h-150v-100h-100v400h300zM500 700v-100h200v100h-200z" />
<glyph unicode="&#xe197;" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -207t-85 -207t-205 -86.5h-128v250q0 21 -14.5 35.5t-35.5 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-250h-222q-80 0 -136 57.5t-56 136.5q0 69 43 122.5t108 67.5q-2 19 -2 37q0 100 49 185 t134 134t185 49zM525 500h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -244q-13 -16 -32 -16t-32 16l-223 244q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe198;" d="M502 1089q110 0 201 -59.5t135 -156.5q43 15 89 15q121 0 206 -86.5t86 -206.5q0 -99 -60 -181t-150 -110l-378 360q-13 16 -31.5 16t-31.5 -16l-381 -365h-9q-79 0 -135.5 57.5t-56.5 136.5q0 69 43 122.5t108 67.5q-2 19 -2 38q0 100 49 184.5t133.5 134t184.5 49.5z M632 467l223 -228q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5q199 204 223 228q19 19 31.5 19t32.5 -19z" />
<glyph unicode="&#xe199;" d="M700 100v100h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-100h-50q-21 0 -35.5 -14.5t-14.5 -35.5v-50h400v50q0 21 -14.5 35.5t-35.5 14.5h-50z" />
<glyph unicode="&#xe200;" d="M600 1179q94 0 167.5 -56.5t99.5 -145.5q89 -6 150.5 -71.5t61.5 -155.5q0 -61 -29.5 -112.5t-79.5 -82.5q9 -29 9 -55q0 -74 -52.5 -126.5t-126.5 -52.5q-55 0 -100 30v-251q21 0 35.5 -14.5t14.5 -35.5v-50h-300v50q0 21 14.5 35.5t35.5 14.5v251q-45 -30 -100 -30 q-74 0 -126.5 52.5t-52.5 126.5q0 18 4 38q-47 21 -75.5 65t-28.5 97q0 74 52.5 126.5t126.5 52.5q5 0 23 -2q0 2 -1 10t-1 13q0 116 81.5 197.5t197.5 81.5z" />
<glyph unicode="&#xe201;" d="M1010 1010q111 -111 150.5 -260.5t0 -299t-150.5 -260.5q-83 -83 -191.5 -126.5t-218.5 -43.5t-218.5 43.5t-191.5 126.5q-111 111 -150.5 260.5t0 299t150.5 260.5q83 83 191.5 126.5t218.5 43.5t218.5 -43.5t191.5 -126.5zM476 1065q-4 0 -8 -1q-121 -34 -209.5 -122.5 t-122.5 -209.5q-4 -12 2.5 -23t18.5 -14l36 -9q3 -1 7 -1q23 0 29 22q27 96 98 166q70 71 166 98q11 3 17.5 13.5t3.5 22.5l-9 35q-3 13 -14 19q-7 4 -15 4zM512 920q-4 0 -9 -2q-80 -24 -138.5 -82.5t-82.5 -138.5q-4 -13 2 -24t19 -14l34 -9q4 -1 8 -1q22 0 28 21 q18 58 58.5 98.5t97.5 58.5q12 3 18 13.5t3 21.5l-9 35q-3 12 -14 19q-7 4 -15 4zM719.5 719.5q-49.5 49.5 -119.5 49.5t-119.5 -49.5t-49.5 -119.5t49.5 -119.5t119.5 -49.5t119.5 49.5t49.5 119.5t-49.5 119.5zM855 551q-22 0 -28 -21q-18 -58 -58.5 -98.5t-98.5 -57.5 q-11 -4 -17 -14.5t-3 -21.5l9 -35q3 -12 14 -19q7 -4 15 -4q4 0 9 2q80 24 138.5 82.5t82.5 138.5q4 13 -2.5 24t-18.5 14l-34 9q-4 1 -8 1zM1000 515q-23 0 -29 -22q-27 -96 -98 -166q-70 -71 -166 -98q-11 -3 -17.5 -13.5t-3.5 -22.5l9 -35q3 -13 14 -19q7 -4 15 -4 q4 0 8 1q121 34 209.5 122.5t122.5 209.5q4 12 -2.5 23t-18.5 14l-36 9q-3 1 -7 1z" />
<glyph unicode="&#xe202;" d="M700 800h300v-380h-180v200h-340v-200h-380v755q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM700 300h162l-212 -212l-212 212h162v200h100v-200zM520 0h-395q-10 0 -17.5 7.5t-7.5 17.5v395zM1000 220v-195q0 -10 -7.5 -17.5t-17.5 -7.5h-195z" />
<glyph unicode="&#xe203;" d="M700 800h300v-520l-350 350l-550 -550v1095q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM862 200h-162v-200h-100v200h-162l212 212zM480 0h-355q-10 0 -17.5 7.5t-7.5 17.5v55h380v-80zM1000 80v-55q0 -10 -7.5 -17.5t-17.5 -7.5h-155v80h180z" />
<glyph unicode="&#xe204;" d="M1162 800h-162v-200h100l100 -100h-300v300h-162l212 212zM200 800h200q27 0 40 -2t29.5 -10.5t23.5 -30t7 -57.5h300v-100h-600l-200 -350v450h100q0 36 7 57.5t23.5 30t29.5 10.5t40 2zM800 400h240l-240 -400h-800l300 500h500v-100z" />
<glyph unicode="&#xe205;" d="M650 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM1000 850v150q41 0 70.5 -29.5t29.5 -70.5v-800 q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-1 0 -20 4l246 246l-326 326v324q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM412 250l-212 -212v162h-200v100h200v162z" />
<glyph unicode="&#xe206;" d="M450 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM800 850v150q41 0 70.5 -29.5t29.5 -70.5v-500 h-200v-300h200q0 -36 -7 -57.5t-23.5 -30t-29.5 -10.5t-40 -2h-600q-41 0 -70.5 29.5t-29.5 70.5v800q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM1212 250l-212 -212v162h-200v100h200v162z" />
<glyph unicode="&#xe209;" d="M658 1197l637 -1104q23 -38 7 -65.5t-60 -27.5h-1276q-44 0 -60 27.5t7 65.5l637 1104q22 39 54 39t54 -39zM704 800h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM500 300v-100h200 v100h-200z" />
<glyph unicode="&#xe210;" d="M425 1100h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM825 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM25 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5zM425 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5 v150q0 10 7.5 17.5t17.5 7.5zM25 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe211;" d="M700 1200h100v-200h-100v-100h350q62 0 86.5 -39.5t-3.5 -94.5l-66 -132q-41 -83 -81 -134h-772q-40 51 -81 134l-66 132q-28 55 -3.5 94.5t86.5 39.5h350v100h-100v200h100v100h200v-100zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100 h-950l138 100h-13q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe212;" d="M600 1300q40 0 68.5 -29.5t28.5 -70.5h-194q0 41 28.5 70.5t68.5 29.5zM443 1100h314q18 -37 18 -75q0 -8 -3 -25h328q41 0 44.5 -16.5t-30.5 -38.5l-175 -145h-678l-178 145q-34 22 -29 38.5t46 16.5h328q-3 17 -3 25q0 38 18 75zM250 700h700q21 0 35.5 -14.5 t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-150v-200l275 -200h-950l275 200v200h-150q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe213;" d="M600 1181q75 0 128 -53t53 -128t-53 -128t-128 -53t-128 53t-53 128t53 128t128 53zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13 l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe214;" d="M600 1300q47 0 92.5 -53.5t71 -123t25.5 -123.5q0 -78 -55.5 -133.5t-133.5 -55.5t-133.5 55.5t-55.5 133.5q0 62 34 143l144 -143l111 111l-163 163q34 26 63 26zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45 zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe215;" d="M600 1200l300 -161v-139h-300q0 -57 18.5 -108t50 -91.5t63 -72t70 -67.5t57.5 -61h-530q-60 83 -90.5 177.5t-30.5 178.5t33 164.5t87.5 139.5t126 96.5t145.5 41.5v-98zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100 h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe216;" d="M600 1300q41 0 70.5 -29.5t29.5 -70.5v-78q46 -26 73 -72t27 -100v-50h-400v50q0 54 27 100t73 72v78q0 41 29.5 70.5t70.5 29.5zM400 800h400q54 0 100 -27t72 -73h-172v-100h200v-100h-200v-100h200v-100h-200v-100h200q0 -83 -58.5 -141.5t-141.5 -58.5h-400 q-83 0 -141.5 58.5t-58.5 141.5v400q0 83 58.5 141.5t141.5 58.5z" />
<glyph unicode="&#xe218;" d="M150 1100h900q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM125 400h950q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-283l224 -224q13 -13 13 -31.5t-13 -32 t-31.5 -13.5t-31.5 13l-88 88h-524l-87 -88q-13 -13 -32 -13t-32 13.5t-13 32t13 31.5l224 224h-289q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM541 300l-100 -100h324l-100 100h-124z" />
<glyph unicode="&#xe219;" d="M200 1100h800q83 0 141.5 -58.5t58.5 -141.5v-200h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100v200q0 83 58.5 141.5t141.5 58.5zM100 600h1000q41 0 70.5 -29.5 t29.5 -70.5v-300h-1200v300q0 41 29.5 70.5t70.5 29.5zM300 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200zM1100 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200z" />
<glyph unicode="&#xe221;" d="M480 1165l682 -683q31 -31 31 -75.5t-31 -75.5l-131 -131h-481l-517 518q-32 31 -32 75.5t32 75.5l295 296q31 31 75.5 31t76.5 -31zM108 794l342 -342l303 304l-341 341zM250 100h800q21 0 35.5 -14.5t14.5 -35.5v-50h-900v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe223;" d="M1057 647l-189 506q-8 19 -27.5 33t-40.5 14h-400q-21 0 -40.5 -14t-27.5 -33l-189 -506q-8 -19 1.5 -33t30.5 -14h625v-150q0 -21 14.5 -35.5t35.5 -14.5t35.5 14.5t14.5 35.5v150h125q21 0 30.5 14t1.5 33zM897 0h-595v50q0 21 14.5 35.5t35.5 14.5h50v50 q0 21 14.5 35.5t35.5 14.5h48v300h200v-300h47q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-50z" />
<glyph unicode="&#xe224;" d="M900 800h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-375v591l-300 300v84q0 10 7.5 17.5t17.5 7.5h375v-400zM1200 900h-200v200zM400 600h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-650q-10 0 -17.5 7.5t-7.5 17.5v950q0 10 7.5 17.5t17.5 7.5h375v-400zM700 700h-200v200z " />
<glyph unicode="&#xe225;" d="M484 1095h195q75 0 146 -32.5t124 -86t89.5 -122.5t48.5 -142q18 -14 35 -20q31 -10 64.5 6.5t43.5 48.5q10 34 -15 71q-19 27 -9 43q5 8 12.5 11t19 -1t23.5 -16q41 -44 39 -105q-3 -63 -46 -106.5t-104 -43.5h-62q-7 -55 -35 -117t-56 -100l-39 -234q-3 -20 -20 -34.5 t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l12 70q-49 -14 -91 -14h-195q-24 0 -65 8l-11 -64q-3 -20 -20 -34.5t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l26 157q-84 74 -128 175l-159 53q-19 7 -33 26t-14 40v50q0 21 14.5 35.5t35.5 14.5h124q11 87 56 166l-111 95 q-16 14 -12.5 23.5t24.5 9.5h203q116 101 250 101zM675 1000h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h250q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe226;" d="M641 900l423 247q19 8 42 2.5t37 -21.5l32 -38q14 -15 12.5 -36t-17.5 -34l-139 -120h-390zM50 1100h106q67 0 103 -17t66 -71l102 -212h823q21 0 35.5 -14.5t14.5 -35.5v-50q0 -21 -14 -40t-33 -26l-737 -132q-23 -4 -40 6t-26 25q-42 67 -100 67h-300q-62 0 -106 44 t-44 106v200q0 62 44 106t106 44zM173 928h-80q-19 0 -28 -14t-9 -35v-56q0 -51 42 -51h134q16 0 21.5 8t5.5 24q0 11 -16 45t-27 51q-18 28 -43 28zM550 727q-32 0 -54.5 -22.5t-22.5 -54.5t22.5 -54.5t54.5 -22.5t54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5zM130 389 l152 130q18 19 34 24t31 -3.5t24.5 -17.5t25.5 -28q28 -35 50.5 -51t48.5 -13l63 5l48 -179q13 -61 -3.5 -97.5t-67.5 -79.5l-80 -69q-47 -40 -109 -35.5t-103 51.5l-130 151q-40 47 -35.5 109.5t51.5 102.5zM380 377l-102 -88q-31 -27 2 -65l37 -43q13 -15 27.5 -19.5 t31.5 6.5l61 53q19 16 14 49q-2 20 -12 56t-17 45q-11 12 -19 14t-23 -8z" />
<glyph unicode="&#xe227;" d="M625 1200h150q10 0 17.5 -7.5t7.5 -17.5v-109q79 -33 131 -87.5t53 -128.5q1 -46 -15 -84.5t-39 -61t-46 -38t-39 -21.5l-17 -6q6 0 15 -1.5t35 -9t50 -17.5t53 -30t50 -45t35.5 -64t14.5 -84q0 -59 -11.5 -105.5t-28.5 -76.5t-44 -51t-49.5 -31.5t-54.5 -16t-49.5 -6.5 t-43.5 -1v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-100v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-175q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v600h-75q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5h175v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h100v75q0 10 7.5 17.5t17.5 7.5zM400 900v-200h263q28 0 48.5 10.5t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-263zM400 500v-200h363q28 0 48.5 10.5 t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-363z" />
<glyph unicode="&#xe230;" d="M212 1198h780q86 0 147 -61t61 -147v-416q0 -51 -18 -142.5t-36 -157.5l-18 -66q-29 -87 -93.5 -146.5t-146.5 -59.5h-572q-82 0 -147 59t-93 147q-8 28 -20 73t-32 143.5t-20 149.5v416q0 86 61 147t147 61zM600 1045q-70 0 -132.5 -11.5t-105.5 -30.5t-78.5 -41.5 t-57 -45t-36 -41t-20.5 -30.5l-6 -12l156 -243h560l156 243q-2 5 -6 12.5t-20 29.5t-36.5 42t-57 44.5t-79 42t-105 29.5t-132.5 12zM762 703h-157l195 261z" />
<glyph unicode="&#xe231;" d="M475 1300h150q103 0 189 -86t86 -189v-500q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
<glyph unicode="&#xe232;" d="M475 1300h96q0 -150 89.5 -239.5t239.5 -89.5v-446q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
<glyph unicode="&#xe233;" d="M1294 767l-638 -283l-378 170l-78 -60v-224l100 -150v-199l-150 148l-150 -149v200l100 150v250q0 4 -0.5 10.5t0 9.5t1 8t3 8t6.5 6l47 40l-147 65l642 283zM1000 380l-350 -166l-350 166v147l350 -165l350 165v-147z" />
<glyph unicode="&#xe234;" d="M250 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM650 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM1050 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
<glyph unicode="&#xe235;" d="M550 1100q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 700q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 300q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
<glyph unicode="&#xe236;" d="M125 1100h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM125 700h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM125 300h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe237;" d="M350 1200h500q162 0 256 -93.5t94 -256.5v-500q0 -165 -93.5 -257.5t-256.5 -92.5h-500q-165 0 -257.5 92.5t-92.5 257.5v500q0 165 92.5 257.5t257.5 92.5zM900 1000h-600q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h600q41 0 70.5 29.5 t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5zM350 900h500q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-500q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 14.5 35.5t35.5 14.5zM400 800v-200h400v200h-400z" />
<glyph unicode="&#xe238;" d="M150 1100h1000q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe239;" d="M650 1187q87 -67 118.5 -156t0 -178t-118.5 -155q-87 66 -118.5 155t0 178t118.5 156zM300 800q124 0 212 -88t88 -212q-124 0 -212 88t-88 212zM1000 800q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM300 500q124 0 212 -88t88 -212q-124 0 -212 88t-88 212z M1000 500q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM700 199v-144q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v142q40 -4 43 -4q17 0 57 6z" />
<glyph unicode="&#xe240;" d="M745 878l69 19q25 6 45 -12l298 -295q11 -11 15 -26.5t-2 -30.5q-5 -14 -18 -23.5t-28 -9.5h-8q1 0 1 -13q0 -29 -2 -56t-8.5 -62t-20 -63t-33 -53t-51 -39t-72.5 -14h-146q-184 0 -184 288q0 24 10 47q-20 4 -62 4t-63 -4q11 -24 11 -47q0 -288 -184 -288h-142 q-48 0 -84.5 21t-56 51t-32 71.5t-16 75t-3.5 68.5q0 13 2 13h-7q-15 0 -27.5 9.5t-18.5 23.5q-6 15 -2 30.5t15 25.5l298 296q20 18 46 11l76 -19q20 -5 30.5 -22.5t5.5 -37.5t-22.5 -31t-37.5 -5l-51 12l-182 -193h891l-182 193l-44 -12q-20 -5 -37.5 6t-22.5 31t6 37.5 t31 22.5z" />
<glyph unicode="&#xe241;" d="M1200 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM500 450h-25q0 15 -4 24.5t-9 14.5t-17 7.5t-20 3t-25 0.5h-100v-425q0 -11 12.5 -17.5t25.5 -7.5h12v-50h-200v50q50 0 50 25v425h-100q-17 0 -25 -0.5t-20 -3t-17 -7.5t-9 -14.5t-4 -24.5h-25v150h500v-150z" />
<glyph unicode="&#xe242;" d="M1000 300v50q-25 0 -55 32q-14 14 -25 31t-16 27l-4 11l-289 747h-69l-300 -754q-18 -35 -39 -56q-9 -9 -24.5 -18.5t-26.5 -14.5l-11 -5v-50h273v50q-49 0 -78.5 21.5t-11.5 67.5l69 176h293l61 -166q13 -34 -3.5 -66.5t-55.5 -32.5v-50h312zM412 691l134 342l121 -342 h-255zM1100 150v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" />
<glyph unicode="&#xe243;" d="M50 1200h1100q21 0 35.5 -14.5t14.5 -35.5v-1100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5zM611 1118h-70q-13 0 -18 -12l-299 -753q-17 -32 -35 -51q-18 -18 -56 -34q-12 -5 -12 -18v-50q0 -8 5.5 -14t14.5 -6 h273q8 0 14 6t6 14v50q0 8 -6 14t-14 6q-55 0 -71 23q-10 14 0 39l63 163h266l57 -153q11 -31 -6 -55q-12 -17 -36 -17q-8 0 -14 -6t-6 -14v-50q0 -8 6 -14t14 -6h313q8 0 14 6t6 14v50q0 7 -5.5 13t-13.5 7q-17 0 -42 25q-25 27 -40 63h-1l-288 748q-5 12 -19 12zM639 611 h-197l103 264z" />
<glyph unicode="&#xe244;" d="M1200 1100h-1200v100h1200v-100zM50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 1000h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM700 900v-300h300v300h-300z" />
<glyph unicode="&#xe245;" d="M50 1200h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 700h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM700 600v-300h300v300h-300zM1200 0h-1200v100h1200v-100z" />
<glyph unicode="&#xe246;" d="M50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-350h100v150q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-150h100v-100h-100v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v150h-100v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM700 700v-300h300v300h-300z" />
<glyph unicode="&#xe247;" d="M100 0h-100v1200h100v-1200zM250 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM300 1000v-300h300v300h-300zM250 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe248;" d="M600 1100h150q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-100h450q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h350v100h-150q-21 0 -35.5 14.5 t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h150v100h100v-100zM400 1000v-300h300v300h-300z" />
<glyph unicode="&#xe249;" d="M1200 0h-100v1200h100v-1200zM550 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM600 1000v-300h300v300h-300zM50 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe250;" d="M865 565l-494 -494q-23 -23 -41 -23q-14 0 -22 13.5t-8 38.5v1000q0 25 8 38.5t22 13.5q18 0 41 -23l494 -494q14 -14 14 -35t-14 -35z" />
<glyph unicode="&#xe251;" d="M335 635l494 494q29 29 50 20.5t21 -49.5v-1000q0 -41 -21 -49.5t-50 20.5l-494 494q-14 14 -14 35t14 35z" />
<glyph unicode="&#xe252;" d="M100 900h1000q41 0 49.5 -21t-20.5 -50l-494 -494q-14 -14 -35 -14t-35 14l-494 494q-29 29 -20.5 50t49.5 21z" />
<glyph unicode="&#xe253;" d="M635 865l494 -494q29 -29 20.5 -50t-49.5 -21h-1000q-41 0 -49.5 21t20.5 50l494 494q14 14 35 14t35 -14z" />
<glyph unicode="&#xe254;" d="M700 741v-182l-692 -323v221l413 193l-413 193v221zM1200 0h-800v200h800v-200z" />
<glyph unicode="&#xe255;" d="M1200 900h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300zM0 700h50q0 21 4 37t9.5 26.5t18 17.5t22 11t28.5 5.5t31 2t37 0.5h100v-550q0 -22 -25 -34.5t-50 -13.5l-25 -2v-100h400v100q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v550h100q25 0 37 -0.5t31 -2 t28.5 -5.5t22 -11t18 -17.5t9.5 -26.5t4 -37h50v300h-800v-300z" />
<glyph unicode="&#xe256;" d="M800 700h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-100v-550q0 -22 25 -34.5t50 -14.5l25 -1v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v550h-100q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h800v-300zM1100 200h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300z" />
<glyph unicode="&#xe257;" d="M701 1098h160q16 0 21 -11t-7 -23l-464 -464l464 -464q12 -12 7 -23t-21 -11h-160q-13 0 -23 9l-471 471q-7 8 -7 18t7 18l471 471q10 9 23 9z" />
<glyph unicode="&#xe258;" d="M339 1098h160q13 0 23 -9l471 -471q7 -8 7 -18t-7 -18l-471 -471q-10 -9 -23 -9h-160q-16 0 -21 11t7 23l464 464l-464 464q-12 12 -7 23t21 11z" />
<glyph unicode="&#xe259;" d="M1087 882q11 -5 11 -21v-160q0 -13 -9 -23l-471 -471q-8 -7 -18 -7t-18 7l-471 471q-9 10 -9 23v160q0 16 11 21t23 -7l464 -464l464 464q12 12 23 7z" />
<glyph unicode="&#xe260;" d="M618 993l471 -471q9 -10 9 -23v-160q0 -16 -11 -21t-23 7l-464 464l-464 -464q-12 -12 -23 -7t-11 21v160q0 13 9 23l471 471q8 7 18 7t18 -7z" />
<glyph unicode="&#xf8ff;" d="M1000 1200q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM450 1000h100q21 0 40 -14t26 -33l79 -194q5 1 16 3q34 6 54 9.5t60 7t65.5 1t61 -10t56.5 -23t42.5 -42t29 -64t5 -92t-19.5 -121.5q-1 -7 -3 -19.5t-11 -50t-20.5 -73t-32.5 -81.5t-46.5 -83t-64 -70 t-82.5 -50q-13 -5 -42 -5t-65.5 2.5t-47.5 2.5q-14 0 -49.5 -3.5t-63 -3.5t-43.5 7q-57 25 -104.5 78.5t-75 111.5t-46.5 112t-26 90l-7 35q-15 63 -18 115t4.5 88.5t26 64t39.5 43.5t52 25.5t58.5 13t62.5 2t59.5 -4.5t55.5 -8l-147 192q-12 18 -5.5 30t27.5 12z" />
<glyph unicode="&#x1f511;" d="M250 1200h600q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-500l-255 -178q-19 -9 -32 -1t-13 29v650h-150q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM400 1100v-100h300v100h-300z" />
<glyph unicode="&#x1f6aa;" d="M250 1200h750q39 0 69.5 -40.5t30.5 -84.5v-933l-700 -117v950l600 125h-700v-1000h-100v1025q0 23 15.5 49t34.5 26zM500 525v-100l100 20v100z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 106 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
/**
* Simplified Chinese translation for bootstrap-datetimepicker
* Yuan Cheung <advanimal@gmail.com>
*/
;(function($){
$.fn.datetimepicker.dates['zh-CN'] = {
days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
daysMin: ["", "", "", "", "", "", "", ""],
months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
today: "今天",
suffix: [],
meridiem: ["上午", "下午"]
};
}(jQuery));

View File

@ -0,0 +1,240 @@
/**
* Bootstrap tab组件封装
* @author billjiang qq:475572229
* @created 2017/7/24
*
*/
(function ($, window, document, undefined) {
'use strict';
var pluginName = 'tabs';
//入口方法
$.fn[pluginName] = function (options) {
var self = $(this);
if (this == null)
return null;
var data = this.data(pluginName);
if (!data) {
data = new BaseTab(this, options);
self.data(pluginName, data);
}
return data;
};
var BaseTab = function (element, options) {
this.$element = $(element);
this.options = $.extend(true, {}, this.default, options);
this.init();
}
//默认配置
BaseTab.prototype.default = {
showIndex: 0, //默认显示页索引
loadAll: true//true=一次全部加在页面,false=只加在showIndex指定的页面其他点击时加载提高响应速度
}
/**
* 结构模板
* <ul class="nav nav-tabs" id="myTab">
<li class="active"><a data-toggle="tab" href="#home">Home</a></li>
<li><a data-toggle="tab" href="#profile">Profile</a><i class="fa fa-remove closeable" title="关闭"></i></li>
<li><a data-toggle="tab" href="#messages">Messages</a></li>
<li><a data-toggle="tab" href="#settings">Settings</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="home">home1111</div>
<div class="tab-pane active" id="profile">profile11111</div>
<div class="tab-pane" id="messages">messages111</div>
<div class="tab-pane" id="settings">settings1111</div>
* </div>
*
*/
BaseTab.prototype.template = {
ul_nav: '<ul id="myTab" class=" nav-my-tab nav nav-tabs"></ul>',
ul_li: '<li><a href="#{0}" data-toggle="tab"><span>{1}</span></a></li>',
ul_li_close: '<i class="fa fa-remove closeable" title="关闭"></i>',
div_content: '<div class="tab-content" ></div>',
div_content_panel: '<div class="tab-pane fade" id="{0}"></div>'
}
//初始化
BaseTab.prototype.init = function () {
if (!this.options.data || this.options.data.length == 0) {
console.error("请指定tab页数据");
return;
}
//当前显示的显示的页面是否超出索引
if (this.options.showIndex < 0 || this.options.showIndex > this.options.data.length - 1) {
console.error("showIndex超出了范围");
//指定为默认值
this.options.showIndex = this.default.showIndex;
}
//清除原来的tab页
this.$element.html("");
this.builder(this.options.data);
}
//使用模板搭建页面结构
BaseTab.prototype.builder = function (data) {
var ul_nav = $(this.template.ul_nav);
ul_nav.on("click", "li", function () {
var id = $(this).children().attr('href').substring(1);
$("#scriptEditor").attr("style", "display:block;");
setScript(id);
})
var div_content = $(this.template.div_content);
for (var i = 0; i < data.length; i++) {
//nav-tab
var ul_li = $(this.template.ul_li.format(data[i].id, data[i].text));
//如果可关闭,插入关闭图标并绑定关闭事件
if (data[i].closeable) {
var ul_li_close = $(this.template.ul_li_close);
ul_li.find("a").append(ul_li_close);
ul_li.find("a").append("&nbsp;");
}
ul_nav.prepend(ul_li);
//div-content
var div_content_panel = $(this.template.div_content_panel.format(data[i].id));
div_content.append(div_content_panel);
}
this.$element.append(ul_nav);
this.$element.append(div_content);
this.loadData();
this.$element.find(".nav-tabs li:eq(" + this.options.showIndex + ") a").tab("show");
}
BaseTab.prototype.loadData = function () {
var self = this;
//tab点击即加载事件
//设置一个值记录每个tab页是否加载过
this.stateObj = {};
var data = this.options.data;
//如果是当前页或者配置了一次性全部加载否则点击tab页时加载
for (var i = 0; i < data.length; i++) {
if (this.options.loadAll || this.options.showIndex == i) {
if (data[i].url) {
this.stateObj[data[i].id] = true;
} else {
console.error("id=" + data[i].id + "的tab页未指定url");
this.stateObj[data[i].id] = false;
}
} else {
this.stateObj[data[i].id] = false;
(function (id, url, parameter) {
self.$element.find(".nav-tabs a[href='#" + id + "']").on('show.bs.tab', function () {
if (!self.stateObj[id]) {
self.stateObj[id] = true;
}
});
}(data[i].id, data[i].url, data[i].parameter))
}
}
}
//新增一个tab页
BaseTab.prototype.addTab = function (obj) {
var self = this;
//nav-tab
var ul_li = $(this.template.ul_li.format(obj.id, obj.text));
//如果可关闭,插入关闭图标并绑定关闭事件
if (obj.closeable) {
var ul_li_close = $(this.template.ul_li_close);
ul_li.find("a").append(ul_li_close);
ul_li.find("a").append("&nbsp;");
}
this.$element.find(".nav-tabs:eq(0)").prepend(ul_li);
//div-content
var div_content_panel = $(this.template.div_content_panel.format(obj.id));
this.$element.find(".tab-content:eq(0)").append(div_content_panel);
this.stateObj[obj.id] = true;
if (obj.closeable) {
this.$element.find(".nav-tabs li a[href='#" + obj.id + "'] i.closeable").click(function () {
var href = $(this).parents("a").attr("href").substring(1);// id
//关闭的时候tab点击事件删除tabData中的数据
var tabData = JSON.parse(localStorage.getItem('tabData'));
tabData = tabData.filter(function (item) {
return item['id'] != href;
});
localStorage.setItem("tabData", JSON.stringify(tabData));
var headId = self.$element.find(".nav-tabs li:eq(0) a").attr("href").substring(1);
if (self.getCurrentTabId() == href) {
self.$element.find(".nav-tabs li:eq(0) a").tab("show");
setScript(headId);
}
//传递tab 删除事件给外部 来减少删除的tab长度
var length = $(this).parents("li").width();
$("#tabContainer").trigger('deleteTab',length);
$(this).parents("li").remove();
$("#" + href).remove();
})
}
this.$element.find(".nav-tabs a[href='#" + obj.id + "']").tab("show");
}
//根据id获取活动也标签名
BaseTab.prototype.find = function (tabId) {
return this.$element.find(".nav-tabs li a[href='#" + tabId + "']").text();
}
// 删除活动页
BaseTab.prototype.remove = function (tabId) {
var self = this;
$("#" + tabId).remove();
this.$element.find(".nav-tabs li a[href='#" + tabId + "']").parents("li").remove();
}
// 重新加载页面
BaseTab.prototype.reload = function (obj) {
var self = this;
if (self.find(obj.id) != null) {
$("#" + obj.id).remove();
this.$element.find(".nav-tabs li a[href='#" + obj.id + "']").parents("li").remove();
self.addTab(obj);
} else {
self.addTab(obj);
}
}
//根据id设置活动tab页
BaseTab.prototype.showTab = function (tabId) {
this.$element.find(".nav-tabs li a[href='#" + tabId + "']").tab("show");
}
//根据id修改text
BaseTab.prototype.changeText = function (tabId,text) {
this.options.data.text = text;
this.$element.find(".nav-tabs li a[href='#" + tabId + "']").children('span').html(text);
}
//获取当前活动tab页的ID
BaseTab.prototype.getCurrentTabId = function () {
var href = this.$element.find(".nav-tabs li.active a").attr("href");
href = href.substring(1);
return href;
}
String.prototype.format = function () {
if (arguments.length == 0) return this;
for (var s = this, i = 0; i < arguments.length; i++)
s = s.replace(new RegExp("\\{" + i + "\\}", "g"), arguments[i]);
return s;
};
})(jQuery, window, document)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
require('../../js/transition.js')
require('../../js/alert.js')
require('../../js/button.js')
require('../../js/carousel.js')
require('../../js/collapse.js')
require('../../js/dropdown.js')
require('../../js/modal.js')
require('../../js/tooltip.js')
require('../../js/popover.js')
require('../../js/scrollspy.js')
require('../../js/tab.js')
require('../../js/affix.js')

View File

@ -0,0 +1,36 @@
// Mixins
// --------------------------------------------------
// Utilities
@import "mixins/hide-text.less";
@import "mixins/opacity.less";
@import "mixins/image.less";
@import "mixins/labels.less";
@import "mixins/reset-filter.less";
@import "mixins/resize.less";
@import "mixins/responsive-visibility.less";
@import "mixins/size.less";
@import "mixins/tab-focus.less";
@import "mixins/reset-text.less";
@import "mixins/text-emphasis.less";
@import "mixins/text-overflow.less";
@import "mixins/vendor-prefixes.less";
// Components
@import "mixins/alerts.less";
@import "mixins/buttons.less";
@import "mixins/panels.less";
@import "mixins/pagination.less";
@import "mixins/list-group.less";
@import "mixins/nav-divider.less";
@import "mixins/forms.less";
@import "mixins/progress-bar.less";
@import "mixins/table-row.less";
// Skins
@import "mixins/background-variant.less";
@import "mixins/border-radius.less";
@import "mixins/gradients.less";
// Layout
@import "mixins/clearfix.less";
@import "mixins/center-block.less";
@import "mixins/nav-vertical-align.less";
@import "mixins/grid-framework.less";
@import "mixins/grid.less";

View File

@ -0,0 +1,14 @@
// Alerts
.alert-variant(@background; @border; @text-color) {
background-color: @background;
border-color: @border;
color: @text-color;
hr {
border-top-color: darken(@border, 5%);
}
.alert-link {
color: darken(@text-color, 10%);
}
}

View File

@ -0,0 +1,9 @@
// Contextual backgrounds
.bg-variant(@color) {
background-color: @color;
a&:hover,
a&:focus {
background-color: darken(@color, 10%);
}
}

View File

@ -0,0 +1,21 @@
// Single side border-radius
.border-top-radius(@radius) {
border-top-right-radius: @radius;
border-top-left-radius: @radius;
}
.border-right-radius(@radius) {
border-bottom-right-radius: @radius;
border-top-right-radius: @radius;
}
.border-bottom-radius(@radius) {
border-bottom-right-radius: @radius;
border-bottom-left-radius: @radius;
}
.border-left-radius(@radius) {
border-bottom-left-radius: @radius;
border-top-left-radius: @radius;
}

View File

@ -0,0 +1,68 @@
// Button variants
//
// Easily pump out default styles, as well as :hover, :focus, :active,
// and disabled options for all buttons
.button-variant(@color; @background; @border) {
color: @color;
background-color: @background;
border-color: @border;
&:focus,
&.focus {
color: @color;
background-color: darken(@background, 10%);
border-color: darken(@border, 25%);
}
&:hover {
color: @color;
background-color: darken(@background, 10%);
border-color: darken(@border, 12%);
}
&:active,
&.active,
.open > .dropdown-toggle& {
color: @color;
background-color: darken(@background, 10%);
border-color: darken(@border, 12%);
&:hover,
&:focus,
&.focus {
color: @color;
background-color: darken(@background, 17%);
border-color: darken(@border, 25%);
}
}
&:active,
&.active,
.open > .dropdown-toggle& {
background-image: none;
}
&.disabled,
&[disabled],
fieldset[disabled] & {
&,
&:hover,
&:focus,
&.focus,
&:active,
&.active {
background-color: @background;
border-color: @border;
}
}
.badge {
color: @background;
background-color: @color;
}
}
// Button sizes
.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
padding: @padding-vertical @padding-horizontal;
font-size: @font-size;
line-height: @line-height;
border-radius: @border-radius;
}

View File

@ -0,0 +1,7 @@
// Center-align a block level element
.center-block() {
display: block;
margin-left: auto;
margin-right: auto;
}

View File

@ -0,0 +1,22 @@
// Clearfix
//
// For modern browsers
// 1. The space content is one way to avoid an Opera bug when the
// contenteditable attribute is included anywhere else in the document.
// Otherwise it causes space to appear at the top and bottom of elements
// that are clearfixed.
// 2. The use of `table` rather than `block` is only necessary if using
// `:before` to contain the top-margins of child elements.
//
// Source: http://nicolasgallagher.com/micro-clearfix-hack/
.clearfix() {
&:before,
&:after {
content: " "; // 1
display: table; // 2
}
&:after {
clear: both;
}
}

View File

@ -0,0 +1,84 @@
// Form validation states
//
// Used in forms.less to generate the form validation CSS for warnings, errors,
// and successes.
.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {
// Color the label and help text
.help-block,
.control-label,
.radio,
.checkbox,
.radio-inline,
.checkbox-inline,
&.radio label,
&.checkbox label,
&.radio-inline label,
&.checkbox-inline label {
color: @text-color;
}
// Set the border and box shadow on specific inputs to match
.form-control {
border-color: @border-color;
.box-shadow(inset 0 1px 1px rgba(0, 0, 0, .075)); // Redeclare so transitions work
&:focus {
border-color: darken(@border-color, 10%);
@shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px lighten(@border-color, 20%);
.box-shadow(@shadow);
}
}
// Set validation states also for addons
.input-group-addon {
color: @text-color;
border-color: @border-color;
background-color: @background-color;
}
// Optional feedback icon
.form-control-feedback {
color: @text-color;
}
}
// Form control focus state
//
// Generate a customized focus state and for any input with the specified color,
// which defaults to the `@input-border-focus` variable.
//
// We highly encourage you to not customize the default value, but instead use
// this to tweak colors on an as-needed basis. This aesthetic change is based on
// WebKit's default styles, but applicable to a wider range of browsers. Its
// usability and accessibility should be taken into account with any change.
//
// Example usage: change the default blue border and shadow to white for better
// contrast against a dark gray background.
.form-control-focus(@color: @input-border-focus) {
@color-rgba: rgba(red(@color), green(@color), blue(@color), .6);
&:focus {
border-color: @color;
outline: 0;
.box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
}
}
// Form control sizing
//
// Relative text size, padding, and border-radii changes for form controls. For
// horizontal sizing, wrap controls in the predefined grid classes. `<select>`
// element gets special love because it's special, and that's a fact!
.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
height: @input-height;
padding: @padding-vertical @padding-horizontal;
font-size: @font-size;
line-height: @line-height;
border-radius: @border-radius;
select& {
height: @input-height;
line-height: @input-height;
}
textarea&,
select[multiple] & {
height: auto;
}
}

View File

@ -0,0 +1,59 @@
// Gradients
#gradient {
// Horizontal gradient, from left to right
//
// Creates two color stops, start and end, by specifying a color and position for each color stop.
// Color stops are not available in IE9 and below.
.horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {
background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+
background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12
background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
background-repeat: repeat-x;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)", argb(@start-color), argb(@end-color))); // IE9 and down
}
// Vertical gradient, from top to bottom
//
// Creates two color stops, start and end, by specifying a color and position for each color stop.
// Color stops are not available in IE9 and below.
.vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {
background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+
background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12
background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
background-repeat: repeat-x;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)", argb(@start-color), argb(@end-color))); // IE9 and down
}
.directional(@start-color: #555; @end-color: #333; @deg: 45deg) {
background-repeat: repeat-x;
background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+
background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12
background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
}
.horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {
background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);
background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);
background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);
background-repeat: no-repeat;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)", argb(@start-color), argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback
}
.vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {
background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);
background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);
background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);
background-repeat: no-repeat;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)", argb(@start-color), argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback
}
.radial(@inner-color: #555; @outer-color: #333) {
background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);
background-image: radial-gradient(circle, @inner-color, @outer-color);
background-repeat: no-repeat;
}
.striped(@color: rgba(255,255,255,.15); @angle: 45deg) {
background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
}
}

View File

@ -0,0 +1,102 @@
// Framework grid generation
//
// Used only by Bootstrap to generate the correct number of grid classes given
// any value of `@grid-columns`.
.make-grid-columns() {
// Common styles for all sizes of grid columns, widths 1-12
.col(@index) {
// initial
@item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
.col((@index + 1), @item);
}
.col(@index, @list) when (@index =< @grid-columns) {
// general; "=<" isn't a typo
@item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
.col((@index + 1), ~"@{list}, @{item}");
}
.col(@index, @list) when (@index > @grid-columns) {
// terminal
@{list} {
position: relative;
// Prevent columns from collapsing when empty
min-height: 1px;
// Inner gutter via padding
padding-left: (@grid-gutter-width / 2);
padding-right: (@grid-gutter-width / 2);
}
}
.col(1); // kickstart it
}
.float-grid-columns(@class) {
.col(@index) {
// initial
@item: ~".col-@{class}-@{index}";
.col((@index + 1), @item);
}
.col(@index, @list) when (@index =< @grid-columns) {
// general
@item: ~".col-@{class}-@{index}";
.col((@index + 1), ~"@{list}, @{item}");
}
.col(@index, @list) when (@index > @grid-columns) {
// terminal
@{list} {
float: left;
}
}
.col(1); // kickstart it
}
.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {
.col-@{class}-@{index} {
width: percentage((@index / @grid-columns));
}
}
.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {
.col-@{class}-push-@{index} {
left: percentage((@index / @grid-columns));
}
}
.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {
.col-@{class}-push-0 {
left: auto;
}
}
.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {
.col-@{class}-pull-@{index} {
right: percentage((@index / @grid-columns));
}
}
.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {
.col-@{class}-pull-0 {
right: auto;
}
}
.calc-grid-column(@index, @class, @type) when (@type = offset) {
.col-@{class}-offset-@{index} {
margin-left: percentage((@index / @grid-columns));
}
}
// Basic looping in LESS
.loop-grid-columns(@index, @class, @type) when (@index >= 0) {
.calc-grid-column(@index, @class, @type);
// next iteration
.loop-grid-columns((@index - 1), @class, @type);
}
// Create grid for specific class
.make-grid(@class) {
.float-grid-columns(@class);
.loop-grid-columns(@grid-columns, @class, width);
.loop-grid-columns(@grid-columns, @class, pull);
.loop-grid-columns(@grid-columns, @class, push);
.loop-grid-columns(@grid-columns, @class, offset);
}

View File

@ -0,0 +1,134 @@
// Grid system
//
// Generate semantic grid columns with these mixins.
// Centered container element
.container-fixed(@gutter: @grid-gutter-width) {
margin-right: auto;
margin-left: auto;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
&:extend(.clearfix all);
}
// Creates a wrapper for a series of columns
.make-row(@gutter: @grid-gutter-width) {
margin-left: ceil((@gutter / -2));
margin-right: floor((@gutter / -2));
&:extend(.clearfix all);
}
// Generate the extra small columns
.make-xs-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
float: left;
width: percentage((@columns / @grid-columns));
min-height: 1px;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
}
.make-xs-column-offset(@columns) {
margin-left: percentage((@columns / @grid-columns));
}
.make-xs-column-push(@columns) {
left: percentage((@columns / @grid-columns));
}
.make-xs-column-pull(@columns) {
right: percentage((@columns / @grid-columns));
}
// Generate the small columns
.make-sm-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
min-height: 1px;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
@media (min-width: @screen-sm-min) {
float: left;
width: percentage((@columns / @grid-columns));
}
}
.make-sm-column-offset(@columns) {
@media (min-width: @screen-sm-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-sm-column-push(@columns) {
@media (min-width: @screen-sm-min) {
left: percentage((@columns / @grid-columns));
}
}
.make-sm-column-pull(@columns) {
@media (min-width: @screen-sm-min) {
right: percentage((@columns / @grid-columns));
}
}
// Generate the medium columns
.make-md-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
min-height: 1px;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
@media (min-width: @screen-md-min) {
float: left;
width: percentage((@columns / @grid-columns));
}
}
.make-md-column-offset(@columns) {
@media (min-width: @screen-md-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-md-column-push(@columns) {
@media (min-width: @screen-md-min) {
left: percentage((@columns / @grid-columns));
}
}
.make-md-column-pull(@columns) {
@media (min-width: @screen-md-min) {
right: percentage((@columns / @grid-columns));
}
}
// Generate the large columns
.make-lg-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
min-height: 1px;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
@media (min-width: @screen-lg-min) {
float: left;
width: percentage((@columns / @grid-columns));
}
}
.make-lg-column-offset(@columns) {
@media (min-width: @screen-lg-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-lg-column-push(@columns) {
@media (min-width: @screen-lg-min) {
left: percentage((@columns / @grid-columns));
}
}
.make-lg-column-pull(@columns) {
@media (min-width: @screen-lg-min) {
right: percentage((@columns / @grid-columns));
}
}

View File

@ -0,0 +1,21 @@
// CSS image replacement
//
// Heads up! v3 launched with only `.hide-text()`, but per our pattern for
// mixins being reused as classes with the same name, this doesn't hold up. As
// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.
//
// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
// Deprecated as of v3.0.1 (will be removed in v4)
.hide-text() {
font: ~"0/0" a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
// New mixin to use as of v3.0.1
.text-hide() {
.hide-text();
}

View File

@ -0,0 +1,25 @@
// Image Mixins
// - Responsive image
// - Retina image
// Responsive image
//
// Keep images from scaling beyond the width of their parents.
.img-responsive(@display: block) {
display: @display;
max-width: 100%; // Part 1: Set a maximum relative to the parent
height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
}
// Retina image
//
// Short retina mixin for setting background-image and -size. Note that the
// spelling of `min--moz-device-pixel-ratio` is intentional.
.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {
background-image: url("@{file-1x}");
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and ( min--moz-device-pixel-ratio: 2), only screen and ( -o-min-device-pixel-ratio: 2/1), only screen and ( min-device-pixel-ratio: 2), only screen and ( min-resolution: 192dpi), only screen and ( min-resolution: 2dppx) {
background-image: url("@{file-2x}");
background-size: @width-1x @height-1x;
}
}

Some files were not shown because too many files have changed in this diff Show More