├── .gitattributes ├── .gitignore ├── ChangeLog.md ├── Pipfile ├── Pipfile.lock ├── README.md ├── images ├── admin_ip_pool_manage.png ├── cmdb_monitor.png ├── customize_column01.png ├── customize_column02.png ├── dashboard.png ├── host.png ├── host_modify.png ├── import_01.png ├── import_02.png ├── monitor_manage.png └── tree_manage.png ├── startup.sh ├── upload └── host_template.xls ├── wsgi.py └── yalamain ├── __init__.py ├── aliyun ├── __init__.py └── ess.py ├── blueprints ├── __init__.py ├── api_assets.py ├── api_monitor.py ├── api_user.py ├── api_v2.py ├── assets.py ├── auth.py ├── errors.py ├── index.py ├── monitor.py └── user.py ├── config.py ├── cron ├── __init__.py └── cornlog.py ├── extensions.py ├── models.py ├── static ├── css │ ├── AdminLTE.min.css │ ├── _all-skins.min.css │ ├── bootstrap-datepicker.min.css │ ├── bootstrap-multiselect.css │ ├── bootstrap-table.min.css │ ├── bootstrap.min.css │ ├── bootstrap3-wysihtml5.min.css │ ├── dataTables.bootstrap.min.css │ ├── daterangepicker.css │ ├── fakeLoader.css │ ├── font-awesome.min.css │ ├── iCheck │ │ └── square │ │ │ ├── blue.css │ │ │ ├── blue.png │ │ │ └── blue@2x.png │ ├── ionicons.min.css │ ├── jquery-jvectormap.css │ ├── jquery.dataTables.min.css │ ├── morris.css │ ├── select2.min.css │ ├── sweetalert2.min.css │ ├── tablesorter.theme.default.css │ ├── ztree-style.css │ └── ztree │ │ └── awesomeStyle │ │ ├── awesome.css │ │ └── img │ │ └── loading.gif ├── font-awesome │ └── fonts │ │ ├── fontawesome-webfontba72.eot │ │ ├── fontawesome-webfontba72.svg │ │ ├── fontawesome-webfontba72.ttf │ │ ├── fontawesome-webfontba72.woff │ │ └── fontawesome-webfontd41d.eot ├── fonts │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ ├── ionicons.eot │ ├── ionicons.svg │ ├── ionicons.ttf │ └── ionicons.woff ├── image │ ├── avatar1.png │ ├── avatar2.png │ ├── avatar3.png │ ├── avatar4.jpg │ └── favicon.ico └── js │ ├── Chart.js │ ├── WdatePicker.js │ ├── adminlte.min.js │ ├── bootstrap-datepicker.min.js │ ├── bootstrap-multiselect.js │ ├── bootstrap-notify.js │ ├── bootstrap-table-maintainColSwitch.min.js │ ├── bootstrap-table-resizable.min.js │ ├── bootstrap-table-zh-CN.min.js │ ├── bootstrap-table.min.js │ ├── bootstrap.min.js │ ├── bootstrap3-wysihtml5.all.min.js │ ├── calendar.js │ ├── colResizable-1.6.min.js │ ├── dataTables.bootstrap.min.js │ ├── daterangepicker.js │ ├── fakeLoader.js │ ├── fastclick.js │ ├── icheck.min.js │ ├── jquery-jvectormap-1.2.2.min.js │ ├── jquery-jvectormap-world-mill-en.js │ ├── jquery-ui.min.js │ ├── jquery.cookie.js │ ├── jquery.dataTables.min.js │ ├── jquery.form.min.js │ ├── jquery.knob.min.js │ ├── jquery.min.js │ ├── jquery.slimscroll.min.js │ ├── jquery.sparkline.min.js │ ├── jquery.tablesorter.min.js │ ├── jquery.tablesorter.widgets.min.js │ ├── ladda.min.js │ ├── lang │ ├── en.js │ ├── zh-cn.js │ └── zh-tw.js │ ├── moment.min.js │ ├── morris.min.js │ ├── raphael.min.js │ ├── select2.full.min.js │ ├── select2.min.js │ ├── skin │ ├── WdatePicker.css │ ├── datePicker.gif │ ├── default │ │ ├── datepicker.css │ │ └── img.gif │ └── whyGreen │ │ ├── bg.jpg │ │ ├── datepicker.css │ │ └── img.gif │ ├── spin.js │ ├── spin.min.js │ ├── sweetalert2.all.min.js │ ├── vue.js │ └── ztree │ └── jquery.ztree.all.min.js └── templates ├── assets ├── department.html ├── host.html ├── host_detail.html ├── ip_pool.html ├── ip_pool_detail.html └── service.html ├── auth └── login.html ├── base.html ├── changelog.html ├── error ├── 400.html ├── 401.html ├── 403.html ├── 404.html ├── 405.html └── 500.html ├── footer.html ├── index.html ├── main-header.html ├── main-sidebar.html ├── monitor ├── domain.html └── tcp_connect_probe.html ├── page.html └── user ├── permission.html ├── roles.html └── users.html /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-language=python 2 | *.js linguist-language=python 3 | *.css linguist-language=python -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | include 3 | lib 4 | share 5 | __pycache__ 6 | .Python 7 | *.pyc 8 | *.log 9 | .settings 10 | .project 11 | .pydevproject 12 | .DS_Store 13 | venv 14 | .idea/ 15 | logs/ 16 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ## V1.0.0 (2020-03-28) 2 | - 第一个对外发布的版本 3 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.tuna.tsinghua.edu.cn/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | flask = "*" 10 | flask-sqlalchemy = "*" 11 | flask-login = "*" 12 | tablib = "*" 13 | ipy = "*" 14 | flask-httpauth = "*" 15 | pymysql = "*" 16 | flask-simpleldap = "*" 17 | gunicorn = "*" 18 | requests = "*" 19 | aliyun-python-sdk-core-v3 = "*" 20 | aliyun-python-sdk-ecs = "*" 21 | aliyun-python-sdk-ess = "*" 22 | python-dotenv = "*" 23 | flask-wtf = "*" 24 | flask-apscheduler = "*" 25 | 26 | [requires] 27 | python_version = "3.7" 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yala 2 | 3 | Yala是该项目的代号。 4 | > Yala是斯里兰卡Yala National Park,自然风景很美,有丰富的野生动物。 5 | 6 | Yala是综合的运维管理平台,使用GNU GPL v2.0开源协议。集成的功能有CMDB、监控域名和端口管理、阿里云主机管理等(v1.0.0版本去掉了阿里云主机管理等功能)。 7 | 8 | ## 开发语言和框架使用 9 | * 编程语言:Python3.7 10 | * 前端Web框架:Bootstrap + jquery + vue.js + AdminLTE2.0 11 | * 后端Web框架:Flask 12 | * 数据存储:MySQL 5.7 13 | 14 | ## 运行环境 15 | 在以下环境已长时间稳定运行 16 | Python 3.7 + CentOS 7.4 + MySQL 5.7 17 | 18 | ## 依赖模块 19 | Python3的虚拟环境使用pipenv管理,具体可查看Pipfile文件 20 | 21 | ## 启动脚本 22 | ``` 23 | cd $Project_basedir 24 | bash startup.sh start 25 | ``` 26 | 27 | ## 项目介绍文档 28 | 29 | ### Dashboard 30 | 31 | Dashboard主要是总揽和数据趋势,还需要进一步完善。 32 | ![dashboard](images/dashboard.png) 33 | 34 | ### CMDB 35 | 36 | **备注:所有的部门、服务、主机信息都是我构造的,不是在生产环境实际使用的** 37 | 38 | 功能列表 39 | - 以树型结构方式管理所有部门、服务、主机,包含每个节点下所有的主机数 40 | - 一级节点为公司名称 41 | - 二级节点为部门 42 | - 三级节点为服务 43 | - 支持树型结构中树节点的增、删、改、查、鼠标拖动等 44 | - 支持主机的信息增、删、改、查 45 | - 支持批量导入和导出主机信息 46 | - 支持批量删除主机 47 | - 支持自定义显示列 48 | - 支持搜索 49 | - 提供鉴权的API接口用于外部调用 50 | 51 | **CMDB页面截图** 52 | ![CMDB页面](images/host.png) 53 | 54 | **树型结构管理** 55 | ![树型结构管理](images/tree_manage.png) 56 | 57 | **自定义显示列** 58 | ![自定义显示列](images/customize_column01.png) ![自定义显示列](images/customize_column02.png) 59 | 60 | **支持批量导入** 61 | ![批量导入](images/import_01.png) ![批量导入](images/import_02.png) 62 | 63 | **主机信息修改** 64 | ![批量导入](images/host_modify.png) 65 | 66 | **通过API获取树型信息和主机信息后呈现的监控** 67 | ![批量导入](images/cmdb_monitor.png) 68 | 69 |
70 | 71 | --- 72 | 73 |
74 | 75 | ### 监控管理 76 | 77 | 监控管理主要提供域名和端口的管理页面和API接口,可配合Prometheus等监控工具使用(也可使用Consul等进行服务的注册管理,看实际需求和便利性) 78 | 79 | ![监控管理](images/monitor_manage.png) 80 | 81 | 82 | ### 管理员配置 83 | 84 | 在管理员配置中提供了IP地址池、部门、服务的管理功能。 85 | 86 | ![管理员配置](images/admin_ip_pool_manage.png) 87 | 88 | 89 | ### 权限管理 90 | 目前的权限控制和管理实现不太规范,但可满足需求,暂不想去修改;待后续有需要的再进行修改。 91 | 92 | 当前角色分为四种,分别对应不同的权限: 93 | - Admin 94 | - Ops 95 | - RD 96 | - AlyOps 97 | 98 | ## 贡献者 99 | 除了我 [xl0shk](https://github.com/xl0shk) 以下同学也都有过贡献代码 100 | [seadog0331](https://github.com/seadog0331) 101 | [Avan1984](https://github.com/Avan1984) 102 | [zak0329](https://github.com/zak0329) 103 | -------------------------------------------------------------------------------- /images/admin_ip_pool_manage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/admin_ip_pool_manage.png -------------------------------------------------------------------------------- /images/cmdb_monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/cmdb_monitor.png -------------------------------------------------------------------------------- /images/customize_column01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/customize_column01.png -------------------------------------------------------------------------------- /images/customize_column02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/customize_column02.png -------------------------------------------------------------------------------- /images/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/dashboard.png -------------------------------------------------------------------------------- /images/host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/host.png -------------------------------------------------------------------------------- /images/host_modify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/host_modify.png -------------------------------------------------------------------------------- /images/import_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/import_01.png -------------------------------------------------------------------------------- /images/import_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/import_02.png -------------------------------------------------------------------------------- /images/monitor_manage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/monitor_manage.png -------------------------------------------------------------------------------- /images/tree_manage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/images/tree_manage.png -------------------------------------------------------------------------------- /startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #-------------------------------------------------- 3 | # yala项目的启动、重启、停止脚本,不一定适用于其他项目。 4 | #-------------------------------------------------- 5 | 6 | PROJECT_NAME="yala" 7 | PROJECT_PORT=30010 8 | PID_FILE="/var/run/${PROJECT_NAME}.pid" 9 | CPU_CORE=`grep -c ^processor /proc/cpuinfo` 10 | 11 | check_running() 12 | { 13 | pid=`cat ${PID_FILE}` 14 | ps aux | grep ${pid} | grep -q ${PROJECT_NAME} 15 | echo $? 16 | } 17 | 18 | 19 | start() 20 | { 21 | cd `dirname $0` 22 | pipenv run gunicorn -w ${CPU_CORE} -b 0.0.0.0:${PROJECT_PORT} wsgi:app -p ${PID_FILE} -D 23 | 24 | sleep 2 25 | 26 | res=`check_running` 27 | 28 | if [[ ${res} -eq 0 ]];then 29 | echo "${PROJECT_NAME} Start Success." 30 | else 31 | echo "${PROJECT_NAME} Start Failed." 32 | rm -f ${PROJECT_NAME} 33 | fi 34 | } 35 | 36 | 37 | stop() 38 | { 39 | if [[ -f ${PID_FILE} ]];then 40 | res=`check_running` 41 | if [[ ${res} -eq 0 ]];then 42 | pid=`cat ${PID_FILE}` 43 | kill ${pid} 44 | sleep 2 45 | res=`ps aux | grep ${PROJECT_NAME} | grep -v 'grep'` 46 | if [[ $? -ne 0 ]];then 47 | echo "${PROJECT_NAME} Stop Success." 48 | else 49 | kill -9 ${pid} 50 | rm -f ${PID_FILE} 51 | echo "${PROJECT_NAME} Force Stop Success." 52 | fi 53 | else 54 | echo "${PID_FILE} exists, but ${PROJECT_NAME} not running" 55 | rm -f ${PID_FILE} 56 | fi 57 | else 58 | echo "${PROJECT_NAME} is not running." 59 | fi 60 | 61 | } 62 | 63 | 64 | usage() 65 | { 66 | echo "$0 stop" 67 | echo "$0 start" 68 | echo "$0 restart" 69 | } 70 | 71 | if [[ $# -ne 1 ]];then 72 | usage 73 | exit 1 74 | fi 75 | 76 | case $1 in 77 | start) 78 | start;; 79 | stop) 80 | stop;; 81 | restart) 82 | stop && sleep 2 && start;; 83 | *) 84 | usage && exit 1 85 | esac 86 | -------------------------------------------------------------------------------- /upload/host_template.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/upload/host_template.xls -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from yalamain import create_app 3 | 4 | app = create_app('development') 5 | 6 | if __name__ == '__main__': 7 | app.run(host='127.0.0.1', port=30010, debug=True) 8 | -------------------------------------------------------------------------------- /yalamain/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import logging 4 | from flask import Flask, g, session, request 5 | from logging.handlers import TimedRotatingFileHandler 6 | 7 | from yalamain.config import config 8 | from yalamain.extensions import db, ldap, login_manager, apscheduler 9 | 10 | from yalamain.blueprints.api_assets import api_assets_bp 11 | from yalamain.blueprints.api_user import api_user_bp 12 | from yalamain.blueprints.api_monitor import api_monitor_bp 13 | from yalamain.blueprints.api_v2 import api_v2_bp 14 | from yalamain.blueprints.assets import assets_bp 15 | from yalamain.blueprints.user import user_bp 16 | from yalamain.blueprints.monitor import monitor_bp 17 | from yalamain.blueprints.index import index_bp 18 | from yalamain.blueprints.auth import auth_bp 19 | from yalamain.cron.cornlog import update_cron_log 20 | 21 | 22 | def create_app(config_name=None): 23 | if config_name is None: 24 | config_name = os.getenv('YALA_CONFIG', 'development') 25 | 26 | app = Flask(__name__) 27 | app.config.from_object(config[config_name]) 28 | app.config['JSON_AS_ASCII'] = False 29 | 30 | register_logging(app) 31 | register_blueprints(app) 32 | register_extensions(app) 33 | register_hooks(app) 34 | 35 | return app 36 | 37 | 38 | def register_extensions(app): 39 | ldap.init_app(app) 40 | db.init_app(app) 41 | login_manager.init_app(app) 42 | apscheduler.init_app(app) 43 | apscheduler.start() 44 | 45 | 46 | def register_blueprints(app): 47 | app.register_blueprint(api_assets_bp, url_prefix='/api/assets') 48 | app.register_blueprint(api_user_bp, url_prefix='/api/user') 49 | app.register_blueprint(api_monitor_bp, url_prefix='/api/monitor') 50 | app.register_blueprint(api_v2_bp, url_prefix='/api/v2') 51 | app.register_blueprint(assets_bp, url_prefix='/assets') 52 | app.register_blueprint(user_bp, url_prefix='/user') 53 | app.register_blueprint(monitor_bp, url_prefix='/monitor') 54 | app.register_blueprint(auth_bp, url_prefix='/auth') 55 | app.register_blueprint(index_bp, url_prefix='/') 56 | 57 | 58 | def register_hooks(app): 59 | @app.before_request 60 | def before_request(): 61 | g.user = None 62 | if 'user_id' in session: 63 | g.user = {} 64 | g.ldap_groups = ldap.get_user_groups(user=session['user_id']) 65 | 66 | 67 | def register_logging(app): 68 | formatter = logging.Formatter('%(asctime)s - %(thread)s - %(levelname)s - %(funcName)s - %(message)s') 69 | 70 | file_handler_info = TimedRotatingFileHandler(app.config.get('INFO_LOG_PATH'), when="midnight", backupCount=10, encoding='UTF-8') 71 | file_handler_info.setFormatter(formatter) 72 | file_handler_info.setLevel(logging.INFO) 73 | app.logger.addHandler(file_handler_info) 74 | app.logger.setLevel(logging.INFO) 75 | -------------------------------------------------------------------------------- /yalamain/aliyun/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/aliyun/__init__.py -------------------------------------------------------------------------------- /yalamain/aliyun/ess.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from flask import current_app 3 | from aliyunsdkcore.client import AcsClient 4 | from aliyunsdkess.request.v20140828 import DescribeScalingGroupsRequest 5 | from aliyunsdkess.request.v20140828 import DescribeScalingInstancesRequest 6 | from aliyunsdkecs.request.v20140526 import DescribeInstancesRequest 7 | from aliyunsdkcore.acs_exception.exceptions import ServerException, ClientException 8 | import json 9 | 10 | 11 | class AliyunESS(object): 12 | 13 | def __init__(self, region_id='cn-hangzhou'): 14 | self.access_key_id = current_app.config['ACCESS_KEY_ID'] 15 | self.access_key_secret = current_app.config['ACCESS_KEY_SECRET'] 16 | self.region_id = region_id 17 | 18 | self.client = AcsClient(self.access_key_id, self.access_key_secret, self.region_id) 19 | 20 | def aliyun_request(self, request): 21 | try: 22 | response = json.loads(self.client.do_action_with_exception(request)) 23 | except ServerException as err: 24 | # 包含常见异常返回和公共错误码 25 | current_app.logger.error(err) 26 | return None 27 | except ClientException as err: 28 | current_app.logger.error(err) 29 | return None 30 | except Exception as err: 31 | current_app.logger.error(err) 32 | return None 33 | return response 34 | 35 | def describe_ess_groups(self): 36 | request = DescribeScalingGroupsRequest.DescribeScalingGroupsRequest() 37 | request.set_accept_format('json') 38 | response = self.aliyun_request(request) 39 | return response.get('ScalingGroups').get('ScalingGroup') 40 | 41 | def aliyun_describe_ess_groups_instances(self, scaling_group_id): 42 | """ 43 | :获取指定scaling_group_id中的所有instances 44 | :param scaling_group_id: 弹性伸缩组ID 45 | :return: 指定Client所在Region,指定scaling_group_id的所有主机列表 46 | """ 47 | ess_page_size = 50 # 阿里云ESS接口一次最多可返回50条数据 48 | ecs_page_size = 100 # 阿里云ECS接口一次最多可返回100条数据 49 | instance_id_list = [] 50 | ecs_instance_list = [] 51 | 52 | try: 53 | request = DescribeScalingInstancesRequest.DescribeScalingInstancesRequest() 54 | request.set_accept_format('json') 55 | request.set_ScalingGroupId(scaling_group_id) 56 | request.set_PageSize(PageSize=ess_page_size) 57 | 58 | response = json.loads(self.client.do_action_with_exception(request)) 59 | except ClientException as e: 60 | current_app.logger.error(e) 61 | return [] 62 | except ServerException as e: 63 | current_app.logger.error(e) 64 | return [] 65 | except Exception as e: 66 | current_app.logger.error(e) 67 | return [] 68 | 69 | if response is not None: 70 | instance_total_count = response.get('TotalCount') 71 | # 阿里云接口默认会返回50条数据,所以如果scaling_instance_list<50可直接处理,如果>50,进入条件instance_total_count > 50 72 | scaling_instance_list = response.get('ScalingInstances').get('ScalingInstance') 73 | for scaling_instance in scaling_instance_list: 74 | instance_id_list.append(scaling_instance.get('InstanceId', str(0))) 75 | else: 76 | current_app.logger.warning('Response is None.') 77 | return [] 78 | 79 | if instance_total_count > 50: 80 | total_page_num = int(instance_total_count / ess_page_size) + 1 81 | page_num = 2 82 | 83 | while page_num <= total_page_num: 84 | # while按每次请求50条数据的方式处理instance_total_count>50 85 | request.set_PageNumber(PageNumber=page_num) 86 | response = json.loads(self.client.do_action_with_exception(request)) 87 | if response is not None: 88 | scaling_instance_list = response.get('ScalingInstances').get('ScalingInstance') 89 | for scaling_instance in scaling_instance_list: 90 | instance_id_list.append(scaling_instance.get('InstanceId', str(0))) 91 | else: 92 | current_app.logger.warning('Response is None.') 93 | continue 94 | page_num += 1 95 | 96 | # 因为阿里云SDK一次查询最长支持100个instance id,所以以100为长度对列表进行分段成新的列表 97 | instance_id_list_100s = [instance_id_list[x:x + 100] for x in range(0, len(instance_id_list), 100)] 98 | 99 | ecs_request = DescribeInstancesRequest.DescribeInstancesRequest() 100 | ecs_request.set_PageSize(PageSize=ecs_page_size) 101 | ecs_request.set_accept_format('json') 102 | 103 | for i in instance_id_list_100s: 104 | ecs_request.set_InstanceIds(i) 105 | 106 | ecs_response = json.loads(self.client.do_action_with_exception(ecs_request)) 107 | if ecs_response is not None: 108 | ecs_list = ecs_response['Instances']['Instance'] 109 | ecs_instance_list = ecs_instance_list + ecs_list 110 | else: 111 | current_app.logger.warning('Response is None.') 112 | continue 113 | return ecs_instance_list 114 | -------------------------------------------------------------------------------- /yalamain/blueprints/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/blueprints/__init__.py -------------------------------------------------------------------------------- /yalamain/blueprints/api_v2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint 3 | from flask import abort, url_for, g, jsonify, current_app 4 | from functools import wraps 5 | from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth 6 | from sqlalchemy.exc import SQLAlchemyError 7 | 8 | from yalamain.models import Host, IPPool, User, AnonymousUser 9 | 10 | 11 | basic_auth = HTTPBasicAuth() 12 | token_auth = HTTPTokenAuth('Bearer') 13 | multi_auth = MultiAuth(basic_auth, token_auth) 14 | 15 | api_v2_bp = Blueprint('api_v2', __name__) 16 | 17 | 18 | @basic_auth.verify_password 19 | def verify_password(token_or_username, password): 20 | """ 21 | :basic_auth verify_password 22 | """ 23 | if not token_or_username: 24 | g.current_user = AnonymousUser() 25 | return True 26 | 27 | if not password: 28 | g.current_user = User.verify_auth_token(token_or_username) 29 | g.token_used = True 30 | return g.current_user is not None 31 | 32 | try: 33 | user = User.query.filter_by(name=token_or_username).first() 34 | except SQLAlchemyError as sql_err: 35 | current_app.logger.error(sql_err) 36 | return False 37 | else: 38 | if not user: 39 | return False 40 | 41 | g.current_user = user 42 | g.token_used = False 43 | 44 | return user.verify_password(password) 45 | 46 | 47 | @token_auth.verify_token 48 | def verify_token(token): 49 | """ 50 | :token_auth verify_token 51 | """ 52 | g.current_user = None 53 | g.current_user_role = None 54 | try: 55 | data = User.get_auth_token_data(token) 56 | except Exception as err: 57 | current_app.logger.error(err) 58 | return False 59 | if data is not None: 60 | if 'username' in data and 'role' in data: 61 | g.current_user = data['username'] 62 | g.current_user_role = data['role'] 63 | return User.query.get(data['id']) 64 | return False 65 | 66 | 67 | @token_auth.error_handler 68 | def token_auth_failed(): 69 | return jsonify({'resCode': '403', 'resMsg': 'token auth failed.'}), 403 70 | 71 | 72 | def roles_required(*roles): 73 | """Decorator which specifies that a user must have at least one of the 74 | specified roles. Example:: 75 | 76 | @app.route('/create_post') 77 | @roles_accepted('editor', 'author') 78 | def create_post(): 79 | return 'Create Post' 80 | 81 | The current user must have either the `editor` role or `author` role in 82 | order to view the page. 83 | 84 | :param args: The possible roles. 85 | """ 86 | 87 | def wrapper(fn): 88 | @wraps(fn) 89 | def decorated_view(*args, **kwargs): 90 | if g.current_user_role not in roles: 91 | return forbidden('unauthorized to access to this api') 92 | return fn(*args, **kwargs) 93 | 94 | return decorated_view 95 | 96 | return wrapper 97 | 98 | 99 | @token_auth.error_handler 100 | def token_auth_failed(): 101 | return jsonify({'code': '403', 'msg': 'token认证失败'}), 403 102 | 103 | 104 | @basic_auth.error_handler 105 | def auth_error(): 106 | return unauthorized('Invalid credentials') 107 | 108 | 109 | def page_not_found(e): 110 | response = jsonify({'error': 'not found'}) 111 | response.status_code = 404 112 | return response 113 | 114 | 115 | def forbidden(message): 116 | response = jsonify({'error': 'forbidden', 'message': message}) 117 | response.status_code = 403 118 | return response 119 | 120 | 121 | def unauthorized(message): 122 | response = jsonify({'error': 'unauthorized', 'message': message}) 123 | response.status_code = 401 124 | return response 125 | 126 | 127 | @api_v2_bp.route('/token', methods=['GET']) 128 | @basic_auth.login_required 129 | def api_v2_token(): 130 | """ 131 | TODO:后续用户登录成功后,直接返回token信息. 132 | """ 133 | if g.current_user.is_anonymous or g.token_used: 134 | return unauthorized('Invalid credentials') 135 | token = g.current_user.generate_auth_token(expiration=3600) 136 | return jsonify({ 137 | 'token': token.decode('utf-8'), 138 | 'expiration': 3600 139 | }) 140 | 141 | 142 | @api_v2_bp.route('/assets/host/list', methods=['GET']) 143 | @basic_auth.login_required 144 | def api_v2_assets_host_list(): 145 | """ 146 | :获取主机列表 147 | """ 148 | host_list = [] 149 | 150 | if g.current_user.is_anonymous or g.token_used: 151 | return unauthorized('Invalid Credentials.') 152 | 153 | try: 154 | hosts = Host.query.all() 155 | except SQLAlchemyError as err: 156 | current_app.logger.error(err) 157 | return jsonify({ 158 | 'resCode': -1, 159 | 'errMsg': 'Query Error.', 160 | }) 161 | except Exception as err: 162 | current_app.logger.error(err) 163 | return jsonify({ 164 | 'result': -1, 165 | 'errMsg': 'Other Error.', 166 | }) 167 | 168 | if hosts is None: 169 | abort(404) 170 | else: 171 | try: 172 | for host in hosts: 173 | try: 174 | ippool = IPPool.query.get(host.innerIP) 175 | # TODO:针对每个innerIP进行查询,会存在性能问题,待优化. 176 | except SQLAlchemyError as err: 177 | current_app.logger.error(err) 178 | ippool = None 179 | except Exception as err: 180 | current_app.logger.error(err) 181 | ippool = None 182 | 183 | host_list.append({ 184 | 'url': url_for('api.get_host', host_id=host.id, _external=True), 185 | 'innerIP': ippool.IP if ippool is not None else '', 186 | 'outerIP': host.outerIP, 187 | 'elasticIP': host.elasticIP, 188 | 'hostname': host.hostname, 189 | 'cpu': host.cpu, 190 | 'memory': host.memory, 191 | 'disk': host.disk, 192 | 'os': host.os, 193 | 'device': host.device.type if host.device is not None else '', 194 | 'use_dpt': host.user_dpt.name if host.user_dpt is not None else '', 195 | 'user': host.users.fullname if host.users is not None else '', 196 | 'owner_dpt': host.owner_dpt.name if host.owner_dpt is not None else '', 197 | 'operator': host.operator.name if host.operator is not None else '', 198 | 'host_model': host.host_model, 199 | 'service': host.service.name if host.service is not None else '', 200 | 'cabinet': host.cabinet.code if host.cabinet is not None else '', 201 | 'create_date': host.create_date, 202 | 'payment': host.payment, 203 | 'host_status': host.status, 204 | 'comment': host.comment, 205 | }) 206 | return jsonify({ 207 | 'resCode': 1, 208 | 'resData': host_list 209 | }) 210 | except Exception as err: 211 | return jsonify({ 212 | 'resCode': -1, 213 | 'errMsg': str(err), 214 | }) 215 | 216 | 217 | @api_v2_bp.route('/assets/host/', methods=['GET']) 218 | @token_auth.login_required 219 | @roles_required('Admin', 'Ops') 220 | def api_v2_assets_host_id(host_id): 221 | try: 222 | host = Host.query.get(host_id) 223 | if host is None: 224 | abort(404) 225 | ippool = IPPool.query.get(host.innerIP) 226 | host_info = { 227 | 'url': url_for('api.get_host', host_id=host.id, _external=True), 228 | 'innerIP': ippool.IP if ippool is not None else '', 229 | 'outerIP': host.outerIP, 230 | 'elasticIP': host.elasticIP, 231 | 'hostname': host.hostname, 232 | 'cpu': host.cpu, 233 | 'memory': host.memory, 234 | 'disk': host.disk, 235 | 'os': host.os, 236 | 'device': host.device.type if host.device is not None else '', 237 | 'use_dpt': host.user_dpt.name if host.user_dpt is not None else '', 238 | 'user': host.users.name if host.users is not None else '', 239 | 'owner_dpt': host.owner_dpt.name if host.owner_dpt is not None else '', 240 | 'operator': host.operator.name if host.operator is not None else '', 241 | 'host_model': host.host_model, 242 | 'service': host.service.name if host.service is not None else '', 243 | 'cabinet': host.cabinet.code if host.cabinet is not None else '', 244 | 'create_date': host.create_date, 245 | 'payment': host.payment, 246 | 'host_status': host.status, 247 | 'comment': host.comment, 248 | } 249 | return jsonify({ 250 | 'resCode': 1, 251 | 'resData': host_info, 252 | }) 253 | except Exception as e: 254 | return jsonify({ 255 | 'resCode': -1, 256 | 'errMsg': str(e), 257 | }) 258 | -------------------------------------------------------------------------------- /yalamain/blueprints/assets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint 3 | from flask import render_template, request, jsonify 4 | from flask_login import login_required 5 | from sqlalchemy import exc 6 | 7 | from yalamain.models import * 8 | 9 | assets_bp = Blueprint('assets', __name__) 10 | 11 | 12 | @assets_bp.route('/host', methods=['GET']) 13 | @login_required 14 | def assets_host(): 15 | """ 16 | :主机管理页面 17 | """ 18 | if request.method == 'GET': 19 | return render_template('assets/host.html', menu='host_list') 20 | 21 | 22 | @assets_bp.route('/host/detail/', methods=['GET']) 23 | @login_required 24 | def assets_host_detail(): 25 | """ 26 | :TODO: 仅仅返回单个主机的详细信息,没必要使用分页。后续修改. 27 | """ 28 | try: 29 | host_id = request.args.get('host_id') 30 | page = request.args.get('page', 1, type=int) 31 | pagination = Host.query.filter_by(id=host_id).paginate(page, per_page=current_app.config['PER_PAGE'], 32 | error_out=False) 33 | hosts = pagination.items 34 | return render_template('assets/host_detail.html', hosts=hosts, menu='host_detail', pagination=pagination) 35 | except Exception as e: 36 | return jsonify({ 37 | 'resCode': -1, 38 | 'errMsg': str(e), 39 | }) 40 | 41 | 42 | @assets_bp.route('/service', methods=['GET']) 43 | @login_required 44 | def assets_service(): 45 | if request.method == 'GET': 46 | page = request.args.get('page', 1, type=int) 47 | try: 48 | pagination = Service.query.paginate(page, per_page=current_app.config['PER_PAGE'], error_out=False) 49 | services = pagination.items 50 | except exc.SQLAlchemyError as sql_err: 51 | current_app.logger.error(sql_err) 52 | pagination = None 53 | services = None 54 | except Exception as e: 55 | current_app.logger.error(e) 56 | pagination = None 57 | services = None 58 | 59 | return render_template('assets/service.html', services=services, menu='service_list', pagination=pagination) 60 | 61 | 62 | @assets_bp.route('/department', methods=['GET']) 63 | @login_required 64 | def assets_department(): 65 | if request.method == 'GET': 66 | page = request.args.get('page', 1, type=int) 67 | try: 68 | pagination = Department.query.paginate(page, per_page=current_app.config['PER_PAGE'], error_out=False) 69 | dpts = pagination.items 70 | except exc.SQLAlchemyError as sql_err: 71 | current_app.logger.error(sql_err) 72 | pagination = None 73 | dpts = None 74 | except Exception as e: 75 | current_app.logger.error(e) 76 | pagination = None 77 | dpts = None 78 | return render_template('assets/department.html', dpts=dpts, menu='department_list', pagination=pagination) 79 | 80 | 81 | @assets_bp.route('/ip/pool', methods=['GET']) 82 | @login_required 83 | def assets_ip_pool(): 84 | """ 85 | :IP地址池列表页面 86 | """ 87 | if request.method == 'GET': 88 | page = request.args.get('page', 1, type=int) 89 | try: 90 | pagination = IPSegment.query.paginate(page, per_page=current_app.config['PER_PAGE'], error_out=False) 91 | ip_segments = pagination.items 92 | except exc.SQLAlchemyError as sql_err: 93 | current_app.logger.error(sql_err) 94 | ip_segments = None 95 | pagination = None 96 | except Exception as err: 97 | current_app.logger.error(err) 98 | ip_segments = None 99 | pagination = None 100 | return render_template('assets/ip_pool.html', ip_segments=ip_segments, menu='ip_pool_list', pagination=pagination) 101 | 102 | 103 | @assets_bp.route('/ip/pool/detail', methods=['GET']) 104 | @login_required 105 | def asset_ip_pool_detail(): 106 | """ 107 | :查看某IP地址段详细的IP地址列表 108 | """ 109 | try: 110 | ip_segment_id = request.args.get('ip_segment_id') 111 | page = request.args.get('page', 1, type=int) 112 | page_size = current_app.config['PER_PAGE'] 113 | pagination = IPPool.query.filter_by(ip_segment_id=ip_segment_id).paginate(page, per_page=page_size, error_out=False) 114 | ip_pools = pagination.items 115 | except Exception as e: 116 | return jsonify({ 117 | 'result': -1, 118 | 'errMsg': str(e), 119 | }) 120 | return render_template('assets/ip_pool_detail.html', ippools=ip_pools, ip_segment_id=ip_segment_id, 121 | menu='ip_pool_detail_list', pagination=pagination) 122 | -------------------------------------------------------------------------------- /yalamain/blueprints/auth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint 3 | from flask import render_template, redirect, url_for, request, jsonify, session, current_app 4 | from flask_login import login_user, logout_user, login_required, current_user 5 | import uuid 6 | import re 7 | 8 | from yalamain.models import User, Role 9 | from yalamain.extensions import db, ldap 10 | 11 | auth_bp = Blueprint('auth', __name__) 12 | 13 | 14 | @auth_bp.route('/login', methods=['GET', 'POST']) 15 | def auth_login(): 16 | if request.method == 'GET': 17 | return render_template('auth/login.html') 18 | 19 | if request.method == 'POST': 20 | username = request.form.get('username', '') 21 | password = request.form.get('password', '') 22 | remember_me = request.form.get('remember_me', False) 23 | next = request.args.get('next', '') 24 | if username and password: 25 | try: 26 | ldap_username = username + '@dadaabc.net' 27 | # STEP1: 向LDAP查询username和password 28 | ldap_user = ldap.bind_user(ldap_username, password) 29 | if ldap_user is None: 30 | # STEP2: 认证错误处理 31 | current_app.logger.info('{0} login error.'.format(username)) 32 | return jsonify({ 33 | 'resCode': -1, 34 | 'errMsg': u'用户没有权限登录系统,请联系AD管理员处理!' 35 | }) 36 | else: 37 | # STEP3: LDAP认证成功后,通过username查询MySQL 38 | user = User.query.filter_by(name=username).first() 39 | user_ad_name = ldap.get_object_details(ldap_username).get('cn')[0].decode('utf-8') 40 | fullname = re.findall(r'[(](.*?)[)]', user_ad_name)[0] if user_ad_name.find( 41 | '(') >= 0 and user_ad_name.find(')') >= 0 else user_ad_name 42 | 43 | # STEP4: MySQL查询username为空后,插入username数据 44 | if user is None: 45 | u = User() 46 | u.name = username 47 | u.fullname = fullname if fullname is not None else None 48 | u.password = password 49 | u.email = ldap_username 50 | u.status = True 51 | u.role_id = Role.query.filter_by(name='RD').first().id 52 | u.token = str(uuid.uuid3(uuid.NAMESPACE_DNS, ldap_username)) 53 | db.session.add(u) 54 | 55 | # STEP5: username信息写入session 56 | user = User.query.filter_by(name=username).first() 57 | login_user(user, remember_me) 58 | session['username'] = username 59 | 60 | current_app.logger.info('{0} login success.'.format(username)) 61 | return jsonify({ 62 | 'resCode': 1, 63 | 'next': next 64 | }) 65 | except Exception as e: 66 | return jsonify({ 67 | 'resCode': -1, 68 | 'errMsg': str(e), 69 | }) 70 | 71 | 72 | @auth_bp.route('/logout/') 73 | @login_required 74 | def auth_logout(): 75 | current_app.logger.info('{0} logout.'.format(current_user.name)) 76 | logout_user() 77 | return redirect(url_for('auth.login')) 78 | -------------------------------------------------------------------------------- /yalamain/blueprints/errors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint, request, render_template, jsonify 3 | 4 | errors_bp = Blueprint('errors', __name__) 5 | 6 | 7 | @errors_bp.app_errorhandler(400) 8 | def forbidden(): 9 | if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: 10 | response = jsonify({'error': 'forbidden'}) 11 | response.status_code = 400 12 | return response 13 | return render_template('error/400.html'), 400 14 | 15 | 16 | @errors_bp.app_errorhandler(401) 17 | def forbidden(): 18 | if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: 19 | response = jsonify({'error': 'forbidden'}) 20 | response.status_code = 401 21 | return response 22 | return render_template('error/401.html'), 401 23 | 24 | 25 | @errors_bp.app_errorhandler(403) 26 | def forbidden(): 27 | if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: 28 | response = jsonify({'error': 'forbidden'}) 29 | response.status_code = 403 30 | return response 31 | return render_template('error/403.html'), 403 32 | 33 | 34 | @errors_bp.app_errorhandler(404) 35 | def page_not_found(): 36 | if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: 37 | response = jsonify({'error': 'not found'}) 38 | response.status_code = 404 39 | return response 40 | return render_template('error/404.html'), 404 41 | 42 | 43 | @errors_bp.app_errorhandler(500) 44 | def internal_server_error(): 45 | if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: 46 | response = jsonify({'error': 'internal server error'}) 47 | response.status_code = 500 48 | return response 49 | return render_template('error/500.html'), 500 50 | 51 | -------------------------------------------------------------------------------- /yalamain/blueprints/index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint 3 | from flask import render_template, send_file, current_app, Markup 4 | from flask_login import login_required 5 | import os 6 | from sqlalchemy import exc 7 | from datetime import datetime, timedelta 8 | import datetime 9 | 10 | from yalamain.extensions import ldap 11 | from yalamain.models import MonitorDomainInfo, MonitorTcpConnectProbe 12 | from yalamain.models import Host, User, CronLog 13 | 14 | index_bp = Blueprint('index', __name__) 15 | 16 | 17 | @index_bp.route('/') 18 | @ldap.login_required 19 | def login(): 20 | return render_template('auth/login.html') 21 | 22 | 23 | @index_bp.route('/index') 24 | @login_required 25 | def index(): 26 | try: 27 | host_count = Host.query.count() 28 | monitor_domain_count = MonitorDomainInfo.query.count() 29 | monitor_probe_count = MonitorTcpConnectProbe.query.count() 30 | users_count = User.query.count() 31 | except exc.SQLAlchemyError as sql_err: 32 | current_app.logger.error(sql_err) 33 | host_count = 0 34 | monitor_domain_count = 0 35 | monitor_probe_count = 0 36 | users_count = 0 37 | 38 | try: 39 | chart_list = [] 40 | for i in reversed(range(15)): 41 | chart_dict = {} 42 | update_date = (datetime.datetime.now() - datetime.timedelta(days=i)) 43 | update_date = update_date.strftime('%Y-%m-%d') 44 | cron_logs = CronLog.query.filter(CronLog.update_date == update_date).all() 45 | for c in cron_logs: 46 | chart_dict['update_date'] = update_date 47 | chart_dict[c.asset] = c.count 48 | if (len(chart_dict)) == 5: 49 | chart_list.append(chart_dict) 50 | except exc.SQLAlchemyError as sql_err: 51 | chart_list = [] 52 | 53 | return render_template('index.html', menu='dashboard', host_count=host_count, monitor_domain_count=monitor_domain_count, 54 | monitor_probe_count=monitor_probe_count, users_count=users_count, 55 | chart_list=Markup(chart_list)) 56 | 57 | 58 | @index_bp.route('/changeLog') 59 | @login_required 60 | def changelog(): 61 | return render_template('changelog.html') 62 | 63 | 64 | @index_bp.route('/ChangeLog.md') 65 | @login_required 66 | def get_changelog_md(): 67 | base_dir = os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) 68 | return send_file(os.path.join(base_dir, 'ChangeLog.md')) 69 | -------------------------------------------------------------------------------- /yalamain/blueprints/monitor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint, current_app, render_template, request, redirect, url_for, jsonify 3 | from flask_login import login_required, current_user 4 | from datetime import timedelta 5 | from sqlalchemy import exc, or_ 6 | import datetime 7 | import json 8 | 9 | from yalamain.models import MonitorDomainInfo, MonitorTcpConnectProbe 10 | from yalamain.extensions import db 11 | 12 | monitor_bp = Blueprint('monitor', __name__) 13 | 14 | 15 | @monitor_bp.route('/domain', methods=['GET']) 16 | @login_required 17 | def monitor_domain(): 18 | if request.method == 'GET': 19 | page = request.args.get('page', 1, type=int) 20 | search = '' 21 | 22 | try: 23 | pagination = MonitorDomainInfo.query.order_by(MonitorDomainInfo.id).paginate(page, per_page=50, error_out=False) 24 | domains = pagination.items 25 | except exc.SQLAlchemyError as sql_err: 26 | current_app.logger.error('MySQL ERROR: {}'.format(sql_err)) 27 | pagination = None 28 | domains = None 29 | 30 | current_app.logger.info('{0} visit monitor_domain'.format(current_user.name)) 31 | return render_template('monitor/domain.html', domains=domains, search=search, pagination=pagination, menu='monitor_domain') 32 | 33 | 34 | @monitor_bp.route('/domain/search', methods=['GET', 'POST']) 35 | @login_required 36 | def monitor_domain_search(): 37 | if request.method == 'GET': 38 | return redirect(url_for('monitor.monitor_domain')) 39 | 40 | if request.method == 'POST': 41 | search = request.form.get('search') 42 | 43 | if search == '': 44 | return redirect(url_for('monitor.monitor_domain')) 45 | try: 46 | domains = MonitorDomainInfo.query.filter(or_(MonitorDomainInfo.domain.like('%' + search + '%'), 47 | MonitorDomainInfo.rd_maintainer.like('%' + search + '%'), 48 | MonitorDomainInfo.cre_maintainer.like('%' + search + '%'), 49 | MonitorDomainInfo.remark.like('%' + search + '%'), 50 | MonitorDomainInfo.update_time.like('%' + search + '%'))).all() 51 | except exc.SQLAlchemyError as sql_err: 52 | current_app.logger.error('MySQL ERROR: {}'.format(sql_err)) 53 | domains = '' 54 | 55 | current_app.logger.info('{0} search {1}'.format(current_user.name, search)) 56 | return render_template('monitor/domain.html', domains=domains, search=search, menu='monitor_domain') 57 | 58 | 59 | @monitor_bp.route('/tcp/connect/probe', methods=['GET', 'POST', 'PUT', 'DELETE']) 60 | @login_required 61 | def monitor_tcp_connect_probe(): 62 | if request.method == 'GET': 63 | page = request.args.get('page', 1, type=int) 64 | search = '' 65 | 66 | try: 67 | pagination = MonitorTcpConnectProbe.query.order_by(MonitorTcpConnectProbe.service_name).paginate(page, per_page=50, 68 | error_out=False) 69 | tcp_connect_probes = pagination.items 70 | except exc.SQLAlchemyError as sql_err: 71 | current_app.logger.error('MySQL ERROR: {}'.format(sql_err)) 72 | pagination = None 73 | tcp_connect_probes = None 74 | 75 | current_app.logger.info('{0} visit monitor_tcp_connect_probe'.format(current_user.name)) 76 | return render_template('monitor/tcp_connect_probe.html', tcp_connect_probes=tcp_connect_probes, search=search, 77 | pagination=pagination, menu='monitor_tcp_connect_probe') 78 | 79 | 80 | @monitor_bp.route('/tcp/connect/probe/search', methods=['GET', 'POST']) 81 | @login_required 82 | def monitor_tcp_connect_probe_search(): 83 | if request.method == 'GET': 84 | return redirect(url_for('monitor.monitor_tcp_connect_probe')) 85 | 86 | if request.method == 'POST': 87 | search = request.form.get('search') 88 | 89 | if search == '': 90 | return redirect(url_for('monitor.monitor_tcp_connect_probe')) 91 | 92 | try: 93 | tcp_connect_probes = MonitorTcpConnectProbe.query.filter(or_(MonitorTcpConnectProbe.service_name.like('%' + search + '%'), 94 | MonitorTcpConnectProbe.target.like('%' + search + '%'), 95 | MonitorTcpConnectProbe.server_ip.like('%' + search + '%'), 96 | MonitorTcpConnectProbe.tcp_port.like('%' + search + '%'), 97 | MonitorTcpConnectProbe.service_owner.like('%' + search + '%'))).all() 98 | except exc.SQLAlchemyError as sql_err: 99 | current_app.logger.error('MySQL ERROR: {}'.format(sql_err)) 100 | tcp_connect_probes = '' 101 | 102 | current_app.logger.info('{0} search {1}'.format(current_user.name, search)) 103 | return render_template('monitor/tcp_connect_probe.html', tcp_connect_probes=tcp_connect_probes, search=search, 104 | menu='monitor_tcp_connect_probe') 105 | -------------------------------------------------------------------------------- /yalamain/blueprints/user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint 3 | from flask import render_template, redirect, url_for, request, jsonify, current_app 4 | from flask_login import login_required 5 | from sqlalchemy import exc, or_, exists, not_ 6 | import uuid 7 | import json 8 | 9 | from yalamain.models import User, Role, Permission, role_permission 10 | from yalamain.extensions import db 11 | 12 | user_bp = Blueprint('user', __name__) 13 | 14 | 15 | @user_bp.route('/user', methods=['GET', 'POST']) 16 | @login_required 17 | def user_user(): 18 | if request.method == 'GET': 19 | page = request.args.get('page', 1, type=int) 20 | search = '' 21 | try: 22 | pagination = User.query.paginate(page, per_page=current_app.config['PER_PAGE'], error_out=False) 23 | users = pagination.items 24 | except exc.SQLAlchemyError as sql_err: 25 | current_app.logger.error(sql_err) 26 | users = None 27 | pagination = None 28 | except Exception as e: 29 | current_app.logger.error(e) 30 | users = None 31 | pagination = None 32 | return render_template('user/users.html', users=users, search=search, menu='user_list', pagination=pagination) 33 | 34 | if request.method == 'POST': 35 | search = request.form.get('search', '') 36 | 37 | if not search: 38 | return redirect(url_for('user.user_user')) 39 | 40 | try: 41 | users = User.query.filter( 42 | or_(User.name.like('%' + search + '%'), 43 | User.fullname.like('%' + search + '%'), 44 | exists().where(Role.id == User.role_id).where(Role.name.like('%' + search + '%')), 45 | User.email.like('%' + search + '%'))).all() 46 | except exc.SQLAlchemyError as sql_err: 47 | current_app.logger.error(sql_err) 48 | users = None 49 | pagination = None 50 | except Exception as e: 51 | current_app.logger.error(e) 52 | users = None 53 | pagination = None 54 | return render_template('user/users.html', users=users, search=search, menu='user_list') 55 | 56 | 57 | @user_bp.route('/role', methods=['GET']) 58 | @login_required 59 | def user_role(): 60 | if request.method == 'GET': 61 | page = request.args.get('page', 1, type=int) 62 | page_size = current_app.config['PER_PAGE'] 63 | try: 64 | pagination = Role.query.paginate(page, per_page=page_size, error_out=False) 65 | roles = pagination.items 66 | except exc.SQLAlchemyError as sql_err: 67 | current_app.logger.error(sql_err) 68 | roles = None 69 | pagination = None 70 | except Exception as e: 71 | current_app.logger.error(e) 72 | roles = None 73 | pagination = None 74 | return render_template('user/roles.html', roles=roles, menu='role_list', pagination=pagination) 75 | 76 | 77 | @user_bp.route('/permission', methods=['GET']) 78 | @login_required 79 | def user_permission(): 80 | if request.method == 'GET': 81 | page = request.args.get('page', 1, type=int) 82 | page_size = current_app.config['PER_PAGE'] 83 | try: 84 | pagination = Permission.query.paginate(page, per_page=page_size, error_out=False) 85 | permissions = pagination.items 86 | except exc.SQLAlchemyError as sql_err: 87 | current_app.logger.error(sql_err) 88 | permissions = None 89 | pagination = None 90 | except Exception as e: 91 | current_app.logger.error(e) 92 | permissions = None 93 | pagination = None 94 | return render_template('user/permission.html', permissions=permissions, menu='permission_list', pagination=pagination) 95 | -------------------------------------------------------------------------------- /yalamain/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import ldap 3 | import os 4 | 5 | 6 | class Config(object): 7 | PER_PAGE = 25 8 | SECRET_KEY = 'Your secret key.' 9 | UPLOAD_DIR = 'upload' 10 | 11 | LDAP_HOST = 'Your ldap host.' 12 | LDAP_BASE_DN = 'Your ldap base_dn.' 13 | LDAP_USERNAME = 'Your ldap username.' 14 | LDAP_PASSWORD = 'Your ldap password.' 15 | LDAP_LOGIN_VIEW = 'auth.auth_login' 16 | LDAP_CUSTOM_OPTIONS = {ldap.OPT_REFERRALS: 0} 17 | 18 | SQLALCHEMY_COMMIT_ON_TEARDOWN = True 19 | SQLALCHEMY_TRACK_MODIFICATIONS = False 20 | SQLALCHEMY_RECORD_QUERIES = True 21 | SQLALCHEMY_POOL_RECYCLE = 3600 22 | 23 | ACCESS_KEY_ID = "Your aliyun access_key_id" 24 | ACCESS_KEY_SECRET = "Your aliyun access_key_secret" 25 | 26 | SCHEDULER_API_ENABLED = True 27 | 28 | @staticmethod 29 | def init_app(app): 30 | pass 31 | 32 | 33 | class DevelopmentConfig(Config): 34 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://test:test@127.0.0.1:3306/test' 35 | SQLALCHEMY_BINDS = { 36 | } 37 | 38 | BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 39 | INFO_LOG_PATH = os.path.join(BASE_DIR, 'logs/yala.log') 40 | ERROR_LOG_PATH = os.path.join(BASE_DIR, 'logs/yala_error.log') 41 | 42 | 43 | class TestingConfig(Config): 44 | """ 45 | :目前没有使用到测试环境 46 | """ 47 | pass 48 | 49 | 50 | class ProductionConfig(Config): 51 | SQLALCHEMY_DATABASE_URI = '' 52 | SQLALCHEMY_BINDS = { 53 | } 54 | 55 | INFO_LOG_PATH = 'Your production env info log path.' 56 | ERROR_LOG_PATH = 'Your production env error log path.' 57 | 58 | 59 | config = { 60 | 'development': DevelopmentConfig, 61 | 'testing': TestingConfig, 62 | 'production': ProductionConfig 63 | } 64 | -------------------------------------------------------------------------------- /yalamain/cron/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/cron/__init__.py -------------------------------------------------------------------------------- /yalamain/cron/cornlog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | from flask import current_app 4 | from sqlalchemy import exc 5 | 6 | from yalamain.extensions import db, apscheduler 7 | from yalamain.models import Host, User, CronLog, MonitorTcpConnectProbe, MonitorDomainInfo 8 | 9 | 10 | @apscheduler.task('cron', id='update_cron_log', hour=16, minute=11) 11 | def update_cron_log(): 12 | with apscheduler.app.app_context(): 13 | this_day = time.strftime("%Y-%m-%d", time.localtime()) 14 | update_date = this_day 15 | host_count = Host.query.count() 16 | monitor_domain_count = MonitorDomainInfo.query.count() 17 | monitor_probe_count = MonitorTcpConnectProbe.query.count() 18 | users_count = User.query.count() 19 | assets = ['host', 'domain', 'probe', 'users'] 20 | for asset in assets: 21 | if asset == 'host': 22 | count = host_count 23 | if asset == 'domain': 24 | count = monitor_domain_count 25 | if asset == 'probe': 26 | count = monitor_probe_count 27 | if asset == 'users': 28 | count = users_count 29 | 30 | try: 31 | query_update_date = CronLog.query.filter_by(asset=asset, update_date=this_day).first() 32 | except exc.SQLAlchemyError as sql_err: 33 | current_app.logger.error('MySQL ERROR: {}.'.format(sql_err)) 34 | 35 | if query_update_date is None: 36 | try: 37 | cronlog = CronLog(asset=asset, update_date=update_date, count=count) 38 | db.session.add(cronlog) 39 | db.session.commit() 40 | except exc.SQLAlchemyError as sql_err: 41 | current_app.logger.error('MySQL ERROR: {}.'.format(sql_err)) -------------------------------------------------------------------------------- /yalamain/extensions.py: -------------------------------------------------------------------------------- 1 | # -*-coding:utf-8-*- 2 | from flask_sqlalchemy import SQLAlchemy 3 | from flask_login import LoginManager 4 | from flask_simpleldap import LDAP 5 | from flask_apscheduler import APScheduler 6 | 7 | db = SQLAlchemy() 8 | ldap = LDAP() 9 | apscheduler = APScheduler() 10 | 11 | login_manager = LoginManager() 12 | login_manager.login_view = 'auth.login' 13 | 14 | 15 | from yalamain.models import AnonymousUser, User 16 | login_manager.anonymous_user = AnonymousUser 17 | 18 | 19 | @login_manager.user_loader 20 | def load_user(user_id): 21 | return User.query.get(int(user_id)) 22 | -------------------------------------------------------------------------------- /yalamain/static/css/bootstrap-multiselect.css: -------------------------------------------------------------------------------- 1 | span.multiselect-native-select{position:relative}span.multiselect-native-select select{border:0!important;clip:rect(0 0 0 0)!important;height:1px!important;margin:-1px -1px -1px -3px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;left:50%;top:30px}.multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .input-group{margin:5px}.multiselect-container .multiselect-reset .input-group{width:93%}.multiselect-container>li{padding:0}.multiselect-container>li>a.multiselect-all label{font-weight:700}.multiselect-container>li.multiselect-group label{margin:0;padding:3px 20px;height:100%;font-weight:700}.multiselect-container>li.multiselect-group-clickable label{cursor:pointer}.multiselect-container>li>a{padding:0}.multiselect-container>li>a>label{margin:0;height:100%;cursor:pointer;font-weight:400;padding:3px 20px 3px 40px}.multiselect-container>li>a>label.checkbox,.multiselect-container>li>a>label.radio{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.btn-group>.btn-group:nth-child(2)>.multiselect.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.form-inline .multiselect-container label.checkbox,.form-inline .multiselect-container label.radio{padding:3px 20px 3px 40px}.form-inline .multiselect-container li a label.checkbox input[type=checkbox],.form-inline .multiselect-container li a label.radio input[type=radio]{margin-left:-20px;margin-right:0} -------------------------------------------------------------------------------- /yalamain/static/css/bootstrap-table.min.css: -------------------------------------------------------------------------------- 1 | .fixed-table-container .bs-checkbox,.fixed-table-container .no-records-found{text-align:center}.fixed-table-body thead th .th-inner,.table td,.table th{box-sizing:border-box}.bootstrap-table .table{margin-bottom:0!important;border-bottom:1px solid #ddd;border-collapse:collapse!important;border-radius:1px}.bootstrap-table .table:not(.table-condensed),.bootstrap-table .table:not(.table-condensed)>tbody>tr>td,.bootstrap-table .table:not(.table-condensed)>tbody>tr>th,.bootstrap-table .table:not(.table-condensed)>tfoot>tr>td,.bootstrap-table .table:not(.table-condensed)>tfoot>tr>th,.bootstrap-table .table:not(.table-condensed)>thead>tr>td{padding:8px}.bootstrap-table .table.table-no-bordered>tbody>tr>td,.bootstrap-table .table.table-no-bordered>thead>tr>th{border-right:2px solid transparent}.bootstrap-table .table.table-no-bordered>tbody>tr>td:last-child{border-right:none}.fixed-table-container{position:relative;clear:both;border:1px solid #ddd;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px}.fixed-table-container.table-no-bordered{border:1px solid transparent}.fixed-table-footer,.fixed-table-header{overflow:hidden}.fixed-table-footer{border-top:1px solid #ddd}.fixed-table-body{overflow-x:auto;overflow-y:auto;height:100%}.fixed-table-container table{width:100%}.fixed-table-container thead th{height:0;padding:0;margin:0;border-left:1px solid #ddd}.fixed-table-container thead th:focus{outline:transparent solid 0}.fixed-table-container thead th:first-child:not([data-not-first-th]){border-left:none;border-top-left-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px}.fixed-table-container tbody td .th-inner,.fixed-table-container thead th .th-inner{padding:8px;line-height:24px;vertical-align:top;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fixed-table-container thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px}.fixed-table-container thead th .both{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC')}.fixed-table-container thead th .asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==)}.fixed-table-container thead th .desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=)}.fixed-table-container th.detail{width:30px}.fixed-table-container tbody td{border-left:1px solid #ddd}.fixed-table-container tbody tr:first-child td{border-top:none}.fixed-table-container tbody td:first-child{border-left:none}.fixed-table-container tbody .selected td{background-color:#f5f5f5}.fixed-table-container input[type=radio],.fixed-table-container input[type=checkbox]{margin:0 auto!important}.fixed-table-pagination .pagination-detail,.fixed-table-pagination div.pagination{margin-top:10px;margin-bottom:10px}.fixed-table-pagination div.pagination .pagination{margin:0}.fixed-table-pagination .pagination a{padding:6px 12px;line-height:1.428571429}.fixed-table-pagination .pagination-info{line-height:34px;margin-right:5px}.fixed-table-pagination .btn-group{position:relative;display:inline-block;vertical-align:middle}.fixed-table-pagination .dropup .dropdown-menu{margin-bottom:0}.fixed-table-pagination .page-list{display:inline-block}.fixed-table-toolbar .columns-left{margin-right:5px}.fixed-table-toolbar .columns-right{margin-left:5px}.fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429}.fixed-table-toolbar .bs-bars,.fixed-table-toolbar .columns,.fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px;line-height:34px}.fixed-table-pagination li.disabled a{pointer-events:none;cursor:default}.fixed-table-loading{display:none;position:absolute;top:42px;right:0;bottom:0;left:0;z-index:99;background-color:#fff;text-align:center}.fixed-table-body .card-view .title{font-weight:700;display:inline-block;min-width:30%;text-align:left!important}.table td,.table th{vertical-align:middle}.fixed-table-toolbar .dropdown-menu{text-align:left;max-height:300px;overflow:auto}.fixed-table-toolbar .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.fixed-table-toolbar .btn-group>.btn-group>.btn{border-radius:0}.fixed-table-toolbar .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.fixed-table-toolbar .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .table>thead>tr>th{vertical-align:bottom;border-bottom:1px solid #ddd}.bootstrap-table .table thead>tr>th{padding:0;margin:0}.bootstrap-table .fixed-table-footer tbody>tr>td{padding:0!important}.bootstrap-table .fixed-table-footer .table{border-bottom:none;border-radius:0;padding:0!important}.bootstrap-table .pull-right .dropdown-menu{right:0;left:auto}p.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}.fixed-table-pagination:after,.fixed-table-toolbar:after{content:"";display:block;clear:both}.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100%!important;background:#FFF} -------------------------------------------------------------------------------- /yalamain/static/css/bootstrap3-wysihtml5.min.css: -------------------------------------------------------------------------------- 1 | /*! bootstrap3-wysihtml5-bower 2014-09-26 */ 2 | 3 | ul.wysihtml5-toolbar{margin:0;padding:0;display:block}ul.wysihtml5-toolbar::after{clear:both;display:table;content:""}ul.wysihtml5-toolbar>li{float:left;display:list-item;list-style:none;margin:0 5px 10px 0}ul.wysihtml5-toolbar a[data-wysihtml5-command=bold]{font-weight:700}ul.wysihtml5-toolbar a[data-wysihtml5-command=italic]{font-style:italic}ul.wysihtml5-toolbar a[data-wysihtml5-command=underline]{text-decoration:underline}ul.wysihtml5-toolbar a.btn.wysihtml5-command-active{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05);background-color:#D9D9D9;outline:0}ul.wysihtml5-commands-disabled .dropdown-menu{display:none!important}ul.wysihtml5-toolbar div.wysihtml5-colors{display:block;width:50px;height:20px;margin-top:2px;margin-left:5px;position:absolute;pointer-events:none}ul.wysihtml5-toolbar a.wysihtml5-colors-title{padding-left:70px}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=black]{background:#000!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=silver]{background:silver!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=gray]{background:gray!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=maroon]{background:maroon!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=red]{background:red!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=purple]{background:purple!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=green]{background:green!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=olive]{background:olive!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=navy]{background:navy!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=blue]{background:#00f!important}ul.wysihtml5-toolbar div[data-wysihtml5-command-value=orange]{background:orange!important}.glyphicon-quote:before{content:"\201C";font-family:Georgia,serif;font-size:50px;position:absolute;top:-4px;left:-3px;max-height:100%}.glyphicon-quote:after{content:"\0000a0"} -------------------------------------------------------------------------------- /yalamain/static/css/dataTables.bootstrap.min.css: -------------------------------------------------------------------------------- 1 | table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0} 2 | -------------------------------------------------------------------------------- /yalamain/static/css/daterangepicker.css: -------------------------------------------------------------------------------- 1 | .daterangepicker { 2 | position: absolute; 3 | color: inherit; 4 | background-color: #fff; 5 | border-radius: 4px; 6 | width: 278px; 7 | padding: 4px; 8 | margin-top: 1px; 9 | top: 100px; 10 | left: 20px; 11 | /* Calendars */ } 12 | .daterangepicker:before, .daterangepicker:after { 13 | position: absolute; 14 | display: inline-block; 15 | border-bottom-color: rgba(0, 0, 0, 0.2); 16 | content: ''; } 17 | .daterangepicker:before { 18 | top: -7px; 19 | border-right: 7px solid transparent; 20 | border-left: 7px solid transparent; 21 | border-bottom: 7px solid #ccc; } 22 | .daterangepicker:after { 23 | top: -6px; 24 | border-right: 6px solid transparent; 25 | border-bottom: 6px solid #fff; 26 | border-left: 6px solid transparent; } 27 | .daterangepicker.opensleft:before { 28 | right: 9px; } 29 | .daterangepicker.opensleft:after { 30 | right: 10px; } 31 | .daterangepicker.openscenter:before { 32 | left: 0; 33 | right: 0; 34 | width: 0; 35 | margin-left: auto; 36 | margin-right: auto; } 37 | .daterangepicker.openscenter:after { 38 | left: 0; 39 | right: 0; 40 | width: 0; 41 | margin-left: auto; 42 | margin-right: auto; } 43 | .daterangepicker.opensright:before { 44 | left: 9px; } 45 | .daterangepicker.opensright:after { 46 | left: 10px; } 47 | .daterangepicker.dropup { 48 | margin-top: -5px; } 49 | .daterangepicker.dropup:before { 50 | top: initial; 51 | bottom: -7px; 52 | border-bottom: initial; 53 | border-top: 7px solid #ccc; } 54 | .daterangepicker.dropup:after { 55 | top: initial; 56 | bottom: -6px; 57 | border-bottom: initial; 58 | border-top: 6px solid #fff; } 59 | .daterangepicker.dropdown-menu { 60 | max-width: none; 61 | z-index: 3001; } 62 | .daterangepicker.single .ranges, .daterangepicker.single .calendar { 63 | float: none; } 64 | .daterangepicker.show-calendar .calendar { 65 | display: block; } 66 | .daterangepicker .calendar { 67 | display: none; 68 | max-width: 270px; 69 | margin: 4px; } 70 | .daterangepicker .calendar.single .calendar-table { 71 | border: none; } 72 | .daterangepicker .calendar th, .daterangepicker .calendar td { 73 | white-space: nowrap; 74 | text-align: center; 75 | min-width: 32px; } 76 | .daterangepicker .calendar-table { 77 | border: 1px solid #fff; 78 | padding: 4px; 79 | border-radius: 4px; 80 | background-color: #fff; } 81 | .daterangepicker table { 82 | width: 100%; 83 | margin: 0; } 84 | .daterangepicker td, .daterangepicker th { 85 | text-align: center; 86 | width: 20px; 87 | height: 20px; 88 | border-radius: 4px; 89 | border: 1px solid transparent; 90 | white-space: nowrap; 91 | cursor: pointer; } 92 | .daterangepicker td.available:hover, .daterangepicker th.available:hover { 93 | background-color: #eee; 94 | border-color: transparent; 95 | color: inherit; } 96 | .daterangepicker td.week, .daterangepicker th.week { 97 | font-size: 80%; 98 | color: #ccc; } 99 | .daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date { 100 | background-color: #fff; 101 | border-color: transparent; 102 | color: #999; } 103 | .daterangepicker td.in-range { 104 | background-color: #ebf4f8; 105 | border-color: transparent; 106 | color: #000; 107 | border-radius: 0; } 108 | .daterangepicker td.start-date { 109 | border-radius: 4px 0 0 4px; } 110 | .daterangepicker td.end-date { 111 | border-radius: 0 4px 4px 0; } 112 | .daterangepicker td.start-date.end-date { 113 | border-radius: 4px; } 114 | .daterangepicker td.active, .daterangepicker td.active:hover { 115 | background-color: #357ebd; 116 | border-color: transparent; 117 | color: #fff; } 118 | .daterangepicker th.month { 119 | width: auto; } 120 | .daterangepicker td.disabled, .daterangepicker option.disabled { 121 | color: #999; 122 | cursor: not-allowed; 123 | text-decoration: line-through; } 124 | .daterangepicker select.monthselect, .daterangepicker select.yearselect { 125 | font-size: 12px; 126 | padding: 1px; 127 | height: auto; 128 | margin: 0; 129 | cursor: default; } 130 | .daterangepicker select.monthselect { 131 | margin-right: 2%; 132 | width: 56%; } 133 | .daterangepicker select.yearselect { 134 | width: 40%; } 135 | .daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect { 136 | width: 50px; 137 | margin-bottom: 0; } 138 | .daterangepicker .input-mini { 139 | border: 1px solid #ccc; 140 | border-radius: 4px; 141 | color: #555; 142 | height: 30px; 143 | line-height: 30px; 144 | display: block; 145 | vertical-align: middle; 146 | margin: 0 0 5px 0; 147 | padding: 0 6px 0 28px; 148 | width: 100%; } 149 | .daterangepicker .input-mini.active { 150 | border: 1px solid #08c; 151 | border-radius: 4px; } 152 | .daterangepicker .daterangepicker_input { 153 | position: relative; } 154 | .daterangepicker .daterangepicker_input i { 155 | position: absolute; 156 | left: 8px; 157 | top: 8px; } 158 | .daterangepicker.rtl .input-mini { 159 | padding-right: 28px; 160 | padding-left: 6px; } 161 | .daterangepicker.rtl .daterangepicker_input i { 162 | left: auto; 163 | right: 8px; } 164 | .daterangepicker .calendar-time { 165 | text-align: center; 166 | margin: 5px auto; 167 | line-height: 30px; 168 | position: relative; 169 | padding-left: 28px; } 170 | .daterangepicker .calendar-time select.disabled { 171 | color: #ccc; 172 | cursor: not-allowed; } 173 | 174 | .ranges { 175 | font-size: 11px; 176 | float: none; 177 | margin: 4px; 178 | text-align: left; } 179 | .ranges ul { 180 | list-style: none; 181 | margin: 0 auto; 182 | padding: 0; 183 | width: 100%; } 184 | .ranges li { 185 | font-size: 13px; 186 | background-color: #f5f5f5; 187 | border: 1px solid #f5f5f5; 188 | border-radius: 4px; 189 | color: #08c; 190 | padding: 3px 12px; 191 | margin-bottom: 8px; 192 | cursor: pointer; } 193 | .ranges li:hover { 194 | background-color: #08c; 195 | border: 1px solid #08c; 196 | color: #fff; } 197 | .ranges li.active { 198 | background-color: #08c; 199 | border: 1px solid #08c; 200 | color: #fff; } 201 | 202 | /* Larger Screen Styling */ 203 | @media (min-width: 564px) { 204 | .daterangepicker { 205 | width: auto; } 206 | .daterangepicker .ranges ul { 207 | width: 160px; } 208 | .daterangepicker.single .ranges ul { 209 | width: 100%; } 210 | .daterangepicker.single .calendar.left { 211 | clear: none; } 212 | .daterangepicker.single.ltr .ranges, .daterangepicker.single.ltr .calendar { 213 | float: left; } 214 | .daterangepicker.single.rtl .ranges, .daterangepicker.single.rtl .calendar { 215 | float: right; } 216 | .daterangepicker.ltr { 217 | direction: ltr; 218 | text-align: left; } 219 | .daterangepicker.ltr .calendar.left { 220 | clear: left; 221 | margin-right: 0; } 222 | .daterangepicker.ltr .calendar.left .calendar-table { 223 | border-right: none; 224 | border-top-right-radius: 0; 225 | border-bottom-right-radius: 0; } 226 | .daterangepicker.ltr .calendar.right { 227 | margin-left: 0; } 228 | .daterangepicker.ltr .calendar.right .calendar-table { 229 | border-left: none; 230 | border-top-left-radius: 0; 231 | border-bottom-left-radius: 0; } 232 | .daterangepicker.ltr .left .daterangepicker_input { 233 | padding-right: 12px; } 234 | .daterangepicker.ltr .calendar.left .calendar-table { 235 | padding-right: 12px; } 236 | .daterangepicker.ltr .ranges, .daterangepicker.ltr .calendar { 237 | float: left; } 238 | .daterangepicker.rtl { 239 | direction: rtl; 240 | text-align: right; } 241 | .daterangepicker.rtl .calendar.left { 242 | clear: right; 243 | margin-left: 0; } 244 | .daterangepicker.rtl .calendar.left .calendar-table { 245 | border-left: none; 246 | border-top-left-radius: 0; 247 | border-bottom-left-radius: 0; } 248 | .daterangepicker.rtl .calendar.right { 249 | margin-right: 0; } 250 | .daterangepicker.rtl .calendar.right .calendar-table { 251 | border-right: none; 252 | border-top-right-radius: 0; 253 | border-bottom-right-radius: 0; } 254 | .daterangepicker.rtl .left .daterangepicker_input { 255 | padding-left: 12px; } 256 | .daterangepicker.rtl .calendar.left .calendar-table { 257 | padding-left: 12px; } 258 | .daterangepicker.rtl .ranges, .daterangepicker.rtl .calendar { 259 | text-align: right; 260 | float: right; } } 261 | @media (min-width: 730px) { 262 | .daterangepicker .ranges { 263 | width: auto; } 264 | .daterangepicker.ltr .ranges { 265 | float: left; } 266 | .daterangepicker.rtl .ranges { 267 | float: right; } 268 | .daterangepicker .calendar.left { 269 | clear: none !important; } } 270 | -------------------------------------------------------------------------------- /yalamain/static/css/iCheck/square/blue.css: -------------------------------------------------------------------------------- 1 | /* iCheck plugin Square skin, blue 2 | ----------------------------------- */ 3 | .icheckbox_square-blue, 4 | .iradio_square-blue { 5 | display: inline-block; 6 | *display: inline; 7 | vertical-align: middle; 8 | margin: 0; 9 | padding: 0; 10 | width: 22px; 11 | height: 22px; 12 | background: url(blue.png) no-repeat; 13 | border: none; 14 | cursor: pointer; 15 | } 16 | 17 | .icheckbox_square-blue { 18 | background-position: 0 0; 19 | } 20 | .icheckbox_square-blue.hover { 21 | background-position: -24px 0; 22 | } 23 | .icheckbox_square-blue.checked { 24 | background-position: -48px 0; 25 | } 26 | .icheckbox_square-blue.disabled { 27 | background-position: -72px 0; 28 | cursor: default; 29 | } 30 | .icheckbox_square-blue.checked.disabled { 31 | background-position: -96px 0; 32 | } 33 | 34 | .iradio_square-blue { 35 | background-position: -120px 0; 36 | } 37 | .iradio_square-blue.hover { 38 | background-position: -144px 0; 39 | } 40 | .iradio_square-blue.checked { 41 | background-position: -168px 0; 42 | } 43 | .iradio_square-blue.disabled { 44 | background-position: -192px 0; 45 | cursor: default; 46 | } 47 | .iradio_square-blue.checked.disabled { 48 | background-position: -216px 0; 49 | } 50 | 51 | /* Retina support */ 52 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 53 | only screen and (-moz-min-device-pixel-ratio: 1.5), 54 | only screen and (-o-min-device-pixel-ratio: 3/2), 55 | only screen and (min-device-pixel-ratio: 1.5) { 56 | .icheckbox_square-blue, 57 | .iradio_square-blue { 58 | background-image: url(blue@2x.png); 59 | -webkit-background-size: 240px 24px; 60 | background-size: 240px 24px; 61 | } 62 | } -------------------------------------------------------------------------------- /yalamain/static/css/iCheck/square/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/css/iCheck/square/blue.png -------------------------------------------------------------------------------- /yalamain/static/css/iCheck/square/blue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/css/iCheck/square/blue@2x.png -------------------------------------------------------------------------------- /yalamain/static/css/jquery-jvectormap.css: -------------------------------------------------------------------------------- 1 | svg { 2 | touch-action: none; 3 | } 4 | 5 | .jvectormap-container { 6 | width: 100%; 7 | height: 100%; 8 | position: relative; 9 | overflow: hidden; 10 | touch-action: none; 11 | } 12 | 13 | .jvectormap-tip { 14 | position: absolute; 15 | display: none; 16 | border: solid 1px #CDCDCD; 17 | border-radius: 3px; 18 | background: #292929; 19 | color: white; 20 | font-family: sans-serif, Verdana; 21 | font-size: smaller; 22 | padding: 3px; 23 | } 24 | 25 | .jvectormap-zoomin, .jvectormap-zoomout, .jvectormap-goback { 26 | position: absolute; 27 | left: 10px; 28 | border-radius: 3px; 29 | background: #292929; 30 | padding: 3px; 31 | color: white; 32 | cursor: pointer; 33 | line-height: 10px; 34 | text-align: center; 35 | box-sizing: content-box; 36 | } 37 | 38 | .jvectormap-zoomin, .jvectormap-zoomout { 39 | width: 10px; 40 | height: 10px; 41 | } 42 | 43 | .jvectormap-zoomin { 44 | top: 10px; 45 | } 46 | 47 | .jvectormap-zoomout { 48 | top: 30px; 49 | } 50 | 51 | .jvectormap-goback { 52 | bottom: 10px; 53 | z-index: 1000; 54 | padding: 6px; 55 | } 56 | 57 | .jvectormap-spinner { 58 | position: absolute; 59 | left: 0; 60 | top: 0; 61 | right: 0; 62 | bottom: 0; 63 | background: center no-repeat url(data:image/gif;base64,R0lGODlhIAAgAPMAAP///wAAAMbGxoSEhLa2tpqamjY2NlZWVtjY2OTk5Ly8vB4eHgQEBAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHRLYKhKP1oZmADdEAAAh+QQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY/CZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB+A4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6+Ho7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq+B6QDtuetcaBPnW6+O7wDHpIiK9SaVK5GgV543tzjgGcghAgAh+QQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK++G+w48edZPK+M6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE+G+cD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm+FNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk+aV+oJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0/VNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc+XiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30/iI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE/jiuL04RGEBgwWhShRgQExHBAAh+QQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR+ipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY+Yip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd+MFCN6HAAIKgNggY0KtEBAAh+QQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1+vsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d+jYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg+ygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0+bm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h+Kr0SJ8MFihpNbx+4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX+BP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA==); 64 | } 65 | 66 | .jvectormap-legend-title { 67 | font-weight: bold; 68 | font-size: 14px; 69 | text-align: center; 70 | } 71 | 72 | .jvectormap-legend-cnt { 73 | position: absolute; 74 | } 75 | 76 | .jvectormap-legend-cnt-h { 77 | bottom: 0; 78 | right: 0; 79 | } 80 | 81 | .jvectormap-legend-cnt-v { 82 | top: 0; 83 | right: 0; 84 | } 85 | 86 | .jvectormap-legend { 87 | background: black; 88 | color: white; 89 | border-radius: 3px; 90 | } 91 | 92 | .jvectormap-legend-cnt-h .jvectormap-legend { 93 | float: left; 94 | margin: 0 10px 10px 0; 95 | padding: 3px 3px 1px 3px; 96 | } 97 | 98 | .jvectormap-legend-cnt-h .jvectormap-legend .jvectormap-legend-tick { 99 | float: left; 100 | } 101 | 102 | .jvectormap-legend-cnt-v .jvectormap-legend { 103 | margin: 10px 10px 0 0; 104 | padding: 3px; 105 | } 106 | 107 | .jvectormap-legend-cnt-h .jvectormap-legend-tick { 108 | width: 40px; 109 | } 110 | 111 | .jvectormap-legend-cnt-h .jvectormap-legend-tick-sample { 112 | height: 15px; 113 | } 114 | 115 | .jvectormap-legend-cnt-v .jvectormap-legend-tick-sample { 116 | height: 20px; 117 | width: 20px; 118 | display: inline-block; 119 | vertical-align: middle; 120 | } 121 | 122 | .jvectormap-legend-tick-text { 123 | font-size: 12px; 124 | } 125 | 126 | .jvectormap-legend-cnt-h .jvectormap-legend-tick-text { 127 | text-align: center; 128 | } 129 | 130 | .jvectormap-legend-cnt-v .jvectormap-legend-tick-text { 131 | display: inline-block; 132 | vertical-align: middle; 133 | line-height: 20px; 134 | padding-left: 3px; 135 | } -------------------------------------------------------------------------------- /yalamain/static/css/morris.css: -------------------------------------------------------------------------------- 1 | .morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0} 2 | .morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0} 3 | -------------------------------------------------------------------------------- /yalamain/static/css/tablesorter.theme.default.css: -------------------------------------------------------------------------------- 1 | /************* 2 | Default Theme 3 | *************/ 4 | /* overall */ 5 | .tablesorter-default { 6 | width: 100%; 7 | /*font: 12px/18px Arial, Sans-serif;*/ 8 | color: #333; 9 | background-color: #fff; 10 | border-spacing: 0; 11 | margin: 10px 0 15px; 12 | text-align: left; 13 | } 14 | 15 | /* header */ 16 | .tablesorter-default th, 17 | .tablesorter-default thead td { 18 | font-weight: bold; 19 | color: #000; 20 | background-color: #fff; 21 | border-collapse: collapse; 22 | border-bottom: #ccc 2px solid; 23 | padding: 0; 24 | } 25 | .tablesorter-default tfoot th, 26 | .tablesorter-default tfoot td { 27 | border: 0; 28 | } 29 | .tablesorter-default .header, 30 | .tablesorter-default .tablesorter-header { 31 | background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==); 32 | background-position: center right; 33 | background-repeat: no-repeat; 34 | cursor: pointer; 35 | white-space: normal; 36 | padding: 4px 20px 4px 4px; 37 | } 38 | .tablesorter-default thead .headerSortUp, 39 | .tablesorter-default thead .tablesorter-headerSortUp, 40 | .tablesorter-default thead .tablesorter-headerAsc { 41 | background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); 42 | border-bottom: #000 2px solid; 43 | } 44 | .tablesorter-default thead .headerSortDown, 45 | .tablesorter-default thead .tablesorter-headerSortDown, 46 | .tablesorter-default thead .tablesorter-headerDesc { 47 | background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); 48 | border-bottom: #000 2px solid; 49 | } 50 | .tablesorter-default thead .sorter-false { 51 | background-image: none; 52 | cursor: default; 53 | padding: 4px; 54 | } 55 | 56 | /* tfoot */ 57 | .tablesorter-default tfoot .tablesorter-headerSortUp, 58 | .tablesorter-default tfoot .tablesorter-headerSortDown, 59 | .tablesorter-default tfoot .tablesorter-headerAsc, 60 | .tablesorter-default tfoot .tablesorter-headerDesc { 61 | border-top: #000 2px solid; 62 | } 63 | 64 | /* tbody */ 65 | .tablesorter-default td { 66 | background-color: #fff; 67 | border-bottom: #ccc 1px solid; 68 | padding: 4px; 69 | vertical-align: top; 70 | } 71 | 72 | /* hovered row colors */ 73 | /*.tablesorter-default tbody > tr.hover > td,*/ 74 | /*.tablesorter-default tbody > tr:hover > td,*/ 75 | /*.tablesorter-default tbody > tr.even:hover > td,*/ 76 | .tablesorter-default tbody > tr.odd:hover > td { 77 | /*background-color: #fff;*/ 78 | /*color: #000;*/ 79 | background-color: #f9f9f9; 80 | } 81 | 82 | /* table processing indicator */ 83 | .tablesorter-default .tablesorter-processing { 84 | background-position: center center !important; 85 | background-repeat: no-repeat !important; 86 | /* background-image: url(images/loading.gif) !important; */ 87 | background-image: url('data:image/gif;base64,R0lGODlhFAAUAKEAAO7u7lpaWgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgACACwAAAAAFAAUAAACQZRvoIDtu1wLQUAlqKTVxqwhXIiBnDg6Y4eyx4lKW5XK7wrLeK3vbq8J2W4T4e1nMhpWrZCTt3xKZ8kgsggdJmUFACH5BAEKAAIALAcAAAALAAcAAAIUVB6ii7jajgCAuUmtovxtXnmdUAAAIfkEAQoAAgAsDQACAAcACwAAAhRUIpmHy/3gUVQAQO9NetuugCFWAAAh+QQBCgACACwNAAcABwALAAACE5QVcZjKbVo6ck2AF95m5/6BSwEAIfkEAQoAAgAsBwANAAsABwAAAhOUH3kr6QaAcSrGWe1VQl+mMUIBACH5BAEKAAIALAIADQALAAcAAAIUlICmh7ncTAgqijkruDiv7n2YUAAAIfkEAQoAAgAsAAAHAAcACwAAAhQUIGmHyedehIoqFXLKfPOAaZdWAAAh+QQFCgACACwAAAIABwALAAACFJQFcJiXb15zLYRl7cla8OtlGGgUADs=') !important; 88 | } 89 | 90 | /* Zebra Widget - row alternating colors */ 91 | .tablesorter-default tr.odd > td { 92 | /*background-color: #dfdfdf;*/ 93 | background-color: #f9f9f9; 94 | } 95 | .tablesorter-default tr.even > td { 96 | /*background-color: #efefef;*/ 97 | } 98 | 99 | /* Column Widget - column sort colors */ 100 | .tablesorter-default tr.odd td.primary { 101 | background-color: #bfbfbf; 102 | } 103 | .tablesorter-default td.primary, 104 | .tablesorter-default tr.even td.primary { 105 | background-color: #d9d9d9; 106 | } 107 | .tablesorter-default tr.odd td.secondary { 108 | background-color: #d9d9d9; 109 | } 110 | .tablesorter-default td.secondary, 111 | .tablesorter-default tr.even td.secondary { 112 | background-color: #e6e6e6; 113 | } 114 | .tablesorter-default tr.odd td.tertiary { 115 | background-color: #e6e6e6; 116 | } 117 | .tablesorter-default td.tertiary, 118 | .tablesorter-default tr.even td.tertiary { 119 | background-color: #f2f2f2; 120 | } 121 | 122 | /* caption */ 123 | .tablesorter-default > caption { 124 | background-color: #fff; 125 | } 126 | 127 | /* filter widget */ 128 | .tablesorter-default .tablesorter-filter-row { 129 | background-color: #eee; 130 | } 131 | .tablesorter-default .tablesorter-filter-row td { 132 | background-color: #eee; 133 | border-bottom: #ccc 1px solid; 134 | line-height: normal; 135 | text-align: center; /* center the input */ 136 | -webkit-transition: line-height 0.1s ease; 137 | -moz-transition: line-height 0.1s ease; 138 | -o-transition: line-height 0.1s ease; 139 | transition: line-height 0.1s ease; 140 | } 141 | /* optional disabled input styling */ 142 | .tablesorter-default .tablesorter-filter-row .disabled { 143 | opacity: 0.5; 144 | filter: alpha(opacity=50); 145 | cursor: not-allowed; 146 | } 147 | /* hidden filter row */ 148 | .tablesorter-default .tablesorter-filter-row.hideme td { 149 | /*** *********************************************** ***/ 150 | /*** change this padding to modify the thickness ***/ 151 | /*** of the closed filter row (height = padding x 2) ***/ 152 | padding: 2px; 153 | /*** *********************************************** ***/ 154 | margin: 0; 155 | line-height: 0; 156 | cursor: pointer; 157 | } 158 | .tablesorter-default .tablesorter-filter-row.hideme * { 159 | height: 1px; 160 | min-height: 0; 161 | border: 0; 162 | padding: 0; 163 | margin: 0; 164 | /* don't use visibility: hidden because it disables tabbing */ 165 | opacity: 0; 166 | filter: alpha(opacity=0); 167 | } 168 | /* filters */ 169 | .tablesorter-default input.tablesorter-filter, 170 | .tablesorter-default select.tablesorter-filter { 171 | width: 95%; 172 | height: auto; 173 | margin: 4px auto; 174 | padding: 4px; 175 | background-color: #fff; 176 | border: 1px solid #bbb; 177 | color: #333; 178 | -webkit-box-sizing: border-box; 179 | -moz-box-sizing: border-box; 180 | box-sizing: border-box; 181 | -webkit-transition: height 0.1s ease; 182 | -moz-transition: height 0.1s ease; 183 | -o-transition: height 0.1s ease; 184 | transition: height 0.1s ease; 185 | } 186 | /* rows hidden by filtering (needed for child rows) */ 187 | .tablesorter .filtered { 188 | display: none; 189 | } 190 | 191 | /* ajax error row */ 192 | .tablesorter .tablesorter-errorRow td { 193 | text-align: center; 194 | cursor: pointer; 195 | background-color: #e6bf99; 196 | } 197 | -------------------------------------------------------------------------------- /yalamain/static/css/ztree/awesomeStyle/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/css/ztree/awesomeStyle/img/loading.gif -------------------------------------------------------------------------------- /yalamain/static/font-awesome/fonts/fontawesome-webfontba72.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/font-awesome/fonts/fontawesome-webfontba72.eot -------------------------------------------------------------------------------- /yalamain/static/font-awesome/fonts/fontawesome-webfontba72.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/font-awesome/fonts/fontawesome-webfontba72.ttf -------------------------------------------------------------------------------- /yalamain/static/font-awesome/fonts/fontawesome-webfontba72.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/font-awesome/fonts/fontawesome-webfontba72.woff -------------------------------------------------------------------------------- /yalamain/static/font-awesome/fonts/fontawesome-webfontd41d.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/font-awesome/fonts/fontawesome-webfontd41d.eot -------------------------------------------------------------------------------- /yalamain/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /yalamain/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /yalamain/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /yalamain/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /yalamain/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /yalamain/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /yalamain/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /yalamain/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /yalamain/static/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/ionicons.eot -------------------------------------------------------------------------------- /yalamain/static/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/ionicons.ttf -------------------------------------------------------------------------------- /yalamain/static/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/fonts/ionicons.woff -------------------------------------------------------------------------------- /yalamain/static/image/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/image/avatar1.png -------------------------------------------------------------------------------- /yalamain/static/image/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/image/avatar2.png -------------------------------------------------------------------------------- /yalamain/static/image/avatar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/image/avatar3.png -------------------------------------------------------------------------------- /yalamain/static/image/avatar4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/image/avatar4.jpg -------------------------------------------------------------------------------- /yalamain/static/image/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xl0shk/yala/a66b9e693bac365d47d2bd54ced15dcd5913aebf/yalamain/static/image/favicon.ico -------------------------------------------------------------------------------- /yalamain/static/js/WdatePicker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * My97 DatePicker 4.8 3 | * License: http://www.my97.net/license.asp 4 | */ 5 | var $dp,WdatePicker;(function(){var l={ 6 | $langList:[ 7 | {name:"en",charset:"UTF-8"}, 8 | {name:"zh-cn",charset:"gb2312"}, 9 | {name:"zh-tw",charset:"GBK"} 10 | ], 11 | $skinList:[ 12 | {name:"default",charset:"gb2312"}, 13 | {name:"whyGreen",charset:"gb2312"}, 14 | {name:"blue",charset:"gb2312"}, 15 | {name:"green",charset:"gb2312"}, 16 | {name:"simple",charset:"gb2312"}, 17 | {name:"ext",charset:"gb2312"}, 18 | {name:"blueFresh",charset:"gb2312"}, 19 | {name:"twoer",charset:"gb2312"}, 20 | {name:"YcloudRed",charset:"gb2312"}], 21 | $wdate:true, 22 | $crossFrame:false, 23 | $preLoad:false, 24 | $dpPath:"", 25 | doubleCalendar:false, 26 | enableKeyboard:true, 27 | enableInputMask:true, 28 | autoUpdateOnChanged:null, 29 | weekMethod:"MSExcel", 30 | position:{}, 31 | lang:"auto", 32 | skin:"default", 33 | dateFmt:"yyyy-MM-dd", 34 | realDateFmt:"yyyy-MM-dd", 35 | realTimeFmt:"HH:mm:ss", 36 | realFullFmt:"%Date %Time", 37 | minDate:"0001-01-01 00:00:00", 38 | maxDate:"9999-12-31 23:59:59", 39 | minTime:"00:00:00", 40 | maxTime:"23:59:59", 41 | startDate:"", 42 | alwaysUseStartDate:false, 43 | yearOffset:1911, 44 | firstDayOfWeek:0, 45 | isShowWeek:false, 46 | highLineWeekDay:true, 47 | isShowClear:true, 48 | isShowToday:true, 49 | isShowOK:true, 50 | isShowOthers:true, 51 | readOnly:false, 52 | errDealMode:0, 53 | autoPickDate:null, 54 | qsEnabled:true, 55 | autoShowQS:false, 56 | hmsMenuCfg:{H:[1,6],m:[5,6],s:[15,4]}, 57 | 58 | opposite:false,specialDates:null,specialDays:null,disabledDates:null,disabledDays:null,onpicking:null,onpicked:null,onclearing:null,oncleared:null,ychanging:null,ychanged:null,Mchanging:null,Mchanged:null,dchanging:null,dchanged:null,Hchanging:null,Hchanged:null,mchanging:null,mchanged:null,schanging:null,schanged:null,eCont:null,vel:null,elProp:"",errMsg:"",quickSel:[],has:{},getRealLang:function(){var d=l.$langList;for(var e=0;e0?1:0;var K=new Date(this.dt.y,this.dt.M,0).getDate();this.dt.d=Math.min(K+M,this.dt.d)}}}}if(this.dt.refresh()){return this.dt}}return""},show:function(){var K=E[z].getElementsByTagName("div"),J=100000;for(var e=0;eJ){J=L}}this.dd.style.zIndex=J+2;r(this.dd,"block");r(this.dd.firstChild,"")},unbind:function(e){e=this.$(e);if(e.initcfg){t(e,"onclick",function(){g(e.initcfg)});t(e,"onfocus",function(){g(e.initcfg)})}},hide:function(){r(this.dd,"none")},attachEvent:k};for(var d in w){E.$dp[d]=w[d]}$dp=E.$dp}function k(I,J,w,d){if(I.addEventListener){var e=J.replace(/on/,"");w._ieEmuEventHandler=function(K){return w(K)};I.addEventListener(e,w._ieEmuEventHandler,d)}else{I.attachEvent(J,w)}}function t(w,I,e){if(w.removeEventListener){var d=I.replace(/on/,"");e._ieEmuEventHandler=function(J){return e(J)};w.removeEventListener(d,e._ieEmuEventHandler,false)}else{w.detachEvent(I,e)}}function C(w,e,d){if(typeof w!=typeof e){return false}if(typeof w=="object"){if(!d){for(var I in w){if(typeof e[I]=="undefined"){return false}if(!C(w[I],e[I],true)){return false}}}return true}else{if(typeof w=="function"&&typeof e=="function"){return w.toString()==e.toString()}else{return w==e}}}function q(){var I,w,d=n[z][H]("script");for(var e=0;e0){I=I.substring(0,w+1)}if(I){break}}return I}function m(w,I,J){var d=n[z][H]("HEAD").item(0),e=n[z].createElement("link");if(d){e.href=w;e.rel="stylesheet";e.type="text/css";if(I){e.title=I}if(J){e.charset=J}d.appendChild(e)}}function p(I){I=I||E;var L=0,d=0;while(I!=E){var N=I.parent[z][H]("iframe");for(var J=0;JI.scrollTop||d.scrollLeft>I.scrollLeft))?d:I;return{top:J.scrollTop,left:J.scrollLeft}}function s(d){try{var w=d?(d.srcElement||d.target):null;if($dp.cal&&!$dp.eCont&&$dp.dd&&w!=$dp.el&&$dp.dd.style.display=="block"){$dp.cal.close()}}catch(d){}}function A(){$dp.status=2}var G,j;function g(M,d){if(!$dp){return}b();var J={};for(var L in M){J[L]=M[L]}for(var L in l){if(L.substring(0,1)!="$"&&J[L]===undefined){J[L]=l[L]}}if(d){if(!w()){j=j||setInterval(function(){if(E[z].readyState=="complete"){clearInterval(j)}g(null,true)},50);return}if($dp.status==0){$dp.status=1;J.el=i;a(J,true)}else{return}}else{if(J.eCont){J.eCont=$dp.$(J.eCont);J.el=i;J.autoPickDate=true;J.qsEnabled=false;a(J)}else{if(l.$preLoad&&$dp.status!=2){return}var I=N();if(n.event===I||I){J.srcEl=I.srcElement||I.target;I.cancelBubble=true}J.el=J.el=$dp.$(J.el||J.srcEl);if(J.el==null){alert("WdatePicker:el is null!\nexample:onclick=\"WdatePicker({el:this})\"");return;}try{if(!J.el||J.el.My97Mark===true||J.el.disabled||($dp.dd&&r($dp.dd)!="none"&&$dp.dd.style.left!="-970px")){if(J.el.My97Mark){J.el.My97Mark=false}return}}catch(K){}if(I&&J.el.nodeType==1&&!C(J.el.initcfg,M)){$dp.unbind(J.el);k(J.el,I.type=="focus"?"onclick":"onfocus",function(){g(M)});J.el.initcfg=M}a(J)}}function w(){if(h&&E!=n&&E[z].readyState!="complete"){return false}return true}function N(){if(f){try{func=N.caller;while(func!=null){var O=func.arguments[0];if(O&&(O+"").indexOf("Event")>=0){return O}func=func.caller}}catch(P){}return null}return event}}function c(e,d){return e.currentStyle?e.currentStyle[d]:document.defaultView.getComputedStyle(e,false)[d]}function r(e,d){if(e){if(d!=null){e.style.display=d}else{return c(e,"display")}}}function a(e,d){var K=e.el?e.el.nodeName:"INPUT";if(d||e.eCont||new RegExp(/input|textarea|div|span|p|a/ig).test(K)){e.elProp=K=="INPUT"?"value":"innerHTML"}else{return}if(e.lang=="auto"){e.lang=h?navigator.browserLanguage.toLowerCase():navigator.language.toLowerCase()}if(!e.eCont){for(var J in e){$dp[J]=e[J]}}if(!$dp.dd||e.eCont||($dp.dd&&(e.getRealLang().name!=$dp.dd.lang||e.skin!=$dp.dd.skin))){if(e.eCont){w(e.eCont,e)}else{$dp.dd=E[z].createElement("DIV");$dp.dd.style.cssText="position:absolute";E[z].body.appendChild($dp.dd);w($dp.dd,e);if(d){$dp.dd.style.left=$dp.dd.style.top="-970px"}else{$dp.show();I($dp)}}}else{if($dp.cal){$dp.show();$dp.cal.init();if(!$dp.eCont){I($dp)}}}function w(V,P){var O=E[z].domain,S=false,M='';V.innerHTML=M;var L=l.$langList,U=l.$skinList,T;try{T=V.lastChild.contentWindow[z]}catch(Q){S=true;V.removeChild(V.lastChild);var N=E[z].createElement("iframe");N.hideFocus=true;N.frameBorder=0;N.scrolling="no";N.src="javascript:(function(){var d=document;d.open();d.domain='"+O+"';})()";V.appendChild(N);setTimeout(function(){T=V.lastChild.contentWindow[z];R()},97);return}R();function R(){var Y=P.getRealLang();V.lang=Y.name;V.skin=P.skin;var X=[" 104 | 123 | {% endblock %} -------------------------------------------------------------------------------- /yalamain/templates/assets/ip_pool_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "page.html" as page %} 3 | {% block content %} 4 |
5 |
6 |

7 | 管理员配置 8 | 9 | 默认显示10条数据 10 | 11 |

12 | 17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 | IP池信息 25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% for item in ippools %} 37 | 38 | 39 | 40 | 41 | {% endfor %} 42 | 43 |
IP状态
{{ item.IP }}{{ item.status }}
44 |
45 | {% if pagination %} 46 | 49 | {% endif %} 50 |
51 |
52 |
53 |
54 |
55 |
56 | {% endblock %} 57 | {% block bottom_js %} 58 | {% endblock %} -------------------------------------------------------------------------------- /yalamain/templates/auth/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Yala 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 134 | 135 | -------------------------------------------------------------------------------- /yalamain/templates/base.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Yala 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 54 | {% block header_css %} 55 | {% endblock %} 56 | 57 | 58 | 59 |
60 | {% include 'main-header.html' %} 61 | {% include 'main-sidebar.html' %} 62 | 63 | {% block content %} 64 | {% endblock %} 65 | 66 | {% include 'footer.html' %} 67 |
68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 127 | {% block bottom_js %} 128 | {% endblock %} 129 | 130 | -------------------------------------------------------------------------------- /yalamain/templates/changelog.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "page.html" as page %} 3 | {% block content %} 4 |
5 |
6 |

7 | ChangeLog 8 |

9 |
10 |
11 |
12 |
13 |

14 | [[ log.version ]] 15 |

16 |

[[ detailItem ]]

17 |
18 |
19 |
20 |
21 | {% endblock %} 22 | 23 | {% block bottom_js %} 24 | 67 | {% endblock %} 68 | -------------------------------------------------------------------------------- /yalamain/templates/error/400.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 400 6 | 7 | 22 | 23 | 24 |
25 |
26 | 29 |
30 |

Bad Request!

31 |

32 |
33 |
34 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /yalamain/templates/error/401.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 401 6 | 7 | 22 | 23 | 24 |
25 |
26 | 29 |
30 |

Unauthorized!

31 |

32 |
33 |
34 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /yalamain/templates/error/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 403 6 | 7 | 22 | 23 | 24 |
25 |
26 | 29 |
30 |

您没有权限访问该页面!

31 |

32 |
33 |
34 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /yalamain/templates/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 6 | 7 | 22 | 23 | 24 |
25 |
26 | 29 |
30 |

您打开的页面不存在,请联系管理员进行处理!

31 |

32 |
33 |
34 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /yalamain/templates/error/405.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 405 6 | 7 | 22 | 23 | 24 |
25 |
26 | 29 |
30 |

Method Not Allowed!

31 |

32 |
33 |
34 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /yalamain/templates/error/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 500 6 | 7 | 22 | 23 | 24 |
25 |
26 | 29 |
30 |

内部服务器错误,请联系管理员进行处理!

31 |

32 |
33 |
34 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /yalamain/templates/footer.html: -------------------------------------------------------------------------------- 1 |
2 | YALA       Version 1.0.0       3 | ChangeLog 4 |
-------------------------------------------------------------------------------- /yalamain/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "page.html" as page %} 3 | {% block content %} 4 |
5 |
6 |

7 | 首页 8 |

9 | 13 |
14 |
15 |
16 |
17 |
18 |
19 | Hosts 20 |
主机总数
21 |
22 |
23 |

{{ host_count }}

24 | All hosts 25 |
26 |
27 |
28 |
29 |
30 |
31 | Domain 32 |
监控域名管理
33 |
34 |
35 |

{{ monitor_domain_count }}

36 | Monitor domain 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 | Probe 45 |
监控端口管理
46 |
47 |
48 |

{{ monitor_probe_count }}

49 | Monitor count 50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 | Users 58 |
用户管理
59 |
60 |
61 |

{{ users_count }}

62 | All users 63 |
64 |
65 |
66 |
67 | 68 |
69 |
70 |
71 |
72 |

数据趋势图

73 | 74 |
75 | 77 | 78 |
79 |
80 |
81 |
82 |
83 | 84 |
85 |
86 |
87 | 88 |
89 |
90 | {% endblock %} 91 | 92 | {% block bottom_js %} 93 | 127 | 128 | {% endblock %} -------------------------------------------------------------------------------- /yalamain/templates/main-header.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 52 |
-------------------------------------------------------------------------------- /yalamain/templates/main-sidebar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /yalamain/templates/page.html: -------------------------------------------------------------------------------- 1 | {% macro pagination_widget(pagination, endpoint) %} 2 |
    3 | 4 | 6 | « 7 | 8 | 9 | {% for p in pagination.iter_pages() %} 10 | {% if p %} 11 | {% if p == pagination.page %} 12 |
  • 13 | {{ p }} 14 |
  • 15 | {% else %} 16 |
  • 17 | {{ p }} 18 |
  • 19 | {% endif %} 20 | {% else %} 21 |
  • 22 | {% endif %} 23 | {% endfor %} 24 | 25 | 27 | » 28 | 29 | 30 |
31 | {% endmacro %} 32 | --------------------------------------------------------------------------------