├── .gitignore ├── .settings ├── org.eclipse.core.resources.prefs └── org.eclipse.ltk.core.refactoring.prefs ├── LICENSE ├── README-en.md ├── README.md ├── db_setup ├── DDL │ └── oracle.sql └── data │ ├── 0_EDWIN_CALENDAR_DAY_DATA.SQL │ ├── 1_EDWIN_TEAM_CFG_DATA.SQL │ ├── 2_EDWIN_CHECK_ITM_CFG_DATA_BUILTIN.SQL │ ├── 3_EDWIN_PAGE_DATA.sql │ ├── 4_EDWIN_PAGELET_DATA.sql │ └── 5_EDWIN_PAGELET_CHECK_LIST_DATA.sql ├── docs ├── change_log.md ├── check_item_cfg.md ├── install_and_deploy.md ├── screenshots │ ├── dashboard.png │ └── page.png └── write_your_own_agent.md ├── edwinAgent ├── __init__.py ├── agents │ ├── __init__.py │ ├── check_item_sample_nonnumerical.py │ ├── check_item_sample_numerical.py │ ├── detect_threadstuck_in_log_file.py │ └── web_page_logon_sample.py ├── bin │ ├── check_item_sample_nonnumerical.bat │ ├── check_item_sample_nonnumerical.sh │ ├── check_item_sample_numerical.bat │ ├── check_item_sample_numerical.sh │ ├── detect_threadstuck_in_log_file.sh │ └── locks │ │ └── keep_for_git.txt ├── common │ ├── __init__.py │ ├── api_helper.py │ ├── conf.py │ ├── const.py │ ├── database_tera.py │ ├── my_logging.py │ └── os_helper.py └── site_packages │ ├── __init__.py │ ├── dbRowFactory │ ├── __init__.py │ └── pyDbRowFactory.py │ ├── jssh │ └── __init__.py │ └── logwatch_glob.py ├── edwinServer ├── __init__.py ├── bin │ ├── alarm_send.bat │ ├── alarm_send.sh │ ├── locks │ │ └── keep_for_git.txt │ ├── runserver.bat │ └── runserver.sh ├── common │ ├── __init__.py │ ├── conf.py │ ├── const.py │ ├── convert_helper.py │ ├── database_all.py │ ├── database_meta.py │ ├── database_tera.py │ ├── job_state_updater.py │ ├── mail_helper.py │ ├── mail_service.py │ ├── model_meta.py │ ├── my_logging.py │ └── os_helper.py ├── scripts │ ├── __init__.py │ └── alarm_send.py ├── site_packages │ ├── ConcurrentLogHandler084 │ │ ├── PKG-INFO │ │ ├── __init__.py │ │ ├── bugfix_by_me.txt │ │ ├── cloghandler.py │ │ └── portalocker.py │ ├── __init__.py │ ├── dbRowFactory │ │ ├── __init__.py │ │ └── pyDbRowFactory.py │ └── pypyodbc.py └── web │ ├── __init__.py │ ├── app │ ├── __init__.py │ ├── api.py │ ├── excepts.py │ ├── static │ │ ├── css │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap.min.css │ │ │ ├── dashboard.css │ │ │ └── large_svg.css │ │ ├── favicon.ico │ │ ├── favicon_2.ico │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ ├── img │ │ │ ├── box_arrow.gif │ │ │ ├── box_bottomborder.gif │ │ │ ├── box_leftborder.gif │ │ │ ├── box_lowerleft.gif │ │ │ ├── box_lowerright.gif │ │ │ ├── box_rightborder.gif │ │ │ ├── box_topborder.gif │ │ │ ├── box_upperleft.gif │ │ │ ├── box_upperright.gif │ │ │ ├── bug-high.png │ │ │ ├── bug-low.png │ │ │ ├── bug-medium.png │ │ │ ├── check_green.gif │ │ │ ├── comment.gif │ │ │ ├── critical.png │ │ │ ├── face16.png │ │ │ ├── face24.png │ │ │ ├── face32.png │ │ │ ├── feed.png │ │ │ ├── file.png │ │ │ ├── folder.png │ │ │ ├── glyphicons-halflings-white.png │ │ │ ├── glyphicons-halflings.png │ │ │ ├── highslide │ │ │ │ ├── controlbar2.gif │ │ │ │ ├── controlbar3.gif │ │ │ │ ├── controlbar4-hover.gif │ │ │ │ ├── controlbar4.gif │ │ │ │ ├── fullexpand.gif │ │ │ │ ├── geckodimmer.png │ │ │ │ ├── loader.gif │ │ │ │ ├── loader.white.gif │ │ │ │ ├── outlines │ │ │ │ │ ├── Outlines.psd │ │ │ │ │ ├── beveled.png │ │ │ │ │ ├── drop-shadow.png │ │ │ │ │ ├── glossy-dark.png │ │ │ │ │ ├── outer-glow.png │ │ │ │ │ ├── rounded-black.png │ │ │ │ │ └── rounded-white.png │ │ │ │ ├── resize.gif │ │ │ │ ├── zoomin.cur │ │ │ │ └── zoomout.cur │ │ │ ├── home.gif │ │ │ ├── ico_user.png │ │ │ ├── instruct.gif │ │ │ ├── loading.gif │ │ │ ├── loading3.gif │ │ │ ├── logo.gif │ │ │ ├── normal.png │ │ │ ├── outdated.png │ │ │ ├── outdated2.png │ │ │ ├── outdated3.jpg │ │ │ ├── p.gif │ │ │ ├── parent.png │ │ │ ├── plus_green.gif │ │ │ ├── separator.gif │ │ │ └── warning.png │ │ └── js │ │ │ ├── bootstrap.min.js │ │ │ ├── html5shiv.js │ │ │ ├── jquery.min.js │ │ │ ├── pygal-tooltips.js │ │ │ ├── respond.js │ │ │ └── svg.jquery.js │ ├── templates │ │ ├── base.html │ │ ├── base_no_sidebar.html │ │ ├── check_item.html │ │ ├── index.html │ │ ├── page.html │ │ ├── team.html │ │ └── test_svg.html │ └── views.py │ ├── conf.py │ └── runserver.py ├── requirements_Agent.txt └── requirements_Server.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | eggs/ 15 | lib/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | 24 | # Installer logs 25 | pip-log.txt 26 | pip-delete-this-directory.txt 27 | 28 | # Unit test / coverage reports 29 | htmlcov/ 30 | .tox/ 31 | .coverage 32 | .cache 33 | nosetests.xml 34 | coverage.xml 35 | 36 | # Translations 37 | *.mo 38 | 39 | # Mr Developer 40 | .mr.developer.cfg 41 | .project 42 | .pydevproject 43 | 44 | # Rope 45 | .ropeproject 46 | 47 | # Django stuff: 48 | *.log 49 | *.pot 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//edwinAgent/__init__.py=utf-8 3 | encoding//edwinAgent/agents/check_item_sample_nonnumerical.py=utf-8 4 | encoding//edwinAgent/agents/check_item_sample_numerical.py=utf-8 5 | encoding//edwinAgent/agents/detect_threadstuck_in_log_file.py=utf-8 6 | encoding//edwinAgent/common/api_helper.py=utf-8 7 | encoding//edwinAgent/common/conf.py=utf-8 8 | encoding//edwinAgent/common/const.py=utf-8 9 | encoding//edwinAgent/common/database_tera.py=utf-8 10 | encoding//edwinAgent/common/os_helper.py=utf-8 11 | encoding//edwinAgent/site_packages/dbRowFactory/pyDbRowFactory.py=utf-8 12 | encoding//edwinServer/__init__.py=utf-8 13 | encoding//edwinServer/common/conf.py=utf-8 14 | encoding//edwinServer/common/const.py=utf-8 15 | encoding//edwinServer/common/database_all.py=utf-8 16 | encoding//edwinServer/common/database_meta.py=utf-8 17 | encoding//edwinServer/common/database_tera.py=utf-8 18 | encoding//edwinServer/common/job_state_updater.py=utf-8 19 | encoding//edwinServer/common/mail_helper.py=utf-8 20 | encoding//edwinServer/common/mail_service.py=utf-8 21 | encoding//edwinServer/common/model_meta.py=utf-8 22 | encoding//edwinServer/common/os_helper.py=utf-8 23 | encoding//edwinServer/scripts/alarm_send.py=utf-8 24 | encoding//edwinServer/site_packages/dbRowFactory/pyDbRowFactory.py=utf-8 25 | encoding//edwinServer/site_packages/pypyodbc.py=utf-8 26 | encoding//edwinServer/web/__init__.py=utf-8 27 | encoding//edwinServer/web/app/__init__.py=utf-8 28 | encoding//edwinServer/web/app/api.py=utf-8 29 | encoding//edwinServer/web/app/excepts.py=utf-8 30 | encoding//edwinServer/web/app/templates/base.html=UTF-8 31 | encoding//edwinServer/web/app/templates/index.html=UTF-8 32 | encoding//edwinServer/web/app/templates/page.html=UTF-8 33 | encoding//edwinServer/web/app/views.py=utf-8 34 | encoding//edwinServer/web/conf.py=utf-8 35 | encoding//edwinServer/web/runserver.py=utf-8 36 | -------------------------------------------------------------------------------- /.settings/org.eclipse.ltk.core.refactoring.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false 3 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | ##What is edwin 2 | edwin is one alerting and monitoring system written in Python. 3 | 4 | 5 | ##Why to invent edwin 6 | Many monitoring softwares existed out there, such as Nagios, Ganglia, Zenoss. 7 | I just want to re-invent a new wheel to explore the Python language power. 8 | We can use edwin to monitor any metrics besides server status 9 | 10 | 11 | ##edwin Components 12 | 1. **edwinServer.Web**, served as web service and web GUI. 13 | Deployed on server machine. It requires Python 2.7. 14 | 2. **edwinServer.Scripts**, send alarm and do other housekeeping tasks. 15 | Deployed on server machine. It requires Python 2.7. 16 | 3. **edwinAgent**, do check task, and register check result to server. 17 | Deployed on client machines. It requires Jython 2.7 or Python 2.7. Since Jython is more easy to install, Jython is recommended. 18 | 19 | 20 | ##How to work 21 | Let us to break down several steps below, 22 | 1. The script of edwinAgent check first, then send result to edwinServer.Web via web service. 23 | 2. edwinServer.Scripts.send_alarm will send out alarm if one checking result is abnormal. 24 | 3. In edwinServer.Web, the checking results can be shown in dash-board. 25 | 26 | 27 | ##Features 28 | 1. Every check item has two kinds of abnormal limit, they are **warning** and **critical**. 29 | For warning events, they will be sent via email. 30 | For critical events, they can be sent via SMS and calling if you want. 31 | 2. Edwin allows you to customize alarm delivery options, includes, 32 | one option to allow repeated email alarm 33 | one option to allow repeated SMS alarm 34 | one option to allow repeated call alarm 35 | 3. Basic statistics trend charts are available for every check item. 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | ## 简单介绍一下edwin 3 | edwin是一个报警和监控平台, 可以使用它监控任意东西, 如有异常(分为警告级和严重级), 可以发出报警. 可以自定义报警的通知方式, 比如邮件/短信/电话. 另外, 它提供一个web UI, 能以dashboard形式展现监控指标的状态. 4 | 5 | edwin对于监控项目的组织形式, 由小到大是: check item -> pagelet -> page -> dashboard. 另外,可以为 check item指定一个或多个 team 来负责. 这样灵活的组织形式, 使得edwin非常适合管理大量监控条目. 6 | 7 | 一句话, **edwin 是一个非常适合团队使用的监控报警平台, 而且也适合多个团队共用. ** 8 | 9 | 10 | ## edwin 组件 11 | 1. **edwinServer.Web**, 做为web service和 web 展现页面. 12 | 需要部署在你的服务器上, 需要Python2.7, 具体见 requirements_Server.txt 文档 13 | 2. **edwinServer.Scripts**, 有脚本专门发送告警 14 | 部署在你的服务器上, 推荐和edwinServer.Web放在同一个机器上, 当然也可以不放在一起. 安装环境见 requirements_Server.txt 文档 15 | 3. **edwinAgent**, 你的检查脚本即是一个agent, edwin已经提供了agent开发示例 16 | 部署在你想要的机器上, 运行环境是jython2.7和python2.7, 考虑到jython安装非常方便, 推荐使用jython2.7 17 | 18 | 19 | 20 | ## edwin是如何工作的 21 | 22 | ###首先声明两个概念, 下面的文档将会用到这两个概念. 23 | 1. 检查状态, 即你的check item是否正常, 在edwin中, 有三种检查状态:正常/警告级异常/严重异常 24 | 2. 检查结果, 分为两种: 即可以可量化的, 以及也不可量化的. 对于可量化的, 你的agent需要登记检查值, 对于不可量化的check item, 你的agent需要登记检查状态. 25 | 26 | ###edwin工作方式非常简单, 一共三步: 27 | 1. 在客户端机器上运行你的agent脚本, 即完成检查, 通过web service将检查结果保存在服务器端. 28 | 2. *dwinServer.Scripts.send_alarm* 组件定时查看检查结果, 如有异常会发出报警. 29 | 3. *edwinServer.Web* 会在web页面上以醒目的方式展现检查结果. 30 | 31 | 32 | 33 | ## guide 说明 34 | 1. 对于可量化的check item, 你需要在数据库中设置警告级临界值和严重级临界值. 你的agent只需要登记实际检查值即可, 服务器端自动判断报警级别. 35 | 2. 对于不可量化的check item, 你的agent程序需要给出检查状态, 即正常抑或警告或紧急. 36 | 3. 告警发送方式 37 | *对于警告级异常, edwin会以email方式发出警告 38 | *对于紧急级异常, edwin会以邮件/短信/电话的方式发出警告, 当然可以禁掉短信/电话的告警方式 39 | 4. 重复警告的处理 40 | 谁都不想收到一堆重复的警告, 尤其是不那么重要或者检查频率高的check item, edwin为每个check item提供三个开关来控制是否允许重复告警, 分别是ALLOW_REPEATED_MAIL_ALARM、ALLOW_REPEATED_SMS_ALARM和ALLOW_REPEATED_CALL_ALARM 41 | 5. Web提供为每个check item提供基本的趋势图, 帮助我们回顾检查的结果 42 | 43 | ## 抓图 44 | dashboard 45 | page -------------------------------------------------------------------------------- /db_setup/DDL/oracle.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE EDWIN_TEAM_CFG 2 | ( 3 | OWNER_TEAM_CODE NVARCHAR2(50) 4 | , EMAIL_TO_LIST VARCHAR2(255) 5 | , SMS_MAIL_TO VARCHAR2(255) 6 | , SMS_MAIL_TITLE NVARCHAR2(255) 7 | , PHONE_MAIL_TO VARCHAR2(255) 8 | , PHONE_MAIL_TITLE NVARCHAR2(255) 9 | ); 10 | 11 | ALTER TABLE EDWIN_TEAM_CFG 12 | ADD CONSTRAINT PK_EDWIN_TEAM_CFG PRIMARY KEY (OWNER_TEAM_CODE); 13 | 14 | 15 | 16 | 17 | CREATE TABLE EDWIN_CHECK_ITM_CFG 18 | ( 19 | ITM_CODE NVARCHAR2(50) not null 20 | ,ITM_TITLE NVARCHAR2(50) not null 21 | ,ITM_CATEGORY NVARCHAR2(50) not null 22 | ,ENABLED_FLAG CHAR(1) default 'Y' not null 23 | ,HOST NVARCHAR2(50) --only for reference 24 | ,CHECK_SCRIPT NVARCHAR2(255) --only for reference 25 | ,CHECK_INTERVAL_MINUTE NUMBER not null --, -1 for one that need not check outdated 26 | ,CHECK_VALUE_IS_NUMBER CHAR(1) default 'N' not null 27 | ,DESCRIPTION NVARCHAR2(255) 28 | ,OWNER_TEAM_LIST NVARCHAR2(150) NOT NULL -- team_code list, delimited by ; or , symbol 29 | ,WARNING_LIMIT NUMBER -- mandatory column if CHECK_VALUE_IS_NUMBER=Y 30 | ,CRITICAL_LIMIT NUMBER -- mandatory column if CHECK_VALUE_IS_NUMBER=Y 31 | ,SHADOW_DATA VARCHAR2(150) -- we can use this column to store any data. The value will copy to EDWIN_CHECK_ITM_LOG table when checking result recorded 32 | ,WARNING_MAIL_CC VARCHAR2(255) 33 | ,CRITICAL_MAIL_CC VARCHAR2(255) 34 | ,CRITICAL_SMS_FLAG CHAR(1) default 'N' NOT NULL 35 | ,CRITICAL_CALL_FLAG CHAR(1) default 'N' NOT NULL 36 | ,ALLOW_REPEATED_SMS_ALARM CHAR(1) default 'N' NOT NULL 37 | ,ALLOW_REPEATED_CALL_ALARM CHAR(1) default 'N' NOT NULL 38 | ,ALLOW_REPEATED_MAIL_ALARM CHAR(1) default 'N' NOT NULL 39 | ) 40 | ; 41 | 42 | 43 | ALTER TABLE EDWIN_CHECK_ITM_CFG 44 | ADD CONSTRAINT PK_EDWIN_CHECK_ITM_CFG PRIMARY KEY (ITM_CODE); 45 | 46 | 47 | 48 | create table EDWIN_PAGE 49 | ( 50 | PAGE_CODE NVARCHAR2(50) not null, 51 | PAGE_TITLE NVARCHAR2(50) not null, 52 | DISPLAY_FLAG CHAR(1) default 'Y' not null, 53 | DESCRIPTION NVARCHAR2(255) 54 | ) 55 | ; 56 | ALTER TABLE EDWIN_PAGE 57 | ADD CONSTRAINT PK_EDWIN_PAGE PRIMARY KEY (PAGE_CODE); 58 | 59 | 60 | 61 | create table EDWIN_PAGELET 62 | ( 63 | 64 | PAGELET_CODE NVARCHAR2(50) not null, 65 | PAGELET_TITLE NVARCHAR2(50) not null, 66 | PAGE_CODE VARCHAR2(50) not null, 67 | DISPLAY_ORDER NUMBER not null, 68 | DISPLAY_FLAG CHAR(1) default 'Y' not null, 69 | DESCRIPTION NVARCHAR2(255) 70 | ) 71 | ; 72 | ALTER TABLE EDWIN_PAGELET 73 | ADD CONSTRAINT PK_EDWIN_PAGELET PRIMARY KEY (PAGELET_CODE); 74 | 75 | 76 | 77 | 78 | create table EDWIN_PAGELET_CHECK_LIST 79 | ( 80 | PAGELET_CODE NVARCHAR2(50) not null, 81 | CHECK_ITM_CODE NVARCHAR2(50) not null, 82 | DISPLAY_ORDER NUMBER not null, 83 | DISPLAY_FLAG CHAR(1) default 'Y' not null 84 | ) 85 | ; 86 | ALTER TABLE EDWIN_PAGELET_CHECK_LIST 87 | ADD CONSTRAINT PK_EDWIN_PAGELET_CHECK_LIST PRIMARY KEY (PAGELET_CODE,CHECK_ITM_CODE); 88 | 89 | 90 | 91 | 92 | CREATE TABLE EDWIN_CALENDAR_DAY 93 | ( 94 | DATE_ID VARCHAR2(8) 95 | ,WEEK_ID VARCHAR2(10) 96 | ,MONTH_ID VARCHAR2(6) 97 | ,YEAR_ID VARCHAR2(4) 98 | ) 99 | ; 100 | 101 | ALTER TABLE EDWIN_CALENDAR_DAY 102 | ADD CONSTRAINT PK_EDWIN_CALENDAR_DAY PRIMARY KEY (DATE_ID); 103 | 104 | 105 | CREATE TABLE EDWIN_CHECK_ITM_STATUS 106 | ( 107 | ITM_CODE NVARCHAR2(50) not null, 108 | LAST_CHECK_TIMESTAMP VARCHAR2(30), 109 | LAST_STATUS VARCHAR2(10), 110 | LAST_VALUE NUMBER, 111 | LAST_DETAIL_MSG nclob, 112 | LAST_NOTIFICATION_MSG nclob, 113 | IS_WARNING_EVENT CHAR(1), 114 | IS_CRITICAL_EVENT CHAR(1), 115 | IS_NEW_CRITICAL_EVENT CHAR(1), 116 | IS_NEW_WARNING_EVENT CHAR(1) 117 | ) 118 | ; 119 | 120 | 121 | ALTER TABLE EDWIN_CHECK_ITM_STATUS 122 | ADD CONSTRAINT PK_EDWIN_CHECK_ITM_STATUS PRIMARY KEY (ITM_CODE); 123 | 124 | 125 | 126 | 127 | CREATE TABLE EDWIN_CHECK_ITM_LOG 128 | ( 129 | ITM_CODE NVARCHAR2(50) not null 130 | ,CHECK_DATE VARCHAR2(10) NOT NULL --format is '2014-01-17' 131 | ,CHECK_TIMESTAMP VARCHAR2(30) not NULL --format is '2014-01-17 16:20:27.783999' 132 | ,CHECK_STATUS VARCHAR2(50) not NULL 133 | ,CHECK_VALUE NUMBER 134 | ,CHECK_DETAIL_MSG nclob 135 | ,CHECK_NOTIFICATION_MSG nclob 136 | ,WARNING_LIMIT NUMBER 137 | ,CRITICAL_LIMIT NUMBER 138 | ,SHADOW_DATA VARCHAR2(150) 139 | ,IS_WARNING_EVENT CHAR(1) DEFAULT 'N' NOT NULL 140 | ,IS_CRITICAL_EVENT CHAR(1) DEFAULT 'N' NOT NULL 141 | ,IS_NEW_CRITICAL_EVENT CHAR(1) DEFAULT 'N' NOT NULL 142 | ,IS_NEW_WARNING_EVENT CHAR(1) DEFAULT 'N' NOT NULL 143 | ,ALARM_SEND_STATUS VARCHAR2(10) NOT NULL 144 | ,ALARM_SEND_BEGIN_TIME VARCHAR2(30) 145 | ,ALARM_SEND_END_TIME VARCHAR2(30) 146 | ) 147 | ; 148 | 149 | 150 | create index IDX_EDWIN_CHECK_ITM_LOG_1 on EDWIN_CHECK_ITM_LOG (ITM_CODE); 151 | create index IDX_EDWIN_CHECK_ITM_LOG_2 on EDWIN_CHECK_ITM_LOG (CHECK_DATE); 152 | create index IDX_EDWIN_CHECK_ITM_LOG_3 on EDWIN_CHECK_ITM_LOG (CHECK_TIMESTAMP); 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /db_setup/data/1_EDWIN_TEAM_CFG_DATA.SQL: -------------------------------------------------------------------------------- 1 | 2 | insert into EDWIN_TEAM_CFG 3 | ( 4 | OWNER_TEAM_CODE 5 | ,EMAIL_TO_LIST 6 | ,SMS_MAIL_TO 7 | ,SMS_MAIL_TITLE 8 | ,PHONE_MAIL_TO 9 | ,PHONE_MAIL_TITLE 10 | ) 11 | VALUES 12 | ( 13 | 'EDWIN_OP_TEAM' --OWNER_TEAM_CODE 14 | ,'somebody@corp.com' --,EMAIL_TO_LIST 15 | ,'somebody@corp.com' --,SMS_MAIL_TO 16 | ,'YOUR_SMS_MAIL_TITLE' --,SMS_MAIL_TITLE 17 | ,'somebody@corp.com' --,PHONE_MAIL_TO 18 | ,'YOUR_PHONE_MAIL_TITLE' --,PHONE_MAIL_TITLE 19 | ) 20 | ; 21 | 22 | 23 | insert into EDWIN_TEAM_CFG 24 | ( 25 | OWNER_TEAM_CODE 26 | ,EMAIL_TO_LIST 27 | ,SMS_MAIL_TO 28 | ,SMS_MAIL_TITLE 29 | ,PHONE_MAIL_TO 30 | ,PHONE_MAIL_TITLE 31 | ) 32 | VALUES 33 | ( 34 | 'EDWIN_DEV_TEAM' --OWNER_TEAM_CODE 35 | ,'somebody@corp.com' --,EMAIL_TO_LIST 36 | ,'somebody@corp.com' --,SMS_MAIL_TO 37 | ,'YOUR_SMS_MAIL_TITLE' --,SMS_MAIL_TITLE 38 | ,'somebody@corp.com' --,PHONE_MAIL_TO 39 | ,'PHONE_MAIL_TITLE' --,PHONE_MAIL_TITLE 40 | ) 41 | ; 42 | 43 | 44 | -------------------------------------------------------------------------------- /db_setup/data/2_EDWIN_CHECK_ITM_CFG_DATA_BUILTIN.SQL: -------------------------------------------------------------------------------- 1 | INSERT INTO EDWIN_CHECK_ITM_CFG 2 | ( 3 | ITM_CODE 4 | ,ITM_TITLE 5 | ,ITM_CATEGORY 6 | ,ENABLED_FLAG 7 | ,HOST 8 | ,CHECK_SCRIPT 9 | ,CHECK_INTERVAL_MINUTE 10 | ,CHECK_VALUE_IS_NUMBER 11 | ,DESCRIPTION 12 | ,OWNER_TEAM_LIST 13 | ,WARNING_LIMIT 14 | ,CRITICAL_LIMIT 15 | ,SHADOW_DATA 16 | ,WARNING_MAIL_CC 17 | ,CRITICAL_MAIL_CC 18 | ,CRITICAL_SMS_FLAG 19 | ,CRITICAL_CALL_FLAG 20 | ,ALLOW_REPEATED_SMS_ALARM 21 | ,ALLOW_REPEATED_CALL_ALARM 22 | ,ALLOW_REPEATED_MAIL_ALARM 23 | ) 24 | VALUES 25 | ( 26 | 'BUILTIN_EXCEPTION_NOTIFY' ---,ITM_CODE 27 | ,'BUILTIN_EXCEPTION_NOTIFY' ---,ITM_TITLE 28 | ,'reserved category'---,ITM_CATEGORY 29 | ,'Y' ---,ENABLED_FLAG 30 | ,'' ---,HOST 31 | ,'' ---,CHECK_SCRIPT 32 | ,-1 ---,CHECK_INTERVAL_MINUTE, -1 for one that need not check outdated 33 | ,'N' ---,CHECK_VALUE_IS_NUMBER 34 | ,'' ---,DESCRIPTION 35 | ,'EDWIN_OP_TEAM'---,OWNER_TEAM_LIST --team_code list, delimited by , or ; symbol 36 | ,null ---,WARNING_LIMIT 37 | ,null ---,CRITICAL_LIMIT 38 | ,null ---,SHADOW_DATA 39 | ,null ---,WARNING_MAIL_CC 40 | ,'somebody@corp.com' ---,CRITICAL_MAIL_CC 41 | ,'N' ---,CRITICAL_SMS_FLAG 42 | ,'N' ---,CRITICAL_CALL_FLAG 43 | ,'N' ---,ALLOW_REPEATED_SMS_ALARM 44 | ,'N' ---,ALLOW_REPEATED_CALL_ALARM 45 | ,'Y' ---,ALLOW_REPEATED_MAIL_ALARM 46 | ) 47 | ; 48 | 49 | 50 | 51 | INSERT INTO EDWIN_CHECK_ITM_CFG 52 | ( 53 | ITM_CODE 54 | ,ITM_TITLE 55 | ,ITM_CATEGORY 56 | ,ENABLED_FLAG 57 | ,HOST 58 | ,CHECK_SCRIPT 59 | ,CHECK_INTERVAL_MINUTE 60 | ,CHECK_VALUE_IS_NUMBER 61 | ,DESCRIPTION 62 | ,OWNER_TEAM_LIST 63 | ,WARNING_LIMIT 64 | ,CRITICAL_LIMIT 65 | ,SHADOW_DATA 66 | ,WARNING_MAIL_CC 67 | ,CRITICAL_MAIL_CC 68 | ,CRITICAL_SMS_FLAG 69 | ,CRITICAL_CALL_FLAG 70 | ,ALLOW_REPEATED_SMS_ALARM 71 | ,ALLOW_REPEATED_CALL_ALARM 72 | ,ALLOW_REPEATED_MAIL_ALARM 73 | ) 74 | VALUES 75 | ( 76 | 'UNIT_TEST_NONNUMERICAL' ---,ITM_CODE 77 | ,'UNIT_TEST_NONNUMERICAL' ---,ITM_TITLE 78 | ,'UnitTesting category' ---,ITM_CATEGORY 79 | ,'Y' ---,ENABLED_FLAG 80 | ,'some_machine' ---,HOST 81 | ,'some_full_path' ---,CHECK_SCRIPT 82 | ,2 ---,CHECK_INTERVAL_MINUTE, -1 for one that need not check outdated 83 | ,'N' ---,CHECK_VALUE_IS_NUMBER 84 | ,'' ---,DESCRIPTION 85 | ,'EDWIN_DEV_TEAM' ---,OWNER_TEAM_LIST --team_code list, delimited by , or ; symbol 86 | ,null ---,WARNING_LIMIT 87 | ,null ---,CRITICAL_LIMIT 88 | ,null ---,SHADOW_DATA 89 | ,null ---,WARNING_MAIL_CC 90 | ,'somebody@corp.com' ---,CRITICAL_MAIL_CC 91 | ,'N' ---,CRITICAL_SMS_FLAG 92 | ,'N' ---,CRITICAL_CALL_FLAG 93 | ,'N' ---,ALLOW_REPEATED_SMS_ALARM 94 | ,'N' ---,ALLOW_REPEATED_CALL_ALARM 95 | ,'N' ---,ALLOW_REPEATED_MAIL_ALARM 96 | ) 97 | ; 98 | 99 | 100 | INSERT INTO EDWIN_CHECK_ITM_CFG 101 | ( 102 | ITM_CODE 103 | ,ITM_TITLE 104 | ,ITM_CATEGORY 105 | ,ENABLED_FLAG 106 | ,HOST 107 | ,CHECK_SCRIPT 108 | ,CHECK_INTERVAL_MINUTE 109 | ,CHECK_VALUE_IS_NUMBER 110 | ,DESCRIPTION 111 | ,OWNER_TEAM_LIST 112 | ,WARNING_LIMIT 113 | ,CRITICAL_LIMIT 114 | ,SHADOW_DATA 115 | ,WARNING_MAIL_CC 116 | ,CRITICAL_MAIL_CC 117 | ,CRITICAL_SMS_FLAG 118 | ,CRITICAL_CALL_FLAG 119 | ,ALLOW_REPEATED_SMS_ALARM 120 | ,ALLOW_REPEATED_CALL_ALARM 121 | ,ALLOW_REPEATED_MAIL_ALARM 122 | ) 123 | VALUES 124 | ( 125 | 'UNIT_TEST_NUMERICAL' ---,ITM_CODE 126 | ,'UNIT_TEST_NUMERICAL' ---,ITM_TITLE 127 | ,'UnitTesting category' ---,ITM_CATEGORY 128 | ,'Y' ---,ENABLED_FLAG 129 | ,'some_machine' ---,HOST 130 | ,'some_full_path' ---,CHECK_SCRIPT 131 | ,2 ---,CHECK_INTERVAL_MINUTE, -1 for one that need not check outdated 132 | ,'Y' ---,CHECK_VALUE_IS_NUMBER 133 | ,'' ---,DESCRIPTION 134 | ,'EDWIN_DEV_TEAM' ---,OWNER_TEAM_LIST --team_code list, delimited by , or ; symbol 135 | ,70 ---,WARNING_LIMIT 136 | ,90 ---,CRITICAL_LIMIT 137 | ,null ---,SHADOW_DATA 138 | ,null ---,WARNING_MAIL_CC 139 | ,'somebody@corp.com' ---,CRITICAL_MAIL_CC 140 | ,'N' ---,CRITICAL_SMS_FLAG 141 | ,'N' ---,CRITICAL_CALL_FLAG 142 | ,'N' ---,ALLOW_REPEATED_SMS_ALARM 143 | ,'N' ---,ALLOW_REPEATED_CALL_ALARM 144 | ,'N' ---,ALLOW_REPEATED_MAIL_ALARM 145 | ) 146 | ; 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /db_setup/data/3_EDWIN_PAGE_DATA.sql: -------------------------------------------------------------------------------- 1 | insert into EDWIN_PAGE (PAGE_CODE, PAGE_TITLE, DISPLAY_FLAG, DESCRIPTION) 2 | values ('BUILTIN_PAGE', 'BUILTIN', 'N', 'to show system notification'); 3 | 4 | insert into EDWIN_PAGE (PAGE_CODE, PAGE_TITLE, DISPLAY_FLAG, DESCRIPTION) 5 | values ('UNIT_TEST_PAGE', 'UnitTest', 'Y', 'Just for unit tesing'); 6 | 7 | -------------------------------------------------------------------------------- /db_setup/data/4_EDWIN_PAGELET_DATA.sql: -------------------------------------------------------------------------------- 1 | insert into EDWIN_PAGELET (PAGELET_CODE, PAGELET_TITLE, PAGE_CODE, DISPLAY_ORDER, DISPLAY_FLAG, DESCRIPTION) 2 | values ('BUILTIN_PAGELET', 'BUILTIN', 'BUILTIN_PAGE', 100, 'Y', 'to show system notifications'); 3 | 4 | insert into EDWIN_PAGELET (PAGELET_CODE, PAGELET_TITLE, PAGE_CODE, DISPLAY_ORDER, DISPLAY_FLAG, DESCRIPTION) 5 | values ('UNIT_TEST_1_PAGELET', 'UNIT_TEST_1', 'UNIT_TEST_PAGE', 100, 'Y', 'just for unit testing'); 6 | 7 | -------------------------------------------------------------------------------- /db_setup/data/5_EDWIN_PAGELET_CHECK_LIST_DATA.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO EDWIN_PAGELET_CHECK_LIST (PAGELET_CODE, CHECK_ITM_CODE, DISPLAY_ORDER, DISPLAY_FLAG) 2 | VALUES ('BUILTIN_PAGELET', 'BUILTIN_EXCEPTION_NOTIFY', 100, 'Y'); 3 | 4 | INSERT INTO EDWIN_PAGELET_CHECK_LIST (PAGELET_CODE, CHECK_ITM_CODE, DISPLAY_ORDER, DISPLAY_FLAG) 5 | VALUES ('UNIT_TEST_1_PAGELET', 'UNIT_TEST_NONNUMERICAL', 100, 'Y'); 6 | 7 | INSERT INTO EDWIN_PAGELET_CHECK_LIST (PAGELET_CODE, CHECK_ITM_CODE, DISPLAY_ORDER, DISPLAY_FLAG) 8 | VALUES ('UNIT_TEST_1_PAGELET', 'UNIT_TEST_NUMERICAL', 200, 'Y'); 9 | 10 | -------------------------------------------------------------------------------- /docs/change_log.md: -------------------------------------------------------------------------------- 1 | ## 主要改进点: 2 | 1. 增加了一个新的api, 用来获取check item的配置信息 3 | 在agent编写和运行过程中, 很可能需要了解check item在DB中的配置信息, 比如是否已经在server端被禁掉, 或者根据server设定的检查指令, 适时作出检查逻辑的调整. 4 | 5 | 6 | 2. 服务端升级了第三方库 pypyodbc到1.3版本. 7 | 原先使用了pypyodbc 1.2版, 实际运行后, 发现了比较严重的memory leak. 升级后, 经长期观察, edwin内存消耗非常平稳. 8 | 9 | 3. 在登记检查结果的api中, 增加了新的参数 check_notification_msg 10 | 原来只有一个检查结果的文字描述, 即check_detail_msg, 如果检查结果异常, 这个文字描述将出现在web展现和报警邮件中. 在实际使用中, 渐渐发现使用一个文字描述不太恰当. 比如报警邮件中, 我们总是希望在邮件中即能看到最详尽的信息(比如报警的极限值等等), 这样收件人看邮件就知道发生了什么. 而web展现中, 因为报警极限值等信息已有展现, 文字检查结果应该是直接了当. 11 | 所以增加了一个新的文本检查结果属性, 即check_notification_msg, 该属性将出现在邮件报警中, 而原有的check_detail_msg, 将专用于web展现中. 12 | 13 | 14 | 4. 检查结果的文字描述属性启用的html写法 15 | check_detail_msg和check_notification_msg 原来没有启用html写法, 展现时, 文字信息经常是长长的一串, 简单的排版都做不到, 比如换行. 现在启用了html写法, web展现和邮件正文就更美观, 当然你需要自己处理html特殊字符的转义, 比如显示小于号, 需要做html转义. -------------------------------------------------------------------------------- /docs/check_item_cfg.md: -------------------------------------------------------------------------------- 1 | # check item的配置 2 | edwin还没有的admin页面来提供check item的配置, 如果你有兴趣的话, 欢迎为edwin提供一个admin模块. 在没有admin模块之前, 我们不得不直接在DB中配置相应的表, 不用担心, 其实非常简单. 3 | 4 | ## 1. team的设置 5 | 表EDWIN_TEAM_CFG中, 可以配置team的基本信息. 字段清单见下: 6 | 7 | | 字段 | 含义 | 备注 | 8 | | -------- | :----- | :---- | 9 | | OWNER_TEAM_CODE| Team的编码 | | 10 | | EMAIL_TO_LIST| Team的邮箱列表 |若是多个邮箱号,以英文逗号或分号分隔 | 11 | | SMS_MAIL_TO| Team的短信告警对应的邮箱号 | | 12 | | SMS_MAIL_TITLE| Team的短信告警对应的邮箱标题 | | 13 | | PHONE_MAIL_TO| Team的电话告警对应的邮箱号 | | 14 | | PHONE_MAIL_TITLE|Team的电话告警对应的邮箱标题 | | 15 | 16 | 17 | ## 2. check item的设置 18 | 在表EDWIN_CHECK_ITM_CFG中配置检查项目. 字段清单见下: 19 | 20 | | 字段 | 含义 | 备注 | 21 | | -------- | :----- | :---- | 22 | |ITM_CODE| check item的编码| 这是唯一键 | 23 | |ITM_TITLE| check item的标题 | 这个将显示在web页面 | 24 | |ITM_CATEGORY | check item的类别 | | 25 | |ENABLED_FLAG| 启用标志, Y为启用, N为禁用 | | 26 | |HOST| 检查agent部署在哪个机器上 | 仅仅是为了信息的完整性, edwin服务器并不会自动启动agent | 27 | |CHECK_SCRIPT| agent程序的详细路径 | 仅仅是为了信息的完整性, edwin服务器并不会自动启动agent | 28 | |CHECK_INTERVAL_MINUTE| agent程序的执行频率 | edwin服务器用这个值来判断你的agent是否超期未运行 | 29 | |CHECK_VALUE_IS_NUMBER| 检查结果是否可以量化, Y为可量化, N为不可量化 | | 30 | |DESCRIPTION | check item的说明 | | 31 | |OWNER_TEAM_LIST| 负责的team code |如果是多个team共同负责, 以英文逗号或分号分隔team code | 32 | |WARNING_LIMIT| 警告级极限值 | 对于检查结果可量化的check item, 必须设定该值 | 33 | |CRITICAL_LIMIT| 紧急级极限值 | 对于检查结果可量化的check item, 必须设定该值 | 34 | |SHADOW_DATA| 影子 Data, 该值将和你的检查值保存到历史表中 |很高端的属性哦, 后面细讲| 35 | |WARNING_MAIL_CC| 警告级异常会抄送給谁 | | 36 | |CRITICAL_MAIL_CC| 紧急级异常会抄送給谁 | | 37 | |CRITICAL_SMS_FLAG| 对于紧急级异常, 是否允许发送短信告警 | | 38 | |CRITICAL_CALL_FLAG| 对于紧急级异常, 是否允许电话告警 | | 39 | |ALLOW_REPEATED_SMS_ALARM| 是否允许重复短信报警 | | 40 | |ALLOW_REPEATED_CALL_ALARM| 是否允许重复电话报警 | | 41 | |ALLOW_REPEATED_MAIL_ALARM | 是否允许重复邮件报警 | | 42 | 43 | **SHADOW_DATA** 的高级用法 44 | 45 | 举例说明吧, 比如我们想要监控windows机器C盘和D盘的使用情况, 假定紧急级的条件分别是, C盘大于70%使用量, D盘80%的使用量. 我们有两种做法: 46 | 47 | 1. 分别C盘和D盘配置不同的check item, 当然对应这需要两个agent程序. 很显然, 两个agent程序代码几乎一模一样, 写两个几乎一模一样的程序, 这并不是好的做法. 另外, 机器一多的话, 按照这样的做法, 我们的check item会暴增, 管理起来也很不便. 48 | 49 | 2. 另一个推荐的做法是, 配置一个check item来支持两个盘的检查, 但由于两个盘的极限值不同, 如何编写这个agent呢? 很简单, 我们为SHADOW_DATA加入一些指令, 比如 "C_disk_critical=0.70 || C_disk_critical=0.80", 在agent程序中,我们通过api获取到这个指令, 并做解析, 得到C盘和D盘各自设定的极限值, 按照极限值检查实际的磁盘使用量即可. 50 | 51 | ## 3. 展现页面的配置 52 | 之前介绍过, edwin对于检查项的组织形式, 由小到大是: check item -> pagelet -> page -> dashboard, 其中page是edwin的一个具体展现页面, 一个展现页面包含多个 pagelet(即豆腐块), 一个豆腐块可以包含多个check item. 另外, edwin提供一个dashboard固定展现页, 自动汇总其他展现页的监控结果. 53 | 54 | edwin可以支持多个team, 为了方便各个team监控, 很自然地可以为不同的team配置不同的page页. 55 | 56 | 在实际的实施中, 常会碰到这样一个现象, 同一个server可能好几个team共用, 对于这个server的监控, 这几个team可能都很关心.edwin 可以很轻松解决这种交叉关注问题. 因为, 在edwin中一个check item 可以出现在多个豆腐块(pagelet)下, 也就意味着, 一个check item可以出现在多个展现页中, 问题解决了, 就这么简单. 57 | 58 | ### 3.1 page页的配置 59 | 表EDWIN_PAGE中, 可以配置page属性, 字段清单见下: 60 | 61 | | 字段 | 含义 | 备注 | 62 | | -------- | :----- | :---- | 63 | |PAGE_CODE | page页的编码| 主键 | 64 | |PAGE_TITLE| page页在web上的显示名称| | 65 | |DISPLAY_FLAG|是否要在web上显示 | Y为显示, N为不显示 | 66 | |DESCRIPTION|| 该page页的描述 | 67 | 68 | 69 | ### 3.2 豆腐块(pagelet)的配置 70 | 表EDWIN_PAGELET中, 可以配置page属性, 字段清单见下: 71 | 72 | | 字段 | 含义 | 备注 | 73 | | -------- | :----- | :---- | 74 | |PAGELET_CODE|pagelet的编码| 主键 | 75 | |PAGELET_TITLE|pagelet在web上的显示名称| | 76 | |PAGE_CODE|所属的page编码| | 77 | |DISPLAY_ORDER|在page上的显示次序| 序号越小越靠前显示 | 78 | |DISPLAY_FLAG|是否要在page上显示| Y为显示, N为不显示 | 79 | |DESCRIPTION|该pagelet的描述| | 80 | 81 | 82 | ### 3.3 豆腐块(pagelet)下要显示哪些check item 83 | 在表EDWIN_PAGELET_CHECK_LIST, 可配置某个豆腐块要显示哪些check item中, 段清单见下: 84 | 85 | | 字段 | 含义 | 备注 | 86 | | -------- | :----- | :---- | 87 | |PAGELET_CODE| pagelet的编码| | 88 | |CHECK_ITM_CODE|包含了哪个check item| | 89 | |DISPLAY_ORDER| check item在pagelet中的显示次序| 序号越小越靠前显示 | 90 | |DISPLAY_FLAG|是否要在pagelet上显示| Y为显示, N为不显示 | 91 | 92 | 93 | 94 | ## 3. 检查结果历史表 95 | 表EDWIN_CHECK_ITM_LOG中, 保存着详尽的检查结果, 字段清单见下: 96 | 97 | | 字段 | 含义 | 备注 | 98 | | -------- | :----- | :---- | 99 | |ITM_CODE| check item的编码| | 100 | |CHECK_DATE|检查日期| | 101 | |CHECK_TIMESTAMP| 检查的详细时间| | 102 | |CHECK_STATUS|检查的结果, 正常/警告级异常/紧急级异常| | 103 | |CHECK_VALUE|检查的具体数值| 仅适用于检查结果可量化的check item | 104 | |CHECK_DETAIL_MSG|检查结果的详尽描述, 用于web展现|支持html写法, 如果你要显示小于号, 需要做html转义| 105 | |CHECK_NOTIFICATION_MSG|检查结果的详尽描述, 用于异常时的邮件通知|支持html写法, 如果你要显示小于号, 需要做html转义 | 106 | |WARNING_LIMIT|警告级极限值| 仅适用于检查结果可量化的check item | 107 | |CRITICAL_LIMIT|紧急级极限值| 仅适用于检查结果可量化的check item | 108 | |SHADOW_DATA|影子 Data, 该值来源于check item配置表的SHADOW_DATA字段 | | 109 | |IS_WARNING_EVENT|是否是警告级异常, 取值Y或N| | 110 | |IS_CRITICAL_EVENT|是否是紧急级异常, 取值Y或N| | 111 | |IS_NEW_WARNING_EVENT|是否是新的警告级异常, 取值Y或N| | 112 | |IS_NEW_CRITICAL_EVENT|是否是新的紧急级异常, 取值Y或N| | 113 | |ALARM_SEND_STATUS|报警发送状态| 有异常才会报警 | 114 | |ALARM_SEND_BEGIN_TIME|报警发送的开始时间| | 115 | |ALARM_SEND_END_TIME|报警发送的结束时间| | 116 | 117 | -------------------------------------------------------------------------------- /docs/install_and_deploy.md: -------------------------------------------------------------------------------- 1 | ##Server side setup 2 | 3 | ###Database setup 4 | 1. create tables, the SQL file is db_setup/DDL/oracle.sql. 5 | It is easy to adopt for other RDBMS. 6 | 2. initialize data in database, run all SQL files in db_setup/data 7 | 8 | ###edwinServer software setup 9 | 1. install Python 2.7 and Python packages listed in requirements_Server.txt 10 | 2. copy edwinServer folder to your server. 11 | 3. configurations: 12 | edwinServer/common/conf.py: change db and email configurations 13 | edwinServer/web/conf.py: change web server configurations 14 | 15 | 16 | ##Configure your own check items 17 | After database setup, you can find some sample data in the following database tables. You can configure your own refer to the sample data. 18 | **EDWIN_TEAM_CFG**: team mailbox and SMS/call settings 19 | **EDWIN_CHECK_ITM_CFG**: check item configuration 20 | **EDWIN_PAGE**: Edwin web can contains one dashboard and many pages, one EDWIN_PAGE record will have one web page 21 | **EDWIN_PAGELET**: relationship of page and pagelet is 1:n 22 | **EDWIN_PAGELET_CHECK_LIST**: relationship of pagelet and check item is 1:n 23 | 24 | 25 | ##Launch server side 26 | 1. launch alarm service: execute edwinServer/bin/alarm_send.bat or alarm_send.sh 27 | 2. launch web server: execute edwinServer/bin/runserver.bat or runserver.sh 28 | 29 | 30 | 31 | ##Agent side setup 32 | 1. install Jython 2.7 33 | 2. copy edwinAgent folder to your computer. 34 | 3. configurations: 35 | edwinAgent/common/conf.py: change web server url 36 | 37 | 38 | ##Launch your agent 39 | 1. execute edwinAgent/bin/your_agent.sh or your_agent.bat 40 | 41 | -------------------------------------------------------------------------------- /docs/screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/docs/screenshots/dashboard.png -------------------------------------------------------------------------------- /docs/screenshots/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/docs/screenshots/page.png -------------------------------------------------------------------------------- /docs/write_your_own_agent.md: -------------------------------------------------------------------------------- 1 | ## How to write your own agent 2 | todo write -------------------------------------------------------------------------------- /edwinAgent/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | __version__='1.0.0' 5 | __author__='Harry Liu' 6 | -------------------------------------------------------------------------------- /edwinAgent/agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinAgent/agents/__init__.py -------------------------------------------------------------------------------- /edwinAgent/agents/check_item_sample_nonnumerical.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Non-numerical checking item sample 4 | ''' 5 | from __future__ import absolute_import 6 | import logging 7 | import logging.config 8 | from edwinAgent.common import const 9 | from edwinAgent.common import conf 10 | from edwinAgent.common import api_helper 11 | from edwinAgent.common import os_helper 12 | from edwinAgent.common import my_logging 13 | 14 | 15 | # logging.basicConfig(level=logging.DEBUG) # use for development 16 | log_file = os_helper.getLoggingFileName(__file__) 17 | root_logger = logging.getLogger() 18 | my_logging.configureLogger(root_logger, log_file, conf.log_level) 19 | logger = logging.getLogger(__name__) # get current file logger 20 | 21 | 22 | def check(): 23 | ''' 24 | get check result, format is (status, detail_msg, notification_msg) 25 | ''' 26 | # TODO: get status and detail_msg by yourself. 27 | status = const.CHECK_STATUS_WARN 28 | detail_msg = "some message here" 29 | notification_msg = "some notification message in your email" 30 | return (status, detail_msg, notification_msg) 31 | 32 | 33 | def main(itm_code): 34 | try: 35 | try: 36 | (status, detail_msg, notification_msg) = check() 37 | except Exception, e: 38 | logger.exception(e) 39 | exception_msg = "%s" % e 40 | api_helper.registerException(itm_code, exception_msg) 41 | else: 42 | logger.info("Check status: %s" % status) 43 | logger.info("Check detail message: %s" % detail_msg) 44 | (successful, echo_msg) = api_helper.updateNonnumericalResult(itm_code, status, detail_msg, notification_msg) 45 | if successful: 46 | logger.info("update status successful.") 47 | else: 48 | logger.info("fail to update status. echo message: %s" % (echo_msg,)) 49 | 50 | except Exception, e: 51 | logger.exception(e) 52 | 53 | 54 | if __name__ == "__main__": 55 | itm_code = 'UNIT_TEST_NONNUMERICAL' 56 | logger.info("=================================") 57 | logger.info("===check item: %s" % itm_code) 58 | logger.info("=================================") 59 | 60 | main(itm_code) 61 | 62 | logger.info("===End to check: %s" % itm_code) 63 | -------------------------------------------------------------------------------- /edwinAgent/agents/check_item_sample_numerical.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | numerical checking item sample 4 | ''' 5 | from __future__ import absolute_import 6 | import logging 7 | import logging.config 8 | from edwinAgent.common import const 9 | from edwinAgent.common import conf 10 | from edwinAgent.common import api_helper 11 | from edwinAgent.common import os_helper 12 | from edwinAgent.common import my_logging 13 | 14 | 15 | # logging.basicConfig(level=logging.DEBUG) # use for development 16 | log_file = os_helper.getLoggingFileName(__file__) 17 | root_logger = logging.getLogger() 18 | my_logging.configureLogger(root_logger, log_file, conf.log_level) 19 | logger = logging.getLogger(__name__) # get current file logger 20 | 21 | 22 | def check(): 23 | ''' 24 | get check result, format is (check_value, detail_msg, notification_msg) 25 | ''' 26 | # TODO: get check_value and detail_msg by yourself. 27 | check_value = 10 28 | detail_msg = "some message here" 29 | notification_msg = "some notification message in your email" 30 | return (check_value, detail_msg, notification_msg) 31 | 32 | 33 | def main(itm_code): 34 | try: 35 | try: 36 | (check_value, detail_msg, notification_msg) = check() 37 | except Exception, e: 38 | logger.exception(e) 39 | exception_msg = "%s" % e 40 | api_helper.registerException(itm_code, exception_msg) 41 | else: 42 | logger.info("Check value: %d" % check_value) 43 | logger.info("Check detail message: %s" % detail_msg) 44 | (successful, echo_msg) = api_helper.updateNumericalResult(itm_code, check_value, detail_msg, notification_msg) 45 | if successful: 46 | logger.info("update status successful.") 47 | else: 48 | logger.info("fail to update status. echo message: %s" % (echo_msg,)) 49 | 50 | except Exception, e: 51 | logger.exception(e) 52 | 53 | 54 | if __name__ == "__main__": 55 | itm_code = 'UNIT_TEST_NUMERICAL' 56 | logger.info("=================================") 57 | logger.info("===check item: %s" % itm_code) 58 | logger.info("=================================") 59 | 60 | main(itm_code) 61 | 62 | logger.info("===End to check: %s" % itm_code) 63 | -------------------------------------------------------------------------------- /edwinAgent/agents/detect_threadstuck_in_log_file.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | to detect threadstuck fatel error in weblogic log file 4 | ''' 5 | from __future__ import absolute_import 6 | import logging 7 | import logging.config 8 | from edwinAgent.common import const 9 | from edwinAgent.common import conf 10 | from edwinAgent.common import api_helper 11 | from edwinAgent.common import os_helper 12 | from edwinAgent.common import my_logging 13 | from edwinAgent.site_packages.logwatch_glob import LogWatcher 14 | 15 | 16 | # Must need Jython 2.7 17 | # TODO: set in_dev_mode=True when application debug 18 | in_dev_mode = False 19 | 20 | app_server = "your_server" 21 | file_pattern = "your_log_file.log" 22 | log_folder = "/some_folder/some_file" 23 | if in_dev_mode: 24 | log_folder = "c://" 25 | 26 | 27 | # logging.basicConfig(level=logging.DEBUG) # use for development 28 | log_file = os_helper.getLoggingFileName(__file__) 29 | root_logger = logging.getLogger() 30 | my_logging.configureLogger(root_logger, log_file, conf.log_level) 31 | logger = logging.getLogger(__name__) # get current file logger 32 | 33 | 34 | def logCaptureCallback(filename, lines): 35 | linesText = "".join(lines) 36 | logger.debug("Your log captured lines:%s" % linesText) 37 | 38 | index = linesText.find("[STUCK]") 39 | # Thread stuck issue happened 40 | if index >= 0: 41 | snippetIndex = index - 500 42 | snippet = linesText[max(0, snippetIndex):] 43 | status = const.CHECK_STATUS_CRITICAL 44 | detail_msg = """It seems your application hang on server %s. 45 | We found [STUCK] word in log file %s. 46 | -------- 47 | Your log snippet:%s""" % (app_server, filename, snippet) 48 | logger.info(detail_msg) 49 | (successful, echo_msg) = api_helper.updateNonnumericalResult(itm_code, status, detail_msg) 50 | if successful: 51 | logger.info("update status successful.") 52 | else: 53 | logger.info("fail to update status. echo message: %s" % (echo_msg,)) 54 | 55 | 56 | def check(): 57 | ''' 58 | get check result, the result format is (status, detail_msg) 59 | ''' 60 | includeSubFolder = False 61 | excludeFileListFile = None 62 | watcher = LogWatcher(log_folder, logCaptureCallback, file_pattern, includeSubFolder, excludeFileListFile, tail_lines=100) 63 | watcher.loop() 64 | 65 | 66 | def main(itm_code): 67 | try: 68 | try: 69 | global state_updater 70 | (status, detail_msg) = check() 71 | except Exception, e: 72 | logger.exception(e) 73 | exception_msg = "%s" % e 74 | api_helper.registerException(itm_code, exception_msg) 75 | else: 76 | # Log capture is aborted, mark as warning 77 | status = const.CHECK_STATUS_WARN 78 | detail_msg = """On server %s, it is aborted to capture the log file (%s,%s). 79 | Then it will cause false alarm when next check.""" % (app_server, log_folder, file_pattern) 80 | logger.info("Check status: %s" % status) 81 | logger.info("Check detail message: %s" % detail_msg) 82 | api_helper.registerException(itm_code, detail_msg) 83 | 84 | except Exception, e: 85 | logger.exception(e) 86 | 87 | 88 | if __name__ == "__main__": 89 | itm_code = "DETECT_THREADSTUCK_IN_LOG_FILE" # configure this check item in your database before run 90 | logger.info("=================================") 91 | logger.info("===check item: %s" % itm_code) 92 | logger.info("=================================") 93 | 94 | main(itm_code) 95 | 96 | logger.info("===End to check: %s" % itm_code) 97 | -------------------------------------------------------------------------------- /edwinAgent/agents/web_page_logon_sample.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | test the landing page response 4 | ''' 5 | from __future__ import absolute_import 6 | import logging 7 | import logging.config 8 | from edwinAgent.common import const 9 | from edwinAgent.common import conf 10 | from edwinAgent.common import api_helper 11 | from edwinAgent.common import os_helper 12 | from edwinAgent.common import my_logging 13 | import datetime 14 | 15 | 16 | 17 | #logging.basicConfig(level=logging.DEBUG) # use for development 18 | log_file=os_helper.getLoggingFileName(__file__) 19 | root_logger=logging.getLogger() 20 | my_logging.configureLogger(root_logger, log_file, conf.log_level) 21 | logger=logging.getLogger(__name__) # get current file logger 22 | 23 | 24 | 25 | 26 | 27 | def check(): 28 | ''' 29 | get check result, the result format is (check_value, detail_msg) 30 | ''' 31 | import urllib, urllib2, cookielib 32 | cj = cookielib.CookieJar() 33 | tim1=datetime.datetime.now() 34 | opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) 35 | # prepare form elements 36 | login_data = urllib.urlencode({'employeeNo' : 'your_name', 'password' : 'your_pwd'}) 37 | login_url='http://10.1.2.3/your_site/userlogin' 38 | #post the login form 39 | f=opener.open(login_url, login_data) 40 | responseStr = f.read() 41 | if responseStr.find('Your username or password is wrong!')>=0: 42 | f.close() 43 | opener.close() 44 | raise Exception('your_site system, Your username or password is wrong!') 45 | else: 46 | #to visit the landing page 47 | #tim1=datetime.datetime.now() 48 | operation_url='http://10.1.2.3/your_site/reports/report.do?method=getTableData' 49 | 50 | f=opener.open(operation_url) 51 | responseStr = f.read() 52 | tim2=datetime.datetime.now() 53 | f.close() 54 | opener.close() 55 | check_value=(tim2-tim1).total_seconds() 56 | detail_msg="Get response time." 57 | return (check_value,detail_msg) 58 | 59 | 60 | 61 | 62 | def main(itm_code): 63 | try: 64 | try: 65 | (check_value,detail_msg)=check() 66 | except Exception , e: 67 | logger.exception(e) 68 | exception_msg="%s"%e 69 | api_helper.registerException(itm_code,exception_msg) 70 | else: 71 | logger.info("Check value: %d"%check_value) 72 | logger.info("Check detail message: %s"%detail_msg) 73 | (successful, echo_msg)=api_helper.updateNumericalResult(itm_code, check_value, detail_msg) 74 | if successful: 75 | logger.info("update status successful.") 76 | else: 77 | logger.info("fail to update status. echo message: %s"%(echo_msg,)) 78 | 79 | except Exception, e: 80 | logger.exception(e) 81 | 82 | 83 | if __name__=="__main__": 84 | itm_code='WEB_PAGE_LOGON_SAMPLE' 85 | logger.info("=================================") 86 | logger.info("===check item: %s"%itm_code) 87 | logger.info("=================================") 88 | 89 | main(itm_code) 90 | 91 | logger.info("===End to check: %s"%itm_code) 92 | 93 | -------------------------------------------------------------------------------- /edwinAgent/bin/check_item_sample_nonnumerical.bat: -------------------------------------------------------------------------------- 1 | setlocal 2 | 3 | rem # set jython executable 4 | set JYTHON_BIN=D:\pythonenv\jython271\jython.bat 5 | 6 | rem # base path, it is the parent of root package path 7 | set BASE_PATH=D:\corp_files\trunk\workspace\Edwin 8 | 9 | rem # set your python script file. Please be noticed to trim the tailed space of file name 10 | set MY_PY_SCRIPT=check_item_sample_nonnumerical.py 11 | 12 | 13 | rem #================================= 14 | rem # do not change the following code 15 | rem #================================= 16 | set JYTHONPATH=%BASE_PATH% 17 | 18 | set SCRIPT_PATH=%BASE_PATH%\edwinAgent\agents 19 | set BIN_PATH=%BASE_PATH%\edwinAgent\bin 20 | SET LOCK_FILE=%BIN_PATH%\locks\%MY_PY_SCRIPT%.lock 21 | SET MY_PY_FILE=%SCRIPT_PATH%\%MY_PY_SCRIPT% 22 | 23 | dir %LOCK_FILE% 24 | if not %errorlevel% == 0 ( 25 | echo '' > %LOCK_FILE% 26 | %JYTHON_BIN% %MY_PY_FILE% 27 | del %LOCK_FILE% 28 | ) 29 | 30 | endlocal 31 | 32 | -------------------------------------------------------------------------------- /edwinAgent/bin/check_item_sample_nonnumerical.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #load profile 4 | source ~/.bash_profile 5 | 6 | # set jython executable 7 | JYTHON_BIN=/home/user1/pythonenv/jython271/jython.sh 8 | 9 | # base path, it is the parent of root package path 10 | BASE_PATH=/home/user1/trunk/workspace/Edwin 11 | 12 | # set your python script file. Please be noticed to trim the tailed space of file name 13 | MY_PY_SCRIPT=check_item_sample_nonnumerical.py 14 | 15 | 16 | #================================= 17 | # do not change the following code 18 | #================================= 19 | JYTHONPATH=$BASE_PATH 20 | 21 | SCRIPT_PATH=$BASE_PATH/edwinAgent/agents 22 | BIN_PATH=$BASE_PATH/edwinAgent/bin 23 | LOCK_FILE=$BIN_PATH/locks/$MY_PY_SCRIPT.lock 24 | MY_PY_FILE=$SCRIPT_PATH/$MY_PY_SCRIPT 25 | 26 | cnt=`ls $LOCK_FILE|wc -l` 27 | if [ ${cnt} -lt 1 ] 28 | then 29 | touch $LOCK_FILE 30 | $JYTHON_BIN $MY_PY_FILE 31 | rm $LOCK_FILE 32 | fi 33 | 34 | -------------------------------------------------------------------------------- /edwinAgent/bin/check_item_sample_numerical.bat: -------------------------------------------------------------------------------- 1 | setlocal 2 | 3 | rem # set jython executable 4 | set JYTHON_BIN=D:\pythonenv\jython271\jython.bat 5 | 6 | rem # base path, it is the parent of root package path 7 | set BASE_PATH=D:\corp_files\trunk\workspace\Edwin 8 | 9 | rem # set your python script file. Please be noticed to trim the tailed space of file name 10 | set MY_PY_SCRIPT=check_item_sample_numerical.py 11 | 12 | 13 | rem #================================= 14 | rem # do not change the following code 15 | rem #================================= 16 | set JYTHONPATH=%BASE_PATH% 17 | 18 | set SCRIPT_PATH=%BASE_PATH%\edwinAgent\agents 19 | set BIN_PATH=%BASE_PATH%\edwinAgent\bin 20 | SET LOCK_FILE=%BIN_PATH%\locks\%MY_PY_SCRIPT%.lock 21 | SET MY_PY_FILE=%SCRIPT_PATH%\%MY_PY_SCRIPT% 22 | 23 | dir %LOCK_FILE% 24 | if not %errorlevel% == 0 ( 25 | echo '' > %LOCK_FILE% 26 | %JYTHON_BIN% %MY_PY_FILE% 27 | del %LOCK_FILE% 28 | ) 29 | 30 | endlocal 31 | 32 | -------------------------------------------------------------------------------- /edwinAgent/bin/check_item_sample_numerical.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #load profile 4 | source ~/.bash_profile 5 | 6 | # set jython executable 7 | JYTHON_BIN=/home/user1/pythonenv/jython271/jython.sh 8 | 9 | # base path, it is the parent of root package path 10 | BASE_PATH=/home/user1/trunk/workspace/Edwin 11 | 12 | # set your python script file. Please be noticed to trim the tailed space of file name 13 | MY_PY_SCRIPT=check_item_sample_numerical.py 14 | 15 | 16 | #================================= 17 | # do not change the following code 18 | #================================= 19 | JYTHONPATH=$BASE_PATH 20 | 21 | SCRIPT_PATH=$BASE_PATH/edwinAgent/agents 22 | BIN_PATH=$BASE_PATH/edwinAgent/bin 23 | LOCK_FILE=$BIN_PATH/locks/$MY_PY_SCRIPT.lock 24 | MY_PY_FILE=$SCRIPT_PATH/$MY_PY_SCRIPT 25 | 26 | cnt=`ls $LOCK_FILE|wc -l` 27 | if [ ${cnt} -lt 1 ] 28 | then 29 | touch $LOCK_FILE 30 | $JYTHON_BIN $MY_PY_FILE 31 | rm $LOCK_FILE 32 | fi 33 | 34 | -------------------------------------------------------------------------------- /edwinAgent/bin/detect_threadstuck_in_log_file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #load profile 4 | source ~/.bash_profile 5 | 6 | # set jython executable 7 | JYTHON_BIN=/home/user1/pythonenv/jython271/jython.sh 8 | 9 | # base path, it is the parent of root package path 10 | BASE_PATH=/home/user1/trunk/workspace/Edwin 11 | 12 | # set your python script file. Please be noticed to trim the tailed space of file name 13 | MY_PY_SCRIPT=detect_threadstuck_in_log_file.py 14 | 15 | 16 | #================================= 17 | # do not change the following code 18 | #================================= 19 | JYTHONPATH=$BASE_PATH 20 | 21 | SCRIPT_PATH=$BASE_PATH/edwinAgent/agents 22 | BIN_PATH=$BASE_PATH/edwinAgent/bin 23 | LOCK_FILE=$BIN_PATH/locks/$MY_PY_SCRIPT.lock 24 | MY_PY_FILE=$SCRIPT_PATH/$MY_PY_SCRIPT 25 | 26 | cnt=`ls $LOCK_FILE|wc -l` 27 | if [ ${cnt} -lt 1 ] 28 | then 29 | touch $LOCK_FILE 30 | $JYTHON_BIN $MY_PY_FILE 31 | rm $LOCK_FILE 32 | fi 33 | 34 | -------------------------------------------------------------------------------- /edwinAgent/bin/locks/keep_for_git.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinAgent/bin/locks/keep_for_git.txt -------------------------------------------------------------------------------- /edwinAgent/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinAgent/common/__init__.py -------------------------------------------------------------------------------- /edwinAgent/common/api_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | 6 | 7 | from __future__ import absolute_import 8 | import json 9 | import urllib2 10 | from . import conf 11 | import logging 12 | 13 | 14 | class check_item_cfg(object): 15 | 16 | def __init__(self): 17 | self.itm_code = "" 18 | self.itm_title = "" 19 | self.itm_category = "" 20 | self.enabled_flag = "" 21 | self.host = "" 22 | self.check_script = "" 23 | self.check_interval_minute = "" 24 | self.check_value_is_number = "" 25 | self.description = "" 26 | self.warning_limit = "" 27 | self.critical_limit = "" 28 | self.shadow_data = "" 29 | self.owner_team_list = "" 30 | self.warning_mail_cc = "" 31 | self.critical_mail_cc = "" 32 | self.critical_sms_flag = "" 33 | self.critical_call_flag = "" 34 | self.allow_repeated_sms_alarm = "" 35 | self.allow_repeated_call_alarm = "" 36 | self.allow_repeated_mail_alarm = "" 37 | 38 | 39 | def _updateResult(check_itm, check_value, status, detail_msg, notification_msg=''): 40 | logger = logging.getLogger(__name__) 41 | logger.info("Begin to update checking result via web API.") 42 | 43 | data = {'status': status, 44 | 'value': check_value, 45 | 'detail_msg': detail_msg, 46 | 'notification_msg': notification_msg 47 | } 48 | data_json = json.dumps(data) 49 | url = "%s/api/v1.0/results/%s" % (conf.web_url, check_itm) 50 | req = urllib2.Request(url, data_json, {'Content-Type': 'application/json'}) 51 | 52 | f = urllib2.urlopen(req) 53 | httpCodes = f.getcode() 54 | responseStr = f.read() 55 | f.close() 56 | json_data = json.loads(responseStr) 57 | echo_msg = json_data['echo_msg'] 58 | successful = httpCodes in [200, 201, 202] 59 | return (successful, echo_msg) 60 | 61 | 62 | def getCheckItemCfg(check_itm): 63 | logger = logging.getLogger(__name__) 64 | logger.info("Begin to get check item %s configuration info." % (check_itm)) 65 | 66 | url = "%s/api/v1.0/info/%s" % (conf.web_url, check_itm) 67 | req = urllib2.Request(url) 68 | 69 | f = urllib2.urlopen(req) 70 | httpCodes = f.getcode() 71 | responseStr = f.read() 72 | f.close() 73 | json_data = json.loads(responseStr) 74 | result = check_item_cfg() 75 | successful = httpCodes in [200, 201, 202] 76 | if successful: 77 | result.itm_code = json_data['itm_code'] 78 | result.itm_title = json_data['itm_title'] 79 | result.itm_category = json_data['itm_category'] 80 | result.enabled_flag = json_data['enabled_flag'] 81 | result.host = json_data['host'] 82 | result.check_script = json_data['check_script'] 83 | result.check_interval_minute = json_data['check_interval_minute'] 84 | result.check_value_is_number = json_data['check_value_is_number'] 85 | result.description = json_data['description'] 86 | result.warning_limit = json_data['warning_limit'] 87 | result.critical_limit = json_data['critical_limit'] 88 | result.shadow_data = json_data['shadow_data'] 89 | result.owner_team_list = json_data['owner_team_list'] 90 | result.warning_mail_cc = json_data['warning_mail_cc'] 91 | result.critical_mail_cc = json_data['critical_mail_cc'] 92 | result.critical_sms_flag = json_data['critical_sms_flag'] 93 | result.critical_call_flag = json_data['critical_call_flag'] 94 | result.allow_repeated_sms_alarm = json_data['allow_repeated_sms_alarm'] 95 | result.allow_repeated_call_alarm = json_data['allow_repeated_call_alarm'] 96 | result.allow_repeated_mail_alarm = json_data['allow_repeated_mail_alarm'] 97 | return result 98 | 99 | 100 | def updateNonnumericalResult(check_itm, status, detail_msg, notification_msg=''): 101 | check_value = 0 102 | return _updateResult(check_itm, check_value, status, detail_msg, notification_msg) 103 | 104 | 105 | def updateNumericalResult(check_itm, check_value, detail_msg, notification_msg=''): 106 | status = "" 107 | return _updateResult(check_itm, check_value, status, detail_msg, notification_msg) 108 | 109 | 110 | def registerException(check_itm, exception_msg): 111 | logger = logging.getLogger(__name__) 112 | logger.info("Begin to register checking exception via web API.") 113 | 114 | data = { 115 | 'exception_msg': exception_msg 116 | } 117 | data_json = json.dumps(data) 118 | url = "%s/api/v1.0/exceptions/%s" % (conf.web_url, check_itm) 119 | req = urllib2.Request(url, data_json, {'Content-Type': 'application/json'}) 120 | f = urllib2.urlopen(req) 121 | httpCodes = f.getcode() 122 | responseStr = f.read() 123 | f.close() 124 | json_data = json.loads(responseStr) 125 | echo_msg = json_data['echo_msg'] 126 | successful = httpCodes in [200, 201, 202] 127 | return (successful, echo_msg) 128 | -------------------------------------------------------------------------------- /edwinAgent/common/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | ''' 4 | from __future__ import absolute_import 5 | import logging 6 | 7 | # edwinServer.web site url 8 | web_url = "http://10.100.10.1:5000" 9 | 10 | 11 | # logging 12 | log_level = logging.INFO 13 | 14 | 15 | # Maybe you want to check data in your some database. 16 | # Configure db connection here, then you can access it in your agent 17 | tera_driver_jdbc = "com.teradata.jdbc.TeraDriver" 18 | tera_url_jdbc = "jdbc:teradata://10.100.100.101/DATABASE=dbc,TMODE=ANSI,CHARSET=UTF8,LOB_SUPPORT=off" 19 | tera_uid_jdbc = "your_account" 20 | tera_pwd_jdbc = "your_pwd" 21 | -------------------------------------------------------------------------------- /edwinAgent/common/const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | 6 | from __future__ import absolute_import 7 | 8 | # check status, please DO NOT change 9 | CHECK_STATUS_NORMAL = "NORMAL" 10 | CHECK_STATUS_WARN = "WARNING" 11 | CHECK_STATUS_CRITICAL = "CRITICAL" 12 | -------------------------------------------------------------------------------- /edwinAgent/common/database_tera.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | ''' 4 | from __future__ import absolute_import 5 | from __future__ import with_statement 6 | import logging 7 | import threading 8 | from . import os_helper 9 | if os_helper.isJython(): 10 | from com.ziclix.python.sql import zxJDBC 11 | else: 12 | from ..site_packages import pypyodbc 13 | from . import conf 14 | 15 | 16 | # connection=None # use global connection object to keep connection 17 | threadVar = threading.local() # thread local variable to keep connection 18 | 19 | 20 | def closeConnection_jdbc(): 21 | logger = logging.getLogger(__name__) 22 | #global connection 23 | try: 24 | _ = threadVar.connection 25 | except: 26 | threadVar.connection = None 27 | 28 | if (threadVar.connection is not None) and (threadVar.connection.closed == False): 29 | threadVar.connection.close() 30 | logger.info("Disconnect tera database.") 31 | threadVar.connection = None 32 | 33 | 34 | def closeConnection_odbc(): 35 | logger = logging.getLogger(__name__) 36 | #global connection 37 | try: 38 | _ = threadVar.connection 39 | except: 40 | threadVar.connection = None 41 | 42 | if (threadVar.connection is not None) and (threadVar.connection.connected): 43 | threadVar.connection.close() 44 | logger.info("Disconnect Teradata database.") 45 | threadVar.connection = None 46 | 47 | 48 | def openConnection_jdbc(): 49 | logger = logging.getLogger(__name__) 50 | #global connection 51 | try: 52 | _ = threadVar.connection 53 | except: 54 | threadVar.connection = None 55 | 56 | if threadVar.connection is None: 57 | logger.info('To get database Teradata connection') 58 | threadVar.connection = zxJDBC.connect(conf.tera_url_jdbc, conf.tera_uid_jdbc, conf.tera_pwd_jdbc, conf.tera_driver_jdbc) 59 | logger.info('Teradata connection: %s', (threadVar.connection,)) 60 | return threadVar.connection 61 | 62 | 63 | def openConnection_odbc(): 64 | logger = logging.getLogger(__name__) 65 | #global connection 66 | try: 67 | _ = threadVar.connection 68 | except: 69 | threadVar.connection = None 70 | 71 | if threadVar.connection is None: 72 | logger.info('To get database Teradata connection') 73 | threadVar.connection = pypyodbc.connect(conf.tera_url_odbc) 74 | logger.info('Teradata connection: %s', (threadVar.connection,)) 75 | return threadVar.connection 76 | 77 | 78 | if os_helper.isJython(): 79 | openConnection = openConnection_jdbc 80 | closeConnection = closeConnection_jdbc 81 | else: 82 | openConnection = openConnection_odbc 83 | closeConnection = closeConnection_odbc 84 | -------------------------------------------------------------------------------- /edwinAgent/common/my_logging.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 2013-4-15 3 | ''' 4 | import sys 5 | import logging 6 | from .os_helper import isWindows, isJython 7 | 8 | 9 | # ConcurrentRotatingFileHandler is the best rotating file handler. 10 | # Jython: ConcurrentRotatingFileHandler does not support Jython. 11 | # Reason: pywin32 cannot work on windows, fcntl missed on linux 12 | # Windows: If ConcurrentRotatingFileHandler does not install, use TimedRotatingFileHandler rather than RotatingFileHandler. 13 | # Reason: because RotatingFileHandler will fail when the file size is up to maxBytes on windows, 14 | # And TimedRotatingFileHandler will fail if the log file is being written at 23:59 15 | # But the chance of TimedRotatingFileHandler failure is less than RotatingFileHandler 16 | 17 | 18 | root_logger = None 19 | useRotatingFileHandler = True 20 | if isJython(): 21 | if isWindows(): 22 | useRotatingFileHandler = False 23 | from logging.handlers import TimedRotatingFileHandler 24 | else: 25 | useRotatingFileHandler = True 26 | from logging.handlers import RotatingFileHandler as RFHandler 27 | else: 28 | useRotatingFileHandler = True 29 | try: 30 | from ..site_packages.ConcurrentLogHandler084.cloghandler import ConcurrentRotatingFileHandler as RFHandler 31 | except ImportError, ex: 32 | print(ex) 33 | from warnings import warn 34 | warn("ConcurrentLogHandler package failed to import. Use built-in log handler instead.") 35 | if isWindows(): 36 | useRotatingFileHandler = False 37 | from logging.handlers import TimedRotatingFileHandler 38 | else: 39 | useRotatingFileHandler = True 40 | from logging.handlers import RotatingFileHandler as RFHandler 41 | 42 | 43 | def configureLogger(logger, log_file, log_level, console_ouput=True): 44 | ''' 45 | Only the root logger need to configure. 46 | ''' 47 | # create RotatingFileHandler and set level to debug 48 | global root_logger 49 | root_logger = logger 50 | if (log_file is not None): 51 | print("log_file=%s" % (log_file)) 52 | if useRotatingFileHandler: 53 | fh = RFHandler(filename=log_file, mode='a', maxBytes=10 * 1024 * 1024, backupCount=10, encoding='utf-8') 54 | else: 55 | fh = TimedRotatingFileHandler(filename=log_file, when='midnight', interval=1, backupCount=10, encoding='utf-8') 56 | 57 | formatter = logging.Formatter(fmt='%(asctime)s,pid=%(process)d,tid=%(thread)d,%(name)s,%(levelname)s:%(message)s') 58 | fh.setFormatter(formatter) 59 | logger.addHandler(fh) 60 | logger.setLevel(log_level) 61 | 62 | # create consoleHanlder 63 | if (console_ouput): 64 | ch = logging.StreamHandler() 65 | shortFormatter = logging.Formatter(fmt='%(asctime)s,pid=%(process)d,tid=%(thread)d,%(name)s,%(levelname)s:%(message)s', datefmt="%H:%M:%S") 66 | ch.setFormatter(shortFormatter) 67 | logger.addHandler(ch) 68 | logger.setLevel(log_level) 69 | 70 | # catch the uncatched exception 71 | sys.excepthook = my_excepthook 72 | 73 | 74 | def my_excepthook(exc_type, exc_value, exc_traceback): 75 | root_logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) 76 | -------------------------------------------------------------------------------- /edwinAgent/common/os_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from __future__ import absolute_import 5 | from __future__ import with_statement 6 | import os 7 | import platform 8 | import subprocess 9 | import socket 10 | from datetime import datetime 11 | import time 12 | 13 | 14 | def getIpAddr(): 15 | myname = socket.getfqdn(socket.gethostname()) 16 | myaddr = socket.gethostbyname(myname) 17 | return myaddr 18 | 19 | 20 | def getHostName(): 21 | return platform.node() 22 | 23 | 24 | def isJython(): 25 | return (platform.platform().find('Java') >= 0) 26 | 27 | 28 | def isWindows(): 29 | ''' 30 | remark: sys.platform and os.name cannot identify in Jython, so use platform.platform() 31 | ''' 32 | return (platform.platform().find('Windows') >= 0) 33 | 34 | 35 | def isLinux(): 36 | ''' 37 | remark: sys.platform and os.name cannot identify in Jython, so use platform.platform() 38 | ''' 39 | return (platform.platform().find('Linux') >= 0) 40 | 41 | 42 | def isMac(): 43 | ''' 44 | remark: sys.platform and os.name cannot identify in Jython, so use platform.platform() 45 | ''' 46 | plat = platform.platform() 47 | return (plat.find('Darwin') >= 0) or (plat.find('MacOS') >= 0) 48 | 49 | 50 | def isPosix(): 51 | if isJython() == False: 52 | return os.name == 'posix' 53 | elif isWindows(): 54 | return False 55 | elif isMac(): 56 | return False 57 | else: 58 | return True 59 | 60 | 61 | def getNewLineSeperator(): 62 | if isWindows(): 63 | return '\r\n' 64 | elif isMac(): 65 | return '\r' 66 | else: 67 | return '\n' 68 | 69 | 70 | def launchCmdLine(*popenargs, **kwargs): 71 | ''' 72 | run command line, return process object 73 | ''' 74 | # capture cmd output 75 | # For windows, shell should be False, but there is a bug to run cmd.exe built-in command. http://bugs.python.org/issue8224, we have to set shell=True 76 | # For Linux, shell=True 77 | if isWindows(): 78 | shellValue = True 79 | else: 80 | shellValue = True 81 | 82 | process = subprocess.Popen(shell=shellValue, stdout=subprocess.PIPE, stderr=subprocess.PIPE, *popenargs, **kwargs) 83 | return process 84 | 85 | 86 | def waitResultOfCmdProcess(process): 87 | ''' 88 | check process result, return exitcode, output, error message together 89 | ''' 90 | output, error = process.communicate() 91 | exitcode = process.wait() 92 | return (exitcode, output, error) 93 | 94 | 95 | def waitAndLogResultOfCmdProcess(process, logFile, processStartTime=None): 96 | ''' 97 | check process result, 98 | and then save output, error message together to log file, 99 | return exitcode 100 | ''' 101 | (exitcode, output, error) = waitResultOfCmdProcess(process) 102 | 103 | if logFile is not None: 104 | newlines = getNewLineSeperator() 105 | with open(logFile, "w") as f: 106 | f.write("---process id: %d" % process.pid) 107 | f.write(newlines) 108 | 109 | if processStartTime is not None: 110 | f.write("---started at: %s" % processStartTime) 111 | f.write(newlines) 112 | 113 | f.write("---ended at: %s" % datetime.now()) 114 | f.write(newlines) 115 | 116 | f.write("---exit code: %d" % exitcode) 117 | f.write(newlines) 118 | 119 | f.write("---error message: ") 120 | f.write(newlines) 121 | f.write(error) 122 | f.write(newlines) 123 | 124 | f.write("---output message: ") 125 | f.write(newlines) 126 | f.write(output) 127 | f.write(newlines) 128 | 129 | f.close() 130 | return exitcode 131 | 132 | 133 | def getFileLastModifiyTime(fname): 134 | mtime=time.ctime(os.stat(fname).st_mtime) 135 | tim=datetime.strptime(mtime,'%a %b %d %H:%M:%S %Y') 136 | return tim 137 | 138 | 139 | def getPath(fullFileName): 140 | ''' 141 | c://1 -> c:\\1 142 | 143 | ''' 144 | (dirName, unused) = os.path.split(fullFileName) 145 | return os.path.normpath(dirName) 146 | 147 | 148 | def getPathAndFileName(fullFileName): 149 | ''' 150 | return (path, fileName) 151 | ''' 152 | (dirName, fileName) = os.path.split(fullFileName) 153 | return (os.path.normpath(dirName), fileName) 154 | 155 | 156 | def getLoggingFileName(py_main_file, log_short_path='logs'): 157 | (base_path, py_file_name) = getPathAndFileName(py_main_file) 158 | log_path = os.path.join(base_path, log_short_path) 159 | (log_short_name, ext) = os.path.splitext(py_file_name) 160 | if not os.path.exists(log_path): 161 | os.mkdir(log_path) 162 | return os.path.join(log_path, log_short_name + '.log') 163 | 164 | 165 | def sleep(SLEEP_MINUTES): 166 | # time.sleep() cannot wake up again, so use Java Thread.sleep() instead 167 | # time.sleep(SLEEP_MINUTES*60) 168 | if isJython(): 169 | 170 | from java.lang import Thread 171 | Thread.sleep(SLEEP_MINUTES * 60 * 1000) 172 | else: 173 | import time 174 | time.sleep(SLEEP_MINUTES * 60) 175 | -------------------------------------------------------------------------------- /edwinAgent/site_packages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinAgent/site_packages/__init__.py -------------------------------------------------------------------------------- /edwinAgent/site_packages/dbRowFactory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinAgent/site_packages/dbRowFactory/__init__.py -------------------------------------------------------------------------------- /edwinAgent/site_packages/jssh/__init__.py: -------------------------------------------------------------------------------- 1 | #http://www.blogjava.net/cpegtop/articles/384466.html 2 | #put ganymed-ssh2-build210.jar to jre lib/ext folder 3 | 4 | from java.io import BufferedReader 5 | from java.io import IOException 6 | from java.io import InputStream 7 | from java.io import InputStreamReader 8 | from ch.ethz.ssh2 import Connection 9 | from ch.ethz.ssh2 import Session 10 | from ch.ethz.ssh2 import StreamGobbler 11 | 12 | 13 | import sys 14 | import os 15 | import logging 16 | 17 | 18 | class Jssh(): 19 | logger=logging.getLogger(__name__) 20 | 21 | def __init__(self, hostname, user, password): 22 | self.hostname = hostname 23 | self.user = user 24 | self.password = password 25 | 26 | 27 | def excmd(self, sshcmd): 28 | ''' 29 | return (connected_ok, response_array) 30 | ''' 31 | try: 32 | return self._excmd(sshcmd) 33 | except Exception, ex: 34 | connected_ok=False 35 | resp = [] 36 | resp.append('%s'%ex) 37 | return (connected_ok,resp) 38 | 39 | 40 | def _excmd(self, sshcmd): 41 | ''' 42 | return (connected_ok, response_array) 43 | ''' 44 | connected_ok=True 45 | resp = [] 46 | try: 47 | conn = Connection(self.hostname) 48 | conn.connect() 49 | self.logger.info('ssh connection created.') 50 | isAuthenticated = conn.authenticateWithPassword(self.user, self.password) 51 | if not isAuthenticated: 52 | connected_ok=False 53 | self.logger.error('ssh failed to authenticatd.') 54 | else: 55 | self.logger.info('ssh authenticated.') 56 | sess = conn.openSession() 57 | 58 | self.logger.info('ssh session created.') 59 | sess.execCommand(sshcmd) 60 | self.logger.info('ssh command issued. cmd is %s'%sshcmd) 61 | 62 | stdout = StreamGobbler(sess.getStdout()) 63 | br = BufferedReader(InputStreamReader(stdout)) 64 | while True: 65 | line = br.readLine() 66 | if line is None: 67 | break 68 | else : 69 | resp.append(line) 70 | self.logger.warning('ssh command output: '%resp) 71 | except IOException ,ex: 72 | connected_ok=False 73 | #print "oops..error,", ex 74 | self.logger.error('ssh exception: %s'% ex) 75 | finally: 76 | sess.close() 77 | self.logger.info('ssh session closed.') 78 | conn.close() 79 | self.logger.info('ssh connection closed.') 80 | return (connected_ok,resp) 81 | 82 | 83 | 84 | def ssh_exec(hostname,user,password,sshcmd): 85 | ss = Jssh(hostname,user,password) 86 | return ss.excmd(sshcmd) 87 | 88 | 89 | 90 | if __name__ == '__main__': 91 | ssh_exec(hostname='XXXXX', user='root', password='XXXX',sshcmd='ls') 92 | -------------------------------------------------------------------------------- /edwinServer/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | __version__='1.0.0' 5 | __author__='Harry Liu' 6 | -------------------------------------------------------------------------------- /edwinServer/bin/alarm_send.bat: -------------------------------------------------------------------------------- 1 | setlocal 2 | 3 | rem # set python executable 4 | set PYTHON_BIN=D:\pythonenv\python272_flask\Scripts\python.exe 5 | 6 | rem # base path, it is the parent of root package path 7 | set BASE_PATH=D:\corp_files\trunk\workspace\Edwin 8 | 9 | rem # set your python script file. Please be noticed to trim the tailed space of file name 10 | set MY_PY_SCRIPT=alarm_send.py 11 | 12 | 13 | rem #================================= 14 | rem # do not change the following code 15 | rem #================================= 16 | set PYTHONPATH=%BASE_PATH% 17 | 18 | set SCRIPT_PATH=%BASE_PATH%\edwinServer\scripts 19 | set BIN_PATH=%BASE_PATH%\edwinServer\bin 20 | SET LOCK_FILE=%BIN_PATH%\locks\%MY_PY_SCRIPT%.lock 21 | SET MY_PY_FILE=%SCRIPT_PATH%\%MY_PY_SCRIPT% 22 | 23 | dir %LOCK_FILE% 24 | if not %errorlevel% == 0 ( 25 | echo '' > %LOCK_FILE% 26 | %PYTHON_BIN% %MY_PY_FILE% 27 | del %LOCK_FILE% 28 | ) 29 | 30 | endlocal 31 | 32 | -------------------------------------------------------------------------------- /edwinServer/bin/alarm_send.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #set python executable 4 | PYTHON_BIN=/home/user1/pythonenv/python27/scripts/python 5 | 6 | # base path, it is the parent of root package path 7 | BASE_PATH=/home/user1/trunk/workspace/Edwin 8 | 9 | # set your python script file. Please be noticed to trim the tailed space of file name 10 | MY_PY_SCRIPT=alarm_send.py 11 | 12 | 13 | #================================= 14 | # do not change the following code 15 | #================================= 16 | PYTHONPATH=$BASE_PATH 17 | 18 | SCRIPT_PATH=$BASE_PATH/edwinServer/scripts 19 | BIN_PATH=$BASE_PATH/edwinServer/bin 20 | LOCK_FILE=$BIN_PATH/locks/$MY_PY_SCRIPT.lock 21 | MY_PY_FILE=$SCRIPT_PATH/$MY_PY_SCRIPT 22 | 23 | cnt=`ls $LOCK_FILE|wc -l` 24 | if [ ${cnt} -lt 1 ] 25 | then 26 | touch LOCK_FILE 27 | $PYTHON_BIN $MY_PY_FILE 28 | rm $LOCK_FILE 29 | fi 30 | 31 | -------------------------------------------------------------------------------- /edwinServer/bin/locks/keep_for_git.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/bin/locks/keep_for_git.txt -------------------------------------------------------------------------------- /edwinServer/bin/runserver.bat: -------------------------------------------------------------------------------- 1 | setlocal 2 | 3 | rem # set python executable 4 | set PYTHON_BIN=D:\pythonenv\python272_flask\Scripts\python.exe 5 | 6 | rem # base path, it is the parent of root package path 7 | set BASE_PATH=D:\corp_files\trunk\workspace\Edwin 8 | 9 | rem # set your python script file. Please be noticed to trim the tailed space of file name 10 | set MY_PY_SCRIPT=runserver.py 11 | 12 | 13 | rem #================================= 14 | rem # do not change the following code 15 | rem #================================= 16 | set PYTHONPATH=%BASE_PATH% 17 | 18 | set SCRIPT_PATH=%BASE_PATH%\edwinServer\web 19 | set BIN_PATH=%BASE_PATH%\edwinServer\bin 20 | SET LOCK_FILE=%BIN_PATH%\locks\%MY_PY_SCRIPT%.lock 21 | SET MY_PY_FILE=%SCRIPT_PATH%\%MY_PY_SCRIPT% 22 | 23 | dir %LOCK_FILE% 24 | if not %errorlevel% == 0 ( 25 | echo '' > %LOCK_FILE% 26 | %PYTHON_BIN% %MY_PY_FILE% 27 | del %LOCK_FILE% 28 | ) 29 | 30 | endlocal 31 | 32 | -------------------------------------------------------------------------------- /edwinServer/bin/runserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #set python executable 4 | PYTHON_BIN=/home/user1/pythonenv/python27/scripts/python 5 | 6 | # base path, it is the parent of root package path 7 | BASE_PATH=/home/user1/trunk/workspace/Edwin 8 | 9 | # set your python script file. Please be noticed to trim the tailed space of file name 10 | MY_PY_SCRIPT=runserver.py 11 | 12 | 13 | #================================= 14 | # do not change the following code 15 | #================================= 16 | PYTHONPATH=$BASE_PATH 17 | 18 | SCRIPT_PATH=$BASE_PATH/edwinServer/web 19 | BIN_PATH=$BASE_PATH/edwinServer/bin 20 | LOCK_FILE=$BIN_PATH/locks/$MY_PY_SCRIPT.lock 21 | MY_PY_FILE=$SCRIPT_PATH/$MY_PY_SCRIPT 22 | 23 | cnt=`ls $LOCK_FILE|wc -l` 24 | if [ ${cnt} -lt 1 ] 25 | then 26 | touch $LOCK_FILE 27 | $PYTHON_BIN $MY_PY_FILE 28 | rm $LOCK_FILE 29 | fi 30 | 31 | -------------------------------------------------------------------------------- /edwinServer/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/common/__init__.py -------------------------------------------------------------------------------- /edwinServer/common/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | from __future__ import absolute_import 6 | import logging 7 | 8 | # logging 9 | log_level = logging.INFO 10 | log_level = logging.DEBUG 11 | 12 | 13 | # edwinServer odbc configuration 14 | metadb_url_odbc = "DSN=my_edwin_database;UID=username_abc;PWD=pwd_123" 15 | 16 | 17 | # script run mode for alarm_send.py 18 | alarm_send_in_daemon_mode = False 19 | 20 | 21 | # email account for alarm_send.py 22 | use_mailx_settings = False 23 | smtp_host = "smtp.gmail.com" 24 | smtp_port = 465 25 | smtp_over_ssl = True 26 | mail_user = "SENDER@gmail.com" 27 | mail_pwd = "MYPASSWD" 28 | 29 | 30 | # edwin web url 31 | # This url will be embedded in notification email. 32 | # Be noted that host should not be localhost, 127.0.0.1 , 0.0.0.0 33 | web_url = "http://10.100.10.1:5000" 34 | -------------------------------------------------------------------------------- /edwinServer/common/const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | constant 4 | ''' 5 | 6 | from __future__ import absolute_import 7 | 8 | # root node of web side bar 9 | DASHBOARD_NODE = '[Dash board]' 10 | 11 | 12 | # web page/pagelet summary status, please DO NOT change 13 | SUMMARY_STATUS_NORMAL = "Normal" 14 | SUMMARY_STATUS_OUTDATED = "Outdated" 15 | SUMMARY_STATUS_WARNING = "Warning" 16 | SUMMARY_STATUS_CRITICAL = "Critical" 17 | 18 | 19 | # check status, please DO NOT change 20 | CHECK_STATUS_NORMAL = "NORMAL" 21 | CHECK_STATUS_WARN = "WARNING" 22 | CHECK_STATUS_CRITICAL = "CRITICAL" 23 | 24 | 25 | # check value for non-numerical check item, please DO NOT change 26 | CHECK_VALUE_NA = 'NA' 27 | 28 | 29 | # alarm send status, please DO NOT change 30 | ALARM_SEND_STATUS_WAITING = 'WAITING' 31 | ALARM_SEND_STATUS_SENDING = 'SENDING' 32 | ALARM_SEND_STATUS_FAILED = 'FAILED' 33 | ALARM_SEND_STATUS_SUCCESSFUL = 'SUCCESSFUL' 34 | ALARM_SEND_STATUS_NA = 'NA' 35 | 36 | 37 | # built-in and sample check item list, please DO NOT change. 38 | # The following check items need to add in table of edwin_check_cfg 39 | # if other check item failed to run, the system will notify out 40 | CHECK_ITEM_BUILTIN_EXCEPTION_NOTIFY = 'BUILTIN_EXCEPTION_NOTIFY' 41 | 42 | 43 | # alarm mail subject prefix, change it by yourself 44 | EMAIL_SUBJECT_PREFIX_WARN = 'Edwin warning alert' 45 | EMAIL_SUBJECT_PREFIX_CRITICAL = 'Edwin critical alert' 46 | -------------------------------------------------------------------------------- /edwinServer/common/convert_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | from __future__ import absolute_import 6 | import inspect 7 | 8 | 9 | def object_to_dict(obj, exclude_private = True): 10 | pr = {} 11 | for name in dir(obj): 12 | value = getattr(obj, name) 13 | if not name.startswith('__') and not inspect.ismethod(value): 14 | if exclude_private and name.startswith('_'): 15 | pass 16 | else: 17 | pr[name] = value 18 | return pr -------------------------------------------------------------------------------- /edwinServer/common/database_all.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | from __future__ import absolute_import 6 | from __future__ import with_statement 7 | from . import database_meta 8 | from . import database_tera 9 | 10 | 11 | def closeAllConnections(): 12 | database_meta.closeConnection() 13 | database_tera.closeConnection() 14 | -------------------------------------------------------------------------------- /edwinServer/common/database_meta.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | from __future__ import absolute_import 6 | from __future__ import with_statement 7 | import logging 8 | import threading 9 | from . import os_helper 10 | if os_helper.isJython(): 11 | from com.ziclix.python.sql import zxJDBC 12 | else: 13 | from ..site_packages import pypyodbc 14 | from . import conf 15 | 16 | 17 | # connection=None # use global connection object to keep connection 18 | threadVar = threading.local() # thread local variable to keep connection 19 | 20 | 21 | def closeConnection_jdbc(): 22 | logger = logging.getLogger(__name__) 23 | #global connection 24 | try: 25 | _ = threadVar.connection 26 | except: 27 | threadVar.connection = None 28 | 29 | if (threadVar.connection is not None) and (threadVar.connection.closed == False): 30 | threadVar.connection.close() 31 | logger.info("Disconnect meta database.") 32 | threadVar.connection = None 33 | 34 | 35 | def closeConnection_odbc(): 36 | logger = logging.getLogger(__name__) 37 | #global connection 38 | try: 39 | _ = threadVar.connection 40 | except: 41 | threadVar.connection = None 42 | 43 | if (threadVar.connection is not None) and (threadVar.connection.connected): 44 | threadVar.connection.close() 45 | logger.info("Disconnect meta database.") 46 | threadVar.connection = None 47 | 48 | 49 | def openConnection_jdbc(): 50 | logger = logging.getLogger(__name__) 51 | #global connection 52 | try: 53 | _ = threadVar.connection 54 | except: 55 | threadVar.connection = None 56 | 57 | if threadVar.connection is None: 58 | logger.info('To get database meta connection') 59 | threadVar.connection = zxJDBC.connect(conf.metadb_url_jdbc, conf.metadb_uid_jdbc, conf.metadb_pwd_jdbc, conf.metadb_driver_jdbc) 60 | logger.info('Meta connection: %s', (threadVar.connection,)) 61 | logger.debug("DB connection used: %s", threadVar.connection) 62 | return threadVar.connection 63 | 64 | 65 | def openConnection_odbc(): 66 | logger = logging.getLogger(__name__) 67 | #global connection 68 | try: 69 | _ = threadVar.connection 70 | except: 71 | threadVar.connection = None 72 | 73 | if threadVar.connection is None: 74 | logger.info('To get database meta connection') 75 | threadVar.connection = pypyodbc.connect(conf.metadb_url_odbc) 76 | logger.info('Meta connection: %s', (threadVar.connection,)) 77 | return threadVar.connection 78 | 79 | 80 | if os_helper.isJython(): 81 | openConnection = openConnection_jdbc 82 | closeConnection = closeConnection_jdbc 83 | else: 84 | openConnection = openConnection_odbc 85 | closeConnection = closeConnection_odbc 86 | -------------------------------------------------------------------------------- /edwinServer/common/database_tera.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | ''' 4 | from __future__ import absolute_import 5 | from __future__ import with_statement 6 | import logging 7 | import threading 8 | from . import os_helper 9 | if os_helper.isJython(): 10 | from com.ziclix.python.sql import zxJDBC 11 | else: 12 | from ..site_packages import pypyodbc 13 | from . import conf 14 | 15 | 16 | # connection=None # use global connection object to keep connection 17 | threadVar = threading.local() # thread local variable to keep connection 18 | 19 | 20 | def closeConnection_jdbc(): 21 | logger = logging.getLogger(__name__) 22 | #global connection 23 | try: 24 | _ = threadVar.connection 25 | except: 26 | threadVar.connection = None 27 | 28 | if (threadVar.connection is not None) and (threadVar.connection.closed == False): 29 | threadVar.connection.close() 30 | logger.info("Disconnect tera database.") 31 | threadVar.connection = None 32 | 33 | 34 | def closeConnection_odbc(): 35 | logger = logging.getLogger(__name__) 36 | #global connection 37 | try: 38 | _ = threadVar.connection 39 | except: 40 | threadVar.connection = None 41 | 42 | if (threadVar.connection is not None) and (threadVar.connection.connected): 43 | threadVar.connection.close() 44 | logger.info("Disconnect Teradata database.") 45 | threadVar.connection = None 46 | 47 | 48 | def openConnection_jdbc(): 49 | logger = logging.getLogger(__name__) 50 | #global connection 51 | try: 52 | _ = threadVar.connection 53 | except: 54 | threadVar.connection = None 55 | 56 | if threadVar.connection is None: 57 | logger.info('To get database Teradata connection') 58 | threadVar.connection = zxJDBC.connect(conf.tera_url_jdbc, conf.tera_uid_jdbc, conf.tera_pwd_jdbc, conf.tera_driver_jdbc) 59 | logger.info('Teradata connection: %s', (threadVar.connection,)) 60 | return threadVar.connection 61 | 62 | 63 | def openConnection_odbc(): 64 | logger = logging.getLogger(__name__) 65 | #global connection 66 | try: 67 | _ = threadVar.connection 68 | except: 69 | threadVar.connection = None 70 | 71 | if threadVar.connection is None: 72 | logger.info('To get database Teradata connection') 73 | threadVar.connection = pypyodbc.connect(conf.tera_url_odbc) 74 | logger.info('Teradata connection: %s', (threadVar.connection,)) 75 | return threadVar.connection 76 | 77 | 78 | if os_helper.isJython(): 79 | openConnection = openConnection_jdbc 80 | closeConnection = closeConnection_jdbc 81 | else: 82 | openConnection = openConnection_odbc 83 | closeConnection = closeConnection_odbc 84 | -------------------------------------------------------------------------------- /edwinServer/common/job_state_updater.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | 6 | from __future__ import absolute_import 7 | import logging 8 | from datetime import datetime 9 | from .model_meta import dashboard_check_cfg, dashboard_check_log 10 | from . import const 11 | 12 | 13 | class JobStateUpdater(object): 14 | 15 | def __init__(self, itm_code): 16 | self.logger = logging.getLogger(__name__) 17 | self.logger.info('To update check result for %s' % itm_code) 18 | 19 | self.itm_code = itm_code 20 | self.check_cfg = dashboard_check_cfg.getCfgInDb(itm_code) 21 | self.logger.debug('Check item cfg object: %s' % (self.check_cfg)) 22 | 23 | def isUndefinedCheckItem(self): 24 | return self.check_cfg is None 25 | 26 | def resultShouldBeNumerical(self): 27 | return self.check_cfg.check_value_is_number == 'Y' 28 | 29 | def updateNumericalResult(self, check_value, detail_msg, notification_msg=''): 30 | ''' 31 | update check result for numeric check_item 32 | ''' 33 | 34 | status = self._getStatusByValue(check_value) 35 | self._updateCheckResult(check_value, status, detail_msg, notification_msg) 36 | 37 | def updateNonnumericalResult(self, status, detail_msg, notification_msg=''): 38 | ''' 39 | update check result for non-numeric check_item 40 | ''' 41 | check_value = const.CHECK_VALUE_NA 42 | self._updateCheckResult(check_value, status, detail_msg, notification_msg) 43 | 44 | def registerCheckingException(self, exception_msg): 45 | ''' 46 | Register checking exception. 47 | It always generate one critical alarm in check item of CHECK_ITEM_BUILTIN_EXCEPTION_NOTIFY 48 | ''' 49 | notifyJob = JobStateUpdater(const.CHECK_ITEM_BUILTIN_EXCEPTION_NOTIFY) 50 | status = const.CHECK_STATUS_CRITICAL 51 | detail_msg = "Check item %s has exception. Exception message: %s " % (self.itm_code, exception_msg) 52 | notifyJob.updateNonnumericalResult(status, detail_msg) 53 | 54 | def _getStatusByValue(self, check_value): 55 | ''' 56 | get status according to check value and spec 57 | ''' 58 | self.logger.debug("check_value_is_number=%s" % self.check_cfg.check_value_is_number) 59 | try: 60 | assert(self.check_cfg.check_value_is_number == 'Y') 61 | except Exception as ex: 62 | self.logger.exception("%s" % ex) 63 | raise ex 64 | result = const.CHECK_STATUS_NORMAL 65 | 66 | if self.check_cfg.smaller_is_better: 67 | if check_value >= self.check_cfg.critical_limit: 68 | result = const.CHECK_STATUS_CRITICAL 69 | elif check_value >= self.check_cfg.warning_limit: 70 | result = const.CHECK_STATUS_WARN 71 | else: 72 | if check_value <= self.check_cfg.critical_limit: 73 | result = const.CHECK_STATUS_CRITICAL 74 | elif check_value <= self.check_cfg.warning_limit: 75 | result = const.CHECK_STATUS_WARN 76 | 77 | return result 78 | 79 | def _updateCheckResult(self, check_value, status, detail_msg, notification_msg=''): 80 | ''' 81 | update check result in cfg table and log table 82 | ''' 83 | if self.check_cfg.enabled_flag != 'Y': 84 | self.logger.info('The check item %s is disable in database. So the coming result will be not stored anymore.' % (self.itm_code)) 85 | return 86 | 87 | self.logger.info('check_value %s' % check_value) 88 | self.logger.info('check_status %s' % status) 89 | 90 | check_timestamp = datetime.now().strftime("%Y%m%d %H:%M:%S.%f") # '%s'%datetime.now() 91 | try: 92 | assert(status in [const.CHECK_STATUS_NORMAL, const.CHECK_STATUS_WARN, const.CHECK_STATUS_CRITICAL]) 93 | except Exception as ex: 94 | self.logger.exception(ex) 95 | raise ex 96 | 97 | # update status in check_item cfg table 98 | self.check_cfg.updateStatus(check_value, status, check_timestamp, detail_msg, notification_msg) 99 | 100 | is_critical_event = self.check_cfg.is_critical_event 101 | if is_critical_event == 'Y': 102 | self.logger.warn("The alarm is a critical level alarm.") 103 | is_warning_event = self.check_cfg.is_warning_event 104 | if is_warning_event == 'Y': 105 | self.logger.warn("The alarm is a warning level alarm.") 106 | 107 | is_new_critical_event = self.check_cfg.is_new_critical_event 108 | if is_new_critical_event == 'Y': 109 | self.logger.warn("The alarm is a new critical level alarm.") 110 | is_new_warning_event = self.check_cfg.is_new_warning_event 111 | if is_new_warning_event == 'Y': 112 | self.logger.warn("The alarm is a new warning level alarm.") 113 | 114 | check_log = dashboard_check_log() 115 | check_log.initCheckStatus(self.itm_code, check_timestamp, status, check_value, 116 | self.check_cfg.warning_limit, self.check_cfg.critical_limit, 117 | self.check_cfg.shadow_data, detail_msg, notification_msg, 118 | is_critical_event, is_warning_event, 119 | is_new_critical_event, is_new_warning_event) 120 | check_log.insertLog() 121 | -------------------------------------------------------------------------------- /edwinServer/common/mail_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | from __future__ import absolute_import 6 | import logging 7 | import os 8 | 9 | 10 | class MailxMailSender(object): 11 | logger = logging.getLogger(__name__) 12 | 13 | def __init__(self): 14 | # read mail settings from configure file 15 | pass 16 | 17 | def _send(self, to_list, cc_list, subject, body): 18 | to_list.extend(cc_list) 19 | mailTo = ','.join(to_list) 20 | cmdFmt = "echo '%s' |mailx -s '%s' '%s'" 21 | cmd = cmdFmt % (body, subject, mailTo) 22 | self.logger.info("mailx cmd:%s" % cmd) 23 | exitcode = 0 24 | try: 25 | exitcode = os.system(cmd) 26 | return True 27 | if (exitcode != 0): 28 | self.logger.error("mailx exitcode =%d" % exitcode) 29 | return False 30 | except (Exception), ex: 31 | self.logger.exception(ex) 32 | return False 33 | 34 | def send_plain_mail(self, to_list, cc_list, subject, body): 35 | self.logger.info('send_plain_mail() called.') 36 | self._send(to_list, cc_list, subject, body) 37 | 38 | def send_html_mail(self, to_list, cc_list, subject, body): 39 | self.logger.info('send_html_mail() called.') 40 | self._send(to_list, cc_list, subject, body) 41 | 42 | 43 | class LoggingMailSender(object): 44 | logger = logging.getLogger(__name__) 45 | 46 | def __init__(self): 47 | # read mail settings from configure file 48 | pass 49 | 50 | def _send(self, to_list, cc_list, subject, body): 51 | self.logger.info("""Mail sent to logging. Find details below, 52 | to_list: %s 53 | cc_list: %s 54 | subject: %s 55 | body: %s""" % (to_list, cc_list, subject, body)) 56 | return True 57 | 58 | def send_plain_mail(self, to_list, cc_list, subject, body): 59 | self.logger.info('send_plain_mail() called.') 60 | self._send(to_list, cc_list, subject, body) 61 | 62 | def send_html_mail(self, to_list, cc_list, subject, body): 63 | self.logger.info('send_html_mail() called.') 64 | self._send(to_list, cc_list, subject, body) 65 | 66 | 67 | class SmtpMailSender(object): 68 | logger = logging.getLogger(__name__) 69 | 70 | def __init__(self, smtp_host, smtp_port, smtp_over_ssl, mail_user, mail_pwd): 71 | # read mail settings from configure file 72 | self.smtp_host = smtp_host 73 | self.smtp_port = smtp_port 74 | self.smtp_over_ssl = smtp_over_ssl 75 | self.mail_user = mail_user 76 | self.mail_pwd = mail_pwd 77 | 78 | def send_plain_mail(self, to_list, cc_list, subject, body): 79 | self.logger.info('send_plain_mail() called.') 80 | 81 | import smtplib 82 | from email.mime.text import MIMEText 83 | 84 | # Construct email 85 | msgRoot = MIMEText(body) 86 | msgRoot['Subject'] = subject 87 | msgRoot['From'] = self.mail_user 88 | msgRoot['To'] = ",".join(to_list) 89 | msgRoot['CC'] = ",".join(cc_list) 90 | #msgRoot['BCC'] =",".join(cc_list) 91 | 92 | try: 93 | if not self.smtp_over_ssl: 94 | if self.smtp_port == '': 95 | s = smtplib.SMTP(self.smtp_host) 96 | else: 97 | s = smtplib.SMTP(self.smtp_host, self.smtp_port) 98 | else: 99 | if self.smtp_port == '': 100 | s = smtplib.SMTP_SSL(self.smtp_host) 101 | else: 102 | s = smtplib.SMTP_SSL(self.smtp_host, self.smtp_port) 103 | 104 | s.set_debuglevel(True) # print stmp actions to stdout 105 | if self.mail_pwd: 106 | s.login(self.mail_user, self.mail_pwd) 107 | 108 | to_addrs = to_list + cc_list 109 | s.sendmail(self.mail_user, to_addrs, msgRoot.as_string()) 110 | #s.sendmail(from_addr ,to_addrs, 'test message') 111 | s.quit() 112 | 113 | self.logger.info("""Mail sent. Find details below, 114 | to_list: %s 115 | cc_list: %s 116 | subject: %s 117 | body: %s""" % (to_list, cc_list, subject, body)) 118 | return True 119 | except Exception, ex: 120 | self.logger.exception(ex) 121 | return False 122 | 123 | def send_html_mail(self, to_list, cc_list, subject, body): 124 | self.logger.info('send_html_mail() called.') 125 | 126 | import smtplib 127 | from email.mime.text import MIMEText 128 | from email.mime.multipart import MIMEMultipart 129 | 130 | # Construct email 131 | msgRoot = MIMEMultipart('related') 132 | msgRoot['Subject'] = subject 133 | msgRoot['From'] = self.mail_user 134 | msgRoot['To'] = ",".join(to_list) 135 | msgRoot['CC'] = ",".join(cc_list) 136 | #msgRoot['BCC'] =",".join(cc_list) 137 | msgRoot.preamble = 'This is a multi-part message in MIME format.' 138 | 139 | # Encapsulate the plain and HTML versions of the message body in an 140 | # 'alternative' part, so message agents can decide which they want to display. 141 | msgAlternative = MIMEMultipart('alternative') 142 | msgRoot.attach(msgAlternative) 143 | 144 | # Add plain content 145 | msgText = MIMEText('This is HTML mail. If you see this message, which means you will not see the real mail content.', 'plain') 146 | msgAlternative.attach(msgText) 147 | 148 | # add html content 149 | msgText = MIMEText(body, 'html') 150 | msgAlternative.attach(msgText) 151 | 152 | try: 153 | if not self.smtp_over_ssl: 154 | if self.smtp_port == '': 155 | s = smtplib.SMTP(self.smtp_host) 156 | else: 157 | s = smtplib.SMTP(self.smtp_host, self.smtp_port) 158 | else: 159 | if self.smtp_port == '': 160 | s = smtplib.SMTP_SSL(self.smtp_host) 161 | else: 162 | s = smtplib.SMTP_SSL(self.smtp_host, self.smtp_port) 163 | 164 | s.set_debuglevel(True) # print stmp actions to stdout 165 | if self.mail_pwd: 166 | s.login(self.mail_user, self.mail_pwd) 167 | 168 | to_addrs = to_list + cc_list 169 | s.sendmail(self.mail_user, to_addrs, msgRoot.as_string()) 170 | #s.sendmail(from_addr ,to_addrs, 'test message') 171 | s.quit() 172 | 173 | self.logger.info("""Mail sent. Find details below, 174 | to_list: %s 175 | cc_list: %s 176 | subject: %s 177 | body: %s""" % (to_list, cc_list, subject, body)) 178 | return True 179 | except Exception, ex: 180 | self.logger.exception(ex) 181 | return False 182 | -------------------------------------------------------------------------------- /edwinServer/common/mail_service.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | 6 | 7 | from __future__ import absolute_import 8 | import logging 9 | from .mail_helper import LoggingMailSender, MailxMailSender, SmtpMailSender 10 | from .model_meta import dashboard_check_cfg 11 | from .model_meta import team_mail_cfg 12 | from . import conf 13 | from . import const 14 | import os 15 | 16 | 17 | class MailSender(object): 18 | logger = logging.getLogger(__name__) 19 | 20 | def __init__(self): 21 | # TODO: set in_dev_mode=True when application debug 22 | self.in_dev_mode = False 23 | self.use_mailx_settings = conf.use_mailx_settings 24 | 25 | self.team_list = None 26 | self.currentAlarm = None 27 | self.currentCheckCfg = None 28 | self.current_itm_code = "" 29 | self.all_team_list = team_mail_cfg.getAllCfgInDb() 30 | 31 | self.logging_mail_service = LoggingMailSender() 32 | self.mailx_mail_service = MailxMailSender() 33 | self.stmp_mail_service = SmtpMailSender(conf.smtp_host, 34 | conf.smtp_port, 35 | conf.smtp_over_ssl, 36 | conf.mail_user, 37 | conf.mail_pwd) 38 | 39 | def _normalize_to_list(self, mailToStr): 40 | ''' 41 | convert mailToString into list, and remove the duplicated mailbox 42 | result: list 43 | ''' 44 | mailToStr = mailToStr.lower() 45 | mailTo = mailToStr.replace(';', ',') 46 | mailTo = mailTo.split(',') 47 | # mailTo=list(set(mailTo)) # remove duplicate 48 | mailTo = map(lambda x: x.strip(), mailTo) 49 | mailTo = filter(lambda x: x != '', mailTo) # remove the empty string 50 | return mailTo 51 | 52 | def filter_team_list(self): 53 | ''' 54 | return the related teams to this alarm 55 | ''' 56 | team_owners = self.currentCheckCfg.owner_team_list.replace(',', ';') 57 | team_owner_list = team_owners.split(';') 58 | self.team_list = [team for team in self.all_team_list if team.owner_team_code in team_owner_list] 59 | 60 | def _getTeamEMailRecipients(self): 61 | ''' 62 | get email boxes of related teams 63 | result format: [] 64 | ''' 65 | mail_to = "" 66 | for team in self.team_list: 67 | mail_to = mail_to + ',' + team.email_to_list 68 | return self._normalize_to_list(mail_to) 69 | 70 | def _getWarnEMailRecipients(self): 71 | ''' 72 | result format: (to_list, warning_mail_cc) 73 | ''' 74 | mailTo = self._getTeamEMailRecipients() 75 | mailCc = self.currentCheckCfg.warning_mail_cc 76 | return (mailTo, self._normalize_to_list(mailCc)) 77 | 78 | def _getCriticalEMailRecipients(self, for_mailx=True): 79 | ''' 80 | result format: (to_list, warning_mail_cc) 81 | ''' 82 | mailTo = self._getTeamEMailRecipients() 83 | mailCc = self.currentCheckCfg.critical_mail_cc 84 | return (mailTo, self._normalize_to_list(mailCc)) 85 | 86 | def _getPhoneRecipientAndTitle(self): 87 | ''' 88 | get Phone Recipient and title, the result format is [(to_1, title_1),(to_2, title_2)] 89 | ''' 90 | result = [] 91 | for team in self.team_list: 92 | result.append((team.phone_mail_to, team.phone_mail_title)) 93 | return result 94 | 95 | def _getSMSRecipientAndTitle(self): 96 | ''' 97 | get SMS Recipient and title, the result format is [(to_1, title_1),(to_2, title_2)] 98 | ''' 99 | result = [] 100 | for team in self.team_list: 101 | result.append((team.sms_mail_to, team.sms_mail_title)) 102 | return result 103 | 104 | def _executeSendCmd(self, mailTo, mailCc, subject, body, html_format=False): 105 | if self.in_dev_mode: 106 | self.logging_mail_service.send_plain_mail(mailTo, mailCc, subject, body) 107 | else: 108 | if self.use_mailx_settings: 109 | self.mailx_mail_service.send_plain_mail(mailTo, mailCc, subject, body) 110 | else: 111 | if html_format: 112 | self.stmp_mail_service.send_html_mail(mailTo, mailCc, subject, body) 113 | else: 114 | self.stmp_mail_service.send_plain_mail(mailTo, mailCc, subject, body) 115 | 116 | def send(self, alarmLogObj): 117 | # change current objects: 118 | self.currentAlarm = alarmLogObj 119 | itm_code = alarmLogObj.itm_code 120 | self.current_itm_code = itm_code 121 | self.currentCheckCfg = dashboard_check_cfg.getCfgInDb(itm_code) 122 | self.filter_team_list() 123 | 124 | if self.currentAlarm.check_status == const.CHECK_STATUS_WARN: 125 | # warning alarm 126 | if self.currentAlarm.is_new_warning_event == 'Y': 127 | self.logger.info("It is a new warning alarm. It will send email.") 128 | self._sendByEmail() 129 | else: 130 | self.logger.info("It is a repeated warning alarm.") 131 | if self.currentCheckCfg.allow_repeated_mail_alarm == 'Y': 132 | self._sendByEmail() 133 | else: 134 | self.logger.info("Email repeated alarm is bypassed.") 135 | 136 | else: # critical alarm 137 | if self.currentAlarm.is_new_critical_event == 'Y': 138 | self.logger.info("It is a new critical alarm. It will send email.") 139 | self._sendByEmail() 140 | if self.currentCheckCfg.critical_call_flag == 'Y': 141 | self._sendByPhone() 142 | else: 143 | self.logger.info("CallPhone alarm is disabled for this check item.") 144 | if self.currentCheckCfg.critical_sms_flag == 'Y': 145 | self._sendBySms() 146 | else: 147 | self.logger.info("SMS alarm is disabled for this check item.") 148 | else: 149 | self.logger.info("It is a repeated critical alarm.") 150 | if self.currentCheckCfg.allow_repeated_mail_alarm == 'Y': 151 | self._sendByEmail() 152 | else: 153 | self.logger.info("Email repeated alarm is bypassed.") 154 | if self.currentCheckCfg.critical_call_flag == 'Y' and self.currentCheckCfg.allow_repeated_call_alarm == 'Y': 155 | self._sendByPhone() 156 | else: 157 | self.logger.info("CallPhone repeated alarm is bypassed.") 158 | if self.currentCheckCfg.critical_sms_flag == 'Y' and self.currentCheckCfg.allow_repeated_sms_alarm == 'Y': 159 | self._sendBySms() 160 | else: 161 | self.logger.info("SMS alarm repeated is bypassed.") 162 | 163 | def _sendByEmail(self): 164 | self.logger.info("Begin to send EMail alarm.") 165 | 166 | if self.currentAlarm.check_status == const.CHECK_STATUS_CRITICAL: 167 | prefix = const.EMAIL_SUBJECT_PREFIX_CRITICAL 168 | (mailTo, mailCc) = self._getCriticalEMailRecipients() 169 | else: 170 | prefix = const.EMAIL_SUBJECT_PREFIX_WARN 171 | (mailTo, mailCc) = self._getWarnEMailRecipients() 172 | subject = "%s, check item: %s" % (prefix, self.currentCheckCfg.itm_title) 173 | 174 | msg = self.currentAlarm.check_notification_msg 175 | if msg is None or msg.strip() == '': 176 | msg = self.currentAlarm.check_detail_msg 177 | 178 | if self.currentAlarm.check_value: 179 | body = '''Please find details below, 180 |
%s 181 |
==================== 182 |
Check item code :%s 183 |
Warning limit :%s 184 |
Critical limit: %s 185 |
Current check value: %s 186 |
Current check status: %s 187 |
Current check timestamp: %s 188 |
==================== 189 |
Click this link to find more details. 190 | ''' % (msg, 191 | self.current_itm_code, 192 | self.currentAlarm.warning_limit, 193 | self.currentAlarm.critical_limit, 194 | self.currentAlarm.check_value, 195 | self.currentAlarm.check_status, 196 | self.currentAlarm.check_timestamp, 197 | conf.web_url, 198 | self.currentAlarm.itm_code 199 | ) 200 | else: 201 | body = '''Please find details below, 202 |
%s 203 |
==================== 204 |
Check item code: %s 205 |
Current check status: %s 206 |
Current check timestamp: %s 207 |
==================== 208 |
Click this link to find more details. 209 | ''' % (msg, 210 | self.current_itm_code, 211 | self.currentAlarm.check_status, 212 | self.currentAlarm.check_timestamp, 213 | conf.web_url, 214 | self.currentAlarm.itm_code 215 | ) 216 | 217 | self._executeSendCmd(mailTo, mailCc, subject, body, html_format=True) 218 | 219 | self.logger.info("End to send EMail alarm.") 220 | 221 | def _sendBySms(self): 222 | self.logger.info("Begin to send SMS alarm.") 223 | 224 | teams = self._getSMSRecipientAndTitle() 225 | for team in teams: 226 | (to, title) = team 227 | prefix = const.EMAIL_SUBJECT_PREFIX_CRITICAL 228 | body = "%s, check item: %s. Please check details in your mailbox. " % (prefix, self.currentCheckCfg.itm_title) 229 | self._executeSendCmd([to], [], title, body) 230 | 231 | self.logger.info("End to send SMS alarm.") 232 | 233 | def _sendByPhone(self): 234 | self.logger.info("Begin to send CallPhone alarm.") 235 | 236 | teams = self._getPhoneRecipientAndTitle() 237 | for team in teams: 238 | (to, title) = team 239 | prefix = const.EMAIL_SUBJECT_PREFIX_CRITICAL 240 | body = "%s, check item: %s. Please check details in your mailbox. " % (prefix, self.currentCheckCfg.itm_title) 241 | self._executeSendCmd([to], [], title, body) 242 | 243 | self.logger.info("End to send CallPhone alarm.") 244 | -------------------------------------------------------------------------------- /edwinServer/common/my_logging.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 2013-4-15 3 | ''' 4 | import sys 5 | import logging 6 | from .os_helper import isWindows, isJython 7 | 8 | 9 | # ConcurrentRotatingFileHandler is the best rotating file handler. 10 | # Jython: ConcurrentRotatingFileHandler does not support Jython. 11 | # Reason: pywin32 cannot work on windows, fcntl missed on linux 12 | # Windows: If ConcurrentRotatingFileHandler does not install, use TimedRotatingFileHandler rather than RotatingFileHandler. 13 | # Reason: because RotatingFileHandler will fail when the file size is up to maxBytes on windows, 14 | # And TimedRotatingFileHandler will fail if the log file is being written at 23:59 15 | # But the chance of TimedRotatingFileHandler failure is less than RotatingFileHandler 16 | 17 | 18 | root_logger = None 19 | useRotatingFileHandler = True 20 | if isJython(): 21 | if isWindows(): 22 | useRotatingFileHandler = False 23 | from logging.handlers import TimedRotatingFileHandler 24 | else: 25 | useRotatingFileHandler = True 26 | from logging.handlers import RotatingFileHandler as RFHandler 27 | else: 28 | useRotatingFileHandler = True 29 | try: 30 | from ..site_packages.ConcurrentLogHandler084.cloghandler import ConcurrentRotatingFileHandler as RFHandler 31 | except ImportError, ex: 32 | print(ex) 33 | from warnings import warn 34 | warn("ConcurrentLogHandler package failed to import. Use built-in log handler instead.") 35 | if isWindows(): 36 | useRotatingFileHandler = False 37 | from logging.handlers import TimedRotatingFileHandler 38 | else: 39 | useRotatingFileHandler = True 40 | from logging.handlers import RotatingFileHandler as RFHandler 41 | 42 | 43 | def configureLogger(logger, log_file, log_level, console_ouput=True): 44 | ''' 45 | Only the root logger need to configure. 46 | ''' 47 | # create RotatingFileHandler and set level to debug 48 | global root_logger 49 | root_logger = logger 50 | if (log_file is not None): 51 | print("log_file=%s" % (log_file)) 52 | if useRotatingFileHandler: 53 | fh = RFHandler(filename=log_file, mode='a', maxBytes=10 * 1024 * 1024, backupCount=10, encoding='utf-8') 54 | else: 55 | fh = TimedRotatingFileHandler(filename=log_file, when='midnight', interval=1, backupCount=10, encoding='utf-8') 56 | 57 | formatter = logging.Formatter(fmt='%(asctime)s,pid=%(process)d,tid=%(thread)d,%(name)s,%(levelname)s:%(message)s') 58 | fh.setFormatter(formatter) 59 | logger.addHandler(fh) 60 | logger.setLevel(log_level) 61 | 62 | # create consoleHanlder 63 | if (console_ouput): 64 | ch = logging.StreamHandler() 65 | shortFormatter = logging.Formatter(fmt='%(asctime)s,pid=%(process)d,tid=%(thread)d,%(name)s,%(levelname)s:%(message)s', datefmt="%H:%M:%S") 66 | ch.setFormatter(shortFormatter) 67 | logger.addHandler(ch) 68 | logger.setLevel(log_level) 69 | 70 | # catch the uncatched exception 71 | sys.excepthook = my_excepthook 72 | 73 | 74 | def my_excepthook(exc_type, exc_value, exc_traceback): 75 | root_logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) 76 | -------------------------------------------------------------------------------- /edwinServer/common/os_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from __future__ import absolute_import 5 | from __future__ import with_statement 6 | import os 7 | import platform 8 | import subprocess 9 | import socket 10 | from datetime import datetime 11 | import time 12 | 13 | 14 | def getIpAddr(): 15 | myname = socket.getfqdn(socket.gethostname()) 16 | myaddr = socket.gethostbyname(myname) 17 | return myaddr 18 | 19 | 20 | def getHostName(): 21 | return platform.node() 22 | 23 | 24 | def isJython(): 25 | return (platform.platform().find('Java') >= 0) 26 | 27 | 28 | def isWindows(): 29 | ''' 30 | remark: sys.platform and os.name cannot identify in Jython, so use platform.platform() 31 | ''' 32 | return (platform.platform().find('Windows') >= 0) 33 | 34 | 35 | def isLinux(): 36 | ''' 37 | remark: sys.platform and os.name cannot identify in Jython, so use platform.platform() 38 | ''' 39 | return (platform.platform().find('Linux') >= 0) 40 | 41 | 42 | def isMac(): 43 | ''' 44 | remark: sys.platform and os.name cannot identify in Jython, so use platform.platform() 45 | ''' 46 | plat = platform.platform() 47 | return (plat.find('Darwin') >= 0) or (plat.find('MacOS') >= 0) 48 | 49 | 50 | def isPosix(): 51 | if isJython() == False: 52 | return os.name == 'posix' 53 | elif isWindows(): 54 | return False 55 | elif isMac(): 56 | return False 57 | else: 58 | return True 59 | 60 | 61 | def getNewLineSeperator(): 62 | if isWindows(): 63 | return '\r\n' 64 | elif isMac(): 65 | return '\r' 66 | else: 67 | return '\n' 68 | 69 | 70 | def launchCmdLine(*popenargs, **kwargs): 71 | ''' 72 | run command line, return process object 73 | ''' 74 | # capture cmd output 75 | # For windows, shell should be False, but there is a bug to run cmd.exe built-in command. http://bugs.python.org/issue8224, we have to set shell=True 76 | # For Linux, shell=True 77 | if isWindows(): 78 | shellValue = True 79 | else: 80 | shellValue = True 81 | 82 | process = subprocess.Popen(shell=shellValue, stdout=subprocess.PIPE, stderr=subprocess.PIPE, *popenargs, **kwargs) 83 | return process 84 | 85 | 86 | def waitResultOfCmdProcess(process): 87 | ''' 88 | check process result, return exitcode, output, error message together 89 | ''' 90 | output, error = process.communicate() 91 | exitcode = process.wait() 92 | return (exitcode, output, error) 93 | 94 | 95 | def waitAndLogResultOfCmdProcess(process, logFile, processStartTime=None): 96 | ''' 97 | check process result, 98 | and then save output, error message together to log file, 99 | return exitcode 100 | ''' 101 | (exitcode, output, error) = waitResultOfCmdProcess(process) 102 | 103 | if logFile is not None: 104 | newlines = getNewLineSeperator() 105 | with open(logFile, "w") as f: 106 | f.write("---process id: %d" % process.pid) 107 | f.write(newlines) 108 | 109 | if processStartTime is not None: 110 | f.write("---started at: %s" % processStartTime) 111 | f.write(newlines) 112 | 113 | f.write("---ended at: %s" % datetime.now()) 114 | f.write(newlines) 115 | 116 | f.write("---exit code: %d" % exitcode) 117 | f.write(newlines) 118 | 119 | f.write("---error message: ") 120 | f.write(newlines) 121 | f.write(error) 122 | f.write(newlines) 123 | 124 | f.write("---output message: ") 125 | f.write(newlines) 126 | f.write(output) 127 | f.write(newlines) 128 | 129 | f.close() 130 | return exitcode 131 | 132 | 133 | def getFileLastModifiyTime(fname): 134 | mtime = time.ctime(os.stat(fname).st_mtime) 135 | tim = datetime.strptime(mtime[8:], "%d %H:%M:%S %Y") 136 | return tim 137 | 138 | 139 | def getPath(fullFileName): 140 | ''' 141 | c://1 -> c:\\1 142 | 143 | ''' 144 | (dirName, unused) = os.path.split(fullFileName) 145 | return os.path.normpath(dirName) 146 | 147 | 148 | def getPathAndFileName(fullFileName): 149 | ''' 150 | return (path, fileName) 151 | ''' 152 | (dirName, fileName) = os.path.split(fullFileName) 153 | return (os.path.normpath(dirName), fileName) 154 | 155 | 156 | def getLoggingFileName(py_main_file, log_short_path='logs'): 157 | (base_path, py_file_name) = getPathAndFileName(py_main_file) 158 | log_path = os.path.join(base_path, log_short_path) 159 | (log_short_name, ext) = os.path.splitext(py_file_name) 160 | if not os.path.exists(log_path): 161 | os.mkdir(log_path) 162 | return os.path.join(log_path, log_short_name + '.log') 163 | 164 | 165 | def sleep(SLEEP_MINUTES): 166 | # time.sleep() cannot wake up again, so use Java Thread.sleep() instead 167 | # time.sleep(SLEEP_MINUTES*60) 168 | if isJython(): 169 | 170 | from java.lang import Thread 171 | Thread.sleep(SLEEP_MINUTES * 60 * 1000) 172 | else: 173 | import time 174 | time.sleep(SLEEP_MINUTES * 60) 175 | -------------------------------------------------------------------------------- /edwinServer/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/scripts/__init__.py -------------------------------------------------------------------------------- /edwinServer/scripts/alarm_send.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | ''' 5 | from __future__ import absolute_import 6 | import time 7 | import logging 8 | import logging.config 9 | from edwinServer.common import my_logging 10 | from edwinServer.common import const 11 | from edwinServer.common import conf 12 | from edwinServer.common import os_helper 13 | from edwinServer.common import database_all 14 | from edwinServer.common.mail_service import MailSender 15 | from edwinServer.common.model_meta import dashboard_check_log 16 | 17 | 18 | # logging.basicConfig(level=logging.DEBUG) # use for development 19 | log_file = os_helper.getLoggingFileName(__file__) 20 | root_logger = logging.getLogger() 21 | my_logging.configureLogger(root_logger, log_file, conf.log_level) 22 | logger = logging.getLogger(__name__) # get current file logger 23 | 24 | 25 | def check(): 26 | logger.info("Get the alarm list need to send out.") 27 | alarms = dashboard_check_log.getAlarmSendList() 28 | 29 | alarmCount = len(alarms) 30 | logger.info("There are %d alarms totally." % (alarmCount)) 31 | 32 | if alarms: 33 | mailSender = MailSender() 34 | for alarm in alarms: 35 | logger.info("---Begin to send alarm %s." % alarm) 36 | alarm.markSendAsStarted() 37 | 38 | logger.info("---To send alarm %s." % alarm) 39 | mailSender.send(alarm) 40 | 41 | sendStatus = const.ALARM_SEND_STATUS_SUCCESSFUL 42 | alarm.markSendAsFinished(sendStatus) 43 | logger.info("---End to send alarm %s." % alarm) 44 | database_all.closeAllConnections() 45 | 46 | 47 | def main(): 48 | in_daemon_mode = conf.alarm_send_in_daemon_mode 49 | if not in_daemon_mode: 50 | check() 51 | else: 52 | from apscheduler.scheduler import Scheduler 53 | minuteScheduler = Scheduler() 54 | sleep_seconds = 60 # just 60 seconds 55 | minuteScheduler.add_interval_job(check, seconds=sleep_seconds) 56 | minuteScheduler.start() 57 | while 1: 58 | time.sleep(9999) 59 | minuteScheduler.shutdown() 60 | 61 | 62 | if __name__ == "__main__": 63 | logger.info("=================================") 64 | logger.info("===module: alarm_send") 65 | logger.info("=================================") 66 | 67 | main() 68 | 69 | logger.info("===End to run module: alarm_send") 70 | -------------------------------------------------------------------------------- /edwinServer/site_packages/ConcurrentLogHandler084/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: ConcurrentLogHandler 3 | Version: 0.8.4 4 | Summary: Concurrent logging handler (drop-in replacement for RotatingFileHandler) 5 | Home-page: http://pypi.python.org/pypi/ConcurrentLogHandler 6 | Author: Lowell Alleman 7 | Author-email: lowell87@gmail.com 8 | License: http://www.apache.org/licenses/LICENSE-2.0 9 | Description: Overview 10 | ======== 11 | This module provides an additional log handler for Python's standard logging 12 | package (PEP 282). This handler will write log events to log file which is 13 | rotated when the log file reaches a certain size. Multiple processes can 14 | safely write to the same log file concurrently. 15 | 16 | Details 17 | ======= 18 | .. _portalocker: http://code.activestate.com/recipes/65203/ 19 | 20 | The ``ConcurrentRotatingFileHandler`` class is a drop-in replacement for 21 | Python's standard log handler ``RotatingFileHandler``. This module uses file 22 | locking so that multiple processes can concurrently log to a single file without 23 | dropping or clobbering log events. This module provides a file rotation scheme 24 | like with ``RotatingFileHanler``. Extra care is taken to ensure that logs 25 | can be safely rotated before the rotation process is started. (This module works 26 | around the file rename issue with ``RotatingFileHandler`` on Windows, where a 27 | rotation failure means that all subsequent log events are dropped). 28 | 29 | This module attempts to preserve log records at all cost. This means that log 30 | files will grow larger than the specified maximum (rotation) size. So if disk 31 | space is tight, you may want to stick with ``RotatingFileHandler``, which will 32 | strictly adhere to the maximum file size. 33 | 34 | If you have multiple instances of a script (or multiple scripts) all running at 35 | the same time and writing to the same log file, then *all* of the scripts should 36 | be using ``ConcurrentRotatingFileHandler``. You should not attempt to mix 37 | and match ``RotatingFileHandler`` and ``ConcurrentRotatingFileHandler``. 38 | 39 | This package bundles `portalocker`_ to deal with file locking. Please be aware 40 | that portalocker only supports Unix (posix) an NT platforms at this time, and 41 | therefore this package only supports those platforms as well. 42 | 43 | Installation 44 | ============ 45 | Use the following command to install this package:: 46 | 47 | easy_install ConcurrentLogHandler 48 | 49 | If you are installing from source, you can use:: 50 | 51 | python setup.py install 52 | 53 | 54 | Examples 55 | ======== 56 | 57 | Simple Example 58 | -------------- 59 | Here is a example demonstrating how to use this module directly (from within 60 | Python code):: 61 | 62 | from logging import getLogger, INFO 63 | from cloghandler import ConcurrentRotatingFileHandler 64 | import os 65 | 66 | log = getLogger() 67 | # Use an absolute path to prevent file rotation trouble. 68 | logfile = os.path.abspath("mylogfile.log") 69 | # Rotate log after reaching 512K, keep 5 old copies. 70 | rotateHandler = ConcurrentRotatingFileHandler(logfile, "a", 512*1024, 5) 71 | log.addHandler(rotateHandler) 72 | log.setLevel(INFO) 73 | 74 | log.info("Here is a very exciting log message, just for you") 75 | 76 | 77 | Automatic fallback example 78 | -------------------------- 79 | If you are distributing your code and you are unsure if the 80 | `ConcurrentLogHandler` package has been installed everywhere your code will run, 81 | Python makes it easy to gracefully fallback to the built in 82 | `RotatingFileHandler`, here is an example:: 83 | 84 | try: 85 | from cloghandler import ConcurrentRotatingFileHandler as RFHandler 86 | except ImportError: 87 | # Next 2 lines are optional: issue a warning to the user 88 | from warnings import warn 89 | warn("ConcurrentLogHandler package not installed. Using builtin log handler") 90 | from logging.handlers import RotatingFileHandler as RFHandler 91 | 92 | log = getLogger() 93 | rotateHandler = RFHandler("/path/to/mylogfile.log", "a", 1048576, 15) 94 | log.addHandler(rotateHandler) 95 | 96 | 97 | 98 | Config file example 99 | ------------------- 100 | This example shows you how to use this log handler with the logging config file 101 | parser. This allows you to keep your logging configuration code separate from 102 | your application code. 103 | 104 | Example config file: ``logging.ini``:: 105 | 106 | [loggers] 107 | keys=root 108 | 109 | [handlers] 110 | keys=hand01 111 | 112 | [formatters] 113 | keys=form01 114 | 115 | [logger_root] 116 | level=NOTSET 117 | handlers=hand01 118 | 119 | [handler_hand01] 120 | class=handlers.ConcurrentRotatingFileHandler 121 | level=NOTSET 122 | formatter=form01 123 | args=("rotating.log", "a", 512*1024, 5) 124 | 125 | [formatter_form01] 126 | format=%(asctime)s %(levelname)s %(message)s 127 | 128 | Example Python code: ``app.py``:: 129 | 130 | import logging, logging.config 131 | import cloghandler 132 | 133 | logging.config.fileConfig("logging.ini") 134 | log = logging.getLogger() 135 | log.info("Here is a very exciting log message, just for you") 136 | 137 | 138 | Change Log 139 | ========== 140 | 141 | - 0.8.4: Fixed lock-file naming issue 142 | * Resovled a minor issue where lock-files would be improperly named if the 143 | log file contained ".log" in the middle of the log name. For example, if 144 | you log file was "/var/log/mycompany.logging.mysource.log", the lock file 145 | would be named "/var/log/mycompany.ging.mysource.lock", which is not correct. 146 | Thanks to Dirk Rothe for pointing this out. Since this introduce a slight 147 | lock-file behaviour difference, make sure all concurent writers are updated 148 | to 0.8.4 at the same time if this issue effects you. 149 | * Updated ez_setup.py to 0.6c11 150 | 151 | - 0.8.3: Fixed a log file rotation bug and updated docs 152 | * Fixed a bug that happens after log rotation when multiple processes are 153 | witting to the same log file. Each process ends up writing to their own 154 | log file ("log.1" or "log.2" instead of "log"). The fix is simply to reopen 155 | the log file and check the size again. I do not believe this bug results in 156 | data loss; however, this certainly was not the desired behavior. (A big 157 | thanks goes to Oliver Tonnhofer for finding, documenting, and providing a 158 | patch for this bug.) 159 | * Cleanup the docs. (aka "the page you are reading right now") I fixed some 160 | silly mistakes and typos... who writes this stuff? 161 | 162 | - 0.8.2: Minor bug fix release (again) 163 | * Found and resolved another issue with older logging packages that do not 164 | support encoding. 165 | 166 | - 0.8.1: Minor bug fix release 167 | * Now importing "codecs" directly; I found some slight differences in the 168 | logging module in different Python 2.4.x releases that caused the module to 169 | fail to load. 170 | 171 | - 0.8.0: Minor feature release 172 | * Add better support for using ``logging.config.fileConfig()``. This class 173 | is now available using ``class=handlers.ConcurrentRotatingFileHandler``. 174 | * Minor changes in how the ``filename`` parameter is handled when given a 175 | relative path. 176 | 177 | - 0.7.4: Minor bug fix 178 | * Fixed a typo in the package description (incorrect class name) 179 | * Added a change log; which you are reading now. 180 | * Fixed the ``close()`` method to no longer assume that stream is still 181 | open. 182 | 183 | To-do 184 | ===== 185 | * This module has had minimal testing in a multi-threaded process. I see no 186 | reason why this should be an issue, but no stress-testing has been done in a 187 | threaded situation. If this is important to you, you could always add 188 | threading support to the ``stresstest.py`` script and send me the patch. 189 | 190 | Platform: nt 191 | Platform: posix 192 | Classifier: Development Status :: 4 - Beta 193 | Classifier: Development Status :: 5 - Production/Stable 194 | Classifier: Topic :: System :: Logging 195 | Classifier: Operating System :: POSIX 196 | Classifier: Operating System :: Microsoft :: Windows 197 | Classifier: Programming Language :: Python 198 | Classifier: Topic :: Software Development :: Libraries :: Python Modules 199 | Classifier: License :: OSI Approved :: Apache Software License 200 | -------------------------------------------------------------------------------- /edwinServer/site_packages/ConcurrentLogHandler084/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/site_packages/ConcurrentLogHandler084/__init__.py -------------------------------------------------------------------------------- /edwinServer/site_packages/ConcurrentLogHandler084/bugfix_by_me.txt: -------------------------------------------------------------------------------- 1 | ======================= 2 | exception 3 | ======================= 4 | self.stream.flush() 5 | ValueError: I/O operation on closed file 6 | Error in sys.exitfunc: 7 | 8 | 9 | ======================= 10 | fix 11 | ======================= 12 | Line 194: 13 | self.stream.flush() 14 | 15 | Add closed evaluation before flush(), 16 | if self.stream.closed==False: 17 | self.stream.flush() 18 | 19 | 20 | -------------------------------------------------------------------------------- /edwinServer/site_packages/ConcurrentLogHandler084/portalocker.py: -------------------------------------------------------------------------------- 1 | #@PydevCodeAnalysisIgnore 2 | # portalocker.py - Cross-platform (posix/nt) API for flock-style file locking. 3 | # Requires python 1.5.2 or better. 4 | """Cross-platform (posix/nt) API for flock-style file locking. 5 | 6 | Synopsis: 7 | 8 | import portalocker 9 | file = open("somefile", "r+") 10 | portalocker.lock(file, portalocker.LOCK_EX) 11 | file.seek(12) 12 | file.write("foo") 13 | file.close() 14 | 15 | If you know what you're doing, you may choose to 16 | 17 | portalocker.unlock(file) 18 | 19 | before closing the file, but why? 20 | 21 | Methods: 22 | 23 | lock( file, flags ) 24 | unlock( file ) 25 | 26 | Constants: 27 | 28 | LOCK_EX 29 | LOCK_SH 30 | LOCK_NB 31 | 32 | Exceptions: 33 | 34 | LockException 35 | 36 | Notes: 37 | 38 | For the 'nt' platform, this module requires the Python Extensions for Windows. 39 | Be aware that this may not work as expected on Windows 95/98/ME. 40 | 41 | History: 42 | 43 | I learned the win32 technique for locking files from sample code 44 | provided by John Nielsen in the documentation 45 | that accompanies the win32 modules. 46 | 47 | Author: Jonathan Feinberg , 48 | Lowell Alleman 49 | Version: $Id: portalocker.py 5488 2008-05-21 20:49:38Z lowell $ 50 | 51 | 52 | http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203 53 | """ 54 | 55 | 56 | __all__ = [ 57 | "lock", 58 | "unlock", 59 | "LOCK_EX", 60 | "LOCK_SH", 61 | "LOCK_NB", 62 | "LockException", 63 | ] 64 | 65 | import os 66 | from types import IntType 67 | 68 | class LockException(Exception): 69 | # Error codes: 70 | LOCK_FAILED = 1 71 | 72 | if os.name == 'nt': 73 | import win32con 74 | import win32file 75 | import pywintypes 76 | LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK 77 | LOCK_SH = 0 # the default 78 | LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY 79 | # is there any reason not to reuse the following structure? 80 | __overlapped = pywintypes.OVERLAPPED() 81 | elif os.name == 'posix': 82 | import fcntl 83 | LOCK_EX = fcntl.LOCK_EX 84 | LOCK_SH = fcntl.LOCK_SH 85 | LOCK_NB = fcntl.LOCK_NB 86 | else: 87 | raise RuntimeError, "PortaLocker only defined for nt and posix platforms" 88 | 89 | 90 | 91 | def _getfd(file): 92 | """ Get a file-descriptor from a file object or file-descriptor. """ 93 | if hasattr(file, "fileno"): 94 | return file.fileno() 95 | elif type(file) == IntType: 96 | return file 97 | else: 98 | raise TypeError("File object or file descriptor required, but %s " 99 | "was provided." % type(file)) 100 | 101 | 102 | if os.name == 'nt': 103 | def lock(file, flags): 104 | hfile = win32file._get_osfhandle(_getfd(file)) 105 | try: 106 | win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped) 107 | except pywintypes.error, exc_value: 108 | # error: (33, 'LockFileEx', 'The process cannot access the file because another process has locked a portion of the file.') 109 | if exc_value[0] == 33: 110 | raise LockException(LockException.LOCK_FAILED, exc_value[2]) 111 | else: 112 | # Q: Are there exceptions/codes we should be dealing with here? 113 | raise 114 | 115 | def unlock(file): 116 | hfile = win32file._get_osfhandle(_getfd(file)) 117 | try: 118 | win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped) 119 | except pywintypes.error, exc_value: 120 | if exc_value[0] == 158: 121 | # error: (158, 'UnlockFileEx', 'The segment is already unlocked.') 122 | # To match the 'posix' implementation, silently ignore this error 123 | pass 124 | else: 125 | # Q: Are there exceptions/codes we should be dealing with here? 126 | raise 127 | 128 | elif os.name == 'posix': 129 | def lock(file, flags): 130 | try: 131 | fcntl.flock(_getfd(file), flags) 132 | except IOError, exc_value: 133 | # IOError: [Errno 11] Resource temporarily unavailable 134 | if exc_value[0] == 11: 135 | raise LockException(LockException.LOCK_FAILED, exc_value[1]) 136 | else: 137 | raise 138 | 139 | def unlock(file): 140 | fcntl.flock(_getfd(file), fcntl.LOCK_UN) 141 | 142 | 143 | 144 | if __name__ == '__main__': 145 | from time import time, strftime, localtime 146 | import sys 147 | import portalocker 148 | 149 | log = open('log.txt', "a+") 150 | portalocker.lock(log, portalocker.LOCK_EX) 151 | 152 | timestamp = strftime("%m/%d/%Y %H:%M:%S\n", localtime(time())) 153 | log.write( timestamp ) 154 | 155 | print "Wrote lines. Hit enter to release lock." 156 | dummy = sys.stdin.readline() 157 | 158 | log.close() 159 | 160 | -------------------------------------------------------------------------------- /edwinServer/site_packages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/site_packages/__init__.py -------------------------------------------------------------------------------- /edwinServer/site_packages/dbRowFactory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/site_packages/dbRowFactory/__init__.py -------------------------------------------------------------------------------- /edwinServer/web/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | from __future__ import absolute_import 5 | 6 | __version__='1.0.0' 7 | __author__='Harry Liu' 8 | 9 | -------------------------------------------------------------------------------- /edwinServer/web/app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Created on 2014-2-10 4 | 5 | ''' 6 | from __future__ import absolute_import, print_function, unicode_literals 7 | from flask import Flask, g, render_template, send_from_directory, make_response, jsonify 8 | import os 9 | import os.path 10 | from .excepts import InvalidUsage 11 | from ...common import database_meta 12 | from .api import mod as checksModule 13 | from .views import mod as dashboardModule 14 | 15 | _basedir = os.path.abspath(os.path.dirname(__file__)) 16 | configPy = os.path.join(os.path.join(_basedir, os.path.pardir), 'conf.py') 17 | 18 | app = Flask(__name__) # create our application object 19 | app.config.from_pyfile(configPy) 20 | 21 | flask_sqlalchemy_used = False # when Flask-SQLAlchemy used 22 | 23 | 24 | # if app.debug: 25 | # from flask_debugtoolbar import DebugToolbarExtension 26 | # toolbar = DebugToolbarExtension(app) 27 | 28 | app.register_blueprint(checksModule) 29 | app.register_blueprint(dashboardModule) 30 | 31 | 32 | def connect_db(): # when Flask-SQLAlchemy not used 33 | return database_meta.openConnection() 34 | 35 | 36 | def close_db(): 37 | database_meta.closeConnection() 38 | 39 | 40 | @app.before_request 41 | def before_request(): 42 | app.logger.info("before_request() called.") 43 | 44 | """Make sure we are connected to the database each request.""" 45 | if not flask_sqlalchemy_used: 46 | connect_db() 47 | 48 | 49 | @app.teardown_request 50 | def teardown_request(response): 51 | app.logger.info("after_request() called.") 52 | 53 | """Closes the database again at the end of the request.""" 54 | if not flask_sqlalchemy_used: 55 | close_db() 56 | return response 57 | 58 | 59 | #---------------------------------------- 60 | # controllers 61 | #---------------------------------------- 62 | @app.route('/favicon.ico') 63 | def favicon(): 64 | return send_from_directory(os.path.join(app.root_path, 'static'), 65 | 'favicon.ico', mimetype='image/vnd.microsoft.icon') 66 | 67 | 68 | @app.route("/") 69 | def index(): 70 | return render_template('index.html') 71 | 72 | 73 | @app.errorhandler(InvalidUsage) 74 | def handle_invalid_usage(error): 75 | response = jsonify(error.to_dict()) 76 | response.status_code = error.status_code 77 | return response 78 | 79 | 80 | @app.errorhandler(404) 81 | def page_not_found(error): 82 | app.logger.exception(error) 83 | return make_response(jsonify({'error': 'Not found'}), 404) 84 | -------------------------------------------------------------------------------- /edwinServer/web/app/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Created on 2014-2-10 4 | 5 | ''' 6 | from __future__ import absolute_import 7 | from flask import Blueprint, abort, request, flash, jsonify 8 | from flask.globals import current_app 9 | from ...common.job_state_updater import JobStateUpdater 10 | from ...common.model_meta import dashboard_check_cfg 11 | 12 | 13 | mod = Blueprint('checks', __name__) # register the users blueprint module 14 | 15 | 16 | ''' 17 | checkResult_json = { 18 | 'status': 'NORMAL', 19 | 'value': 100 20 | 'detail_msg':'Some detailed message' 21 | 'notification_msg': 'some detailed message for email notification' 22 | } 23 | 24 | exceptionInfo_json = { 25 | 'exception_msg': 'Some exception message' 26 | } 27 | 28 | ''' 29 | 30 | 31 | @mod.route("/api/v1.0/info/", methods=('GET', 'POST')) 32 | def getCheckItemCfg_view(check_itm_code): 33 | cfg = dashboard_check_cfg.getCfgInDb(check_itm_code) 34 | if cfg is None: 35 | flash('Undefined check item %s.' % check_itm_code, 'error') 36 | current_app.logger.error('getCheckItemCfg_view() function: Undefined check item %s.' % check_itm_code) 37 | abort(404) # page not found 38 | else: 39 | dic = {'itm_code': cfg.itm_code, 'itm_title': cfg.itm_title, 'itm_category': cfg.itm_category, 'enabled_flag': cfg.enabled_flag, 'host': cfg.host, 'check_script': cfg.check_script, 'check_interval_minute': cfg.check_interval_minute, 'check_value_is_number': cfg.check_value_is_number, 'description': cfg.description, 'warning_limit': cfg.warning_limit, 'critical_limit': cfg.critical_limit, 'shadow_data': cfg.shadow_data, 'owner_team_list': cfg.owner_team_list, 'warning_mail_cc': cfg.warning_mail_cc, 'critical_mail_cc': cfg.critical_mail_cc, 'critical_sms_flag': cfg.critical_sms_flag, 'critical_call_flag': cfg.critical_call_flag, 'allow_repeated_sms_alarm': cfg.allow_repeated_sms_alarm, 'allow_repeated_call_alarm': cfg.allow_repeated_call_alarm, 'allow_repeated_mail_alarm': cfg.allow_repeated_mail_alarm 40 | } 41 | return jsonify(dic), 201 42 | 43 | 44 | @mod.route("/api/v1.0/results/", methods=('POST', 'GET')) 45 | @mod.route("/api/v1.0/checks/", methods=('POST', 'GET')) 46 | def saveCheckResultAPI_view(check_itm_code): 47 | state_updater = JobStateUpdater(check_itm_code) 48 | if state_updater.isUndefinedCheckItem(): 49 | flash('Undefined check item %s.' % check_itm_code, 'error') 50 | current_app.logger.error('saveCheckResultAPI_view() function: Undefined check item %s.' % check_itm_code) 51 | abort(404) # page not found 52 | 53 | if not request.json: 54 | current_app.logger.error('saveCheckResultAPI_view() function: Payload json must be set.') 55 | abort(400) # bad request 56 | 57 | if state_updater.resultShouldBeNumerical(): 58 | if not 'value' in request.json: 59 | current_app.logger.error('saveCheckResultAPI_view() function: value must be set.') 60 | abort(400) # bad request 61 | else: 62 | value = request.json['value'] 63 | 64 | detail_msg = request.json.get('detail_msg', "") 65 | notification_msg = request.json.get('notification_msg', "") 66 | state_updater.updateNumericalResult(value, detail_msg, notification_msg) 67 | else: 68 | if not request.json or not 'status' in request.json: 69 | abort(400) # bad request 70 | else: 71 | status = request.json['status'] 72 | detail_msg = request.json.get('detail_msg', "") 73 | notification_msg = request.json.get('notification_msg', "") 74 | state_updater.updateNonnumericalResult(status, detail_msg, notification_msg) 75 | 76 | return jsonify({'echo_msg': 'successful'}), 201 77 | 78 | 79 | @mod.route("/api/v1.0/exceptions/", methods=('POST', 'GET')) 80 | def registerExceptionAPI_view(check_itm_code): 81 | exception_msg = request.json.get('exception_msg', "No exception provided.") 82 | state_updater = JobStateUpdater(check_itm_code) 83 | if state_updater.isUndefinedCheckItem(): 84 | exception_msg = "Check item %s not found. The original message: %s" % (check_itm_code, exception_msg) 85 | current_app.logger.error('registerExceptionAPI_view(), Exception message: %s' % exception_msg) 86 | state_updater.registerCheckingException(exception_msg) 87 | return jsonify({'echo_msg': 'successful'}), 201 88 | 89 | 90 | #----------------------------------------- 91 | # test api below 92 | #----------------------------------------- 93 | 94 | 95 | @mod.route("/api/v1.0/test/simple", methods=('GET', 'POST')) 96 | def test_simple_view(): 97 | return jsonify({'request.method': request.method, 'save_status': 'successful'}), 201 98 | 99 | 100 | @mod.route("/api/v1.0/test/str_arg/", methods=('POST', 'GET')) 101 | def test_str_argument_view(check_itm_code): 102 | abort(400) 103 | return jsonify({'request.method': request.method, 'item': check_itm_code, 'save_status': 'successful'}), 201 104 | 105 | 106 | @mod.route("/api/v1.0/test/int_arg/", methods=('POST', 'GET')) 107 | def test_int_argument_view(seq_no): 108 | return jsonify({'request.method': request.method, 'seq_no': seq_no, 'save_status': 'successful'}), 201 109 | 110 | 111 | @mod.route("/api/v1.0/test/json_post/", methods=('POST', 'GET')) 112 | def test_json_post_view(check_itm_code): 113 | if not request.json: 114 | abort(400) # bad request 115 | 116 | if not 'value' in request.json: 117 | abort(400) # bad request 118 | 119 | value = request.json['value'] 120 | detail_msg = request.json.get('detail_msg', "") # if detail_msg is not set, use empty 121 | return jsonify({'request.method': request.method, 'value': value, 'detail_msg': detail_msg, 'save_status': 'successful'}), 201 122 | -------------------------------------------------------------------------------- /edwinServer/web/app/excepts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Created on 2014-3-11 4 | 5 | ''' 6 | 7 | from flask import jsonify 8 | 9 | 10 | class InvalidUsage(Exception): 11 | status_code = 400 12 | 13 | def __init__(self, message, status_code=None, payload=None): 14 | Exception.__init__(self) 15 | self.message = message 16 | if status_code is not None: 17 | self.status_code = status_code 18 | self.payload = payload 19 | 20 | def to_dict(self): 21 | rv = dict(self.payload or ()) 22 | rv['message'] = self.message 23 | return rv 24 | -------------------------------------------------------------------------------- /edwinServer/web/app/static/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | body { 7 | padding-top: 50px; 8 | } 9 | 10 | 11 | /* 12 | * Global add-ons 13 | */ 14 | 15 | .sub-header { 16 | padding-bottom: 10px; 17 | border-bottom: 1px solid #eee; 18 | } 19 | 20 | 21 | /* 22 | * Sidebar 23 | */ 24 | 25 | /* Hide for mobile, show later */ 26 | .sidebar { 27 | display: none; 28 | } 29 | @media (min-width: 768px) { 30 | .sidebar { 31 | position: fixed; 32 | top: 51px; 33 | bottom: 0; 34 | left: 0; 35 | z-index: 1000; 36 | display: block; 37 | padding: 20px; 38 | overflow-x: hidden; 39 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 40 | background-color: #f5f5f5; 41 | border-right: 1px solid #eee; 42 | } 43 | } 44 | 45 | /* Sidebar navigation */ 46 | .nav-sidebar { 47 | margin-right: -21px; /* 20px padding + 1px border */ 48 | margin-bottom: 20px; 49 | margin-left: -20px; 50 | } 51 | .nav-sidebar > li > a { 52 | padding-right: 20px; 53 | padding-left: 20px; 54 | } 55 | .nav-sidebar > .active > a { 56 | color: #fff; 57 | background-color: #428bca; 58 | } 59 | 60 | 61 | 62 | /* 63 | * Main content 64 | */ 65 | 66 | .main { 67 | padding: 20px; 68 | } 69 | @media (min-width: 768px) { 70 | .main { 71 | padding-right: 40px; 72 | padding-left: 40px; 73 | } 74 | } 75 | .main .page-header { 76 | margin-top: 0; 77 | } 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /edwinServer/web/app/static/css/large_svg.css: -------------------------------------------------------------------------------- 1 | svg{ 2 | width:2000px; 3 | } 4 | -------------------------------------------------------------------------------- /edwinServer/web/app/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/favicon.ico -------------------------------------------------------------------------------- /edwinServer/web/app/static/favicon_2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/favicon_2.ico -------------------------------------------------------------------------------- /edwinServer/web/app/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /edwinServer/web/app/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /edwinServer/web/app/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/box_arrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/box_arrow.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/box_bottomborder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/box_bottomborder.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/box_leftborder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/box_leftborder.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/box_lowerleft.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/box_lowerleft.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/box_lowerright.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/box_lowerright.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/box_rightborder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/box_rightborder.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/box_topborder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/box_topborder.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/box_upperleft.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/box_upperleft.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/box_upperright.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/box_upperright.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/bug-high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/bug-high.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/bug-low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/bug-low.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/bug-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/bug-medium.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/check_green.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/check_green.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/comment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/comment.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/critical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/critical.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/face16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/face16.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/face24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/face24.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/face32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/face32.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/feed.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/file.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/folder.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/controlbar2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/controlbar2.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/controlbar3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/controlbar3.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/controlbar4-hover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/controlbar4-hover.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/controlbar4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/controlbar4.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/fullexpand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/fullexpand.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/geckodimmer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/geckodimmer.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/loader.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/loader.white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/loader.white.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/outlines/Outlines.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/outlines/Outlines.psd -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/outlines/beveled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/outlines/beveled.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/outlines/drop-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/outlines/drop-shadow.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/outlines/glossy-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/outlines/glossy-dark.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/outlines/outer-glow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/outlines/outer-glow.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/outlines/rounded-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/outlines/rounded-black.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/outlines/rounded-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/outlines/rounded-white.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/resize.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/resize.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/zoomin.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/zoomin.cur -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/highslide/zoomout.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/highslide/zoomout.cur -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/home.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/home.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/ico_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/ico_user.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/instruct.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/instruct.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/loading.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/loading3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/loading3.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/logo.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/normal.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/outdated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/outdated.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/outdated2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/outdated2.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/outdated3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/outdated3.jpg -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/p.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/p.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/parent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/parent.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/plus_green.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/plus_green.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/separator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/separator.gif -------------------------------------------------------------------------------- /edwinServer/web/app/static/img/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryliu/edwin/0c699709853f3ba469c905136a7de18df8bc4d78/edwinServer/web/app/static/img/warning.png -------------------------------------------------------------------------------- /edwinServer/web/app/static/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | ;(function(window, document) { 5 | /*jshint evil:true */ 6 | /** version */ 7 | var version = '3.7.2'; 8 | 9 | /** Preset options */ 10 | var options = window.html5 || {}; 11 | 12 | /** Used to skip problem elements */ 13 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; 14 | 15 | /** Not all elements can be cloned in IE **/ 16 | var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; 17 | 18 | /** Detect whether the browser supports default html5 styles */ 19 | var supportsHtml5Styles; 20 | 21 | /** Name of the expando, to work with multiple documents or to re-shiv one document */ 22 | var expando = '_html5shiv'; 23 | 24 | /** The id for the the documents expando */ 25 | var expanID = 0; 26 | 27 | /** Cached data for each document */ 28 | var expandoData = {}; 29 | 30 | /** Detect whether the browser supports unknown elements */ 31 | var supportsUnknownElements; 32 | 33 | (function() { 34 | try { 35 | var a = document.createElement('a'); 36 | a.innerHTML = ''; 37 | //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles 38 | supportsHtml5Styles = ('hidden' in a); 39 | 40 | supportsUnknownElements = a.childNodes.length == 1 || (function() { 41 | // assign a false positive if unable to shiv 42 | (document.createElement)('a'); 43 | var frag = document.createDocumentFragment(); 44 | return ( 45 | typeof frag.cloneNode == 'undefined' || 46 | typeof frag.createDocumentFragment == 'undefined' || 47 | typeof frag.createElement == 'undefined' 48 | ); 49 | }()); 50 | } catch(e) { 51 | // assign a false positive if detection fails => unable to shiv 52 | supportsHtml5Styles = true; 53 | supportsUnknownElements = true; 54 | } 55 | 56 | }()); 57 | 58 | /*--------------------------------------------------------------------------*/ 59 | 60 | /** 61 | * Creates a style sheet with the given CSS text and adds it to the document. 62 | * @private 63 | * @param {Document} ownerDocument The document. 64 | * @param {String} cssText The CSS text. 65 | * @returns {StyleSheet} The style element. 66 | */ 67 | function addStyleSheet(ownerDocument, cssText) { 68 | var p = ownerDocument.createElement('p'), 69 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; 70 | 71 | p.innerHTML = 'x'; 72 | return parent.insertBefore(p.lastChild, parent.firstChild); 73 | } 74 | 75 | /** 76 | * Returns the value of `html5.elements` as an array. 77 | * @private 78 | * @returns {Array} An array of shived element node names. 79 | */ 80 | function getElements() { 81 | var elements = html5.elements; 82 | return typeof elements == 'string' ? elements.split(' ') : elements; 83 | } 84 | 85 | /** 86 | * Extends the built-in list of html5 elements 87 | * @memberOf html5 88 | * @param {String|Array} newElements whitespace separated list or array of new element names to shiv 89 | * @param {Document} ownerDocument The context document. 90 | */ 91 | function addElements(newElements, ownerDocument) { 92 | var elements = html5.elements; 93 | if(typeof elements != 'string'){ 94 | elements = elements.join(' '); 95 | } 96 | if(typeof newElements != 'string'){ 97 | newElements = newElements.join(' '); 98 | } 99 | html5.elements = elements +' '+ newElements; 100 | shivDocument(ownerDocument); 101 | } 102 | 103 | /** 104 | * Returns the data associated to the given document 105 | * @private 106 | * @param {Document} ownerDocument The document. 107 | * @returns {Object} An object of data. 108 | */ 109 | function getExpandoData(ownerDocument) { 110 | var data = expandoData[ownerDocument[expando]]; 111 | if (!data) { 112 | data = {}; 113 | expanID++; 114 | ownerDocument[expando] = expanID; 115 | expandoData[expanID] = data; 116 | } 117 | return data; 118 | } 119 | 120 | /** 121 | * returns a shived element for the given nodeName and document 122 | * @memberOf html5 123 | * @param {String} nodeName name of the element 124 | * @param {Document} ownerDocument The context document. 125 | * @returns {Object} The shived element. 126 | */ 127 | function createElement(nodeName, ownerDocument, data){ 128 | if (!ownerDocument) { 129 | ownerDocument = document; 130 | } 131 | if(supportsUnknownElements){ 132 | return ownerDocument.createElement(nodeName); 133 | } 134 | if (!data) { 135 | data = getExpandoData(ownerDocument); 136 | } 137 | var node; 138 | 139 | if (data.cache[nodeName]) { 140 | node = data.cache[nodeName].cloneNode(); 141 | } else if (saveClones.test(nodeName)) { 142 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); 143 | } else { 144 | node = data.createElem(nodeName); 145 | } 146 | 147 | // Avoid adding some elements to fragments in IE < 9 because 148 | // * Attributes like `name` or `type` cannot be set/changed once an element 149 | // is inserted into a document/fragment 150 | // * Link elements with `src` attributes that are inaccessible, as with 151 | // a 403 response, will cause the tab/window to crash 152 | // * Script elements appended to fragments will execute when their `src` 153 | // or `text` property is set 154 | return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; 155 | } 156 | 157 | /** 158 | * returns a shived DocumentFragment for the given document 159 | * @memberOf html5 160 | * @param {Document} ownerDocument The context document. 161 | * @returns {Object} The shived DocumentFragment. 162 | */ 163 | function createDocumentFragment(ownerDocument, data){ 164 | if (!ownerDocument) { 165 | ownerDocument = document; 166 | } 167 | if(supportsUnknownElements){ 168 | return ownerDocument.createDocumentFragment(); 169 | } 170 | data = data || getExpandoData(ownerDocument); 171 | var clone = data.frag.cloneNode(), 172 | i = 0, 173 | elems = getElements(), 174 | l = elems.length; 175 | for(;i -1 || navigator.userAgent.indexOf('MSIE') > -1) { 100 | $vg('.tooltip a').children().unwrap(); 101 | } 102 | return init($vg(ctx)); 103 | }; 104 | 105 | $vg(function() { 106 | var $charts; 107 | $charts = $vg('.pygal-chart'); 108 | if ($charts.size()) { 109 | return $charts.each(function() { 110 | return init_svg(this); 111 | }); 112 | } else { 113 | return init(); 114 | } 115 | }); 116 | 117 | }).call(this); -------------------------------------------------------------------------------- /edwinServer/web/app/static/js/respond.js: -------------------------------------------------------------------------------- 1 | /* Respond.js: min/max-width media query polyfill. (c) Scott Jehl. MIT Lic. j.mp/respondjs */ 2 | (function( w ){ 3 | 4 | "use strict"; 5 | 6 | //exposed namespace 7 | var respond = {}; 8 | w.respond = respond; 9 | 10 | //define update even in native-mq-supporting browsers, to avoid errors 11 | respond.update = function(){}; 12 | 13 | //define ajax obj 14 | var requestQueue = [], 15 | xmlHttp = (function() { 16 | var xmlhttpmethod = false; 17 | try { 18 | xmlhttpmethod = new w.XMLHttpRequest(); 19 | } 20 | catch( e ){ 21 | xmlhttpmethod = new w.ActiveXObject( "Microsoft.XMLHTTP" ); 22 | } 23 | return function(){ 24 | return xmlhttpmethod; 25 | }; 26 | })(), 27 | 28 | //tweaked Ajax functions from Quirksmode 29 | ajax = function( url, callback ) { 30 | var req = xmlHttp(); 31 | if (!req){ 32 | return; 33 | } 34 | req.open( "GET", url, true ); 35 | req.onreadystatechange = function () { 36 | if ( req.readyState !== 4 || req.status !== 200 && req.status !== 304 ){ 37 | return; 38 | } 39 | callback( req.responseText ); 40 | }; 41 | if ( req.readyState === 4 ){ 42 | return; 43 | } 44 | req.send( null ); 45 | }, 46 | isUnsupportedMediaQuery = function( query ) { 47 | return query.replace( respond.regex.minmaxwh, '' ).match( respond.regex.other ); 48 | }; 49 | 50 | //expose for testing 51 | respond.ajax = ajax; 52 | respond.queue = requestQueue; 53 | respond.unsupportedmq = isUnsupportedMediaQuery; 54 | respond.regex = { 55 | media: /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi, 56 | keyframes: /@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi, 57 | comments: /\/\*[^*]*\*+([^/][^*]*\*+)*\//gi, 58 | urls: /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, 59 | findStyles: /@media *([^\{]+)\{([\S\s]+?)$/, 60 | only: /(only\s+)?([a-zA-Z]+)\s?/, 61 | minw: /\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/, 62 | maxw: /\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/, 63 | minmaxwh: /\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi, 64 | other: /\([^\)]*\)/g 65 | }; 66 | 67 | //expose media query support flag for external use 68 | respond.mediaQueriesSupported = w.matchMedia && w.matchMedia( "only all" ) !== null && w.matchMedia( "only all" ).matches; 69 | 70 | //if media queries are supported, exit here 71 | if( respond.mediaQueriesSupported ){ 72 | return; 73 | } 74 | 75 | //define vars 76 | var doc = w.document, 77 | docElem = doc.documentElement, 78 | mediastyles = [], 79 | rules = [], 80 | appendedEls = [], 81 | parsedSheets = {}, 82 | resizeThrottle = 30, 83 | head = doc.getElementsByTagName( "head" )[0] || docElem, 84 | base = doc.getElementsByTagName( "base" )[0], 85 | links = head.getElementsByTagName( "link" ), 86 | 87 | lastCall, 88 | resizeDefer, 89 | 90 | //cached container for 1em value, populated the first time it's needed 91 | eminpx, 92 | 93 | // returns the value of 1em in pixels 94 | getEmValue = function() { 95 | var ret, 96 | div = doc.createElement('div'), 97 | body = doc.body, 98 | originalHTMLFontSize = docElem.style.fontSize, 99 | originalBodyFontSize = body && body.style.fontSize, 100 | fakeUsed = false; 101 | 102 | div.style.cssText = "position:absolute;font-size:1em;width:1em"; 103 | 104 | if( !body ){ 105 | body = fakeUsed = doc.createElement( "body" ); 106 | body.style.background = "none"; 107 | } 108 | 109 | // 1em in a media query is the value of the default font size of the browser 110 | // reset docElem and body to ensure the correct value is returned 111 | docElem.style.fontSize = "100%"; 112 | body.style.fontSize = "100%"; 113 | 114 | body.appendChild( div ); 115 | 116 | if( fakeUsed ){ 117 | docElem.insertBefore( body, docElem.firstChild ); 118 | } 119 | 120 | ret = div.offsetWidth; 121 | 122 | if( fakeUsed ){ 123 | docElem.removeChild( body ); 124 | } 125 | else { 126 | body.removeChild( div ); 127 | } 128 | 129 | // restore the original values 130 | docElem.style.fontSize = originalHTMLFontSize; 131 | if( originalBodyFontSize ) { 132 | body.style.fontSize = originalBodyFontSize; 133 | } 134 | 135 | 136 | //also update eminpx before returning 137 | ret = eminpx = parseFloat(ret); 138 | 139 | return ret; 140 | }, 141 | 142 | //enable/disable styles 143 | applyMedia = function( fromResize ){ 144 | var name = "clientWidth", 145 | docElemProp = docElem[ name ], 146 | currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp, 147 | styleBlocks = {}, 148 | lastLink = links[ links.length-1 ], 149 | now = (new Date()).getTime(); 150 | 151 | //throttle resize calls 152 | if( fromResize && lastCall && now - lastCall < resizeThrottle ){ 153 | w.clearTimeout( resizeDefer ); 154 | resizeDefer = w.setTimeout( applyMedia, resizeThrottle ); 155 | return; 156 | } 157 | else { 158 | lastCall = now; 159 | } 160 | 161 | for( var i in mediastyles ){ 162 | if( mediastyles.hasOwnProperty( i ) ){ 163 | var thisstyle = mediastyles[ i ], 164 | min = thisstyle.minw, 165 | max = thisstyle.maxw, 166 | minnull = min === null, 167 | maxnull = max === null, 168 | em = "em"; 169 | 170 | if( !!min ){ 171 | min = parseFloat( min ) * ( min.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); 172 | } 173 | if( !!max ){ 174 | max = parseFloat( max ) * ( max.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); 175 | } 176 | 177 | // if there's no media query at all (the () part), or min or max is not null, and if either is present, they're true 178 | if( !thisstyle.hasquery || ( !minnull || !maxnull ) && ( minnull || currWidth >= min ) && ( maxnull || currWidth <= max ) ){ 179 | if( !styleBlocks[ thisstyle.media ] ){ 180 | styleBlocks[ thisstyle.media ] = []; 181 | } 182 | styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] ); 183 | } 184 | } 185 | } 186 | 187 | //remove any existing respond style element(s) 188 | for( var j in appendedEls ){ 189 | if( appendedEls.hasOwnProperty( j ) ){ 190 | if( appendedEls[ j ] && appendedEls[ j ].parentNode === head ){ 191 | head.removeChild( appendedEls[ j ] ); 192 | } 193 | } 194 | } 195 | appendedEls.length = 0; 196 | 197 | //inject active styles, grouped by media type 198 | for( var k in styleBlocks ){ 199 | if( styleBlocks.hasOwnProperty( k ) ){ 200 | var ss = doc.createElement( "style" ), 201 | css = styleBlocks[ k ].join( "\n" ); 202 | 203 | ss.type = "text/css"; 204 | ss.media = k; 205 | 206 | //originally, ss was appended to a documentFragment and sheets were appended in bulk. 207 | //this caused crashes in IE in a number of circumstances, such as when the HTML element had a bg image set, so appending beforehand seems best. Thanks to @dvelyk for the initial research on this one! 208 | head.insertBefore( ss, lastLink.nextSibling ); 209 | 210 | if ( ss.styleSheet ){ 211 | ss.styleSheet.cssText = css; 212 | } 213 | else { 214 | ss.appendChild( doc.createTextNode( css ) ); 215 | } 216 | 217 | //push to appendedEls to track for later removal 218 | appendedEls.push( ss ); 219 | } 220 | } 221 | }, 222 | //find media blocks in css text, convert to style blocks 223 | translate = function( styles, href, media ){ 224 | var qs = styles.replace( respond.regex.comments, '' ) 225 | .replace( respond.regex.keyframes, '' ) 226 | .match( respond.regex.media ), 227 | ql = qs && qs.length || 0; 228 | 229 | //try to get CSS path 230 | href = href.substring( 0, href.lastIndexOf( "/" ) ); 231 | 232 | var repUrls = function( css ){ 233 | return css.replace( respond.regex.urls, "$1" + href + "$2$3" ); 234 | }, 235 | useMedia = !ql && media; 236 | 237 | //if path exists, tack on trailing slash 238 | if( href.length ){ href += "/"; } 239 | 240 | //if no internal queries exist, but media attr does, use that 241 | //note: this currently lacks support for situations where a media attr is specified on a link AND 242 | //its associated stylesheet has internal CSS media queries. 243 | //In those cases, the media attribute will currently be ignored. 244 | if( useMedia ){ 245 | ql = 1; 246 | } 247 | 248 | for( var i = 0; i < ql; i++ ){ 249 | var fullq, thisq, eachq, eql; 250 | 251 | //media attr 252 | if( useMedia ){ 253 | fullq = media; 254 | rules.push( repUrls( styles ) ); 255 | } 256 | //parse for styles 257 | else{ 258 | fullq = qs[ i ].match( respond.regex.findStyles ) && RegExp.$1; 259 | rules.push( RegExp.$2 && repUrls( RegExp.$2 ) ); 260 | } 261 | 262 | eachq = fullq.split( "," ); 263 | eql = eachq.length; 264 | 265 | for( var j = 0; j < eql; j++ ){ 266 | thisq = eachq[ j ]; 267 | 268 | if( isUnsupportedMediaQuery( thisq ) ) { 269 | continue; 270 | } 271 | 272 | mediastyles.push( { 273 | media : thisq.split( "(" )[ 0 ].match( respond.regex.only ) && RegExp.$2 || "all", 274 | rules : rules.length - 1, 275 | hasquery : thisq.indexOf("(") > -1, 276 | minw : thisq.match( respond.regex.minw ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ), 277 | maxw : thisq.match( respond.regex.maxw ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ) 278 | } ); 279 | } 280 | } 281 | 282 | applyMedia(); 283 | }, 284 | 285 | //recurse through request queue, get css text 286 | makeRequests = function(){ 287 | if( requestQueue.length ){ 288 | var thisRequest = requestQueue.shift(); 289 | 290 | ajax( thisRequest.href, function( styles ){ 291 | translate( styles, thisRequest.href, thisRequest.media ); 292 | parsedSheets[ thisRequest.href ] = true; 293 | 294 | // by wrapping recursive function call in setTimeout 295 | // we prevent "Stack overflow" error in IE7 296 | w.setTimeout(function(){ makeRequests(); },0); 297 | } ); 298 | } 299 | }, 300 | 301 | //loop stylesheets, send text content to translate 302 | ripCSS = function(){ 303 | 304 | for( var i = 0; i < links.length; i++ ){ 305 | var sheet = links[ i ], 306 | href = sheet.href, 307 | media = sheet.media, 308 | isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; 309 | 310 | //only links plz and prevent re-parsing 311 | if( !!href && isCSS && !parsedSheets[ href ] ){ 312 | // selectivizr exposes css through the rawCssText expando 313 | if (sheet.styleSheet && sheet.styleSheet.rawCssText) { 314 | translate( sheet.styleSheet.rawCssText, href, media ); 315 | parsedSheets[ href ] = true; 316 | } else { 317 | if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base) || 318 | href.replace( RegExp.$1, "" ).split( "/" )[0] === w.location.host ){ 319 | // IE7 doesn't handle urls that start with '//' for ajax request 320 | // manually add in the protocol 321 | if ( href.substring(0,2) === "//" ) { href = w.location.protocol + href; } 322 | requestQueue.push( { 323 | href: href, 324 | media: media 325 | } ); 326 | } 327 | } 328 | } 329 | } 330 | makeRequests(); 331 | }; 332 | 333 | //translate CSS 334 | ripCSS(); 335 | 336 | //expose update for re-running respond later on 337 | respond.update = ripCSS; 338 | 339 | //expose getEmValue 340 | respond.getEmValue = getEmValue; 341 | 342 | //adjust on resize 343 | function callMedia(){ 344 | applyMedia( true ); 345 | } 346 | 347 | if( w.addEventListener ){ 348 | w.addEventListener( "resize", callMedia, false ); 349 | } 350 | else if( w.attachEvent ){ 351 | w.attachEvent( "onresize", callMedia ); 352 | } 353 | })(this); 354 | -------------------------------------------------------------------------------- /edwinServer/web/app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | Edwin - one alerting and monitoring system 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 44 | 45 | 46 | 47 | 48 |
49 |
50 | 51 | 57 | 58 | 59 | 60 | 61 |
62 | {% with messages = get_flashed_messages(with_categories=true) %} 63 | {% if messages %} 64 | {% for category, message in messages %} 65 | {% if category == "error" %} 66 |
{{ message }}
67 | {% else %} 68 |
{{ message }}
69 | {% endif %} 70 | {% endfor %} 71 | {% endif %} 72 | {% endwith %} 73 | 74 | 75 | 76 | 77 |
78 | {% block content %}{% endblock %} 79 |
80 |
81 | 82 |
83 |
84 | 85 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /edwinServer/web/app/templates/base_no_sidebar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | Edwin - one alerting and monitoring system 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | 37 |
38 |
39 | 40 | 41 |
42 | {% with messages = get_flashed_messages(with_categories=true) %} 43 | {% if messages %} 44 | {% for category, message in messages %} 45 | {% if category == "error" %} 46 | {category = "danger"} 47 | {% endif %} 48 |
{{ message }}
49 | {% endfor %} 50 | {% endif %} 51 | {% endwith %} 52 | 53 |
54 | {% block content %}{% endblock %} 55 |
56 |
57 |
58 |
59 | 60 | 61 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /edwinServer/web/app/templates/check_item.html: -------------------------------------------------------------------------------- 1 | {% if not is_xhr|default(false) %}{% extends "base_no_sidebar.html" %}{% endif -%} 2 | 3 | 4 | {% block content %} 5 | 6 |
7 |
8 |
9 |
10 |
11 | {{check_item.itm_title}} 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 116 | 117 | 118 | 119 | 120 | 121 | 126 | 127 | 128 |
Item code{{check_item.itm_code}}Item title{{check_item.itm_title}}
Category{{check_item.itm_category}}Enabled flag{{check_item.enabled_flag}}
Host{{check_item.host}}Check script{% if check_item.check_script %} {{check_item.check_script}} {% endif %}
Number result?{{check_item.check_value_is_number}}Check interval(minute){{check_item.check_interval_minute}}
Warning limit{% if check_item.warning_limit %}{{check_item.warning_limit}} {% endif %}Critical limit{% if check_item.critical_limit %}{{check_item.critical_limit}} {% endif %}
Owner team list 54 | {% for owner in owner_teams %} 55 | 56 | {{owner}} 57 | 58 | ; 59 | {% endfor %} 60 | Warning mailto(cc){% if check_item.warning_mail_cc %} {{check_item.warning_mail_cc}} {% endif %}
Critical mailto(cc){% if check_item.critical_mail_cc %} {{check_item.critical_mail_cc}} {% endif %} Enable SMS for critical{{check_item.critical_sms_flag}}
Enable call for critical{{check_item.critical_call_flag}}Allow repeated mail alarm{{check_item.allow_repeated_mail_alarm}}
Allow repeated call alarm{{check_item.allow_repeated_call_alarm}}Allow repeated SMS alarm{{check_item.allow_repeated_sms_alarm}}
Description 95 | {% if check_item.description %} 96 | {{check_item.description[:500]}} 97 | {% endif %} 98 |
Last status{% if check_item.last_status %} {{check_item.last_status}} {% endif %}Last value{% if check_item.last_value != None %}{{check_item.last_value}} {% endif %}
Last timestamp 112 | {% if check_item.last_check_timestamp %} 113 | {{check_item.last_check_timestamp}} 114 | {% endif %} 115 |
Last message 122 | {% if check_item.last_detail_msg %} 123 | {{check_item.last_detail_msg|safe}} 124 | {% endif %} 125 |
129 |
130 |
131 |
132 | 133 |
134 | 135 | 136 | 137 | 150 | 151 |
152 | 153 | {% if check_item.check_value_is_number=='Y'%} 154 |
155 |
156 |
157 |
158 | 159 | {% if svg_value_stat_short %} 160 | {{svg_value_stat_short|safe}} 161 | {% endif %} 162 |
163 |
164 |
165 | 166 |
167 | 168 |
169 | 170 | 171 |
172 |
173 | 174 | {% if svg_value_stat_long %} 175 | {{svg_value_stat_long|safe}} 176 | {% endif %} 177 |
178 |
179 |
180 |
181 |
182 |
183 | {% endif %} 184 | 185 |
192 | 193 |
194 |
195 |
196 | 197 | {% if svg_status_stat_short %} 198 | {{svg_status_stat_short|safe}} 199 | {% endif %} 200 |
201 |
202 |
203 | 204 |
205 |
206 | 207 | 208 |
209 |
210 | 211 | {% if svg_status_stat_long %} 212 | {{svg_status_stat_long|safe}} 213 | {% endif %} 214 |
215 |
216 |
217 |
218 |
219 |
220 | 221 | 222 | 223 |
224 | 225 | 226 | 227 | {% endblock %} -------------------------------------------------------------------------------- /edwinServer/web/app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% if not is_xhr|default(false) %}{% extends "base.html" %}{% endif -%} 2 | 3 | 4 | {% block sidebar_menu %} 5 |
  • 6 | {{root}} 7 |
  • 8 | {% for p in pages %} 9 |
  • 10 | 11 | 12 | {{p.page_title}} 13 | 14 |
  • 15 | {% endfor%} 16 | {% endblock %} 17 | 18 | 19 | 20 | 21 | {% block content %} 22 |
    23 | 24 | {% for row in page_summary_list|batch(4) %} 25 |
    26 | {% for col in row %} 27 |
    28 | 29 |
    40 | 41 |
    42 | 43 | 44 | {{col.summary_title}} 45 | 46 | {% if col.summary_status=='Critical'%} 47 | 48 | {% elif col.summary_status=='Warning'%} 49 | 50 | {% elif col.summary_status=='Outdated'%} 51 | 52 | {% else %} 53 | 54 | {% endif %} 55 |
    56 | 57 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
    Overview status{{col.summary_status }}
    Normal items{{col.normal_count}}
    Warning items{{col.warning_count}}
    Critical items{{col.critical_count}}
    Outdated items{{col.outdated_count}}
    85 |
    86 |
    87 | {% endfor %} 88 |
    89 | {% endfor %} 90 | 91 |
    92 | {% endblock %} -------------------------------------------------------------------------------- /edwinServer/web/app/templates/page.html: -------------------------------------------------------------------------------- 1 | {% if not is_xhr|default(false) %}{% extends "base.html" %}{% endif -%} 2 | 3 | 4 | {% block sidebar_menu %} 5 |
  • 6 | {{root}} 7 |
  • 8 | {% for p in pages %} 9 |
  • 10 | 11 | 12 | {{p.page_title}} 13 | 14 |
  • 15 | {% endfor%} 16 | {% endblock %} 17 | 18 | 19 | 20 | {% block content %} 21 |
    22 | 23 | {% for row in pagelet_summary_list|batch(4) %} 24 |
    25 | {% for col in row %} 26 |
    27 | 28 |
    39 | 40 |
    41 | 42 | 43 | {{col.summary_title}} 44 | 45 | {% if col.summary_status=='Critical'%} 46 | 47 | {% elif col.summary_status=='Warning'%} 48 | 49 | {% elif col.summary_status=='Outdated'%} 50 | 51 | {% else %} 52 | 53 | {% endif %} 54 |
    55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
    Overview status{{col.summary_status}}
    Normal items{{col.normal_count}}
    Warning items{{col.warning_count}}
    Critical items{{col.critical_count}}
    Outdated items{{col.outdated_count}}
    84 |
    85 |
    86 | {% endfor %} 87 |
    88 | {% endfor %} 89 | 90 |
    91 | 92 | 93 | 94 | 95 |
    96 | 97 | {% for pagelet in pagelet_components%} 98 |
    99 |
    100 |
    101 |
    {{pagelet.pagelet_title}}
    102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | {% for item in pagelet.check_list%} 112 | 113 | 127 | 128 | 140 | 141 | 149 | 150 | 155 | 156 | {% endfor %} 157 | 158 |
    ItemLast valueStatusLast timestampLast message
    114 | {% if item.last_status=='CRITICAL'%} 115 | 116 | {% elif item.last_status=='WARNING'%} 117 | 118 | {% elif item.last_status=='NORMAL' and item.is_outdated %} 119 | 120 | {%else%} 121 | 122 | {% endif %} 123 | 124 | {{item.itm_title}} 125 | 126 | {% if item.last_value != None %}{{item.last_value}} {% endif %} 138 | {% if item.last_status %}{{item.last_status}} {% endif %} 139 | {% if item.last_check_timestamp %} 146 | {{item.last_check_timestamp[:14]}} 147 | {% endif %} 148 | 151 | {% if item.last_detail_msg %} 152 | {{item.last_detail_msg[:250]|safe}} 153 | {% endif %} 154 |
    159 |
    160 |
    161 |
    162 | {% endfor %} 163 | 164 |
    165 | 166 | {% endblock %} -------------------------------------------------------------------------------- /edwinServer/web/app/templates/team.html: -------------------------------------------------------------------------------- 1 | {% if not is_xhr|default(false) %}{% extends "base_no_sidebar.html" %}{% endif -%} 2 | 3 | 4 | {% block content %} 5 | 6 | 7 |
    8 |
    9 |
    10 |
    11 |
    12 | {{team.owner_team_code}} 13 |
    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
    Email_to list{{team.email_to_list}}
    SMS mail_to{{team.sms_mail_to}}
    SMS mail title{{team.sms_mail_title}}
    Phone mail_to{{team.phone_mail_to}}
    Phone mail title{{team.phone_mail_title}}
    37 |
    38 |
    39 |
    40 | 41 |
    42 | 43 | 44 | 45 | 46 | 47 | {% endblock %} -------------------------------------------------------------------------------- /edwinServer/web/app/templates/test_svg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 |
    15 | {{svg_xml|safe}} 16 |
    17 |
    18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /edwinServer/web/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Created on 2014-2-10 4 | 5 | ''' 6 | from __future__ import absolute_import 7 | import os 8 | from datetime import timedelta 9 | _basedir = os.path.abspath(os.path.dirname(__file__)) 10 | 11 | 12 | depoly_on_cherrypy = True 13 | 14 | PORT = 5000 15 | DEBUG = True # enable reload 16 | 17 | SECRET_KEY = os.urandom(24) 18 | PERMANENT_SESSION_LIFETIME = timedelta(seconds=24 * 60 * 60) 19 | 20 | CSRF_ENABLED = True 21 | CSRF_SESSION_KEY = SECRET_KEY 22 | -------------------------------------------------------------------------------- /edwinServer/web/runserver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Created on 2014-2-10 4 | 5 | ''' 6 | 7 | from __future__ import absolute_import 8 | from edwinServer.web.app import app 9 | import logging 10 | import logging.config 11 | from edwinServer.web import conf as web_conf 12 | from edwinServer.common import conf 13 | from edwinServer.common import my_logging 14 | from edwinServer.common import os_helper 15 | 16 | 17 | # logging.basicConfig(level=logging.DEBUG) # use for development 18 | log_file = os_helper.getLoggingFileName(__file__) 19 | root_logger = logging.getLogger() 20 | my_logging.configureLogger(root_logger, log_file, conf.log_level) 21 | 22 | 23 | def run_on_default_server(): 24 | if app.debug: 25 | # app.run(port=PORT, debug=True) # disable to access on other computer 26 | app.run(host='0.0.0.0', port=web_conf.PORT, debug=True) # allow to access on other computer 27 | else: 28 | app.run(host='0.0.0.0', port=web_conf.PORT,) 29 | 30 | 31 | def run_on_cherrypy_server(): 32 | if app.debug: 33 | run_on_cherrypy_auto_reload() 34 | else: 35 | run_on_cherrypy_for_production() 36 | 37 | 38 | def run_on_cherrypy_auto_reload(): 39 | ''' 40 | run on cherrypy with auto_reload option 41 | ''' 42 | import cherrypy 43 | 44 | # Mount the WSGI callable object (app) on the root directory 45 | cherrypy.tree.graft(app, '/') 46 | 47 | # Set the configuration of the web server 48 | cherrypy.config.update({ 49 | 'engine.autoreload_on': True, 50 | 'log.screen': True, 51 | 'server.socket_port': web_conf.PORT, 52 | 'server.socket_host': '0.0.0.0' 53 | }) 54 | 55 | # Start the CherryPy WSGI web server 56 | cherrypy.engine.start() 57 | cherrypy.engine.block() 58 | 59 | 60 | def run_on_cherrypy_for_production(): 61 | ''' 62 | run on cherrypy without auto_reload option 63 | ''' 64 | from cherrypy import wsgiserver 65 | dispatch = wsgiserver.WSGIPathInfoDispatcher({'/': app}) 66 | server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', web_conf.PORT), dispatch) 67 | 68 | try: 69 | server.start() 70 | except KeyboardInterrupt: 71 | server.stop() 72 | 73 | 74 | if __name__ == "__main__": 75 | if web_conf.depoly_on_cherrypy: 76 | run_on_cherrypy_server() 77 | else: 78 | run_on_default_server() 79 | -------------------------------------------------------------------------------- /requirements_Agent.txt: -------------------------------------------------------------------------------- 1 | jytnon 2.7 2 | -------------------------------------------------------------------------------- /requirements_Server.txt: -------------------------------------------------------------------------------- 1 | Python 2.7 2 | Werkzeug>=0.7 3 | itsdangerous>=0.23 4 | Jinja2>=2.4 5 | Flask>=0.10 6 | lxml>=3.3.3 7 | pygal>=1.4.5 8 | pywin32 #if on windows 9 | APScheduler>=2.0.3 #optional 10 | Flask-DebugToolbar>=0.9.0 #optional, for debug 11 | --------------------------------------------------------------------------------