├── .coveragerc
├── .foreman
├── .gitignore
├── .travis.yml
├── CHANGES
├── Dockerfile
├── LICENSE
├── MANIFEST.in
├── Pipfile
├── Procfile
├── Procfile.dev
├── README.md
├── Vagrantfile
├── bin
├── activate
├── activate.fish
├── build-app
└── test_with_coverage
├── cabot
├── __init__.py
├── cabot_config.py
├── cabotapp
│ ├── __init__.py
│ ├── admin.py
│ ├── alert.py
│ ├── apps.py
│ ├── calendar.py
│ ├── graphite.py
│ ├── jenkins.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_auto_20170131_1537.py
│ │ ├── 0003_auto_20170201_1045.py
│ │ ├── 0004_auto_20170802_1327.py
│ │ ├── 0005_auto_20170818_1202.py
│ │ ├── 0006_auto_20170821_1000.py
│ │ ├── 0007_statuscheckresult_consecutive_failures.py
│ │ └── __init__.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── base.py
│ │ └── jenkins_check_plugin.py
│ ├── tasks.py
│ ├── templatetags
│ │ ├── __init__.py
│ │ └── extra.py
│ ├── tests
│ │ ├── __init__.py
│ │ ├── fixtures
│ │ │ ├── __init__.py
│ │ │ ├── cabot_check_skeleton
│ │ │ │ ├── __init__.py
│ │ │ │ └── plugin.py
│ │ │ ├── gcal_response.ics
│ │ │ ├── graphite_avg_response.json
│ │ │ ├── graphite_null_response.json
│ │ │ ├── graphite_response.json
│ │ │ ├── http_response.html
│ │ │ ├── recurring_response.ics
│ │ │ ├── recurring_response_complex.ics
│ │ │ └── recurring_response_notz.ics
│ │ ├── test_plugin_settings.py
│ │ ├── test_setup.py
│ │ ├── test_urlprefix.py
│ │ ├── tests_basic.py
│ │ ├── tests_icmp_check.py
│ │ └── tests_jenkins.py
│ ├── utils.py
│ └── views.py
├── celery.py
├── celeryconfig.py
├── context_processors.py
├── entrypoint.py
├── rest_urls.py
├── settings.py
├── settings_ldap.py
├── settings_utils.py
├── static
│ ├── 404.html
│ ├── 500.html
│ ├── 502.html
│ ├── 503.html
│ ├── 504.html
│ ├── arachnys
│ │ ├── css
│ │ │ ├── base.less
│ │ │ ├── graph.css
│ │ │ └── morris.css
│ │ ├── img
│ │ │ ├── favicon.ico
│ │ │ ├── icon_48x48.png
│ │ │ └── icon_96x96.png
│ │ └── js
│ │ │ ├── d3.js
│ │ │ ├── morris.js
│ │ │ ├── raphael.js
│ │ │ └── rickshaw.js
│ ├── bootstrap
│ │ ├── css
│ │ │ ├── bootstrap.css
│ │ │ └── dashboard.css
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ ├── glyphicons-halflings-regular.woff
│ │ │ └── glyphicons-halflings-regular.woff2
│ │ └── js
│ │ │ ├── bootstrap.js
│ │ │ └── jquery-1.10.2.js
│ ├── favicon.ico
│ ├── robots.txt
│ └── theme
│ │ ├── css
│ │ ├── bootstrap-chosen.css
│ │ ├── bootstrap-datatables.min.css
│ │ ├── bootstrap-responsive.css
│ │ ├── bootstrap.css
│ │ ├── chosen-sprite.png
│ │ ├── chosen-sprite@2x.png
│ │ ├── chosen.css
│ │ └── jquery-ui-1.8.21.custom.css
│ │ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.woff2
│ │ ├── img
│ │ ├── animated-overlay.gif
│ │ ├── arrows-active.png
│ │ ├── arrows-normal.png
│ │ ├── bg-input-focus.png
│ │ ├── bg-input.png
│ │ ├── bg-login.jpg
│ │ ├── bg.jpg
│ │ ├── buttons.gif
│ │ ├── calendar.gif
│ │ ├── chat-left.png
│ │ ├── chat-right.png
│ │ ├── chosen-sprite.png
│ │ ├── close-button-white.png
│ │ ├── close-button.png
│ │ ├── crop.gif
│ │ ├── dbg.jpg
│ │ ├── dialogs.png
│ │ ├── favicon.ico
│ │ ├── glyphicons-halflings-red.png
│ │ ├── glyphicons-halflings-white.png
│ │ ├── glyphicons-halflings.png
│ │ ├── i_16_radio.png
│ │ ├── icons-big.png
│ │ ├── icons-small.png
│ │ ├── logo.png
│ │ ├── logo20.png
│ │ ├── progress.gif
│ │ ├── quicklook-bg.png
│ │ ├── quicklook-icons.png
│ │ ├── resize.png
│ │ ├── spinner-mini.gif
│ │ ├── sprite.png
│ │ ├── toolbar.gif
│ │ ├── toolbar.png
│ │ ├── ui-bg_flat_0_aaaaaa_40x100.png
│ │ ├── ui-bg_flat_75_ffffff_40x100.png
│ │ ├── ui-bg_glass_55_fbf9ee_1x400.png
│ │ ├── ui-bg_glass_65_ffffff_1x400.png
│ │ ├── ui-bg_glass_75_dadada_1x400.png
│ │ ├── ui-bg_glass_75_e6e6e6_1x400.png
│ │ ├── ui-bg_glass_95_fef1ec_1x400.png
│ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png
│ │ ├── ui-icons_222222_256x240.png
│ │ ├── ui-icons_2e83ff_256x240.png
│ │ ├── ui-icons_454545_256x240.png
│ │ ├── ui-icons_888888_256x240.png
│ │ └── ui-icons_cd0a0a_256x240.png
│ │ └── js
│ │ ├── bootstrap.js
│ │ ├── chosen.jquery.js
│ │ ├── custom.js
│ │ ├── jquery-ui.js
│ │ ├── jquery.dataTables.bootstrap.min.js
│ │ ├── jquery.dataTables.min.js
│ │ ├── jquery.sparkline.min.js
│ │ ├── jquery.ui.autocomplete.js
│ │ ├── jquery.ui.core.js
│ │ └── jquery.ui.position.js
├── templates
│ ├── 404.html
│ ├── 500.html
│ ├── base.html
│ ├── base_public.html
│ ├── cabotapp
│ │ ├── _base_form.html
│ │ ├── _instance_list.html
│ │ ├── _service_list.html
│ │ ├── _service_public_list.html
│ │ ├── _statuscheck_list.html
│ │ ├── about.html
│ │ ├── alertpluginuserdata_form.html
│ │ ├── instance_confirm_delete.html
│ │ ├── instance_detail.html
│ │ ├── instance_form.html
│ │ ├── instance_list.html
│ │ ├── plugin_settings_form.html
│ │ ├── service_confirm_delete.html
│ │ ├── service_detail.html
│ │ ├── service_form.html
│ │ ├── service_list.html
│ │ ├── service_public_list.html
│ │ ├── setup.html
│ │ ├── shift_list.html
│ │ ├── statuscheck_confirm_delete.html
│ │ ├── statuscheck_detail.html
│ │ ├── statuscheck_form.html
│ │ ├── statuscheck_list.html
│ │ ├── statuscheck_report.html
│ │ ├── statuscheckresult_detail.html
│ │ └── subscriptions.html
│ └── registration
│ │ ├── login.html
│ │ ├── logout.html
│ │ └── social_auth.html
├── urls.py
├── version.py
└── wsgi.py
├── conf
├── default.env
├── development.env.example
├── production.env.example
└── test.env
├── docker-compose-base.yml
├── docker-compose-test.yml
├── docker-compose.yml
├── docker-entrypoint.sh
├── example_local_config.yml
├── gunicorn.conf
├── makemigrations
├── manage.py
├── requirements-dev.txt
├── requirements-plugins.txt
├── requirements.txt
├── setup.cfg
├── setup.py
├── setup_dev.sh
├── tox.ini
└── upstart
└── process.conf.erb
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | plugins =
4 | django_coverage_plugin
5 |
6 | omit = *migrations*
7 |
--------------------------------------------------------------------------------
/.foreman:
--------------------------------------------------------------------------------
1 | # vi: set ft=yaml :
2 |
3 | procfile: Procfile.dev
4 | env: conf/development.env
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .dotcloud/*
2 | dev.db
3 | venv/*
4 | backups/*
5 | static/
6 | cabot/.collectstatic/
7 | node_modules/*
8 | .python-eggs/*
9 | cabot.egg-info
10 | cabot/static/
11 | .env
12 | .DS_Store
13 | celerybeat-schedule
14 | *.pyc
15 | *.swp
16 | *.orig
17 | .vagrant
18 | conf/*.env
19 | dist/
20 | local_config.yml
21 | build/
22 |
23 | .idea
24 | Pipfile.lock
25 | .tox/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | services:
3 | - docker
4 |
5 | before_install:
6 | - sudo pip install tox
7 |
8 | # setup databases
9 | before_script:
10 | - cp conf/development.env.example conf/development.env
11 | - docker-compose build
12 |
13 | script:
14 | - tox
15 | - docker-compose -f docker-compose-test.yml run --rm --entrypoint bin/test_with_coverage test -v2
16 | - git checkout $(git describe --abbrev=0 --tags `git describe --tags`^) && docker-compose build web
17 | - docker-compose run --rm web true
18 | - git checkout - && docker-compose build web
19 | - docker-compose run --rm web true
20 |
21 | after_success:
22 | - sudo pip install codecov
23 | - sudo pip install django_coverage_plugin==1.4.2
24 | - codecov
25 |
--------------------------------------------------------------------------------
/CHANGES:
--------------------------------------------------------------------------------
1 | Version 0.11.16
2 | ---------------
3 |
4 | * Upgrade celery and kombu
5 |
6 | Version 0.11.15
7 | ---------------
8 |
9 | * Fix dockerfile
10 | * Add support for changing default celery queue name (using CELERY_DEFAULT_QUEUE env variable)
11 |
12 | Version 0.11.14
13 | ---------------
14 |
15 | * Add support for celery SQS backend
16 |
17 | Version 0.11.13
18 | ---------------
19 |
20 | * Fix bug where jenkins checks were always passing
21 | * Reduce Arachnys branding a bit
22 | * Fix 404 page for logged out users
23 | * Style forms with django-bootstrap-form
24 | * [[#605](https://github.com/arachnys/cabot/issues/605)] Fix http check forms auto-filling username/password
25 | * [[#607](https://github.com/arachnys/cabot/issues/607)] Fix checks for websites service non utf-8 content
26 |
27 | Version 0.11.12
28 | ---------------
29 |
30 | * Upgrade django to 1.11.11
31 | * Debounce JenkinsCheck on the number of job failures
32 | - Previously it would fail after cabot checked the jenkins api N times, even if the jenkins job had only failed once
33 |
34 | Version 0.11.11
35 | ---------------
36 |
37 | * Fix /api/oncall endpoint not working with basic auth
38 |
39 | Version 0.11.10
40 | ---------------
41 |
42 | * Add /api/oncall endpoint
43 |
44 | Version 0.11.9
45 | --------------
46 |
47 | * Fix issue where Jenkins environment variables were required on first launch
48 |
49 | Version 0.11.8
50 | --------------
51 |
52 | * Bump cabot-alert-slack to 0.8.2
53 | * Update LDAP dependencies
54 | * Add ENABLE_SUBSCRIPTION and ENABLE_DUTY_ROTA options
55 | * [[#556](https://github.com/arachnys/cabot/issues/556)] Fix issue with HttpStatusCheck with unicode content
56 |
57 | Version 0.11.7
58 | --------------
59 |
60 | * Fix check plugins not displaying checks correctly on service details page
61 |
62 | Version 0.11.6
63 | --------------
64 |
65 | * Add cloudwatch check plugin to dockerfile by default
66 | - Can be enabled by adding "cabot_check_cloudwatch" to CABOT_PLUGINS_ENABLED
67 |
68 | Version 0.11.5
69 | --------------
70 |
71 | * Fix multiple jenkins configs not working properly
72 | - Due to caching on the client, the first config to be checked would always be used
73 |
74 | Version 0.11.4
75 | --------------
76 |
77 | * Switch from jenkinsapi to python-jenkins
78 | - Fixes performance regression introduced in 0.11
79 |
80 | Version 0.11.3
81 | --------------
82 |
83 | * [[#551](https://github.com/arachnys/cabot/issues/551)] Fix in-progress jenkins jobs being marked as failing
84 |
85 | Version 0.11.2
86 | --------------
87 |
88 | * Fix pypi source distribution missing requirements for setup.py
89 |
90 | Version 0.11.1
91 | --------------
92 |
93 | * Fix migration disassociating checks from services/instances
94 | * Fix migration requiring jenkins environment variables are set
95 | * Reduce time to store old check results to 7 days
96 | - Currently stores for 2 months, but there's no actual way to view the old data.
97 |
98 | Version 0.11.0
99 | --------------
100 |
101 | *** BROKEN RELEASE - MIGRATIONS DON'T WORK CORRECTLY ***
102 |
103 | * Jenkins support:
104 | - Fail Jenkins checks when job is unknown
105 | - Use [jenkinsapi](https://pypi.python.org/pypi/jenkinsapi) to talk to Jenkins
106 | - Add option to specify multiple Jenkins backends
107 | > NOTE: This update will delete any recent status check results for jenkins checks
108 | * Add view for public services
109 | * Add support for Google OAuth login
110 | * Add ability to add custom check plugins
111 | - See https://gitlab.com/as-public/cabot-check-skeleton for an example
112 | * Remove deprecated Fabfile and Shell scripts
113 |
114 | Version 0.10.8
115 | --------------
116 |
117 | * Update slack alert to 0.8.1
118 | - fixes names not linking
119 | - only shows the acknowledge button if "SLACK_INTERACTIVE_MESSAGES" is set
120 | - (The feature only works if set up correctly on the slack end)
121 | * Update to django 1.11 (with working django-polymorphic this time)
122 |
123 | Version 0.10.7
124 | --------------
125 |
126 | * Update slack alert plugin
127 | - Now shows an "acknowledge" button within slack
128 | * Fix alert tests not triggering if:
129 | - A user had acknowledged working on the service
130 | - A legitimate alert had been sent recently
131 | * Add support for GitHub OAuth logins
132 | - See http://cabotapp.com/use/users.html
133 |
134 | Version 0.10.6
135 | --------------
136 |
137 | * Fix plugin urls being overridden by plugin settings urls
138 | - This fixes e.g. the twilio callback url not working
139 | * Fix profile settings sidebar links not working
140 |
141 | Version 0.10.5
142 | --------------
143 |
144 | * Fix bug which caused status graphs to sometimes not render
145 | * Fix issue with complex recurring calendar - `'vDDDLists' object is not iterable`
146 | * Fix css regression in logo/title
147 |
148 | Version 0.10.4
149 | --------------
150 |
151 | * Fix basic auth passwords getting reset when editing checks
152 | * Fix plugin alert tests alerting the current duty officer
153 | - They should now always alert only the user that runs the test
154 |
155 | Version 0.10.3
156 | --------------
157 |
158 | * Add plugin settings views with the ability to test alerts.
159 | * Allow user filter for LDAP to be configured
160 | - Set the AUTH_LDAP_USER_FILTER setting to change it (defaults to "(uid=%(user)s)")
161 | * Update cabot-alert-hipchat plugin to 2.0.2
162 | - Fixes bug when both HIPCHAT_URL and HIPCHAT_DOMAIN were set
163 |
164 | Version 0.10.2
165 | --------------
166 |
167 | * Update cabot-alert-hipchat plugin to 2.0.1
168 | - Supports Hipchat API v2
169 | - If HIPCHAT_URL is set, it will use the old v1 api
170 | - Use HIPCHAT_DOMAIN for custom hipchat v2 deployments
171 | * Add interactive api docs (using djangorestframework 3.6) at /docs
172 |
173 | Version 0.10.1
174 | --------------
175 |
176 | * [BUGFIX] Update cabot_alert_twilio to 1.3.1
177 | - 1.3.0 was still broken on django 1.10
178 |
179 | Version 0.10.0
180 | --------------
181 |
182 | * Add feedback notifications when updated profile
183 | * Automatically reload plugins after migrating
184 | * Add cabot_alert_slack as default plugin
185 | * Upgrade to Django 1.10
186 | * Upgrade to Celery 4
187 |
188 | Version 0.9.2
189 | -------------
190 |
191 | * Add /about endpoint
192 | * Fix rota bug when ical had no timezone
193 | * Add User Profile settings link to user dropdown
194 |
195 | Version 0.9.1
196 | -------------
197 |
198 | * Update cabot-alert-twilio to 1.3.0 to work on django 1.10
199 | * Fix Alert preferences form breaking on django 1.8
200 | * Add `cabot` executable instead of using python manage.py (for e.g. migrating)
201 |
202 | Version 0.9.0
203 | -------------
204 |
205 | * Upgrade to Django 1.9
206 |
207 | Version 0.8.7
208 | -------------
209 |
210 | * Fix Alert preferences form breaking on django 1.8
211 |
212 | Version 0.8.6
213 | -------------
214 |
215 | * Add first time setup page
216 | * Remove create_cabot_superuser management command (redundant with first time setup)
217 |
218 | Version 0.8.5
219 | -------------
220 |
221 | * More severe alerts should trigger even if a less severe alert was recently sent
222 | * Update production.env.example email settings
223 | * Convert environment vars to boolean nicely
224 |
225 | > Note: You may have to update your settings if they contain invalid boolean values
226 |
227 | Version 0.8.4
228 | -------------
229 |
230 | * Fix setup.py packaging
231 | * Use whitenoise to serve static files
232 |
233 | > Note: You may have to update your webserver settings for static files to work properly
234 |
235 | Version 0.8.3
236 | -------------
237 |
238 | * BUG: Add missing context processor
239 |
240 | Version 0.8.2
241 | -------------
242 |
243 | * Remove django-smtp-ssl dependency
244 | * Build docker image from alpine
245 | * Refactor docker-compose files
246 | * Fix db_clean task failing on large results tables
247 | * Wait for docker containers to start in docker-entrypoint.sh
248 | * Update CABOT_PLUGINS_ENABLED to compatible plugin versions
249 | * Automatically initialise database, assets and superuser on docker container start
250 |
251 | Version 0.8.1
252 | -------------
253 |
254 | * Fix all workers running celery beat
255 | * Update django-compressor to run on django 1.8
256 | * Fix typo in url testcase
257 | * Update wsgi.py to work with django 1.8
258 |
259 | Version 0.8.0
260 | -------------
261 |
262 | * Upgraded to Django 1.8
263 |
264 | Version 0.7.0
265 | -------------
266 |
267 | * Upgraded to Django 1.7
268 |
269 | Version 0.6.0
270 | -------------
271 |
272 | * Versioning Introduced.
273 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:4-alpine
2 |
3 | ENV PYTHONUNBUFFERED 1
4 |
5 | RUN mkdir /code
6 |
7 | WORKDIR /code
8 |
9 | RUN apk add --no-cache \
10 | python-dev \
11 | py-pip \
12 | postgresql-dev \
13 | gcc \
14 | curl \
15 | curl-dev \
16 | libcurl \
17 | musl-dev \
18 | libffi-dev \
19 | openldap-dev \
20 | ca-certificates \
21 | bash
22 |
23 | RUN npm config set unsafe-perm true
24 | RUN npm install -g \
25 | --registry http://registry.npmjs.org/ \
26 | coffee-script \
27 | less@1.3
28 |
29 | RUN pip install --upgrade pip
30 |
31 | COPY requirements.txt ./
32 | RUN pip install --no-cache-dir -r requirements.txt
33 |
34 | COPY requirements-dev.txt ./
35 | RUN pip install --no-cache-dir -r requirements-dev.txt
36 |
37 | COPY requirements-plugins.txt ./
38 | RUN pip install --no-cache-dir -r requirements-plugins.txt
39 |
40 | ADD . /code/
41 |
42 | ENTRYPOINT ["./docker-entrypoint.sh"]
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Arachnys Information Services Ltd and individual contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include cabot/.collectstatic *
2 | recursive-include cabot/templates *
3 | include requirements.txt
4 | include requirements-plugins.txt
5 | include requirements-dev.txt
6 | include README.md
7 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.python.org/simple"
3 | verify_ssl = true
4 |
5 | [packages]
6 | amqp = ">=2.1.4"
7 | celery = ">=4,<5"
8 | djangorestframework = "*"
9 | django-jsonify = "*"
10 | django-filter = "*"
11 | django-auth-ldap = "*"
12 | anyjson = "*"
13 | dj-database-url = "*"
14 | freezegun = "*"
15 | gevent = "*"
16 | gunicorn = "*"
17 | icalendar = "*"
18 | psycopg2 = "*"
19 | python-dateutil = "*"
20 | pytz = "*"
21 | redis = "*"
22 | requests = "*"
23 | twilio = ">=5,<6"
24 | whitenoise = "*"
25 | coreapi = "*"
26 | Django = ">=1.11,<2"
27 | django_polymorphic = "*"
28 | django_compressor = "*"
29 | Markdown = "*"
30 | Pygments = "*"
31 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn cabot.wsgi:application --config gunicorn.conf
2 | celery: celery worker -A cabot --loglevel=INFO --concurrency=16 -Ofair
3 | beat: celery beat -A cabot --loglevel=INFO
4 |
--------------------------------------------------------------------------------
/Procfile.dev:
--------------------------------------------------------------------------------
1 | web: python manage.py runserver 0.0.0.0:$PORT
2 | celery: celery -A cabot worker --loglevel=DEBUG -c 8 -Ofair
3 | beat: celery -A cabot beat --loglevel=DEBUG
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Cabot
2 | =====
3 | [](https://travis-ci.org/arachnys/cabot)
4 | [](https://badge.fury.io/py/cabot)
5 | [](https://codecov.io/github/arachnys/cabot?branch=master)
6 | [](https://opensource.org/licenses/MIT)
7 | [](https://gitter.im/arachnys/cabot)
8 |
9 | ## Maintainers wanted
10 |
11 | **Cabot is stable and used by hundreds of companies and individuals in production, but it is not actively maintained. We would like to hand over maintenance of the project to one or more responsible and experienced maintainers. Please email cabot@arachnys.com with some information about yourself (github profile and/or CV) if you are interested.**
12 |
13 | ## Why choose Cabot
14 |
15 | Cabot is a free, open-source, self-hosted infrastructure monitoring platform that provides some of the best features of [PagerDuty](http://www.pagerduty.com), [Server Density](http://www.serverdensity.com), [Pingdom](http://www.pingdom.com) and [Nagios](http://www.nagios.org) without their cost and complexity. (Nagios, I'm mainly looking at you.)
16 |
17 | It provides a web interface that allows you to monitor services (e.g. "Stage Redis server", "Production ElasticSearch cluster") and send telephone, sms or hipchat/email alerts to your on-duty team if those services start misbehaving or go down - all without writing a line of code. Best of all, you can use data that you're already pushing to Graphite/statsd to generate alerts, rather than implementing and maintaining a whole new system of data collectors.
18 |
19 | You can alert based on:
20 |
21 | * Metrics from [Graphite](https://github.com/graphite-project/graphite-web)
22 | * Status code and response content of web endpoints
23 | * [Jenkins](http://jenkins-ci.org) build statuses
24 |
25 | We built Cabot as a Christmas project at [Arachnys](https://www.arachnys.com) because we couldn't wrap our heads around Nagios, and nothing else out there seemed to fit our use case. We're open-sourcing it in the hope that others find it useful.
26 |
27 | Cabot is written in Python and uses [Django](https://www.djangoproject.com/), [Bootstrap](http://getbootstrap.com/), [Font Awesome](http://fontawesome.io) and a whole host of other goodies under the hood.
28 |
29 | ## Screenshots
30 |
31 | ### Services dashboard
32 |
33 | 
34 |
35 | ### Single service overview
36 |
37 | 
38 |
39 | ## Quickstart
40 |
41 | Using Docker: Deploy in 5 minutes or less using [official quickstart guide at cabotapp.com](http://cabotapp.com/qs/quickstart.html). (See also https://hub.docker.com/r/cabotapp/cabot/)
42 |
43 | ## How it works
44 |
45 | Docs have moved to [cabotapp.com](http://cabotapp.com)
46 |
47 | Sections:
48 |
49 | * [Configuration](http://cabotapp.com/use/configuration.html)
50 | * [Deployment](http://cabotapp.com/use/deployment.html)
51 | * [Services](http://cabotapp.com/use/services.html)
52 | * [Graphite checks](http://cabotapp.com/use/graphite-checks.html)
53 | * [Jenkins checks](http://cabotapp.com/use/jenkins-checks.html)
54 | * [HTTP checks](http://cabotapp.com/use/http-checks.html)
55 | * [Alerting](http://cabotapp.com/use/alerting.html)
56 | * [Users](http://cabotapp.com/use/users.html)
57 | * [Rota](http://cabotapp.com/use/rota.html)
58 |
59 | For those who want to contribute:
60 |
61 | * [Help develop](http://cabotapp.com/dev/get-started.html)
62 | * [Contribute code](http://cabotapp.com/dev/contribute-code.html)
63 |
64 | ## FAQ
65 |
66 | ### Why "Cabot"?
67 |
68 | My dog is called Cabot and he loves monitoring things. Mainly the presence of food in his immediate surroundings, or perhaps the frequency of squirrel visits to our garden. He also barks loudly to alert us on certain events (e.g. the postman coming to the door).
69 |
70 | 
71 |
72 | It's just a lucky coincidence that his name sounds like he could be an automation tool.
73 |
74 | ## API
75 |
76 | The API has automatically generated documentation available by browsing https://cabot.yourcompany.com/api. The browsable documentation displays example GET requests and lists other allowed HTTP methods.
77 |
78 | To view individual items, append the item `id` to the url. For example, to view `graphite_check` 1, browse:
79 | ```
80 | /api/graphite_checks/1/
81 | ```
82 |
83 | ### Authentication
84 |
85 | The API allows HTTP basic auth using standard Django usernames and passwords as well as session authentication (by submitting the login form on the login page). The API similarly uses standard Django permissions to allow and deny API access.
86 |
87 | All resources are GETable by any authenticated user, but individual permissions must be granted for POST, PUT, and other write methods.
88 |
89 | As an example, for POST access to all `status_check` subclasses, add the following permissions:
90 | ```
91 | cabotapp | status check | Can add graphite status check
92 | cabotapp | status check | Can add http status check
93 | cabotapp | status check | Can add icmp status check
94 | cabotapp | status check | Can add jenkins status check
95 | ```
96 |
97 | Access the Django admin page at https://cabot.yourcompany.com/admin to add/remove users, change user permissions, add/remove groups for group-based permission control, and change group permissions.
98 |
99 | ### Sorting and Filtering
100 |
101 | Sorting and filtering can be used by both REST clients and on the browsable API. All fields visible in the browsable API can be used for filtering and sorting.
102 |
103 | Get all `jenkins_checks` with debounce enabled and CRITICAL importance:
104 | ```
105 | https://cabot.yourcompany.com/api/jenkins_checks/?debounce=1&importance=CRITICAL
106 | ```
107 |
108 | Sort `graphite_checks` by `name` field, ascending:
109 | ```
110 | https://cabot.yourcompany.com/api/graphite_checks/?ordering=name
111 | ```
112 |
113 | Sort by `name` field, descending:
114 | ```
115 | https://cabot.yourcompany.com/api/graphite_checks/?ordering=-name
116 | ```
117 |
118 | Other (non-Cabot specific) examples are available in the [Django REST Framework](http://www.django-rest-framework.org/api-guide/filtering#djangofilterbackend) documentation.
119 |
120 | ## License
121 |
122 | See `LICENSE` file in this repo.
123 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 | require 'yaml'
4 |
5 | # Load local config overrides
6 | local_config = File.file?("local_config.yml") ? YAML.load(File.read("local_config.yml")) : {}
7 |
8 | Vagrant::configure("2") do |config|
9 | # All Vagrant configuration is done here. The most common configuration
10 | # options are documented and commented below. For a complete reference,
11 | # please see the online documentation at vagrantup.com.
12 |
13 | # Every Vagrant virtual environment requires a box to build off of.
14 | config.vm.box = local_config["box"] || "hashicorp/precise64"
15 |
16 | # Virtualbox
17 | config.vm.provider "virtualbox" do |vb|
18 | vb.customize [
19 | "modifyvm", :id,
20 | "--memory", local_config['ram'] || "1024",
21 | "--cpus", local_config['cpu'] || 1,
22 | "--ioapic", "on",
23 | ]
24 | end
25 |
26 | #vmware_fusion
27 | config.vm.provider "vmware_fusion" do |v|
28 | v.vmx["memsize"] = local_config['ram'] || "1024"
29 | v.vmx["numvcpus"] = local_config['cpu'] || 1
30 | end
31 |
32 | # Boot with a GUI so you can see the screen. (Default is headless)
33 | # config.vm.boot_mode = :gui
34 |
35 | # Assign this VM to a host-only network IP, allowing you to access it
36 | # via the IP. Host-only networks can talk to the host machine as well as
37 | # any other machines on the same network, but cannot be accessed (through this
38 | # network interface) by any external networks.
39 | config.vm.network "forwarded_port", guest: 5001, host: 5001
40 |
41 | # Share an additional folder to the guest VM. The first argument is
42 | # an identifier, the second is the path on the guest to mount the
43 | # folder, and the third is the path on the host to the actual folder.
44 | config.vm.synced_folder "./", "/vagrant", create: true
45 |
46 | # Provision the development environment
47 | config.vm.provision :shell do |shell|
48 | shell.inline = 'sudo /vagrant/bin/provision'
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/bin/activate:
--------------------------------------------------------------------------------
1 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
2 | export PATH=$PATH:$DIR/../bin:$DIR/../app
3 | export PYTHONPATH=$PYTHONPATH:$DIR/../app
4 |
--------------------------------------------------------------------------------
/bin/activate.fish:
--------------------------------------------------------------------------------
1 | # fix broken locale
2 | if not python -c 'import locale; locale.getdefaultlocale();' >/dev/null ^&1
3 | set -gx LANG en_US.UTF-8
4 | set -gx LC_ALL en_US.UTF-8
5 | end
6 |
7 | # set paths
8 | set DIR (dirname (status -f))
9 | set -gx PATH $PATH $DIR/../bin $DIR/../app
10 | set -gx PYTHONPATH $PYTHONPATH $DIR/../app
11 |
--------------------------------------------------------------------------------
/bin/build-app:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | python manage.py migrate
4 | python manage.py collectstatic --noinput
5 | # python manage.py compress
6 |
--------------------------------------------------------------------------------
/bin/test_with_coverage:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -x
3 |
4 | output_dir='test-results'
5 | mkdir -p $output_dir
6 |
7 | ./manage.py collectstatic --no-input
8 | TEMPLATE_DEBUG=True coverage run --source="./cabot/" manage.py test $@
9 | status=$?
10 |
11 | coverage report --omit="cabot/cabotapp/tests*"
12 | coverage xml --omit="cabot/cabotapp/tests*" -o $output_dir/coverage.xml
13 | coverage html --omit="cabot/cabotapp/tests*" -d $output_dir/htmlcov/
14 |
15 | exit $status
16 |
--------------------------------------------------------------------------------
/cabot/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | # This will make sure the app is always imported when
4 | # Django starts so that shared_task will use this app.
5 | from .celery import app as celery_app
6 |
7 | from .version import version
8 |
--------------------------------------------------------------------------------
/cabot/cabot_config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | # Credentials for Graphite server to monitor
4 | GRAPHITE_API = os.environ.get('GRAPHITE_API')
5 | GRAPHITE_USER = os.environ.get('GRAPHITE_USER')
6 | GRAPHITE_PASS = os.environ.get('GRAPHITE_PASS')
7 | GRAPHITE_FROM = os.getenv('GRAPHITE_FROM', '-10minute')
8 |
9 | # Credentials for Jenkins server to monitor
10 | JENKINS_API = os.environ.get('JENKINS_API')
11 | JENKINS_USER = os.environ.get('JENKINS_USER')
12 | JENKINS_PASS = os.environ.get('JENKINS_PASS')
13 |
14 | # Point at a public calendar you want to use to schedule a duty rota
15 | CALENDAR_ICAL_URL = os.environ.get('CALENDAR_ICAL_URL')
16 |
17 | # So that links back to the Cabot instance display correctly
18 | WWW_HTTP_HOST = os.environ.get('WWW_HTTP_HOST')
19 | WWW_SCHEME = os.environ.get('WWW_SCHEME', "https")
20 |
21 | HTTP_USER_AGENT = os.environ.get('HTTP_USER_AGENT', 'Cabot')
22 |
23 | # How often should alerts be sent for important failures?
24 | ALERT_INTERVAL = int(os.environ.get('ALERT_INTERVAL', 10))
25 |
26 | # How often should notifications be sent for less important issues?
27 | NOTIFICATION_INTERVAL = int(os.environ.get('NOTIFICATION_INTERVAL', 120))
28 |
29 | # How long should an acknowledgement silence alerts for?
30 | ACKNOWLEDGEMENT_EXPIRY = int(os.environ.get('ACKNOWLEDGEMENT_EXPIRY', 20))
31 |
32 | # Default plugins are used if the user has not specified.
33 | CABOT_PLUGINS_ENABLED = os.environ.get('CABOT_PLUGINS_ENABLED', 'cabot_alert_hipchat,cabot_alert_twilio,cabot_alert_email')
34 |
--------------------------------------------------------------------------------
/cabot/cabotapp/__init__.py:
--------------------------------------------------------------------------------
1 | default_app_config = 'cabot.cabotapp.apps.CabotappConfig'
2 |
--------------------------------------------------------------------------------
/cabot/cabotapp/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from polymorphic.admin import (PolymorphicChildModelAdmin,
3 | PolymorphicParentModelAdmin)
4 |
5 | from .alert import AlertPlugin, AlertPluginUserData
6 | from .models import (AlertAcknowledgement, Instance, JenkinsConfig, Service,
7 | ServiceStatusSnapshot, Shift, StatusCheck,
8 | StatusCheckResult, UserProfile)
9 |
10 |
11 | class StatusCheckAdmin(PolymorphicParentModelAdmin):
12 | base_model = StatusCheck
13 | child_models = StatusCheck.__subclasses__()
14 |
15 |
16 | class ChildStatusCheckAdmin(PolymorphicChildModelAdmin):
17 | base_model = StatusCheck
18 |
19 |
20 | for child_status_check in StatusCheck.__subclasses__():
21 | admin.site.register(child_status_check, ChildStatusCheckAdmin)
22 |
23 | admin.site.register(UserProfile)
24 | admin.site.register(Shift)
25 | admin.site.register(Service)
26 | admin.site.register(ServiceStatusSnapshot)
27 | admin.site.register(StatusCheck, StatusCheckAdmin)
28 | admin.site.register(StatusCheckResult)
29 | admin.site.register(Instance)
30 | admin.site.register(AlertPlugin)
31 | admin.site.register(AlertPluginUserData)
32 | admin.site.register(AlertAcknowledgement)
33 | admin.site.register(JenkinsConfig)
34 |
--------------------------------------------------------------------------------
/cabot/cabotapp/alert.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from django.db import models
4 |
5 |
6 | from polymorphic.models import PolymorphicModel
7 |
8 | logger = logging.getLogger(__name__)
9 |
10 |
11 | class AlertPlugin(PolymorphicModel):
12 | title = models.CharField(max_length=30, unique=True, blank=False, editable=False)
13 | enabled = models.BooleanField(default=True)
14 |
15 | author = None
16 |
17 | def __unicode__(self):
18 | return u'%s' % (self.title)
19 |
20 | def _send_alert(self, service, users, duty_officers):
21 | """
22 | To allow easily monkey patching in hooks for all alerts.
23 | e.g. mocking send_alert for all plugins in testing
24 | """
25 | return self.send_alert(service, users, duty_officers)
26 |
27 | def _send_alert_update(self, service, users, duty_officers):
28 | """
29 | To allow easily monkey patching in hooks for all alerts.
30 | e.g. mocking send_alert_update for all plugins in testing
31 | """
32 | return self.send_alert_update(service, users, duty_officers)
33 |
34 | def send_alert(self, service, users, duty_officers):
35 | """
36 | Implement a send_alert function here that shall be called.
37 | """
38 | return True
39 |
40 |
41 | class AlertPluginUserData(PolymorphicModel):
42 | title = models.CharField(max_length=30, editable=False)
43 | user = models.ForeignKey('UserProfile', editable=False)
44 |
45 | class Meta:
46 | unique_together = ('title', 'user',)
47 |
48 | def __unicode__(self):
49 | return u'%s' % (self.title)
50 |
51 | def serialize(self):
52 | return {}
53 |
54 |
55 | def send_alert(service, duty_officers=None):
56 | users = service.users_to_notify.filter(is_active=True)
57 | for alert in service.alerts.filter(enabled=True):
58 | try:
59 | alert._send_alert(service, users, duty_officers)
60 | except Exception as e:
61 | logging.exception('Could not send %s alert: %s' % (alert.name, e))
62 |
63 |
64 | def send_alert_update(service, duty_officers=None):
65 | users = service.users_to_notify.filter(is_active=True)
66 | for alert in service.alerts.filter(enabled=True):
67 | if hasattr(alert, 'send_alert_update'):
68 | try:
69 | alert._send_alert_update(service, users, duty_officers)
70 | except Exception as e:
71 | logger.exception('Could not send %s alert update: %s' % (alert.name, e))
72 | else:
73 | logger.warning('No send_alert_update method present for %s' % alert.name)
74 |
75 |
76 | def update_alert_plugins():
77 | for plugin_subclass in AlertPlugin.__subclasses__():
78 | plugin = plugin_subclass.objects.get_or_create(title=plugin_subclass.name)
79 | return AlertPlugin.objects.all()
80 |
--------------------------------------------------------------------------------
/cabot/cabotapp/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 | from django.db.models.signals import post_migrate
3 |
4 |
5 | def post_migrate_callback(**kwargs):
6 | from cabot.cabotapp.alert import update_alert_plugins
7 | from cabot.cabotapp.models import create_default_jenkins_config
8 | update_alert_plugins()
9 | create_default_jenkins_config()
10 |
11 | class CabotappConfig(AppConfig):
12 | name = 'cabot.cabotapp'
13 |
14 | def ready(self):
15 | post_migrate.connect(post_migrate_callback, sender=self)
16 |
--------------------------------------------------------------------------------
/cabot/cabotapp/calendar.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from django.conf import settings
3 | from django.utils.timezone import now, get_current_timezone
4 | from dateutil import rrule
5 | from icalendar import Calendar
6 | import requests
7 |
8 | import logging
9 | logger = logging.getLogger(__name__)
10 |
11 |
12 | MAX_FUTURE = 60 # days
13 |
14 |
15 | def ensure_tzaware(dt):
16 | if dt.tzinfo is None:
17 | return get_current_timezone().localize(dt)
18 | return dt
19 |
20 |
21 | def _recurring_component_to_events(component):
22 | """
23 | Given an icalendar component with an "RRULE"
24 | Return a list of events as dictionaries
25 | """
26 | rrule_as_str = component.get('rrule').to_ical()
27 | recur_rule = rrule.rrulestr(rrule_as_str,
28 | dtstart=ensure_tzaware(component.decoded('dtstart')))
29 | recur_set = rrule.rruleset()
30 | recur_set.rrule(recur_rule)
31 | if 'exdate' in component:
32 | lines = component.decoded('exdate')
33 | if not hasattr(lines, '__iter__'):
34 | lines = [lines]
35 | for exdate_line in lines:
36 | for exdate in exdate_line.dts:
37 | recur_set.exdate(ensure_tzaware(exdate.dt))
38 |
39 | # get list of events in MAX_FUTURE days
40 | utcnow = now()
41 | later = utcnow + datetime.timedelta(days=MAX_FUTURE)
42 | start_times = recur_set.between(utcnow, later)
43 |
44 | # build list of events
45 | event_length = component.decoded('dtend') - component.decoded('dtstart')
46 | events = []
47 | for start in start_times:
48 | events.append({
49 | 'start': start,
50 | 'end': start + event_length,
51 | 'summary': component.decoded('summary'),
52 | 'uid': component.decoded('uid'),
53 | 'last_modified': component.decoded('last-modified'),
54 | })
55 | return events
56 |
57 |
58 | def get_calendar_data():
59 | feed_url = settings.CALENDAR_ICAL_URL
60 | resp = requests.get(feed_url)
61 | cal = Calendar.from_ical(resp.content)
62 | return cal
63 |
64 |
65 | def get_events():
66 | events = []
67 | for component in get_calendar_data().walk():
68 | if component.name == 'VEVENT':
69 | if 'rrule' in component:
70 | events.extend(_recurring_component_to_events(component))
71 | else:
72 | try:
73 | events.append({
74 | 'start': component.decoded('dtstart'),
75 | 'end': component.decoded('dtend'),
76 | 'summary': component.decoded('summary'),
77 | 'uid': component.decoded('uid'),
78 | 'last_modified': component.decoded('last-modified'),
79 | })
80 | except KeyError:
81 | logger.debug('Failed to parse VEVENT component: %s',
82 | component.get('uid', 'no uid available'))
83 | return events
84 |
--------------------------------------------------------------------------------
/cabot/cabotapp/graphite.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | import requests
3 | import logging
4 | import time
5 |
6 | graphite_api = settings.GRAPHITE_API
7 | user = settings.GRAPHITE_USER
8 | password = settings.GRAPHITE_PASS
9 | graphite_from = settings.GRAPHITE_FROM
10 | auth = (user, password)
11 |
12 |
13 | def get_data(target_pattern, mins_to_check=None):
14 |
15 | if mins_to_check:
16 | _from = '-%dminute' % mins_to_check
17 | else:
18 | _from = graphite_from
19 |
20 | resp = requests.get(
21 | graphite_api + 'render', auth=auth,
22 | params={
23 | 'target': target_pattern,
24 | 'format': 'json',
25 | 'from': _from,
26 | }
27 | )
28 | resp.raise_for_status()
29 | return resp.json()
30 |
31 |
32 | def get_matching_metrics(pattern):
33 | print 'Getting metrics matching %s' % pattern
34 | resp = requests.get(
35 | graphite_api + 'metrics/find/', auth=auth,
36 | params={
37 | 'query': pattern,
38 | 'format': 'completer'
39 | },
40 | headers={
41 | 'accept': 'application/json'
42 | }
43 | )
44 | resp.raise_for_status()
45 | return resp.json()
46 |
47 |
48 | def get_all_metrics(limit=None):
49 | """Grabs all metrics by navigating find API recursively"""
50 | metrics = []
51 |
52 | def get_leafs_of_node(nodepath):
53 | for obj in get_matching_metrics(nodepath)['metrics']:
54 | if int(obj['is_leaf']) == 1:
55 | metrics.append(obj['path'])
56 | else:
57 | get_leafs_of_node(obj['path'])
58 | get_leafs_of_node('')
59 | return metrics
60 |
61 |
62 | def parse_metric(metric, mins_to_check=5, utcnow=None):
63 | if utcnow is None:
64 | utcnow = time.time()
65 | ret = {
66 | 'num_series_with_data': 0,
67 | 'num_series_no_data': 0,
68 | 'error': None,
69 | 'raw': '',
70 | 'series': [],
71 | }
72 | try:
73 | data = get_data(metric, mins_to_check)
74 | except requests.exceptions.RequestException, e:
75 | ret['error'] = 'Error getting data from Graphite: %s' % e
76 | ret['raw'] = ret['error']
77 | logging.error('Error getting data from Graphite: %s' % e)
78 | return ret
79 | all_values = []
80 | for target in data:
81 | series = {'values': [
82 | float(t[0]) for t in target['datapoints'] if validate_datapoint(t, mins_to_check, utcnow)]}
83 | series["target"] = target["target"]
84 | all_values.extend(series['values'])
85 | if series['values']:
86 | ret['num_series_with_data'] += 1
87 | series['max'] = max(series['values'])
88 | series['min'] = min(series['values'])
89 | series['average_value'] = sum(series['values']) / len(series['values'])
90 | ret['series'].append(series)
91 | else:
92 | ret['num_series_no_data'] += 1
93 | if all_values:
94 | ret['average_value'] = sum(all_values) / len(all_values)
95 | ret['all_values'] = all_values
96 | ret['raw'] = data
97 | return ret
98 |
99 | def validate_datapoint(datapoint, mins_to_check, utcnow):
100 | val, timestamp = datapoint
101 | secs_to_check = 60 * mins_to_check
102 | if val is None:
103 | return False
104 | if timestamp > (utcnow - secs_to_check):
105 | return True
106 | else:
107 | return False
108 |
109 |
110 |
--------------------------------------------------------------------------------
/cabot/cabotapp/jenkins.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from datetime import datetime
4 |
5 | import jenkins
6 | from celery.utils.log import get_task_logger
7 | from django.conf import settings
8 | from django.utils import timezone
9 |
10 | logger = get_task_logger(__name__)
11 |
12 |
13 | def _get_jenkins_client(jenkins_config):
14 | return jenkins.Jenkins(jenkins_config.jenkins_api,
15 | username=jenkins_config.jenkins_user,
16 | password=jenkins_config.jenkins_pass)
17 |
18 | def get_job_status(jenkins_config, jobname):
19 | ret = {
20 | 'active': None,
21 | 'succeeded': None,
22 | 'job_number': None,
23 | 'blocked_build_time': None,
24 | }
25 | client = _get_jenkins_client(jenkins_config)
26 | try:
27 | job = client.get_job_info(jobname)
28 | last_completed_build = job['lastCompletedBuild']
29 | if not last_completed_build:
30 | raise Exception("job has no build")
31 | last_build = client.get_build_info(jobname, last_completed_build['number'])
32 |
33 | if job['lastSuccessfulBuild']:
34 | last_good_build_number = job['lastSuccessfulBuild']['number']
35 | else:
36 | last_good_build_number = 0
37 |
38 | ret['status_code'] = 200
39 | ret['job_number'] = last_build['number']
40 | ret['active'] = job['color'] != 'disabled'
41 | ret['succeeded'] = ret['active'] and last_build['result'] == 'SUCCESS'
42 | ret['consecutive_failures'] = last_build['number'] - last_good_build_number
43 |
44 | if job['inQueue']:
45 | in_queued_since = job['queueItem']['inQueueSince']
46 | time_blocked_since = datetime.utcfromtimestamp(
47 | float(in_queued_since) / 1000).replace(tzinfo=timezone.utc)
48 | ret['blocked_build_time'] = (timezone.now() - time_blocked_since).total_seconds()
49 | ret['queued_job_number'] = job['lastBuild']['number']
50 | return ret
51 | except jenkins.NotFoundException:
52 | ret['status_code'] = 404
53 | return ret
54 |
--------------------------------------------------------------------------------
/cabot/cabotapp/migrations/0002_auto_20170131_1537.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('cabotapp', '0001_initial'),
11 | ]
12 |
13 | operations = [
14 | migrations.RenameField(
15 | model_name='statuscheckresult',
16 | old_name='check',
17 | new_name='status_check',
18 | ),
19 | migrations.AlterIndexTogether(
20 | name='statuscheckresult',
21 | index_together=set([('status_check', 'time_complete'), ('status_check', 'id')]),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/cabot/cabotapp/migrations/0003_auto_20170201_1045.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('cabotapp', '0002_auto_20170131_1537'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='alertplugin',
16 | name='polymorphic_ctype',
17 | field=models.ForeignKey(related_name='polymorphic_cabotapp.alertplugin_set+', editable=False, to='contenttypes.ContentType', null=True),
18 | ),
19 | migrations.AlterField(
20 | model_name='alertpluginuserdata',
21 | name='polymorphic_ctype',
22 | field=models.ForeignKey(related_name='polymorphic_cabotapp.alertpluginuserdata_set+', editable=False, to='contenttypes.ContentType', null=True),
23 | ),
24 | migrations.AlterField(
25 | model_name='statuscheck',
26 | name='polymorphic_ctype',
27 | field=models.ForeignKey(related_name='polymorphic_cabotapp.statuscheck_set+', editable=False, to='contenttypes.ContentType', null=True),
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/cabot/cabotapp/migrations/0004_auto_20170802_1327.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('cabotapp', '0003_auto_20170201_1045'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='Service',
16 | name='is_public',
17 | field=models.BooleanField(default=False, help_text=b'The service will be shown in the public home', verbose_name=b'Is Public'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/cabot/cabotapp/migrations/0005_auto_20170818_1202.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11 on 2017-08-18 12:02
3 | from __future__ import unicode_literals
4 |
5 | import os
6 |
7 | import django.db.models.deletion
8 | from django.contrib.contenttypes.models import ContentType
9 | from django.db import migrations, models
10 |
11 |
12 | def move_old_jenkins_checks(apps, schema_editor):
13 | db_alias = schema_editor.connection.alias
14 |
15 | JenkinsStatusCheck = apps.get_model("cabotapp", "JenkinsStatusCheck")
16 | JenkinsCheck = apps.get_model("cabotapp", "JenkinsCheck")
17 | JenkinsConfig = apps.get_model("cabotapp", "JenkinsConfig")
18 |
19 | # Due to a polymorphic bug, JenkinsStatusCheck actually returns all status checks
20 | # Use this to filter out the other checks.
21 | jenkins_content_type = ContentType.objects.filter(model="jenkinsstatuscheck").first()
22 |
23 | if jenkins_content_type and not JenkinsStatusCheck.objects.filter(polymorphic_ctype_id=jenkins_content_type.id).exists():
24 | return
25 |
26 | if not JenkinsConfig.objects.exists():
27 | JenkinsConfig.objects.create(
28 | name="Default Jenkins",
29 | jenkins_api=os.environ.get("JENKINS_API", "http://jenkins.example.com"),
30 | jenkins_user=os.environ.get("JENKINS_USER", ""),
31 | jenkins_pass=os.environ.get("JENKINS_PASS", ""),
32 | )
33 |
34 | default_config = JenkinsConfig.objects.first()
35 |
36 | for old_check in JenkinsStatusCheck.objects.all():
37 | if old_check.polymorphic_ctype_id != jenkins_content_type.id:
38 | continue
39 | new_check = JenkinsCheck(
40 | active=old_check.active,
41 | allowed_num_failures=old_check.allowed_num_failures,
42 | cached_health=old_check.cached_health,
43 | calculated_status=old_check.calculated_status,
44 | check_type=old_check.check_type,
45 | created_by_id=old_check.created_by_id,
46 | debounce=old_check.debounce,
47 | endpoint=old_check.endpoint,
48 | expected_num_hosts=old_check.expected_num_hosts,
49 | frequency=old_check.frequency,
50 | importance=old_check.importance,
51 | last_run=old_check.last_run,
52 | max_queued_build_time=old_check.max_queued_build_time,
53 | metric=old_check.metric,
54 | name=old_check.name,
55 | password=old_check.password,
56 | status_code=old_check.status_code,
57 | text_match=old_check.text_match,
58 | timeout=old_check.timeout,
59 | username=old_check.username,
60 | value=old_check.value,
61 | jenkins_config=default_config,
62 | # For some reason this isn't handled automatically...
63 | # The model is renamed in the next migration so the ctype
64 | # id stays consistent.
65 | polymorphic_ctype_id=old_check.polymorphic_ctype_id
66 | )
67 | new_check.save(using=db_alias)
68 | new_check.service_set.add(*old_check.service_set.all())
69 | new_check.instance_set.add(*old_check.instance_set.all())
70 | new_check.save(using=db_alias)
71 | old_check.delete(using=db_alias)
72 |
73 |
74 | class Migration(migrations.Migration):
75 |
76 | dependencies = [
77 | ('cabotapp', '0004_auto_20170802_1327'),
78 | ]
79 |
80 | operations = [
81 | migrations.CreateModel(
82 | name='JenkinsCheck',
83 | fields=[
84 | ('statuscheck_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cabotapp.StatusCheck')),
85 | ],
86 | options={
87 | 'abstract': False,
88 | },
89 | bases=('cabotapp.statuscheck',),
90 | ),
91 | migrations.CreateModel(
92 | name='JenkinsConfig',
93 | fields=[
94 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
95 | ('name', models.CharField(max_length=30)),
96 | ('jenkins_api', models.CharField(max_length=2000)),
97 | ('jenkins_user', models.CharField(max_length=2000)),
98 | ('jenkins_pass', models.CharField(max_length=2000)),
99 | ],
100 | ),
101 | migrations.AddField(
102 | model_name='jenkinscheck',
103 | name='jenkins_config',
104 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cabotapp.JenkinsConfig'),
105 | ),
106 | migrations.RunPython(move_old_jenkins_checks)
107 | ]
108 |
--------------------------------------------------------------------------------
/cabot/cabotapp/migrations/0006_auto_20170821_1000.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11 on 2017-08-21 10:00
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('cabotapp', '0005_auto_20170818_1202'),
12 | ]
13 |
14 | operations = [
15 | migrations.DeleteModel(
16 | name='JenkinsStatusCheck',
17 | ),
18 | migrations.RenameModel(
19 | old_name='JenkinsCheck',
20 | new_name='JenkinsStatusCheck',
21 | )
22 | ]
23 |
--------------------------------------------------------------------------------
/cabot/cabotapp/migrations/0007_statuscheckresult_consecutive_failures.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11 on 2017-08-24 11:19
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('cabotapp', '0006_auto_20170821_1000'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='statuscheckresult',
17 | name='consecutive_failures',
18 | field=models.PositiveIntegerField(null=True),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/cabot/cabotapp/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/cabotapp/migrations/__init__.py
--------------------------------------------------------------------------------
/cabot/cabotapp/models/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import *
2 | from .jenkins_check_plugin import *
3 |
--------------------------------------------------------------------------------
/cabot/cabotapp/models/jenkins_check_plugin.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django.db import models
4 |
5 | from ..jenkins import get_job_status
6 | from .base import StatusCheck, StatusCheckResult
7 |
8 |
9 | class JenkinsStatusCheck(StatusCheck):
10 | jenkins_config = models.ForeignKey('JenkinsConfig')
11 |
12 | @property
13 | def check_category(self):
14 | return "Jenkins check"
15 |
16 | @property
17 | def failing_short_status(self):
18 | return 'Job failing on Jenkins'
19 |
20 | def _run(self):
21 | result = StatusCheckResult(status_check=self)
22 | try:
23 | status = get_job_status(self.jenkins_config, self.name)
24 | active = status['active']
25 | result.job_number = status['job_number']
26 | result.consecutive_failures = status['consecutive_failures']
27 | if status['status_code'] == 404:
28 | result.error = u'Job %s not found on Jenkins' % self.name
29 | result.succeeded = False
30 | return result
31 | elif status['status_code'] > 400:
32 | # Will fall through to next block
33 | raise Exception(u'returned %s' % status['status_code'])
34 | except Exception as e:
35 | # If something else goes wrong, we will *not* fail - otherwise
36 | # a lot of services seem to fail all at once.
37 | # Ugly to do it here but...
38 | result.error = u'Error fetching from Jenkins - %s' % e.message
39 | result.succeeded = True
40 | return result
41 |
42 | if not active:
43 | # We will fail if the job has been disabled
44 | result.error = u'Job "%s" disabled on Jenkins' % self.name
45 | result.succeeded = False
46 | else:
47 | if self.max_queued_build_time and status['blocked_build_time']:
48 | if status['blocked_build_time'] > self.max_queued_build_time * 60:
49 | result.succeeded = False
50 | result.error = u'Job "%s" has blocked build waiting for %ss (> %sm)' % (
51 | self.name,
52 | int(status['blocked_build_time']),
53 | self.max_queued_build_time,
54 | )
55 | result.job_number = status['queued_job_number']
56 | else:
57 | result.succeeded = status['succeeded']
58 | else:
59 | result.succeeded = status['succeeded']
60 | if not status['succeeded']:
61 | message = u'Job "%s" failing on Jenkins (%s)' % (self.name, status['consecutive_failures'])
62 | if result.error:
63 | result.error += u'; %s' % message
64 | else:
65 | result.error = message
66 | result.raw_data = status
67 | return result
68 |
69 | def calculate_debounced_passing(self, recent_results, debounce=0):
70 | """
71 | `debounce` is the number of previous job failures we need (not including this)
72 | to mark a search as passing or failing
73 | Returns:
74 | True if passing given debounce factor
75 | False if failing
76 | """
77 | last_result = recent_results[0]
78 | return last_result.consecutive_failures <= debounce
79 |
80 |
81 | class JenkinsConfig(models.Model):
82 | name = models.CharField(max_length=30, blank=False)
83 | jenkins_api = models.CharField(max_length=2000, blank=False)
84 | jenkins_user = models.CharField(max_length=2000, blank=False)
85 | jenkins_pass = models.CharField(max_length=2000, blank=False)
86 |
87 | def __str__(self):
88 | return self.name
89 |
90 |
91 | def create_default_jenkins_config():
92 | if not JenkinsConfig.objects.exists():
93 | if os.environ.get("JENKINS_API"):
94 | JenkinsConfig.objects.create(
95 | name="Default Jenkins",
96 | jenkins_api=os.environ.get("JENKINS_API", "http://jenkins.example.com"),
97 | jenkins_user=os.environ.get("JENKINS_USER", ""),
98 | jenkins_pass=os.environ.get("JENKINS_PASS", ""),
99 | )
100 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tasks.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import random
3 |
4 | from celery.task import task
5 | from django.conf import settings
6 | from django.utils import timezone
7 | from datetime import timedelta
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 |
12 | @task(ignore_result=True)
13 | def run_status_check(check_or_id):
14 | from .models import StatusCheck
15 | if not isinstance(check_or_id, StatusCheck):
16 | check = StatusCheck.objects.get(id=check_or_id)
17 | else:
18 | check = check_or_id
19 | # This will call the subclass method
20 | check.run()
21 |
22 |
23 | @task(ignore_result=True)
24 | def run_all_checks():
25 | from .models import StatusCheck
26 | from datetime import timedelta
27 | checks = StatusCheck.objects.all()
28 | seconds = range(60)
29 | for check in checks:
30 | if check.last_run:
31 | next_schedule = check.last_run + timedelta(minutes=check.frequency)
32 | if (not check.last_run) or timezone.now() > next_schedule:
33 | delay = random.choice(seconds)
34 | logger.debug('Scheduling task for %s seconds from now' % delay)
35 | run_status_check.apply_async((check.id,), countdown=delay)
36 |
37 |
38 | @task(ignore_result=True)
39 | def update_services(ignore_result=True):
40 | # Avoid importerrors and the like from legacy scheduling
41 | return
42 |
43 |
44 | @task(ignore_result=True)
45 | def update_service(service_or_id):
46 | from .models import Service
47 | if not isinstance(service_or_id, Service):
48 | service = Service.objects.get(id=service_or_id)
49 | else:
50 | service = service_or_id
51 | service.update_status()
52 |
53 |
54 | @task(ignore_result=True)
55 | def update_instance(instance_or_id):
56 | from .models import Instance
57 | if not isinstance(instance_or_id, Instance):
58 | instance = Instance.objects.get(id=instance_or_id)
59 | else:
60 | instance = instance_or_id
61 | instance.update_status()
62 |
63 |
64 | @task(ignore_result=True)
65 | def update_shifts():
66 | from .models import update_shifts as _update_shifts
67 | _update_shifts()
68 |
69 |
70 | @task(ignore_result=True)
71 | def clean_db(days_to_retain=7, batch_size=10000):
72 | """
73 | Clean up database otherwise it gets overwhelmed with StatusCheckResults.
74 |
75 | To loop over undeleted results, spawn new tasks to make sure db connection closed etc
76 | """
77 | from .models import StatusCheckResult, ServiceStatusSnapshot, InstanceStatusSnapshot
78 |
79 | to_discard_results = StatusCheckResult.objects.order_by('time_complete').filter(
80 | time_complete__lte=timezone.now() - timedelta(days=days_to_retain)
81 | )
82 | to_discard_service = ServiceStatusSnapshot.objects.order_by('time').filter(
83 | time__lte=timezone.now() - timedelta(days=days_to_retain)
84 | )
85 | to_discard_instance = InstanceStatusSnapshot.objects.order_by('time').filter(
86 | time__lte=timezone.now() - timedelta(days=days_to_retain)
87 | )
88 |
89 | result_ids = to_discard_results[:batch_size].values_list('id', flat=True)
90 | service_snapshot_ids = to_discard_service[:batch_size].values_list('id', flat=True)
91 | instance_snapshot_ids = to_discard_instance[:batch_size].values_list('id', flat=True)
92 |
93 | result_count = result_ids.count()
94 | service_snapshot_count = service_snapshot_ids.count()
95 | instance_snapshot_count = instance_snapshot_ids.count()
96 |
97 | StatusCheckResult.objects.filter(id__in=result_ids).delete()
98 | ServiceStatusSnapshot.objects.filter(id__in=service_snapshot_ids).delete()
99 | InstanceStatusSnapshot.objects.filter(id__in=instance_snapshot_ids).delete()
100 |
101 | # If we reached the batch size on either we need to re-queue to continue cleaning up.
102 | if (
103 | result_count == batch_size or service_snapshot_count == batch_size or instance_snapshot_count == batch_size
104 | ):
105 | clean_db.apply_async(kwargs={
106 | 'days_to_retain': days_to_retain,
107 | 'batch_size': batch_size},
108 | countdown=3)
109 |
--------------------------------------------------------------------------------
/cabot/cabotapp/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/cabotapp/templatetags/__init__.py
--------------------------------------------------------------------------------
/cabot/cabotapp/templatetags/extra.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.conf import settings
3 | from datetime import timedelta
4 |
5 | register = template.Library()
6 |
7 |
8 | @register.simple_tag
9 | def jenkins_human_url(jobname):
10 | return '{}job/{}/'.format(settings.JENKINS_API, jobname)
11 |
12 |
13 | @register.simple_tag
14 | def echo_setting(setting):
15 | return getattr(settings, setting, '')
16 |
17 |
18 | @register.filter(name='format_timedelta')
19 | def format_timedelta(delta):
20 | # Getting rid of microseconds.
21 | return str(timedelta(days=delta.days, seconds=delta.seconds))
22 |
23 | @register.filter
24 | def for_service(objects, service):
25 | return objects.filter(service=service)
26 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/cabotapp/tests/__init__.py
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/cabotapp/tests/fixtures/__init__.py
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/cabot_check_skeleton/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/cabotapp/tests/fixtures/cabot_check_skeleton/__init__.py
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/cabot_check_skeleton/plugin.py:
--------------------------------------------------------------------------------
1 | from cabot.cabotapp.models import StatusCheck
2 | from cabot.cabotapp.views import CheckCreateView
3 | from cabot.cabotapp.views import CheckUpdateView
4 | from cabot.cabotapp.views import StatusCheckForm
5 | from django.http import HttpResponseRedirect
6 | from django.core.urlresolvers import reverse
7 |
8 | class SkeletonStatusCheck(StatusCheck):
9 | edit_url_name = 'update-skeleton-check'
10 | duplicate_url_name = 'duplicate-skeleton-check'
11 |
12 | check_name = 'skeleton'
13 |
14 | class SkeletonStatusCheckForm(StatusCheckForm):
15 | class Meta:
16 | model = SkeletonStatusCheck
17 | fields = ('name',)
18 |
19 | class SkeletonCheckCreateView(CheckCreateView):
20 | model = StatusCheck
21 | form_class = SkeletonStatusCheckForm
22 |
23 | class SkeletonCheckUpdateView(CheckUpdateView):
24 | model = StatusCheck
25 | form_class = SkeletonStatusCheckForm
26 |
27 | def duplicate_check(request, pk):
28 | return HttpResponseRedirect(reverse('update-skeleton-check', kwargs={'pk': 25}))
29 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/gcal_response.ics:
--------------------------------------------------------------------------------
1 | BEGIN:VCALENDAR
2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN
3 | VERSION:2.0
4 | CALSCALE:GREGORIAN
5 | METHOD:PUBLISH
6 | X-WR-CALNAME:Cabottest
7 | X-WR-TIMEZONE:Europe/Copenhagen
8 | X-WR-CALDESC:
9 | BEGIN:VEVENT
10 | DTSTART:20160719T130000Z
11 | DTEND:20160719T160000Z
12 | DTSTAMP:20160719T102406Z
13 | UID:kf6gd4uc1hue70m7gkb7fdtsrc@google.com
14 | CREATED:20160719T091318Z
15 | DESCRIPTION:
16 | LAST-MODIFIED:20160719T091320Z
17 | LOCATION:
18 | SEQUENCE:0
19 | STATUS:CONFIRMED
20 | SUMMARY:troels
21 | TRANSP:OPAQUE
22 | END:VEVENT
23 | END:VCALENDAR
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/graphite_avg_response.json:
--------------------------------------------------------------------------------
1 | [{"target": "PROD", "datapoints": [[20.3, 1442834740], [12.5, 1442834750], [0.1, 1442834760], [0.8, 1442834770], [134.9, 1442834780], [151.0, 1442834790]]}]
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/graphite_null_response.json:
--------------------------------------------------------------------------------
1 | [{"target": "minSeries(hosts.1.df.var.df_complex.free.value,hosts.2.df.var.df_complex.free.value,hosts.3.df.var.df_complex.free.value,hosts.4.df.var.df_complex.free.value)", "datapoints": [[null, 1441144030], [null, 1441144040], [null, 1441144050], [null, 1441144060], [null, 1441144070], [null, 1441144080], [null, 1441144090], [null, 1441144100], [null, 1441144110], [null, 1441144120], [null, 1441144130], [null, 1441144140], [null, 1441144150], [null, 1441144160], [null, 1441144170], [null, 1441144180], [null, 1441144190], [null, 1441144200], [null, 1441144210], [null, 1441144220], [null, 1441144230], [null, 1441144240], [null, 1441144250], [null, 1441144260], [null, 1441144270], [null, 1441144280], [null, 1441144290], [null, 1441144300], [null, 1441144310], [null, 1441144320], [null, 1441144330], [null, 1441144340], [null, 1441144350], [null, 1441144360], [null, 1441144370], [null, 1441144380], [null, 1441144390], [null, 1441144400], [null, 1441144410], [null, 1441144420], [null, 1441144430], [null, 1441144440], [null, 1441144450], [null, 1441144460], [null, 1441144470], [null, 1441144480], [null, 1441144490], [null, 1441144500], [null, 1441144510], [null, 1441144520], [null, 1441144530], [null, 1441144540], [null, 1441144550], [null, 1441144560], [null, 1441144570], [null, 1441144580], [null, 1441144590], [null, 1441144600], [null, 1441144610], [null, 1441144620]]}]
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/graphite_response.json:
--------------------------------------------------------------------------------
1 | [{"target": "PROD", "datapoints": [[8.14908, 1387817760], [8.14908, 1387817820], [8.14908, 1387817880], [8.14908, 1387817940], [8.14908, 1387818000], [8.14908, 1387818060], [8.14908, 1387818120], [8.14908, 1387818180], [8.14908, 1387818240], [8.14908, 1387818300], [9.16092, 1387818360], [9.16092, 1387818420], [9.16092, 1387818480], [9.16092, 1387818540], [9.16092, 1387818600]]}, {"target": "stage", "datapoints": [[8.17349, 1387817760], [8.17349, 1387817820], [8.17349, 1387817880], [8.17349, 1387817940], [8.17349, 1387818000], [8.16242, 1387818060], [8.16242, 1387818120], [8.16242, 1387818180], [8.16242, 1387818240], [8.16242, 1387818300], [8.16242, 1387818360], [8.16242, 1387818420], [8.16242, 1387818480], [8.16242, 1387818540], [8.16242, 1387818600]]}]
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/recurring_response.ics:
--------------------------------------------------------------------------------
1 | BEGIN:VCALENDAR
2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN
3 | VERSION:2.0
4 | CALSCALE:GREGORIAN
5 | METHOD:PUBLISH
6 | X-WR-CALNAME:Cabot Rota
7 | X-WR-TIMEZONE:America/New_York
8 | X-WR-CALDESC:
9 | BEGIN:VTIMEZONE
10 | TZID:America/New_York
11 | X-LIC-LOCATION:America/New_York
12 | BEGIN:DAYLIGHT
13 | TZOFFSETFROM:-0500
14 | TZOFFSETTO:-0400
15 | TZNAME:EDT
16 | DTSTART:19700308T020000
17 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
18 | END:DAYLIGHT
19 | BEGIN:STANDARD
20 | TZOFFSETFROM:-0400
21 | TZOFFSETTO:-0500
22 | TZNAME:EST
23 | DTSTART:19701101T020000
24 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
25 | END:STANDARD
26 | END:VTIMEZONE
27 | BEGIN:VEVENT
28 | DTSTART;TZID=America/New_York:20161129T080000
29 | DTEND;TZID=America/New_York:20161130T080000
30 | RRULE:FREQ=DAILY;INTERVAL=2
31 | DTSTAMP:20161128T214445Z
32 | UID:mep8lmf2s1lhmmm366c6rhhers@google.com
33 | CREATED:20161128T204255Z
34 | DESCRIPTION:
35 | LAST-MODIFIED:20161128T204255Z
36 | LOCATION:
37 | SEQUENCE:0
38 | STATUS:CONFIRMED
39 | SUMMARY:foo
40 | TRANSP:OPAQUE
41 | END:VEVENT
42 | BEGIN:VEVENT
43 | DTSTART;TZID=America/New_York:20161128T080000
44 | DTEND;TZID=America/New_York:20161129T080000
45 | RRULE:FREQ=DAILY;INTERVAL=2
46 | DTSTAMP:20161128T214445Z
47 | UID:s4ftq5jd98cuubmbous3mgst5c@google.com
48 | CREATED:20161128T203907Z
49 | DESCRIPTION:
50 | LAST-MODIFIED:20161128T203947Z
51 | LOCATION:
52 | SEQUENCE:1
53 | STATUS:CONFIRMED
54 | SUMMARY:bar
55 | TRANSP:OPAQUE
56 | END:VEVENT
57 | END:VCALENDAR
58 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/recurring_response_complex.ics:
--------------------------------------------------------------------------------
1 | BEGIN:VCALENDAR
2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN
3 | VERSION:2.0
4 | CALSCALE:GREGORIAN
5 | METHOD:PUBLISH
6 | X-WR-CALNAME:Cabot Rota
7 | X-WR-TIMEZONE:America/New_York
8 | X-WR-CALDESC:
9 | BEGIN:VTIMEZONE
10 | TZID:America/New_York
11 | X-LIC-LOCATION:America/New_York
12 | BEGIN:DAYLIGHT
13 | TZOFFSETFROM:-0500
14 | TZOFFSETTO:-0400
15 | TZNAME:EDT
16 | DTSTART:19700308T020000
17 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
18 | END:DAYLIGHT
19 | BEGIN:STANDARD
20 | TZOFFSETFROM:-0400
21 | TZOFFSETTO:-0500
22 | TZNAME:EST
23 | DTSTART:19701101T020000
24 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
25 | END:STANDARD
26 | END:VTIMEZONE
27 | BEGIN:VEVENT
28 | DTSTART;TZID=America/New_York:20170410T080000
29 | DTEND;TZID=America/New_York:20170411T080000
30 | DTSTAMP:20170426T030852Z
31 | UID:s4ftq5jd98cuubmbous3mgst5c@google.com
32 | RECURRENCE-ID;TZID=America/New_York:20170411T080000
33 | CREATED:20161128T203907Z
34 | DESCRIPTION:
35 | LAST-MODIFIED:20170409T202640Z
36 | LOCATION:
37 | SEQUENCE:2
38 | STATUS:CONFIRMED
39 | SUMMARY:foo
40 | TRANSP:OPAQUE
41 | END:VEVENT
42 | BEGIN:VEVENT
43 | DTSTART;TZID=America/New_York:20170409T080000
44 | DTEND;TZID=America/New_York:20170410T080000
45 | DTSTAMP:20170426T030852Z
46 | UID:mep8lmf2s1lhmmm366c6rhhers@google.com
47 | RECURRENCE-ID;TZID=America/New_York:20170410T080000
48 | CREATED:20161128T204255Z
49 | DESCRIPTION:
50 | LAST-MODIFIED:20170409T202639Z
51 | LOCATION:
52 | SEQUENCE:1
53 | STATUS:CONFIRMED
54 | SUMMARY:bar
55 | TRANSP:OPAQUE
56 | END:VEVENT
57 | BEGIN:VEVENT
58 | DTSTART;TZID=America/New_York:20170411T080000
59 | DTEND;TZID=America/New_York:20170412T080000
60 | DTSTAMP:20170426T030852Z
61 | UID:s4ftq5jd98cuubmbous3mgst5c@google.com
62 | RECURRENCE-ID;TZID=America/New_York:20170409T080000
63 | CREATED:20161128T203907Z
64 | DESCRIPTION:
65 | LAST-MODIFIED:20170409T202637Z
66 | LOCATION:
67 | SEQUENCE:2
68 | STATUS:CONFIRMED
69 | SUMMARY:melissa
70 | TRANSP:OPAQUE
71 | END:VEVENT
72 | BEGIN:VEVENT
73 | DTSTART;TZID=America/New_York:20170219T080000
74 | DTEND;TZID=America/New_York:20170220T080000
75 | DTSTAMP:20170426T030852Z
76 | UID:s4ftq5jd98cuubmbous3mgst5c@google.com
77 | RECURRENCE-ID;TZID=America/New_York:20170220T080000
78 | CREATED:20161128T203907Z
79 | DESCRIPTION:
80 | LAST-MODIFIED:20170208T003612Z
81 | LOCATION:
82 | SEQUENCE:2
83 | STATUS:CONFIRMED
84 | SUMMARY:foo
85 | TRANSP:OPAQUE
86 | END:VEVENT
87 | BEGIN:VEVENT
88 | DTSTART;TZID=America/New_York:20170217T080000
89 | DTEND;TZID=America/New_York:20170219T080000
90 | DTSTAMP:20170426T030852Z
91 | UID:mep8lmf2s1lhmmm366c6rhhers@google.com
92 | RECURRENCE-ID;TZID=America/New_York:20170217T080000
93 | CREATED:20161128T204255Z
94 | DESCRIPTION:
95 | LAST-MODIFIED:20170208T003542Z
96 | LOCATION:
97 | SEQUENCE:0
98 | STATUS:CONFIRMED
99 | SUMMARY:bar
100 | TRANSP:OPAQUE
101 | END:VEVENT
102 | BEGIN:VEVENT
103 | DTSTART;TZID=America/New_York:20161128T080000
104 | DTEND;TZID=America/New_York:20161129T080000
105 | RRULE:FREQ=DAILY;INTERVAL=2
106 | EXDATE;TZID=America/New_York:20170218T080000
107 | DTSTAMP:20170426T030852Z
108 | UID:s4ftq5jd98cuubmbous3mgst5c@google.com
109 | CREATED:20161128T203907Z
110 | DESCRIPTION:
111 | LAST-MODIFIED:20161130T013836Z
112 | LOCATION:
113 | SEQUENCE:1
114 | STATUS:CONFIRMED
115 | SUMMARY:foo
116 | TRANSP:OPAQUE
117 | END:VEVENT
118 | BEGIN:VEVENT
119 | DTSTART;TZID=America/New_York:20161129T080000
120 | DTEND;TZID=America/New_York:20161130T080000
121 | RRULE:FREQ=DAILY;INTERVAL=2
122 | EXDATE;TZID=America/New_York:20170219T080000
123 | DTSTAMP:20170426T030852Z
124 | UID:mep8lmf2s1lhmmm366c6rhhers@google.com
125 | CREATED:20161128T204255Z
126 | DESCRIPTION:
127 | LAST-MODIFIED:20161128T204255Z
128 | LOCATION:
129 | SEQUENCE:0
130 | STATUS:CONFIRMED
131 | SUMMARY:gregory
132 | TRANSP:OPAQUE
133 | END:VEVENT
134 | END:VCALENDAR
135 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/fixtures/recurring_response_notz.ics:
--------------------------------------------------------------------------------
1 | BEGIN:VCALENDAR
2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN
3 | VERSION:2.0
4 | CALSCALE:GREGORIAN
5 | METHOD:PUBLISH
6 | X-WR-CALNAME:Cabot Rota
7 | X-WR-CALDESC:
8 | BEGIN:VTIMEZONE
9 | X-LIC-LOCATION:America/New_York
10 | BEGIN:DAYLIGHT
11 | TZOFFSETFROM:-0500
12 | TZOFFSETTO:-0400
13 | TZNAME:EDT
14 | DTSTART:19700308T020000
15 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
16 | END:DAYLIGHT
17 | BEGIN:STANDARD
18 | TZOFFSETFROM:-0400
19 | TZOFFSETTO:-0500
20 | TZNAME:EST
21 | DTSTART:19701101T020000
22 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
23 | END:STANDARD
24 | END:VTIMEZONE
25 | BEGIN:VEVENT
26 | DTSTART:20161129T080000
27 | DTEND:20161130T080000
28 | RRULE:FREQ=DAILY;INTERVAL=2
29 | DTSTAMP:20161128T214445Z
30 | UID:mep8lmf2s1lhmmm366c6rhhers@google.com
31 | CREATED:20161128T204255Z
32 | DESCRIPTION:
33 | LAST-MODIFIED:20161128T204255Z
34 | LOCATION:
35 | SEQUENCE:0
36 | STATUS:CONFIRMED
37 | SUMMARY:foo
38 | TRANSP:OPAQUE
39 | END:VEVENT
40 | BEGIN:VEVENT
41 | DTSTART:20161128T080000
42 | DTEND:20161129T080000
43 | RRULE:FREQ=DAILY;INTERVAL=2
44 | DTSTAMP:20161128T214445Z
45 | UID:s4ftq5jd98cuubmbous3mgst5c@google.com
46 | CREATED:20161128T203907Z
47 | DESCRIPTION:
48 | LAST-MODIFIED:20161128T203947Z
49 | LOCATION:
50 | SEQUENCE:1
51 | STATUS:CONFIRMED
52 | SUMMARY:bar
53 | TRANSP:OPAQUE
54 | END:VEVENT
55 | END:VCALENDAR
56 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/test_plugin_settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.contrib.auth import get_user_model
3 | from django.shortcuts import resolve_url
4 | from django.test import TestCase
5 |
6 | from mock import patch
7 |
8 | from cabot.cabotapp.alert import AlertPlugin
9 | from cabot.cabotapp.models import Service
10 |
11 |
12 | class PluginSettingsTest(TestCase):
13 | def setUp(self):
14 | self.username = 'testuser'
15 | self.password = 'testuserpassword'
16 | self.user = get_user_model().objects.create(username=self.username)
17 | self.user.set_password(self.password)
18 | self.user.save()
19 | self.client.login(username=self.username, password=self.password)
20 |
21 | def test_global_settings(self):
22 | resp = self.client.get(resolve_url('plugin-settings-global'), follow=True)
23 | self.assertEqual(resp.status_code, 200)
24 |
25 | def test_plugin_settings(self):
26 | plugin = AlertPlugin.objects.first()
27 |
28 | resp = self.client.get(resolve_url('plugin-settings', plugin_name=plugin.title), follow=True)
29 | self.assertEqual(resp.status_code, 200)
30 |
31 | def test_plugin_disable(self):
32 | plugin = AlertPlugin.objects.first()
33 |
34 | resp = self.client.post(resolve_url('plugin-settings', plugin_name=plugin.title), {'enabled': False}, follow=True)
35 | self.assertEqual(resp.status_code, 200)
36 | self.assertIn('Updated Successfully', resp.content)
37 |
38 | @patch('cabot.cabotapp.alert.AlertPlugin._send_alert')
39 | def test_plugin_alert_test(self, fake_send_alert):
40 | plugin = AlertPlugin.objects.first()
41 |
42 | resp = self.client.post(resolve_url('alert-test-plugin'), {'alert_plugin': plugin.id, 'old_status': 'PASSING', 'new_status': 'ERROR'})
43 | self.assertEqual(resp.status_code, 200)
44 | self.assertIn('ok', resp.content)
45 | fake_send_alert.assert_called()
46 |
47 | @patch('cabot.cabotapp.alert.AlertPlugin._send_alert')
48 | def test_global_alert_test(self, fake_send_alert):
49 | service = Service.objects.create(
50 | name='Service',
51 | )
52 |
53 | plugin = AlertPlugin.objects.first()
54 | service.alerts.add(
55 | plugin
56 | )
57 |
58 | resp = self.client.post(resolve_url('alert-test'), {'service': service.id, 'old_status': 'PASSING', 'new_status': 'ERROR'})
59 | self.assertEqual(resp.status_code, 200)
60 | self.assertIn('ok', resp.content)
61 | fake_send_alert.assert_called()
62 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/test_setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from urlparse import urlparse
3 |
4 | from django.contrib.auth import get_user_model
5 | from django.shortcuts import resolve_url
6 | from django.test import TestCase
7 |
8 |
9 | class SetupTest(TestCase):
10 | def test_initial_setup_redirect(self):
11 | resp = self.client.get(resolve_url('login'))
12 |
13 | self.assertEqual(resp.status_code, 302)
14 |
15 | url = urlparse(resp['Location'])
16 | self.assertEqual(url.path, resolve_url('first_time_setup'))
17 |
18 | # Don't redirect if there's already a user
19 | get_user_model().objects.create_user(username='test')
20 | resp = self.client.get(resolve_url('login'))
21 | self.assertEqual(resp.status_code, 200)
22 |
23 | def test_initial_setup_requires(self):
24 | resp = self.client.post(resolve_url('first_time_setup'))
25 | self.assertEqual(resp.status_code, 400)
26 |
27 | def test_initial_setup_post(self):
28 | resp = self.client.post(
29 | resolve_url('first_time_setup'),
30 | data={
31 | 'username': '',
32 | 'password': 'pass'
33 | })
34 | self.assertEqual(resp.status_code, 400)
35 |
36 | resp = self.client.post(
37 | resolve_url('first_time_setup'),
38 | data={
39 | 'username': 'test',
40 | 'password': ''
41 | })
42 | self.assertEqual(resp.status_code, 400)
43 | self.assertFalse(get_user_model().objects.exists())
44 |
45 | resp = self.client.post(
46 | resolve_url('first_time_setup'),
47 | data={
48 | 'username': 'test',
49 | 'password': 'pass'
50 | })
51 | self.assertEqual(resp.status_code, 302)
52 | self.assertTrue(get_user_model().objects.exists())
53 |
54 | def test_initial_setup_post_with_email(self):
55 | resp = self.client.post(
56 | resolve_url('first_time_setup'),
57 | data={
58 | 'username': 'test',
59 | 'email': 'fail',
60 | 'password': 'pass'
61 | })
62 | self.assertEqual(resp.status_code, 400)
63 | self.assertFalse(get_user_model().objects.exists())
64 |
65 | resp = self.client.post(
66 | resolve_url('first_time_setup'),
67 | data={
68 | 'username': 'test',
69 | 'email': 'real@email.com',
70 | 'password': 'pass'
71 | })
72 | self.assertEqual(resp.status_code, 302)
73 | self.assertTrue(get_user_model().objects.exists())
74 |
75 | def test_cant_setup_with_existing_user(self):
76 | get_user_model().objects.create_user(username='test')
77 |
78 | resp = self.client.post(
79 | resolve_url('first_time_setup'),
80 | data={
81 | 'username': 'test',
82 | 'email': 'real@email.com',
83 | 'password': 'pass'
84 | })
85 | self.assertEqual(get_user_model().objects.count(), 1)
86 |
87 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/test_urlprefix.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from django.core.urlresolvers import reverse, clear_url_caches
4 | from django.conf import settings
5 | from django.test.utils import override_settings
6 | from importlib import import_module
7 |
8 | from rest_framework import status, HTTP_HEADER_ENCODING
9 |
10 | from tests_basic import LocalTestCase
11 |
12 | class override_local_settings(override_settings):
13 | def clear_cache(self):
14 | # If we don't do this, nothing gets correctly set for the URL Prefix
15 | urlconf = settings.ROOT_URLCONF
16 | if urlconf in sys.modules:
17 | reload(sys.modules[urlconf])
18 | import_module(urlconf)
19 |
20 | # Don't forget to clear out the cache for `reverse`
21 | clear_url_caches()
22 |
23 | def __init__(self, urlprefix, custom_check_plugins):
24 | urlprefix = urlprefix.rstrip("/")
25 | installed_apps = settings.INSTALLED_APPS
26 | installed_apps += tuple(custom_check_plugins)
27 |
28 | # Have to turn off the compressor here, can't find a way to reload
29 | # the COMPRESS_URL into it on the fly
30 | super(override_local_settings, self).__init__(
31 | URL_PREFIX=urlprefix,
32 | MEDIA_URL="%s/media/" % urlprefix,
33 | STATIC_URL="%s/static/" % urlprefix,
34 | COMPRESS_URL="%s/static/" % urlprefix,
35 | COMPRESS_ENABLED=False,
36 | COMPRESS_PRECOMPILERS=(),
37 | INSTALLED_APPS=installed_apps
38 | )
39 |
40 | def __enter__(self):
41 | super(override_local_settings, self).__enter__()
42 | self.clear_cache()
43 |
44 | def __exit__(self, exc_type, exc_value, traceback):
45 | super(override_local_settings, self).__exit__(exc_type, exc_value, traceback)
46 | self.clear_cache()
47 |
48 | def set_url_prefix_and_custom_check_plugins(prefix, plugins):
49 | return override_local_settings(prefix, plugins)
50 |
51 | class URLPrefixTestCase(LocalTestCase):
52 | def set_url_prefix(self, prefix):
53 | return override_local_settings(prefix, [])
54 |
55 | def test_reverse(self):
56 | prefix = '/test'
57 | before = reverse('services')
58 |
59 | with self.set_url_prefix(prefix):
60 | self.assertNotEqual(reverse('services'), before)
61 | self.assertTrue(reverse('services').startswith(prefix))
62 | self.assertEqual(reverse('services')[len(prefix):], before)
63 |
64 | def test_loginurl(self):
65 | prefix = '/test'
66 |
67 | with self.set_url_prefix(prefix):
68 | loginurl = str(settings.LOGIN_URL)
69 | response = self.client.get(reverse('services'))
70 |
71 | self.assertTrue(loginurl.startswith(prefix))
72 | self.assertTrue(loginurl in response.url)
73 |
74 | def test_query(self):
75 | prefix = '/test'
76 | self.client.login(username=self.username, password=self.password)
77 |
78 | before_services = self.client.get(reverse('services'))
79 | before_systemstatus = self.client.get(reverse('system-status'))
80 |
81 | with self.set_url_prefix(prefix):
82 | response = self.client.get(reverse('services'))
83 |
84 | self.assertEqual(response.status_code, before_services.status_code)
85 | self.assertNotEqual(response.content, before_services.content)
86 |
87 | self.assertIn(reverse('services'), response.content)
88 |
89 | response_systemstatus = self.client.get(reverse('system-status'))
90 |
91 | self.assertEqual(response_systemstatus.status_code, before_systemstatus.status_code)
92 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/tests_icmp_check.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 | from mock import patch
4 |
5 | from cabot.cabotapp.models import (
6 | ICMPStatusCheck,
7 | Instance,
8 | Service,
9 | )
10 |
11 | from .tests_basic import LocalTestCase
12 |
13 | class TestICMPCheckRun(LocalTestCase):
14 |
15 | def setUp(self):
16 | super(TestICMPCheckRun, self).setUp()
17 | self.instance = Instance.objects.create(
18 | name='Instance',
19 | address='1.2.3.4'
20 | )
21 | self.icmp_check = ICMPStatusCheck.objects.create(
22 | name='ICMP Check',
23 | created_by=self.user,
24 | importance=Service.CRITICAL_STATUS,
25 | )
26 | self.instance.status_checks.add(
27 | self.icmp_check)
28 |
29 | self.patch = patch('cabot.cabotapp.models.subprocess.check_output', autospec=True)
30 | self.mock_check_output = self.patch.start()
31 |
32 | def tearDown(self):
33 | self.patch.stop()
34 | super(TestICMPCheckRun, self).tearDown()
35 |
36 | def test_icmp_run_use_instance_address(self):
37 | self.icmp_check.run()
38 | args = ['ping', '-c', '1', u'1.2.3.4']
39 | self.mock_check_output.assert_called_once_with(args, shell=False, stderr=-2)
40 |
41 | def test_icmp_run_success(self):
42 | checkresults = self.icmp_check.statuscheckresult_set.all()
43 | self.assertEqual(len(checkresults), 0)
44 | self.icmp_check.run()
45 | checkresults = self.icmp_check.statuscheckresult_set.all()
46 | self.assertEqual(len(checkresults), 1)
47 | self.assertTrue(self.icmp_check.last_result().succeeded)
48 |
49 | def test_icmp_run_bad_address(self):
50 | self.mock_check_output.side_effect = subprocess.CalledProcessError(2, None, "ping: bad address")
51 | checkresults = self.icmp_check.statuscheckresult_set.all()
52 | self.assertEqual(len(checkresults), 0)
53 | self.icmp_check.run()
54 | checkresults = self.icmp_check.statuscheckresult_set.all()
55 | self.assertEqual(len(checkresults), 1)
56 | self.assertFalse(self.icmp_check.last_result().succeeded)
57 |
58 | def test_icmp_run_inacessible(self):
59 | self.mock_check_output.side_effect = subprocess.CalledProcessError(1, None, "packet loss")
60 | checkresults = self.icmp_check.statuscheckresult_set.all()
61 | self.assertEqual(len(checkresults), 0)
62 | self.icmp_check.run()
63 | checkresults = self.icmp_check.statuscheckresult_set.all()
64 | self.assertEqual(len(checkresults), 1)
65 | self.assertFalse(self.icmp_check.last_result().succeeded)
66 |
--------------------------------------------------------------------------------
/cabot/cabotapp/tests/tests_jenkins.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import unittest
4 | from datetime import timedelta
5 |
6 | import jenkins
7 | from cabot.cabotapp import jenkins as cabot_jenkins
8 | from cabot.cabotapp.models import JenkinsConfig
9 | from cabot.cabotapp.models.jenkins_check_plugin import JenkinsStatusCheck
10 | from django.utils import timezone
11 | from freezegun import freeze_time
12 | from mock import create_autospec, patch
13 |
14 |
15 | class TestGetStatus(unittest.TestCase):
16 |
17 | def setUp(self):
18 | self.job = {
19 | u'inQueue': False,
20 | u'queueItem': None,
21 | u'lastSuccessfulBuild': {
22 | u'number': 12,
23 | },
24 | u'lastCompletedBuild': {
25 | u'number': 12,
26 | },
27 | u'lastBuild': {
28 | u'number': 12,
29 | },
30 | u'color': 'blue'
31 | }
32 |
33 | self.build = {
34 | u'number': 12,
35 | u'result': u'SUCCESS'
36 |
37 | }
38 |
39 | self.mock_client = create_autospec(jenkins.Jenkins)
40 | self.mock_client.get_job_info.return_value = self.job
41 | self.mock_client.get_build_info.return_value = self.build
42 |
43 | self.mock_config = create_autospec(JenkinsConfig)
44 |
45 | @patch("cabot.cabotapp.jenkins._get_jenkins_client")
46 | def test_job_passing(self, mock_jenkins):
47 | mock_jenkins.return_value = self.mock_client
48 |
49 | status = cabot_jenkins.get_job_status(self.mock_config, 'foo')
50 |
51 | expected = {
52 | 'active': True,
53 | 'succeeded': True,
54 | 'job_number': 12,
55 | 'blocked_build_time': None,
56 | 'consecutive_failures': 0,
57 | 'status_code': 200
58 | }
59 | self.assertEqual(status, expected)
60 |
61 | @patch("cabot.cabotapp.jenkins._get_jenkins_client")
62 | def test_job_failing(self, mock_jenkins):
63 | mock_jenkins.return_value = self.mock_client
64 |
65 | self.build[u'result'] = u'FAILURE'
66 | self.job[u'lastSuccessfulBuild'] = {
67 | u'number': 11,
68 | u'result': u'SUCCESS'
69 | }
70 |
71 | jenkins_check = JenkinsStatusCheck(
72 | name="foo",
73 | jenkins_config=JenkinsConfig(
74 | name="name",
75 | jenkins_api="a",
76 | jenkins_user="u",
77 | jenkins_pass="p"
78 | )
79 | )
80 | result = JenkinsStatusCheck._run(jenkins_check)
81 |
82 | self.assertEqual(result.consecutive_failures, 1)
83 | self.assertFalse(result.succeeded)
84 |
85 | @freeze_time('2017-03-02 10:30')
86 | @patch("cabot.cabotapp.jenkins._get_jenkins_client")
87 | def test_job_queued_last_succeeded(self, mock_jenkins):
88 | mock_jenkins.return_value = self.mock_client
89 | self.job[u'lastBuild'] = {u'number': 13}
90 |
91 | self.job[u'inQueue'] = True
92 | self.job['queueItem'] = {
93 | 'inQueueSince': float(timezone.now().strftime('%s')) * 1000
94 | }
95 |
96 | with freeze_time(timezone.now() + timedelta(minutes=10)):
97 | status = cabot_jenkins.get_job_status(self.mock_config, 'foo')
98 |
99 | expected = {
100 | 'active': True,
101 | 'succeeded': True,
102 | 'job_number': 12,
103 | 'queued_job_number': 13,
104 | 'blocked_build_time': 600,
105 | 'consecutive_failures': 0,
106 | 'status_code': 200
107 | }
108 | self.assertEqual(status, expected)
109 |
110 | @freeze_time('2017-03-02 10:30')
111 | @patch("cabot.cabotapp.jenkins._get_jenkins_client")
112 | def test_job_queued_last_failed(self, mock_jenkins):
113 | mock_jenkins.return_value = self.mock_client
114 | self.job[u'lastBuild'] = {u'number': 13}
115 | self.job[u'inQueue'] = True
116 | self.job['queueItem'] = {
117 | 'inQueueSince': float(timezone.now().strftime('%s')) * 1000
118 | }
119 | self.build[u'result'] = u'FAILURE'
120 |
121 | with freeze_time(timezone.now() + timedelta(minutes=10)):
122 | status = cabot_jenkins.get_job_status(self.mock_config, 'foo')
123 |
124 | expected = {
125 | 'active': True,
126 | 'succeeded': False,
127 | 'job_number': 12,
128 | 'queued_job_number': 13,
129 | 'blocked_build_time': 600,
130 | 'consecutive_failures': 0,
131 | 'status_code': 200
132 | }
133 | self.assertEqual(status, expected)
134 |
135 | @patch("cabot.cabotapp.jenkins._get_jenkins_client")
136 | def test_job_unknown(self, mock_jenkins):
137 | self.mock_client.get_job_info.side_effect = jenkins.NotFoundException()
138 | mock_jenkins.return_value = self.mock_client
139 |
140 | status = cabot_jenkins.get_job_status(self.mock_config, 'unknown-job')
141 |
142 | expected = {
143 | 'active': None,
144 | 'succeeded': None,
145 | 'job_number': None,
146 | 'blocked_build_time': None,
147 | 'status_code': 404
148 | }
149 | self.assertEqual(status, expected)
150 |
151 | @patch("cabot.cabotapp.jenkins._get_jenkins_client")
152 | def test_job_no_build(self, mock_jenkins):
153 | unbuilt_job = {
154 | u'inQueue': False,
155 | u'queueItem': None,
156 | u'lastSuccessfulBuild': None,
157 | u'lastCompletedBuild': None,
158 | u'lastBuild': None,
159 | u'color': u'notbuilt'
160 | }
161 | self.mock_client.get_job_info.return_value = unbuilt_job
162 | mock_jenkins.return_value = self.mock_client
163 | with self.assertRaises(Exception):
164 | cabot_jenkins.get_job_status(self.mock_config, 'job-unbuilt')
165 |
166 | @patch("cabot.cabotapp.jenkins._get_jenkins_client")
167 | def test_job_no_good_build(self, mock_jenkins):
168 | self.mock_client.get_job_info.return_value = {
169 | u'inQueue': False,
170 | u'queueItem': None,
171 | u'lastSuccessfulBuild': None,
172 | u'lastCompletedBuild': {
173 | u'number': 1,
174 | },
175 | u'lastBuild': {
176 | u'number': 1,
177 | },
178 | u'color': u'red'
179 | }
180 | self.mock_client.get_build_info.return_value = {
181 | u'number': 1,
182 | u'result': u'FAILURE'
183 | }
184 | mock_jenkins.return_value = self.mock_client
185 | status = cabot_jenkins.get_job_status(self.mock_config, 'job-no-good-build')
186 | expected = {
187 | 'active': True,
188 | 'succeeded': False,
189 | 'job_number': 1,
190 | 'blocked_build_time': None,
191 | 'consecutive_failures': 1,
192 | 'status_code': 200
193 | }
194 | self.assertEqual(status, expected)
195 |
--------------------------------------------------------------------------------
/cabot/cabotapp/utils.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 |
3 |
4 | def cabot_needs_setup():
5 | return not get_user_model().objects.all().exists()
6 |
--------------------------------------------------------------------------------
/cabot/celery.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | import os
4 | from datetime import timedelta
5 |
6 | from django.conf import settings
7 | from celery import Celery
8 |
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cabot.settings')
10 |
11 | app = Celery('cabot')
12 | app.config_from_object('cabot.celeryconfig')
13 | app.autodiscover_tasks()
14 |
15 | app.conf.beat_schedule = {
16 | 'run-all-checks': {
17 | 'task': 'cabot.cabotapp.tasks.run_all_checks',
18 | 'schedule': timedelta(seconds=60),
19 | },
20 | 'update-shifts': {
21 | 'task': 'cabot.cabotapp.tasks.update_shifts',
22 | 'schedule': timedelta(seconds=1800),
23 | },
24 | 'clean-db': {
25 | 'task': 'cabot.cabotapp.tasks.clean_db',
26 | 'schedule': timedelta(seconds=60 * 60 * 24),
27 | },
28 | }
29 |
--------------------------------------------------------------------------------
/cabot/celeryconfig.py:
--------------------------------------------------------------------------------
1 | import os
2 | from cabot.settings_utils import environ_get_list
3 |
4 | broker_url = environ_get_list(['CELERY_BROKER_URL', 'CACHE_URL'])
5 | # Set environment variable if you want to run tests without a redis instance
6 |
7 | task_always_eager = environ_get_list(['CELERY_ALWAYS_EAGER', 'CELERY_TASK_ALWAYS_EAGER'], False)
8 | backend = os.environ.get('CELERY_RESULT_BACKEND', None)
9 | task_default_queue = os.environ.get('CELERY_DEFAULT_QUEUE', 'celery')
10 |
11 | timezone = 'UTC'
12 |
--------------------------------------------------------------------------------
/cabot/context_processors.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 |
4 | def global_settings(request):
5 | return {
6 | 'ENABLE_SUBSCRIPTION': settings.ENABLE_SUBSCRIPTION,
7 | 'ENABLE_DUTY_ROTA': settings.ENABLE_DUTY_ROTA,
8 | }
9 |
--------------------------------------------------------------------------------
/cabot/entrypoint.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 |
5 | def main():
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cabot.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/cabot/rest_urls.py:
--------------------------------------------------------------------------------
1 | from django.db import models as model_fields
2 | from django.conf import settings
3 | from django.conf.urls import url, include
4 | from django.contrib.auth import models as django_models
5 |
6 | from polymorphic.models import PolymorphicModel
7 | from cabot.cabotapp import models, alert
8 | from rest_framework import routers, serializers, viewsets, mixins
9 | import logging
10 |
11 | logger = logging.getLogger(__name__)
12 | router = routers.DefaultRouter()
13 |
14 |
15 | def create_viewset(arg_model, arg_fields, arg_read_only_fields=(), readonly=False):
16 | arg_read_only_fields = ('id',) + arg_read_only_fields
17 | for field in arg_read_only_fields:
18 | if field not in arg_fields:
19 | arg_fields = arg_fields + (field,)
20 |
21 | class Serializer(serializers.ModelSerializer):
22 | class Meta:
23 | model = arg_model
24 | fields = arg_fields
25 | read_only_fields = arg_read_only_fields
26 |
27 | viewset_class = None
28 | if readonly:
29 | viewset_class = viewsets.ReadOnlyModelViewSet
30 | else:
31 | viewset_class = viewsets.ModelViewSet
32 |
33 | class ViewSet(viewset_class):
34 | queryset = arg_model.objects
35 | serializer_class = Serializer
36 | ordering = ['id']
37 | filter_fields = arg_fields
38 |
39 | return ViewSet
40 |
41 | check_group_mixin_fields = (
42 | 'name',
43 | 'users_to_notify',
44 | 'alerts_enabled',
45 | 'status_checks',
46 | 'alerts',
47 | 'hackpad_id',
48 | )
49 |
50 | router.register(r'services', create_viewset(
51 | arg_model=models.Service,
52 | arg_fields=check_group_mixin_fields + (
53 | 'url',
54 | 'instances',
55 | 'overall_status',
56 | ),
57 | ))
58 |
59 | router.register(r'instances', create_viewset(
60 | arg_model=models.Instance,
61 | arg_fields=check_group_mixin_fields + (
62 | 'address',
63 | 'overall_status',
64 | ),
65 | ))
66 |
67 | status_check_fields = (
68 | 'name',
69 | 'active',
70 | 'importance',
71 | 'frequency',
72 | 'debounce',
73 | 'calculated_status',
74 | )
75 |
76 | router.register(r'status_checks', create_viewset(
77 | arg_model=models.StatusCheck,
78 | arg_fields=status_check_fields,
79 | readonly=True,
80 | ))
81 |
82 | router.register(r'icmp_checks', create_viewset(
83 | arg_model=models.ICMPStatusCheck,
84 | arg_fields=status_check_fields,
85 | ))
86 |
87 | router.register(r'graphite_checks', create_viewset(
88 | arg_model=models.GraphiteStatusCheck,
89 | arg_fields=status_check_fields + (
90 | 'metric',
91 | 'check_type',
92 | 'value',
93 | 'expected_num_hosts',
94 | 'allowed_num_failures',
95 | ),
96 | ))
97 |
98 | router.register(r'http_checks', create_viewset(
99 | arg_model=models.HttpStatusCheck,
100 | arg_fields=status_check_fields + (
101 | 'endpoint',
102 | 'username',
103 | 'password',
104 | 'text_match',
105 | 'status_code',
106 | 'timeout',
107 | 'verify_ssl_certificate',
108 | ),
109 | ))
110 |
111 | router.register(r'jenkins_checks', create_viewset(
112 | arg_model=models.JenkinsStatusCheck,
113 | arg_fields=status_check_fields + (
114 | 'max_queued_build_time',
115 | 'jenkins_config',
116 | ),
117 | ))
118 |
119 | # User API is off by default, could expose/allow modifying dangerous fields
120 | if settings.EXPOSE_USER_API:
121 | router.register(r'users', create_viewset(
122 | arg_model=django_models.User,
123 | arg_fields=(
124 | 'password',
125 | 'is_active',
126 | 'groups',
127 | #'user_permissions', # Doesn't work, removing for now
128 | 'username',
129 | 'first_name',
130 | 'last_name',
131 | 'email',
132 | ),
133 | ))
134 |
135 | router.register(r'user_profiles', create_viewset(
136 | arg_model=models.UserProfile,
137 | arg_fields=(
138 | 'user',
139 | 'fallback_alert_user',
140 | ),
141 | ))
142 |
143 |
144 | router.register(r'shifts', create_viewset(
145 | arg_model=models.Shift,
146 | arg_fields=(
147 | 'start',
148 | 'end',
149 | 'user',
150 | 'uid',
151 | 'deleted',
152 | )
153 | ))
154 |
155 | router.register(r'alertplugins', create_viewset(
156 | arg_model=alert.AlertPlugin,
157 | arg_fields=(
158 | 'title',
159 | ),
160 | readonly=True
161 | ))
162 |
--------------------------------------------------------------------------------
/cabot/settings_ldap.py:
--------------------------------------------------------------------------------
1 | import os
2 | import ldap
3 | from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
4 |
5 |
6 | # Baseline configuration.
7 | AUTH_LDAP_SERVER_URI = os.environ.get('AUTH_LDAP_SERVER_URI', 'ldap://ldap.example.com')
8 |
9 | AUTH_LDAP_BIND_DN = os.environ.get('AUTH_LDAP_BIND_DN', 'cn=Manager,dc=example,dc=com')
10 | AUTH_LDAP_BIND_PASSWORD = os.environ.get('AUTH_LDAP_BIND_PASSWORD', '')
11 | AUTH_LDAP_USER_FILTER = os.environ.get('AUTH_LDAP_USER_FILTER', '(uid=%(user)s)')
12 | AUTH_LDAP_USER_SEARCH = LDAPSearch(os.environ.get('AUTH_LDAP_USER_SEARCH', 'ou=user,dc=example,dc=com'),
13 | ldap.SCOPE_SUBTREE, AUTH_LDAP_USER_FILTER)
14 |
15 | # Populate the Django user from the LDAP directory.
16 | AUTH_LDAP_USER_ATTR_MAP = {
17 | 'first_name': 'givenName',
18 | 'last_name': 'sn',
19 | 'email': 'mail',
20 | }
21 |
--------------------------------------------------------------------------------
/cabot/settings_utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | from distutils.util import strtobool
3 |
4 |
5 | def force_bool(val):
6 | return strtobool(str(val))
7 |
8 |
9 | def environ_get_list(names, default=None):
10 | for name in names:
11 | if name in os.environ:
12 | return os.environ[name]
13 | return default
14 |
--------------------------------------------------------------------------------
/cabot/static/404.html:
--------------------------------------------------------------------------------
1 | 404 Error: Page not found
--------------------------------------------------------------------------------
/cabot/static/500.html:
--------------------------------------------------------------------------------
1 | 500 error
--------------------------------------------------------------------------------
/cabot/static/502.html:
--------------------------------------------------------------------------------
1 | 502 error
--------------------------------------------------------------------------------
/cabot/static/503.html:
--------------------------------------------------------------------------------
1 | 503 error
--------------------------------------------------------------------------------
/cabot/static/504.html:
--------------------------------------------------------------------------------
1 | 504 error
--------------------------------------------------------------------------------
/cabot/static/arachnys/css/base.less:
--------------------------------------------------------------------------------
1 | body{
2 | padding-top:50px;
3 | }
4 | .form-group {
5 | ul {
6 | list-style-type: none;
7 | padding: 5px 0 0 0;
8 | }
9 |
10 | input[type="radio"] {
11 | margin-top: -3px;
12 | }
13 | }
14 |
15 | tr.warning a {
16 | text-decoration: line-through;
17 | }
18 |
19 | #graphite_data_container {
20 | max-height: 200px;
21 | overflow: scroll;
22 | }
23 |
24 | .ui-autocomplete {
25 | max-height: 400px;
26 | overflow-y: scroll;
27 | font-size: 10px;
28 | }
29 |
30 | .jqstooltip {
31 | display: none;
32 | }
33 |
34 | div.dataTables_paginate {
35 | float: right;
36 | }
37 |
38 | div.dataTables_info {
39 | float: left;
40 | margin: 20px 0;
41 | }
42 |
43 | div.dataTables_length {
44 | float: right;
45 | }
46 |
47 | .messages {
48 | margin: auto;
49 | width: 50%;
50 | margin-top: 10px;
51 | }
52 |
53 | .navbar-brand > img {
54 | display: inline-block;
55 | }
--------------------------------------------------------------------------------
/cabot/static/arachnys/css/morris.css:
--------------------------------------------------------------------------------
1 | .morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0}
2 | .morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0}
3 |
--------------------------------------------------------------------------------
/cabot/static/arachnys/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/arachnys/img/favicon.ico
--------------------------------------------------------------------------------
/cabot/static/arachnys/img/icon_48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/arachnys/img/icon_48x48.png
--------------------------------------------------------------------------------
/cabot/static/arachnys/img/icon_96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/arachnys/img/icon_96x96.png
--------------------------------------------------------------------------------
/cabot/static/bootstrap/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 | * Top navigation
22 | * Hide default border to remove 1px line.
23 | */
24 | .navbar-fixed-top {
25 | border: 0;
26 | }
27 |
28 | /*
29 | * Sidebar
30 | */
31 |
32 | /* Hide for mobile, show later */
33 | .sidebar {
34 | display: none;
35 | }
36 | @media (min-width: 768px) {
37 | .sidebar {
38 | position: fixed;
39 | top: 51px;
40 | bottom: 0;
41 | left: 0;
42 | z-index: 1000;
43 | display: block;
44 | padding: 20px;
45 | overflow-x: hidden;
46 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
47 | background-color: #f5f5f5;
48 | border-right: 1px solid #eee;
49 | }
50 | }
51 |
52 | /* Sidebar navigation */
53 | .nav-sidebar {
54 | margin-right: -21px; /* 20px padding + 1px border */
55 | margin-bottom: 20px;
56 | margin-left: -20px;
57 | }
58 | .nav-sidebar > li > a {
59 | padding-right: 20px;
60 | padding-left: 20px;
61 | }
62 | .nav-sidebar > .active > a,
63 | .nav-sidebar > .active > a:hover,
64 | .nav-sidebar > .active > a:focus {
65 | color: #fff;
66 | background-color: #428bca;
67 | }
68 |
69 |
70 | /*
71 | * Main content
72 | */
73 |
74 | .main {
75 | padding: 20px;
76 | }
77 | @media (min-width: 768px) {
78 | .main {
79 | padding-right: 40px;
80 | padding-left: 40px;
81 | }
82 | }
83 | .main .page-header {
84 | margin-top: 0;
85 | }
86 |
87 |
88 | /*
89 | * Placeholder dashboard ideas
90 | */
91 |
92 | .placeholders {
93 | margin-bottom: 30px;
94 | text-align: center;
95 | }
96 | .placeholders h4 {
97 | margin-bottom: 0;
98 | }
99 | .placeholder {
100 | margin-bottom: 20px;
101 | }
102 | .placeholder img {
103 | display: inline-block;
104 | border-radius: 50%;
105 | }
106 |
--------------------------------------------------------------------------------
/cabot/static/bootstrap/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/bootstrap/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/cabot/static/bootstrap/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/bootstrap/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/cabot/static/bootstrap/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/bootstrap/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/cabot/static/bootstrap/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/bootstrap/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/cabot/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/favicon.ico
--------------------------------------------------------------------------------
/cabot/static/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
--------------------------------------------------------------------------------
/cabot/static/theme/css/bootstrap-datatables.min.css:
--------------------------------------------------------------------------------
1 | table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody>table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody>table>thead .sorting:after,div.dataTables_scrollBody>table>thead .sorting_asc:after,div.dataTables_scrollBody>table>thead .sorting_desc:after{display:none}div.dataTables_scrollBody>table>tbody>tr:first-child>th,div.dataTables_scrollBody>table>tbody>tr:first-child>td{border-top:none}div.dataTables_scrollFoot>.dataTables_scrollFootInner{box-sizing:content-box}div.dataTables_scrollFoot>.dataTables_scrollFootInner>table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0}
2 |
--------------------------------------------------------------------------------
/cabot/static/theme/css/chosen-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/css/chosen-sprite.png
--------------------------------------------------------------------------------
/cabot/static/theme/css/chosen-sprite@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/css/chosen-sprite@2x.png
--------------------------------------------------------------------------------
/cabot/static/theme/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/cabot/static/theme/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/cabot/static/theme/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/cabot/static/theme/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/cabot/static/theme/img/animated-overlay.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/animated-overlay.gif
--------------------------------------------------------------------------------
/cabot/static/theme/img/arrows-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/arrows-active.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/arrows-normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/arrows-normal.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/bg-input-focus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/bg-input-focus.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/bg-input.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/bg-input.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/bg-login.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/bg-login.jpg
--------------------------------------------------------------------------------
/cabot/static/theme/img/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/bg.jpg
--------------------------------------------------------------------------------
/cabot/static/theme/img/buttons.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/buttons.gif
--------------------------------------------------------------------------------
/cabot/static/theme/img/calendar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/calendar.gif
--------------------------------------------------------------------------------
/cabot/static/theme/img/chat-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/chat-left.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/chat-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/chat-right.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/chosen-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/chosen-sprite.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/close-button-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/close-button-white.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/close-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/close-button.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/crop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/crop.gif
--------------------------------------------------------------------------------
/cabot/static/theme/img/dbg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/dbg.jpg
--------------------------------------------------------------------------------
/cabot/static/theme/img/dialogs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/dialogs.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/favicon.ico
--------------------------------------------------------------------------------
/cabot/static/theme/img/glyphicons-halflings-red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/glyphicons-halflings-red.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/i_16_radio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/i_16_radio.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/icons-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/icons-big.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/icons-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/icons-small.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/logo.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/logo20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/logo20.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/progress.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/progress.gif
--------------------------------------------------------------------------------
/cabot/static/theme/img/quicklook-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/quicklook-bg.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/quicklook-icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/quicklook-icons.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/resize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/resize.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/spinner-mini.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/spinner-mini.gif
--------------------------------------------------------------------------------
/cabot/static/theme/img/sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/sprite.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/toolbar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/toolbar.gif
--------------------------------------------------------------------------------
/cabot/static/theme/img/toolbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/toolbar.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-bg_flat_0_aaaaaa_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-bg_flat_0_aaaaaa_40x100.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-bg_flat_75_ffffff_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-bg_flat_75_ffffff_40x100.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-bg_glass_55_fbf9ee_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-bg_glass_55_fbf9ee_1x400.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-bg_glass_65_ffffff_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-bg_glass_65_ffffff_1x400.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-bg_glass_75_dadada_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-bg_glass_75_dadada_1x400.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-bg_glass_75_e6e6e6_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-bg_glass_75_e6e6e6_1x400.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-bg_glass_95_fef1ec_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-bg_glass_95_fef1ec_1x400.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-bg_highlight-soft_75_cccccc_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-bg_highlight-soft_75_cccccc_1x100.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-icons_222222_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-icons_222222_256x240.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-icons_2e83ff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-icons_2e83ff_256x240.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-icons_454545_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-icons_454545_256x240.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-icons_888888_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-icons_888888_256x240.png
--------------------------------------------------------------------------------
/cabot/static/theme/img/ui-icons_cd0a0a_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arachnys/cabot/eb0b3544f8c8ab2dee4643df191da346a941734f/cabot/static/theme/img/ui-icons_cd0a0a_256x240.png
--------------------------------------------------------------------------------
/cabot/static/theme/js/custom.js:
--------------------------------------------------------------------------------
1 |
2 | /* ---------- Additional functions for data table ---------- */
3 | $.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
4 | {
5 | return {
6 | "iStart": oSettings._iDisplayStart,
7 | "iEnd": oSettings.fnDisplayEnd(),
8 | "iLength": oSettings._iDisplayLength,
9 | "iTotal": oSettings.fnRecordsTotal(),
10 | "iFilteredTotal": oSettings.fnRecordsDisplay(),
11 | "iPage": Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ),
12 | "iTotalPages": Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength )
13 | };
14 | }
15 | $.extend( $.fn.dataTableExt.oPagination, {
16 | "bootstrap": {
17 | "fnInit": function( oSettings, nPaging, fnDraw ) {
18 | var oLang = oSettings.oLanguage.oPaginate;
19 | var fnClickHandler = function ( e ) {
20 | e.preventDefault();
21 | if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) {
22 | fnDraw( oSettings );
23 | }
24 | };
25 |
26 | $(nPaging).append(
27 | '
'
31 | );
32 | var els = $('a', nPaging);
33 | $(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler );
34 | $(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler );
35 | },
36 |
37 | "fnUpdate": function ( oSettings, fnDraw ) {
38 | var iListLength = 5;
39 | var oPaging = oSettings.oInstance.fnPagingInfo();
40 | var an = oSettings.aanFeatures.p;
41 | var i, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2);
42 |
43 | if ( oPaging.iTotalPages < iListLength) {
44 | iStart = 1;
45 | iEnd = oPaging.iTotalPages;
46 | }
47 | else if ( oPaging.iPage <= iHalf ) {
48 | iStart = 1;
49 | iEnd = iListLength;
50 | } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) {
51 | iStart = oPaging.iTotalPages - iListLength + 1;
52 | iEnd = oPaging.iTotalPages;
53 | } else {
54 | iStart = oPaging.iPage - iHalf + 1;
55 | iEnd = iStart + iListLength - 1;
56 | }
57 |
58 | for ( i=0, iLen=an.length ; i'+j+'')
66 | .insertBefore( $('li:last', an[i])[0] )
67 | .bind('click', function (e) {
68 | e.preventDefault();
69 | oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
70 | fnDraw( oSettings );
71 | } );
72 | }
73 |
74 | // add / remove disabled classes from the static elements
75 | if ( oPaging.iPage === 0 ) {
76 | $('li:first', an[i]).addClass('disabled');
77 | } else {
78 | $('li:first', an[i]).removeClass('disabled');
79 | }
80 |
81 | if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
82 | $('li:last', an[i]).addClass('disabled');
83 | } else {
84 | $('li:last', an[i]).removeClass('disabled');
85 | }
86 | }
87 | }
88 | }
89 | });
--------------------------------------------------------------------------------
/cabot/static/theme/js/jquery.dataTables.bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | DataTables Bootstrap 3 integration
3 | ©2011-2015 SpryMedia Ltd - datatables.net/license
4 | */
5 | (function(b){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return b(a,window,document)}):"object"===typeof exports?module.exports=function(a,d){a||(a=window);if(!d||!d.fn.dataTable)d=require("datatables.net")(a,d).$;return b(d,a,a.document)}:b(jQuery,window,document)})(function(b,a,d,m){var f=b.fn.dataTable;b.extend(!0,f.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-5'i><'col-sm-7'p>>",renderer:"bootstrap"});b.extend(f.ext.classes,
6 | {sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm",sProcessing:"dataTables_processing panel panel-default"});f.ext.renderer.pageButton.bootstrap=function(a,h,r,s,j,n){var o=new f.Api(a),t=a.oClasses,k=a.oLanguage.oPaginate,u=a.oLanguage.oAria.paginate||{},e,g,p=0,q=function(d,f){var l,h,i,c,m=function(a){a.preventDefault();!b(a.currentTarget).hasClass("disabled")&&o.page()!=a.data.action&&o.page(a.data.action).draw("page")};
7 | l=0;for(h=f.length;l",{"class":t.sPageButton+" "+g,id:0===r&&"string"===typeof c?a.sTableId+"_"+c:null}).append(b("",{href:"#",
8 | "aria-controls":a.sTableId,"aria-label":u[c],"data-dt-idx":p,tabindex:a.iTabIndex}).html(e)).appendTo(d),a.oApi._fnBindAction(i,{action:c},m),p++)}},i;try{i=b(h).find(d.activeElement).data("dt-idx")}catch(v){}q(b(h).empty().html('').children("ul"),s);i!==m&&b(h).find("[data-dt-idx="+i+"]").focus()};return f});
9 |
--------------------------------------------------------------------------------
/cabot/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
6 | Page not found.
7 |
8 | {% endblock content %}
9 |
--------------------------------------------------------------------------------
/cabot/templates/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Unhandled error. Please look at logs.
4 |
5 |
--------------------------------------------------------------------------------
/cabot/templates/base.html:
--------------------------------------------------------------------------------
1 | {% extends 'base_public.html' %}
2 | {% block header_navbar_menu %}
3 | {% if user.is_authenticated %}
4 |
47 |
77 | {% else %}
78 | {{ block.super }}
79 | {% endif %}
80 | {% endblock header_navbar_menu %}
81 |
--------------------------------------------------------------------------------
/cabot/templates/base_public.html:
--------------------------------------------------------------------------------
1 | {% load static from staticfiles %}
2 |
3 |
4 |
5 |
6 |
7 | {% block title %}Cabot{% endblock title %}
8 |
9 |
10 | {% load compress %}
11 | {% compress css %}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {% endcompress %}
24 |
25 |
26 |
27 | {% compress js %}
28 |
34 | {% endcompress %}
35 |
36 |
37 |
38 |
39 |
40 |
48 |
49 | {% block header_navbar_menu %}
50 |
55 | {% endblock header_navbar_menu %}
56 |
57 |
58 |
59 |
60 |
61 |
67 | {% if messages %}
68 |
69 | {% for message in messages %}
70 |
74 | {% endfor %}
75 |
76 | {% endif %}
77 |
78 | {% block content %}
79 | {% endblock content %}
80 |
81 |
82 |
83 |
84 |
85 | {% load compress %}
86 | {% block js %}
87 |
88 | {% compress js %}
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
125 | {% endcompress %}
126 | {% endblock js %}
127 |
128 |
129 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/_base_form.html:
--------------------------------------------------------------------------------
1 | {% load bootstrap %}
2 |
3 |
8 | {% csrf_token %}
9 | {% for field in form %}
10 |
30 | {% endfor %}
31 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/_instance_list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% if not instances %}
6 |
7 |
8 | No instances configured
9 |
10 |
11 | {% else %}
12 | Name |
13 | Overall |
14 | Active checks |
15 | Disabled checks |
16 | |
17 |
18 |
19 |
20 | {% for instance in instances %}
21 |
22 |
23 | {{instance.name}}
24 | |
25 |
26 | {{ instance.overall_status|lower|capfirst }}
27 | |
28 |
29 | {{ instance.active_status_checks.all.count }}
30 | |
31 |
32 | {{ instance.inactive_status_checks.all.count }}
33 | |
34 |
35 |
36 |
37 |
38 | |
39 |
40 | {% endfor %}
41 |
42 | {% endif %}
43 |
44 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/_service_list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% if not services %}
6 |
7 |
8 | No services configured
9 |
10 |
11 | {% else %}
12 | Name |
13 | Overall |
14 | Active checks |
15 | Disabled checks |
16 | Acknowledgment |
17 | |
18 |
19 |
20 |
21 | {% for service in services %}
22 |
23 |
24 | {{service.name}}
25 | |
26 |
27 | {% if service.alerts_enabled %}{{ service.overall_status|lower|capfirst }}{% else %}Disabled{% endif %}
28 | |
29 |
30 | {{ service.active_status_checks.all.count }}
31 | |
32 |
33 | {{ service.inactive_status_checks.all.count }}
34 | |
35 |
36 | {% if service.overall_status != service.PASSING_STATUS %}
37 | {# #}
38 |
39 |
40 | {% if service.unexpired_acknowledgement %}
41 |
44 | {% else %}
45 |
48 | {% endif %}
49 |
50 |
51 | {% endif %}
52 | |
53 |
54 |
55 |
56 |
57 | |
58 |
59 | {% endfor %}
60 |
61 | {% endif %}
62 |
63 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/_service_public_list.html:
--------------------------------------------------------------------------------
1 | {% if not services %}
2 |
3 |
4 |
5 | No available services monitoring
6 |
7 |
8 | {% else %}
9 |
10 |
11 |
12 |
Name
13 |
14 |
15 |
Overall Checks
16 |
17 |
18 |
Acknowledgment
19 |
20 |
21 | {% for service in services %}
22 |
23 |
24 |
27 |
28 | {% if service.alerts_enabled %}{{ service.overall_status|lower|capfirst }}{% else %}Disabled{% endif %}
29 |
30 |
31 | {% if service.overall_status != service.PASSING_STATUS %}{% if service.unexpired_acknowledgement %}Yes{% else %}No{% endif %}{% endif %}
32 |
33 |
34 | {% endfor %}
35 |
36 | {% endif %}
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/_statuscheck_list.html:
--------------------------------------------------------------------------------
1 | {% load extra %}
2 |
3 |
4 |
5 |
6 |
{{ checks_type|capfirst }} checks
7 |
8 |
9 | {% if checks_type == "All" or checks_type == "Graphite" %}
10 |
11 | {% endif %}
12 | {% if checks_type == "All" or checks_type == "Http" %}
13 |
14 | {% endif %}
15 | {% if checks_type == "All" or checks_type == "Jenkins" %}
16 |
17 | {% endif %}
18 | {% if checks_type == "All" or checks_type == "ICMP" %}
19 |
20 | {% endif %}
21 | {% for checktype in custom_check_types %}
22 | {% if checks_type == "All" or checks_type == checktype.check_name %}
23 |
24 | {% endif %}
25 | {% endfor %}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {% if not checks %}
34 |
No checks configured
35 | {% else %}
36 |
37 |
38 |
39 | Name |
40 | Status |
41 | |
42 | {% if checks_type == "All" %}
43 | Type |
44 | {% endif %}
45 | Test description |
46 | Importance |
47 | Service(s) |
48 | Instance(s) |
49 | |
50 |
51 |
52 |
53 | {% for check in checks %}
54 |
55 |
56 | {{check.name}}
57 | |
58 |
59 | {% if check.active %}
60 |
61 | {{ check.calculated_status|capfirst }}
62 |
63 | {% else %}
64 | Disabled
65 | {% endif %}
66 | |
67 |
68 | {% if not check.cached_health %}
69 | No results available
70 | {% endif %}
71 | |
72 | {% if checks_type == "All" %}
73 |
74 |
75 | |
76 | {% endif %}
77 |
78 | {% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{{ check.metric|truncatechars:70 }} {{ check.check_type }} {{ check.value }}{% if check.expected_num_hosts %} (from {{ check.expected_num_hosts }} hosts){% endif %}{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}ICMP Reply from {{ check.instance_set.all.0.address }}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}Status code {{ check.status_code }} from {{ check.endpoint }}{% if check.text_match %}; match text /{{ check.text_match }}/{% endif %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}Monitor job {{ check.name }}{% if check.max_queued_build_time %}; check no build waiting for >{{ check.max_queued_build_time }} minutes{% endif %}{% endif %}
79 | |
80 | {{ check.get_importance_display }} |
81 |
82 | {% for service in check.service_set.all %}
83 | {{ service.name }}
84 | {% if forloop.last %}
85 | {% else %}
86 | /
87 | {% endif %}
88 | {% endfor %}
89 | {% if not check.service_set.all %}
90 | No service
91 | {% endif %}
92 | |
93 |
94 | {% for instance in check.instance_set.all %}
95 | {{ instance.name }}
96 | {% if forloop.last %}
97 | {% else %}
98 | /
99 | {% endif %}
100 | {% endfor %}
101 | {% if not check.instance_set.all %}
102 | No instance
103 | {% endif %}
104 | |
105 |
109 |
110 |
114 |
115 |
116 |
117 |
118 |
119 | {% if checks_type == "Jenkins" %}
120 |
121 |
122 |
123 | {% endif %}
124 | |
125 |
126 | {% endfor %}
127 |
128 |
129 | {% endif %}
130 |
131 |
132 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/about.html:
--------------------------------------------------------------------------------
1 | {% extends 'base_public.html' %}
2 | {% load static from staticfiles %}
3 | {% block title %}{{ block.super }} - About{% endblock title %}
4 |
5 | {% block content %}
6 |
7 |
8 |
About Cabot
9 |
10 |
11 |
12 |
13 | Version: {{ cabot_version }}
14 |
15 |
16 | {% endblock content %}
17 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/alertpluginuserdata_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% load bootstrap %}
4 |
5 | {% block content %}
6 |
37 | {% endblock %}
38 |
39 | {% load compress %}
40 | {% block js %}
41 | {{ block.super }}
42 | {% compress js %}
43 |
47 | {% endcompress %}
48 | {% endblock %}
49 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/instance_confirm_delete.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 | Delete instance
5 |
8 | {% endblock %}
9 |
10 | {% load compress %}
11 | {% block js %}
12 | {{ block.super }}
13 | {% compress js %}
14 |
17 | {% endcompress %}
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/instance_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load static from staticfiles %}
3 | {% block title %}{{ block.super }} - {{ instance.name }}{% endblock title %}
4 |
5 | {% block content %}
6 |
7 |
8 |
9 |
10 |
{{ instance.name }}
11 |
{{ instance.overall_status|lower|capfirst }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Configuration
20 |
21 |
22 |
Address
23 |
{{ instance.address|urlize|default:"None configured" }}
24 |
25 |
26 |
27 |
Users watching
28 |
29 | {% if not instance.users_to_notify.all %}
30 | No users subscribed
31 | {% else %}
32 | {{ instance.users_to_notify.all|join:", " }}
33 | {% endif %}
34 |
35 |
36 |
37 |
38 |
Alert types
39 |
40 |
41 | {% if instance.email_alert %} Email{% endif %}
42 | {% if instance.hipchat_alert %} Hipchat{% endif %}
43 | {% if instance.sms_alert %} SMS{% endif %}
44 | {% if instance.telephone_alert %} Telephone{% endif %}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
Status (24 hours)
52 |
56 |
57 |
58 |
59 |
60 |
64 | {% include 'cabotapp/_service_list.html' with services=instance.service_set.all %}
65 |
66 |
67 |
68 |
69 |
70 | {% include 'cabotapp/_statuscheck_list.html' with checks=instance.graphite_status_checks.all instance=instance checks_type="Graphite" %}
71 |
72 |
73 |
74 | {% include 'cabotapp/_statuscheck_list.html' with checks=instance.http_status_checks.all instance=instance checks_type="Http" %}
75 |
76 |
77 |
78 | {% include 'cabotapp/_statuscheck_list.html' with checks=instance.jenkins_status_checks.all instance=instance checks_type="Jenkins" %}
79 |
80 |
81 |
82 | {% include 'cabotapp/_statuscheck_list.html' with checks=instance.icmp_status_checks.all instance=instance checks_type="ICMP" %}
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
Status check report
91 |
92 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | {% endblock content %}
129 |
130 | {% block js %}
131 | {% load compress %}
132 | {% load jsonify %}
133 | {{ block.super }}
134 |
137 |
138 | {% compress js %}
139 |
140 |
198 |
208 | {% endcompress %}
209 | {% endblock js %}
210 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/instance_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
{% if form.instance.id %}Edit instance{% else %}New instance{% endif %}
8 |
9 |
10 |
11 |
27 | {% endblock %}
28 |
29 | {% load compress %}
30 | {% block js %}
31 | {{ block.super }}
32 | {% compress js %}
33 |
35 | {% endcompress %}
36 | {% endblock %}
37 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/instance_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
Instances
8 |
11 |
12 |
13 | {% include 'cabotapp/_instance_list.html' %}
14 |
15 |
16 | {% endblock content %}
17 |
18 | {% block js %}
19 | {% load compress %}
20 | {{ block.super }}
21 | {% compress js %}
22 |
25 | {% endcompress %}
26 | {% endblock js %}
27 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/plugin_settings_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% load bootstrap %}
4 |
5 | {% block content %}
6 |
58 | {% endblock %}
59 |
60 | {% load compress %}
61 | {% block js %}
62 | {{ block.super }}
63 | {% compress js %}
64 |
82 | {% endcompress %}
83 | {% endblock %}
84 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/service_confirm_delete.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 | Delete service
5 |
8 | {% endblock %}
9 |
10 | {% load compress %}
11 | {% block js %}
12 | {{ block.super }}
13 | {% compress js %}
14 |
17 | {% endcompress %}
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/service_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
{% if form.instance.id %}Edit service{% else %}New service{% endif %}
8 |
9 |
10 |
11 |
27 | {% endblock %}
28 |
29 | {% load compress %}
30 | {% block js %}
31 | {{ block.super }}
32 | {% compress js %}
33 |
35 | {% endcompress %}
36 | {% endblock %}
37 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/service_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
Services
8 |
11 |
12 |
13 | {% include 'cabotapp/_service_list.html' %}
14 |
15 |
16 | {% endblock content %}
17 |
18 | {% block js %}
19 | {% load compress %}
20 | {{ block.super }}
21 | {% compress js %}
22 |
32 | {% endcompress %}
33 | {% endblock js %}
34 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/service_public_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base_public.html' %}
2 | {% block header_navbar_menu %}
3 |
8 | {{block.super}}
9 | {% endblock header_navbar_menu %}
10 | {% block content %}
11 |
12 |
13 |
14 | {% include 'cabotapp/_service_public_list.html' %}
15 |
16 |
17 | {% endblock content %}
18 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/setup.html:
--------------------------------------------------------------------------------
1 | {% extends 'base_public.html' %}
2 |
3 | {% block content %}
4 | Welcome to Cabot!
5 | It looks like this is your first time launching Cabot. Please create a superuser account.
6 |
7 |
8 |
9 |
Create Super User
10 |
11 |
12 |
13 |
23 |
24 | {% endblock content %}
25 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/shift_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
9 |
10 | {% if not shifts %}
11 |
No shifts downloaded
12 | {% else %}
13 |
14 |
15 |
16 | User |
17 | Shift time |
18 | |
19 |
20 |
21 |
22 | {% for shift in shifts %}
23 |
24 |
25 | {{ shift.user.username }}
26 | |
27 |
28 | {{ shift.start }} - {{ shift.end }}
29 | |
30 |
31 |
32 |
33 |
34 | |
35 |
36 | {% endfor %}
37 |
38 |
39 | {% endif %}
40 |
41 |
42 | {% endblock content %}
43 |
44 | {% block js %}
45 | {% load compress %}
46 | {{ block.super }}
47 | {% compress js %}
48 |
51 | {% endcompress %}
52 | {% endblock js %}
53 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/statuscheck_confirm_delete.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 | Delete check
5 |
8 | {% endblock %}
9 |
10 | {% load compress %}
11 | {% block js %}
12 | {{ block.super }}
13 | {% compress js %}
14 |
17 | {% endcompress %}
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/statuscheck_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% load extra %}
4 |
5 | {% block content %}
6 |
7 |
8 |
9 |
10 |
{{ check.name }}
11 |
{{ check.calculated_status|capfirst }}
12 |
13 |
14 | {% if check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}
15 |
16 | {% endif %}
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
Check results
34 |
35 | {% if checkresults and checkresults.paginator.num_pages > 1 %}
36 |
51 | {% endif %}
52 |
53 |
54 |
55 |
56 | {% if not checkresults %}
57 |
No results for this check
58 | {% else %}
59 |
60 |
61 |
62 | Status |
63 | Time started |
64 | Time complete |
65 | Took (ms) |
66 | Error |
67 |
68 |
69 |
70 | {% for result in checkresults %}
71 |
72 |
73 | {{ result.status }}
74 |
75 | |
76 |
77 | {{ result.time }}
78 | |
79 | {{ result.time_complete }} |
80 | {{ result.took }} |
81 | {{ result.error|default:"" }} |
82 |
83 | {% endfor %}
84 |
85 |
86 | {% endif %}
87 |
88 |
89 |
90 |
91 | {% for service in check.service_set.all %}
92 | {% if service.hackpad_id or service.runbook_link%}
93 |
94 |
95 |
96 | {% if service.runbook_link %}
97 |
100 | {% endif %}
101 | {% if service.hackpad_id %}
102 |
106 | {% endif %}
107 |
108 | {% endif %}
109 | {% endfor %}
110 |
111 | {% endblock content %}
112 |
113 | {% block js %}
114 | {% load compress %}
115 | {{ block.super }}
116 | {% compress js %}
117 |
120 | {% endcompress %}
121 | {% endblock js %}
122 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/statuscheck_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load static from staticfiles %}
3 |
4 | {% block content %}
5 |
6 |
7 |
8 |
{% if form.instance.id %}Edit check{% else %}New check{% endif %}
9 |
10 |
11 |
12 |
29 | {% endblock %}
30 |
31 | {% load compress %}
32 | {% block js %}
33 | {{ block.super }}
34 | {% compress js %}
35 |
119 | {% endcompress %}
120 | {{ form.media }}
121 | {% endblock %}
122 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/statuscheck_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 | {% include 'cabotapp/_statuscheck_list.html' with checks_type="All" %}
6 |
7 | {% endblock content %}
8 |
9 | {% block js %}
10 | {% load compress %}
11 | {{ block.super }}
12 | {% compress js %}
13 |
23 | {% endcompress %}
24 | {% endblock js %}
25 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/statuscheck_report.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% load extra %}
4 |
5 | {% block content %}
6 |
7 |
8 |
9 |
{{ service.name }}
10 |
11 |
12 | {% if not checks %}
13 | No checks
14 | {% else %}
15 | {% for check in checks %}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{ check.name }}
26 |
27 |
28 |
29 |
30 | {% if check.success_rate != None %}
31 | Success rate: {{ check.success_rate|floatformat:"2" }}%.
32 | {% endif %}
33 | {% if check.problems %}
34 |
35 |
36 |
37 | Start time |
38 | End time |
39 | Outage duration |
40 |
41 |
42 |
43 | {% for start_time, end_time, duration in check.problems %}
44 |
45 |
46 | {{ start_time }}
47 | |
48 |
49 | {% if end_time %}{{ end_time }}{% else %}-{% endif %}
50 | |
51 |
52 | {{ duration|format_timedelta }}
53 | |
54 |
55 | {% endfor %}
56 |
57 |
58 | {% endif %}
59 | {% endfor %}
60 | {% endif %}
61 | {% endblock content %}
62 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/statuscheckresult_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load static from staticfiles %}
3 |
4 | {% block content %}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
{{ result.status|capfirst }}
17 |
18 |
19 |
20 |
21 | Name | {{ result.status_check.name }} |
22 |
23 | Status | {{ result.status|capfirst }} |
24 | Time started | {{ result.time }} |
25 | Time complete | {{ result.time_complete }} |
26 | Took | {{ result.took }}ms |
27 | Error | {{ result.error }} |
28 | Raw data | {{ result.raw_data }} |
29 |
30 |
31 |
32 |
33 |
34 | {% endblock %}
35 |
36 | {% block js %}
37 | {% load compress %}
38 | {{ block.super }}
39 |
46 | {% compress js %}
47 |
48 |
68 | {% endcompress %}
69 | {% endblock js %}
70 |
--------------------------------------------------------------------------------
/cabot/templates/cabotapp/subscriptions.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
Subscriptions
8 |
9 |
10 |
11 | {% if not services %}
12 | No services configured
13 | {% else %}
14 |
15 |
16 |
17 | |
18 | {% for user in users %}
19 |
20 | {% if user.email %}{{ user.email }}{% else %}{{ user.username }}{% endif %}
21 | {% if user in duty_officers %}
22 |
23 | On duty
24 |
25 | {% else %}
26 | {% if user.profile.fallback_alert_user %}
27 |
28 | Fallback duty officer
29 |
30 | {% endif %}
31 | {% endif %}
32 |
33 |
34 |
35 | |
36 | {% endfor %}
37 |
38 |
39 |
40 | {% for service in services %}
41 |
42 |
43 | {{service.name}}
44 | {% for alert in service.alerts.all %}
45 | {{ alert }}
46 | {% endfor %}
47 | |
48 | {% for user in users %}
49 |
50 | {% if user in service.users_to_notify.all %}
51 |
52 | {% else %}
53 |
54 | {% endif %}
55 | |
56 | {% endfor %}
57 |
58 | {% endfor %}
59 |
60 |
61 | {% endif %}
62 |
63 |
64 |
65 | {% endblock %}
66 |
--------------------------------------------------------------------------------
/cabot/templates/registration/login.html:
--------------------------------------------------------------------------------
1 | {% extends 'base_public.html' %}
2 |
3 | {% block content %}
4 |
11 |
22 | {% include "./social_auth.html" %}
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/cabot/templates/registration/logout.html:
--------------------------------------------------------------------------------
1 | {% extends 'base_public.html' %}
2 |
3 | {% block content %}
4 |
5 | Thanks for visiting!
6 |
7 | {% endblock %}
--------------------------------------------------------------------------------
/cabot/templates/registration/social_auth.html:
--------------------------------------------------------------------------------
1 | {% load extra %}
2 | {% echo_setting 'AUTH_GOOGLE_OAUTH2' as google_enable %}
3 | {% echo_setting 'AUTH_GITHUB_ORG' as github_enable %}
4 | {% echo_setting 'AUTH_GITHUB_ENTERPRISE_ORG' as github_enterprise_enable %}
5 |
6 |
--------------------------------------------------------------------------------
/cabot/version.py:
--------------------------------------------------------------------------------
1 | try:
2 | import pkg_resources
3 | version = pkg_resources.require("cabot")[0].version
4 | except Exception, ImportError:
5 | version = 'unknown'
6 |
--------------------------------------------------------------------------------
/cabot/wsgi.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cabot.settings')
4 |
5 | from django.core.wsgi import get_wsgi_application
6 | application = get_wsgi_application()
7 |
--------------------------------------------------------------------------------
/conf/default.env:
--------------------------------------------------------------------------------
1 | # Plugins to be loaded at launch
2 | CABOT_PLUGINS_ENABLED=cabot_alert_hipchat,cabot_alert_twilio,cabot_alert_email,cabot_alert_slack
3 |
4 | DEBUG=t
5 | DATABASE_URL=postgres://postgres@db:5432/postgres
6 | DJANGO_SETTINGS_MODULE=cabot.settings
7 | LOG_FILE=/dev/null
8 | PORT=5001
9 |
10 | # You shouldn't need to change anything above this line
11 |
12 | # Base path to include before generated URLs. If not defined, uses `/`
13 | # URL_PREFIX=/
14 |
15 | # Local time zone for this installation. Choices can be found here:
16 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
17 | TIME_ZONE=Etc/UTC
18 |
19 | # URL of calendar to synchronise rota with
20 | CALENDAR_ICAL_URL=http://www.google.com/calendar/ical/example.ics
21 |
22 | # Django settings
23 | CELERY_BROKER_URL=redis://redis:6379/1
24 |
25 | # From parameter for the graphite request. If not defined, by default take -10 minutes
26 | # GRAPHITE_FROM=-10minute
27 |
28 | # User-Agent string used for HTTP checks
29 | HTTP_USER_AGENT=Cabot
30 |
31 | # Used for pointing links back in alerts etc.
32 | WWW_HTTP_HOST=localhost
33 | WWW_SCHEME=http
34 |
35 | # OPTIONAL SETTINGS
36 | #
37 | # # Django admin email
38 | # ADMIN_EMAIL=you@example.com
39 | # CABOT_FROM_EMAIL=cabot@example.com
40 | #
41 | # DJANGO_SECRET_KEY=
42 | #
43 | # Hostname of your Graphite server instance
44 | GRAPHITE_API=http://graphite.example.com/
45 | GRAPHITE_USER=username
46 | GRAPHITE_PASS=password
47 |
48 | # Hipchat integration
49 | HIPCHAT_ALERT_ROOM=room_name_or_id
50 | HIPCHAT_API_KEY=your_hipchat_api_key
51 |
52 | # Jenkins integration
53 | JENKINS_API=https://jenkins.example.com/
54 | JENKINS_USER=username
55 | JENKINS_PASS=password
56 |
57 | # SMTP settings
58 | SES_HOST=email-smtp.us-east-1.amazonaws.com
59 | SES_USER=username
60 | SES_PASS=password
61 | SES_PORT=465
62 |
63 | # Twilio integration for SMS and telephone alerts
64 | TWILIO_ACCOUNT_SID=your_account_sid
65 | TWILIO_AUTH_TOKEN=your_auth_token
66 | TWILIO_OUTGOING_NUMBER=+14155551234
67 |
68 | # Use for LDAP authentication
69 | # AUTH_LDAP=true
70 | # AUTH_LDAP_SERVER_URI=ldap://ldap.example.com
71 | # AUTH_LDAP_BIND_DN="cn=Manager,dc=example,dc=com"
72 | # AUTH_LDAP_BIND_PASSWORD=""
73 | # AUTH_LDAP_USER_FILTER="(uid=%(user)s)"
74 | # AUTH_LDAP_USER_SEARCH="ou=People,dc=example,dc=com"
75 |
--------------------------------------------------------------------------------
/conf/development.env.example:
--------------------------------------------------------------------------------
1 | # Plugins to be loaded at launch
2 | # CABOT_PLUGINS_ENABLED=cabot_alert_hipchat,cabot_alert_twilio,cabot_alert_email,cabot_alert_slack
3 |
4 | DEBUG=t
5 | DATABASE_URL=postgres://postgres@db:5432/postgres
6 | DJANGO_SETTINGS_MODULE=cabot.settings
7 | LOG_FILE=/dev/null
8 | PORT=5001
9 |
10 | # You shouldn't need to change anything above this line
11 |
12 | # Base path to include before generated URLs. If not defined, uses `/`
13 | # URL_PREFIX=/
14 |
15 | # Local time zone for this installation. Choices can be found here:
16 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
17 | TIME_ZONE=Etc/UTC
18 |
19 | # Django admin email
20 | ADMIN_EMAIL=you@example.com
21 | CABOT_FROM_EMAIL=cabot@example.com
22 |
23 | # URL of calendar to synchronise rota with
24 | CALENDAR_ICAL_URL=http://www.google.com/calendar/ical/example.ics
25 |
26 | # Django settings
27 | CELERY_BROKER_URL=redis://redis:6379/1
28 | DJANGO_SECRET_KEY=2FL6ORhHwr5eX34pP9mMugnIOd3jzVuT45f7w430Mt5PnEwbcJgma0q8zUXNZ68A
29 |
30 | # Hostname of your Graphite server instance
31 | GRAPHITE_API=http://graphite.example.com/
32 | GRAPHITE_USER=username
33 | GRAPHITE_PASS=password
34 |
35 | # From parameter for the graphite request. If not defined, by default take -10 minutes
36 | # GRAPHITE_FROM=-10minute
37 |
38 | # User-Agent string used for HTTP checks
39 | HTTP_USER_AGENT=Cabot
40 |
41 | # Hipchat integration
42 | HIPCHAT_ALERT_ROOM=room_name_or_id
43 | HIPCHAT_API_KEY=your_hipchat_api_key
44 |
45 | # Jenkins integration
46 | JENKINS_API=https://jenkins.example.com/
47 | JENKINS_USER=username
48 | JENKINS_PASS=password
49 |
50 | # SMTP settings
51 | EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
52 |
53 | # Twilio integration for SMS and telephone alerts
54 | TWILIO_ACCOUNT_SID=your_account_sid
55 | TWILIO_AUTH_TOKEN=your_auth_token
56 | TWILIO_OUTGOING_NUMBER=+14155551234
57 |
58 | # Used for pointing links back in alerts etc.
59 | WWW_HTTP_HOST=localhost
60 | WWW_SCHEME=http
61 |
62 | # Use for LDAP authentication
63 | # AUTH_LDAP=true
64 | # AUTH_LDAP_SERVER_URI=ldap://ldap.example.com
65 | # AUTH_LDAP_BIND_DN="cn=Manager,dc=example,dc=com"
66 | # AUTH_LDAP_BIND_PASSWORD=""
67 | # AUTH_LDAP_USER_FILTER="(uid=%(user)s)"
68 | # AUTH_LDAP_USER_SEARCH="ou=People,dc=example,dc=com"
69 |
70 | # Use Github Organization for Authentication
71 | # LOGIN_URL=/login/github-org/
72 | # AUTH_GITHUB_ORG=True
73 | # AUTH_GITHUB_ORG_CLIENT_ID=2l34k5j43tb46l2kj234
74 | # AUTH_GITHUB_ORG_CLIENT_SECRET=23l4k5j43l6k546lk5n4kl64j2j3l5k4jjlkj2345
75 | # AUTH_GITHUB_ORG_NAME=myorganization
76 |
77 | # Use Github Enterprise Organization for Authentication
78 | # LOGIN_URL=/login/github-enterprise-org/
79 | # GITHUB_ENTERPRISE_ORG_AUTH=True
80 | # GITHUB_ENTERPRISE_ORG_URL=https://mygithubenterprise.com/
81 | # GITHUB_ENTERPRISE_ORG_API_URL=https://mygithubenterprise.com/api/v3/
82 | # GITHUB_ENTERPRISE_ORG_KEY=alskdjflkj5lk123j345l3
83 | # GITHUB_ENTERPRISE_ORG_SECRET=alskjdflkasjdflqkj5lkntrk13j45lk3451453245
84 | # GITHUB_ENTERPRISE_ORG_NAME=myorganization
85 |
--------------------------------------------------------------------------------
/conf/production.env.example:
--------------------------------------------------------------------------------
1 | ## Cabot UI URL setup
2 | # Base path to include before generated URLs. If not defined, uses `/`
3 | # URL_PREFIX=/
4 |
5 | # Used for pointing links back in alerts etc.
6 | WWW_HTTP_HOST=cabot.example.com
7 | # Probable values: http, https
8 | WWW_SCHEME=http
9 |
10 | ## Local time zone for this installation. Choices can be found here:
11 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
12 | TIME_ZONE=Etc/UTC
13 |
14 | ## URL of calendar to synchronise rota with
15 | CALENDAR_ICAL_URL=http://www.google.com/calendar/ical/example.ics
16 |
17 | ## Django admin email, 'from' address for email alerts
18 | ADMIN_EMAIL=you@example.com
19 | CABOT_FROM_EMAIL=noreply@example.com
20 |
21 | ## Django settings
22 | CELERY_BROKER_URL=redis://:yourredispassword@localhost:6379/1
23 | # Create a random string of mixed case alphanumeric characters, > 40 chars.
24 | # https://www.browserling.com/tools/random-string is a site that can do this
25 | DJANGO_SECRET_KEY=CREATE_A_KEY
26 |
27 | ## Graphite server settings
28 | # Hostname of your Graphite server instance (including trailing slash)
29 | GRAPHITE_API=http://graphite.example.com/
30 | GRAPHITE_USER=username
31 | GRAPHITE_PASS=password
32 |
33 | # Graphite stats are evaluated through a time period to identify transient
34 | # failures. This setting determines how many minutes back a test should
35 | # evaluate.
36 | # If not defined, evaluate 'now through 10 minutes ago' (-10minute)
37 | # GRAPHITE_FROM=-10minute
38 |
39 | ## User-Agent string used for Cabot HTTP checks
40 | HTTP_USER_AGENT=Cabot
41 |
42 | ## Email plugin integration
43 | EMAIL_HOST=smtp.example.com
44 | # SMTP authentication settings. To disable SMTP authentication, comment out
45 | # both EMAIL_USER and EMAIL_PASSWORD.
46 | EMAIL_USER=smtp_username
47 | EMAIL_PASSWORD=smtp_password
48 |
49 | # Typical SMTP port 587 configuration
50 | EMAIL_PORT=587
51 | EMAIL_USE_TLS=1
52 | EMAIL_USE_SSL=0
53 |
54 | # Typical SMTP port 25 configuration (with no SSL/TLS)
55 | # EMAIL_PORT=587
56 | # EMAIL_USE_TLS=0
57 | # EMAIL_USE_SSL=0
58 |
59 | ## Hipchat plugin integration
60 | HIPCHAT_ALERT_ROOM=room_name_or_id
61 | HIPCHAT_API_KEY=your_hipchat_api_key
62 |
63 | ## SLACK
64 | SLACK_ALERT_CHANNEL=channel_name
65 | SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXXXXX/YYYYYY/ZZZZZZ
66 | # SLACK_INTERACTIVE_MESSAGE=True # uncomment if you have cabot added as an app with interactive messages enabled
67 |
68 | ## Twilio plugin integration (for SMS and telephone alerts)
69 | TWILIO_ACCOUNT_SID=your_account_sid
70 | TWILIO_AUTH_TOKEN=your_auth_token
71 | TWILIO_OUTGOING_NUMBER=+14155551234
72 |
73 | ## Jenkins integration
74 | JENKINS_API=https://jenkins.example.com/
75 | JENKINS_USER=username
76 | JENKINS_PASS=password
77 |
78 | ## Use for LDAP authentication
79 | AUTH_LDAP=true
80 | AUTH_LDAP_SERVER_URI=ldap://ldap.example.com
81 | AUTH_LDAP_BIND_DN="cn=Manager,dc=example,dc=com"
82 | AUTH_LDAP_BIND_PASSWORD=""
83 | AUTH_LDAP_USER_FILTER="(uid=%(user)s)"
84 | AUTH_LDAP_USER_SEARCH="ou=People,dc=example,dc=com"
85 |
86 | ###########################################################################
87 | ## You shouldn't need to change anything below this line #
88 | ###########################################################################
89 |
90 | ## Plugins to be loaded at launch
91 | # CABOT_PLUGINS_ENABLED=cabot_alert_email,cabot_alert_hipchat,cabot_alert_twilio,cabot_alert_slack
92 |
93 | ## Database settings
94 | DATABASE_URL=postgres://cabot:cabot@localhost:5432/index
95 | DJANGO_SETTINGS_MODULE=cabot.settings
96 |
97 | DEBUG=False
98 | LOG_FILE=/dev/null
99 |
100 | ## Cabot localhost operating port (point a reverse proxy to here or use Caddy)
101 | PORT=5000
102 |
103 | VENV=/home/ubuntu/venv
104 |
--------------------------------------------------------------------------------
/conf/test.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=sqlite://:memory:
2 |
3 | CELERY_BROKER_URL=memory://
4 | CELERY_ALWAYS_EAGER=True
5 |
6 | SKIP_INIT=True
7 |
--------------------------------------------------------------------------------
/docker-compose-base.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | base:
4 | build: .
5 | image: cabot:web
6 | command: "false"
7 | volumes:
8 | - .:/code
9 | env_file:
10 | - conf/default.env
11 |
--------------------------------------------------------------------------------
/docker-compose-test.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | test:
4 | extends:
5 | file: docker-compose-base.yml
6 | service: base
7 | entrypoint: /usr/bin/python
8 | command: manage.py test -v2
9 | env_file:
10 | - conf/test.env
11 | sut:
12 | image: ubuntu
13 | command: "true"
14 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | web:
4 | extends:
5 | file: docker-compose-base.yml
6 | service: base
7 | env_file:
8 | - conf/development.env
9 | command: python manage.py runserver 0.0.0.0:5001
10 | ports:
11 | - "5001:5001"
12 | links:
13 | - redis
14 | - db
15 |
16 | worker:
17 | extends:
18 | file: docker-compose-base.yml
19 | service: base
20 | env_file:
21 | - conf/development.env
22 | command: celery worker -A cabot --loglevel=DEBUG --concurrency=16 -Ofair
23 | environment:
24 | - SKIP_INIT=1
25 | - WAIT_FOR_MIGRATIONS=1
26 | links:
27 | - redis
28 | - db
29 |
30 | beat:
31 | extends:
32 | file: docker-compose-base.yml
33 | service: base
34 | env_file:
35 | - conf/development.env
36 | command: celery beat -A cabot --loglevel=DEBUG
37 | environment:
38 | - SKIP_INIT=1
39 | - WAIT_FOR_MIGRATIONS=1
40 | links:
41 | - redis
42 | - db
43 |
44 | redis:
45 | image: redis:alpine
46 |
47 | db:
48 | image: postgres:alpine
49 | volumes:
50 | - datavolume:/var/lib/postgresql/data
51 |
52 | volumes:
53 | datavolume:
54 |
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | function wait_for_broker {
5 | set +e
6 | for try in {1..60} ; do
7 | python -c "from kombu import Connection; x=Connection('$CELERY_BROKER_URL', timeout=1); x.connect()" && break
8 | echo "Waiting for celery broker to respond..."
9 | sleep 1
10 | done
11 | }
12 |
13 | function wait_for_database {
14 | set +e
15 | for try in {1..60} ; do
16 | python -c "from django.db import connection; connection.connect()" && break
17 | echo "Waiting for database to respond..."
18 | sleep 1
19 | done
20 | }
21 |
22 | function wait_for_migrations {
23 | set +e
24 | for try in {1..60} ; do
25 | # Kind of ugly but not sure if there's another way to determine if migrations haven't run.
26 | # showmigrations -p returns a checkbox list of migrations, empty checkboxes mean they haven't been run
27 | python manage.py showmigrations -p | grep "\[ \]" &> /dev/null || break
28 | echo "Waiting for database migrations to be run..."
29 | sleep 1
30 | done
31 | }
32 |
33 |
34 | wait_for_broker
35 | wait_for_database
36 |
37 | if [ -z "$SKIP_INIT" ]; then
38 | /code/bin/build-app
39 | fi
40 |
41 | if [ -n "$WAIT_FOR_MIGRATIONS" ]; then
42 | wait_for_migrations
43 | fi
44 |
45 | exec "$@"
46 |
--------------------------------------------------------------------------------
/example_local_config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Example Local Vagrant config
3 | # With this file, you can override the amount of RAM and CPUs allocated
4 | # to the VM, as well as change the base box it uses. Just copy it to
5 | # local_config.yml and edit with whatever values you want.
6 | #
7 | # Note that it doesn't take effect until you vagrant destroy && vagrant up
8 |
9 | # double your pleasure double your CPU and RAM
10 | ram: 2048
11 | cpu: 2
12 | # hashicorp ubuntu doesn't support VMWare, so use box-cutter
13 | box: box-cutter/ubuntu1404
14 |
--------------------------------------------------------------------------------
/gunicorn.conf:
--------------------------------------------------------------------------------
1 | # -*- mode: python -*-
2 | # vi: set ft=python :
3 |
4 | import os
5 |
6 | bind = '127.0.0.1:%s' % os.environ['PORT']
7 | workers = 3
8 |
--------------------------------------------------------------------------------
/makemigrations:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | rm dev.db
3 | foreman run python manage.py syncdb
4 | foreman run python manage.py migrate
5 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 |
6 | if __name__ == "__main__":
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cabot.settings")
8 |
9 | from django.core.management import execute_from_command_line
10 |
11 | execute_from_command_line(sys.argv)
12 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | coverage==4.3.4
2 | django_coverage_plugin==1.5.0
3 | freezegun==0.3.9
4 | mock==2.0.0
5 | ipdb
6 | isort==4.2.15
7 |
--------------------------------------------------------------------------------
/requirements-plugins.txt:
--------------------------------------------------------------------------------
1 | cabot_alert_email==1.4.3
2 | cabot_alert_hipchat==2.0.3
3 | cabot_alert_twilio==1.3.3
4 | cabot_alert_slack==0.8.3
5 | cabot_check_cloudwatch==0.1.2
6 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | amqp==2.1.4
2 | anyjson==0.3.3
3 | appdirs==1.4.3
4 | billiard==3.5.0.2
5 | boto==2.49.0
6 | boto3==1.9.86
7 | botocore==1.12.86
8 | celery==4.2.1
9 | coreapi==2.3.0
10 | coreschema==0.0.4
11 | dj-database-url==0.4.2
12 | Django==1.11.29
13 | django-appconf==1.0.2
14 | django-auth-ldap==1.2.16
15 | django-autocomplete-light==3.2.10
16 | django-bootstrap-form==3.4
17 | django-compressor==2.2
18 | django-filter==1.0.4
19 | django-jsonify==0.3.0
20 | django-polymorphic==1.3
21 | djangorestframework==3.6.2
22 | docutils==0.14
23 | futures==3.2.0
24 | gevent==1.2.1
25 | greenlet==0.4.12
26 | gunicorn==19.7.1
27 | httplib2==0.10.3
28 | icalendar==3.11.3
29 | itypes==1.1.0
30 | Jinja2==2.9.6
31 | jmespath==0.9.3
32 | kombu==4.2.2.post1
33 | Markdown==2.6.8
34 | MarkupSafe==1.0
35 | multi-key-dict==2.0.3
36 | oauthlib==3.0.0
37 | packaging==16.8
38 | pbr==5.1.1
39 | psycopg2==2.7.1
40 | pyasn1==0.4.5
41 | pyasn1-modules==0.2.3
42 | pycurl==7.43.0.1
43 | Pygments==2.2.0
44 | PyJWT==1.7.1
45 | pyparsing==2.2.0
46 | PySocks==1.6.7
47 | python-dateutil==2.6.0
48 | python-jenkins==0.4.15
49 | python-ldap==3.1.0
50 | python-openid==2.2.5
51 | pytz==2017.2
52 | rcssmin==1.0.6
53 | redis==2.10.5
54 | requests==2.13.0
55 | requests-oauthlib==1.2.0
56 | rjsmin==1.0.12
57 | s3transfer==0.1.13
58 | six==1.10.0
59 | social-auth-app-django==1.1.0
60 | social-auth-core==3.0.0
61 | twilio==5.7.0
62 | uritemplate==3.0.0
63 | urllib3==1.24.1
64 | vine==1.1.3
65 | whitenoise==3.3.0
66 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [isort]
2 | known_first_party = cabot
3 | multi_line_output = 0
4 | known_django = django,polymorphic,rest_framework
5 | sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,FIRSTPARTY,LOCALFOLDER
6 | default_section = THIRDPARTY
7 | skip_glob = **/migrations/*
8 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | from setuptools import setup, find_packages
4 | from os import environ as env
5 | import subprocess
6 |
7 | from pip.req import parse_requirements
8 |
9 | requirements = [str(req.req) for req in parse_requirements('requirements.txt', session=False)]
10 | requirements_plugins = [str(req.req) for req in parse_requirements('requirements-plugins.txt', session=False)]
11 |
12 | try:
13 | VERSION = subprocess.check_output(['git', 'describe', '--tags']).strip()
14 | except subprocess.CalledProcessError:
15 | VERSION = '0.dev'
16 |
17 | setup(
18 | name='cabot',
19 | version=VERSION,
20 | description="Self-hosted, easily-deployable monitoring and alerts service"
21 | " - like a lightweight PagerDuty",
22 | long_description=open('README.md').read(),
23 | author="Arachnys",
24 | author_email='info@arachnys.com',
25 | url='http://cabotapp.com',
26 | license='MIT',
27 | install_requires=requirements + requirements_plugins,
28 | packages=find_packages(),
29 | include_package_data=True,
30 | entry_points={
31 | 'console_scripts': [
32 | 'cabot = cabot.entrypoint:main',
33 | ],
34 | },
35 | zip_safe=False
36 | )
37 |
--------------------------------------------------------------------------------
/setup_dev.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | foreman run python manage.py syncdb
3 | foreman run python manage.py migrate
4 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = config,flake8,bashate
3 | skipsdist = True
4 |
5 | [testenv]
6 | whitelist_externals =
7 | bash
8 |
9 | [testenv:config]
10 | commands =
11 | /bin/bash -c 'set -euo pipefail && . ./conf/production.env.example'
12 | /bin/bash -c 'set -euo pipefail && . ./conf/development.env.example'
13 |
14 | [testenv:bashate]
15 | deps = bashate==0.5.1
16 | commands =
17 | # Run bashate check for all bash scripts
18 | # Ignores the following rules:
19 | # E003: Indent not multiple of 4 (we use multiples of 2)
20 | # E006: Line longer than 79 columns
21 | bash -c "grep --recursive --binary-files=without-match \
22 | --files-with-match '^.!.*\(ba\)\?sh$' \
23 | --exclude-dir .tox \
24 | --exclude-dir .git \
25 | {toxinidir} | xargs bashate --error . --verbose --ignore=E003,E006"
26 |
27 | [testenv:flake8]
28 | deps =
29 | flake8
30 | flake8-debugger
31 | commands = flake8 {posargs}
32 |
33 | [testenv:isort]
34 | deps = isort==4.2.15
35 | commands =
36 | bash -c "find {toxinidir} \
37 | -type d \
38 | \( \
39 | -path {toxinidir}/.git -o \
40 | -path {toxinidir}/.tox -o \
41 | -path {toxinidir}/.venv -o \
42 | -path {toxinidir}/plugins -o \
43 | -path {toxinidir}/node_modules \
44 | \) -prune -o \
45 | -name '*.py' \
46 | -print | xargs isort {posargs:--check-only} --verbose"
47 |
48 | [flake8]
49 | exclude = .venv,venv,.tox,dist,doc,build,*.egg,docs,setup.py,*/migrations/
50 | ignore = E121,E123,E125,E126,E127,E128,E131,E222,E226,E231,E251,E261,E265,E302,E305,E402,E714,E722,F401,F403,F405,F841,W391,W605
51 | max-line-length = 160
52 | # E121 continuation line under-indented for hanging indent
53 | # E123 closing bracket does not match indentation of opening bracket's line
54 | # E125 continuation line with same indent as next logical line
55 | # E126 continuation line over-indented for hanging indent
56 | # E127 continuation line over-indented for visual indent
57 | # E128 continuation line under-indented for visual indent
58 | # E131 continuation line unaligned for hanging indent
59 | # E222 multiple spaces after operator
60 | # E226 missing whitespace around arithmetic operator
61 | # E231 missing whitespace after
62 | # E251 unexpected spaces around keyword / parameter equals
63 | # E261 at least two spaces before inline comment
64 | # E265 block comment should start with '# '
65 | # E302 expected 2 blank lines, found 1
66 | # E305 expected 2 blank lines after class or function definition, found 1
67 | # E402 module level import not at top of file
68 | # E714 test for object identity should be 'is not'
69 | # E722 do not use bare except
70 | # F401 Imported but unused
71 | # F403 'from module import *' used; unable to detect undefined names
72 | # F405 foo may be undefined, or defined from star imports
73 | # F841 local variable 'foo' is assigned to but never used
74 | # W391 blank line at end of file
75 | # W605 invalid escape sequence
76 |
--------------------------------------------------------------------------------
/upstart/process.conf.erb:
--------------------------------------------------------------------------------
1 | start on starting <%= app %>-<%= name %>
2 | stop on stopping <%= app %>-<%= name %>
3 | respawn
4 | respawn limit 50 5
5 |
6 | exec su - <%= user %> -c 'cd <%= engine.root %>; export PORT=<%= port %>;<% engine.env.each_pair do |var,env| %> export <%= var.upcase %>='\''<%= env %>'\''; <% end %> export TMPDIR=$TMPDIR/<%= app %>/<%= name %>/<%= num %>; rm -rf $TMPDIR; mkdir -p $TMPDIR; source $VENV/bin/activate; <%= process.command %> >> <%= log %>/<%=name%>-<%=num%>.log 2>&1'
7 |
--------------------------------------------------------------------------------