├── .coveragerc ├── .gitignore ├── .travis.yml ├── CHANGELOG ├── LICENSE ├── MANIFEST.in ├── README.md ├── baobab.front ├── assets │ └── fonts │ │ ├── museo │ │ ├── Museo_Slab_500-webfont.eot │ │ ├── Museo_Slab_500-webfont.svg │ │ ├── Museo_Slab_500-webfont.ttf │ │ └── Museo_Slab_500-webfont.woff │ │ ├── open-sans │ │ ├── OpenSans-Bold-webfont.eot │ │ ├── OpenSans-Bold-webfont.svg │ │ ├── OpenSans-Bold-webfont.ttf │ │ ├── OpenSans-Bold-webfont.woff │ │ ├── OpenSans-BoldItalic-webfont.eot │ │ ├── OpenSans-BoldItalic-webfont.svg │ │ ├── OpenSans-BoldItalic-webfont.ttf │ │ ├── OpenSans-BoldItalic-webfont.woff │ │ ├── OpenSans-Italic-webfont.eot │ │ ├── OpenSans-Italic-webfont.svg │ │ ├── OpenSans-Italic-webfont.ttf │ │ ├── OpenSans-Italic-webfont.woff │ │ ├── OpenSans-Regular-webfont.eot │ │ ├── OpenSans-Regular-webfont.svg │ │ ├── OpenSans-Regular-webfont.ttf │ │ └── OpenSans-Regular-webfont.woff │ │ └── roboto │ │ ├── Google Android License.txt │ │ ├── Roboto-Black-webfont.eot │ │ ├── Roboto-Black-webfont.svg │ │ ├── Roboto-Black-webfont.ttf │ │ ├── Roboto-Black-webfont.woff │ │ ├── Roboto-BlackItalic-webfont.eot │ │ ├── Roboto-BlackItalic-webfont.svg │ │ ├── Roboto-BlackItalic-webfont.ttf │ │ ├── Roboto-BlackItalic-webfont.woff │ │ ├── Roboto-Bold-webfont.eot │ │ ├── Roboto-Bold-webfont.svg │ │ ├── Roboto-Bold-webfont.ttf │ │ ├── Roboto-Bold-webfont.woff │ │ ├── Roboto-BoldCondensed-webfont.eot │ │ ├── Roboto-BoldCondensed-webfont.svg │ │ ├── Roboto-BoldCondensed-webfont.ttf │ │ ├── Roboto-BoldCondensed-webfont.woff │ │ ├── Roboto-BoldCondensedItalic-webfont.eot │ │ ├── Roboto-BoldCondensedItalic-webfont.svg │ │ ├── Roboto-BoldCondensedItalic-webfont.ttf │ │ ├── Roboto-BoldCondensedItalic-webfont.woff │ │ ├── Roboto-BoldItalic-webfont.eot │ │ ├── Roboto-BoldItalic-webfont.svg │ │ ├── Roboto-BoldItalic-webfont.ttf │ │ ├── Roboto-BoldItalic-webfont.woff │ │ ├── Roboto-Condensed-webfont.eot │ │ ├── Roboto-Condensed-webfont.svg │ │ ├── Roboto-Condensed-webfont.ttf │ │ ├── Roboto-Condensed-webfont.woff │ │ ├── Roboto-CondensedItalic-webfont.eot │ │ ├── Roboto-CondensedItalic-webfont.svg │ │ ├── Roboto-CondensedItalic-webfont.ttf │ │ ├── Roboto-CondensedItalic-webfont.woff │ │ ├── Roboto-Italic-webfont.eot │ │ ├── Roboto-Italic-webfont.svg │ │ ├── Roboto-Italic-webfont.ttf │ │ ├── Roboto-Italic-webfont.woff │ │ ├── Roboto-Light-webfont.eot │ │ ├── Roboto-Light-webfont.svg │ │ ├── Roboto-Light-webfont.ttf │ │ ├── Roboto-Light-webfont.woff │ │ ├── Roboto-LightItalic-webfont.eot │ │ ├── Roboto-LightItalic-webfont.svg │ │ ├── Roboto-LightItalic-webfont.ttf │ │ ├── Roboto-LightItalic-webfont.woff │ │ ├── Roboto-Medium-webfont.eot │ │ ├── Roboto-Medium-webfont.svg │ │ ├── Roboto-Medium-webfont.ttf │ │ ├── Roboto-Medium-webfont.woff │ │ ├── Roboto-MediumItalic-webfont.eot │ │ ├── Roboto-MediumItalic-webfont.svg │ │ ├── Roboto-MediumItalic-webfont.ttf │ │ ├── Roboto-MediumItalic-webfont.woff │ │ ├── Roboto-Regular-webfont.eot │ │ ├── Roboto-Regular-webfont.svg │ │ ├── Roboto-Regular-webfont.ttf │ │ ├── Roboto-Regular-webfont.woff │ │ ├── Roboto-Thin-webfont.eot │ │ ├── Roboto-Thin-webfont.svg │ │ ├── Roboto-Thin-webfont.ttf │ │ ├── Roboto-Thin-webfont.woff │ │ ├── Roboto-ThinItalic-webfont.eot │ │ ├── Roboto-ThinItalic-webfont.svg │ │ ├── Roboto-ThinItalic-webfont.ttf │ │ ├── Roboto-ThinItalic-webfont.woff │ │ ├── demo.html │ │ └── stylesheet.css ├── images │ ├── favicon.ico │ ├── header_contactsupport.png │ ├── header_rss.png │ ├── header_twitter.png │ ├── logo_gandi.png │ ├── status_incident.png │ ├── status_incident_detail.png │ ├── status_maintenance.png │ ├── status_maintenance_detail.png │ ├── status_ok.png │ └── status_ok_detail.png ├── index.html ├── js │ ├── .gitignore │ ├── api.js │ ├── elements │ │ ├── events.js │ │ ├── eventsfilter.js │ │ ├── services.js │ │ ├── status.js │ │ └── statustime.js │ ├── ie.js │ ├── main.js │ ├── page │ │ ├── event.js │ │ ├── status.js │ │ └── timeline.js │ ├── router.js │ └── router_utils.js ├── less │ ├── event.less │ ├── fonts.less │ ├── main.less │ ├── reset.less │ ├── resetie.less │ ├── status.less │ └── timeline.less ├── tests │ ├── aliasifyConfig.js │ ├── integration │ │ ├── event.js │ │ ├── service_details.js │ │ ├── services.js │ │ └── timeline.js │ └── mocks │ │ └── api.js └── views │ ├── day.html │ ├── event.html │ ├── maintenances.html │ ├── service-button.html │ ├── service-status-button.html │ └── service.html ├── baobab ├── __init__.py ├── apirest │ ├── __init__.py │ ├── events.py │ ├── modelresource.py │ ├── models.py │ ├── services.py │ ├── status.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_events.py │ │ ├── test_route.py │ │ ├── test_schema.py │ │ ├── test_services.py │ │ ├── test_status.py │ │ └── test_utc_date.py │ └── urls.py ├── backoffice │ ├── __init__.py │ ├── admin.py │ ├── adminform.py │ ├── admininline.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── migrate_fixtures.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_create_services_and_set_timezone_enm.py │ │ ├── 0003_auto__chg_field_event_title.py │ │ ├── 0004_auto__add_field_eventlog_msg_id__add_field_eventlog_msg__add_field_eve.py │ │ ├── 0005_auto__chg_field_event_msg.py │ │ ├── 0006_auto__chg_field_eventlog_msg_id__chg_field_event_msg_id.py │ │ ├── 0007_auto__add_field_event_last_update.py │ │ ├── 0008_set_last_update.py │ │ ├── 0009_move_twitter_to_socialnetwork.py │ │ ├── 0010_auto__del_field_eventlog_msg_id__del_field_event_msg_id.py │ │ └── __init__.py │ ├── modelmanager.py │ ├── models.py │ └── tests │ │ ├── __init__.py │ │ ├── test_event.py │ │ ├── test_event_trigger.py │ │ ├── test_eventlog_trigger.py │ │ └── test_status.py ├── bin │ ├── __init__.py │ └── cmd_baobab.py ├── conf │ └── default.py ├── cron │ ├── __init__.py │ ├── close_event.py │ ├── models.py │ ├── socialnetwork.py │ └── tests │ │ ├── __init__.py │ │ └── test_sn.py ├── fixtures │ ├── __init__.py │ ├── db_backoffice.json │ ├── db_south.json │ ├── db_translate.json │ └── db_user.json ├── front │ ├── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── manage.py ├── middleware.py ├── rss │ ├── __init__.py │ ├── feed.py │ ├── models.py │ └── tests │ │ ├── __init__.py │ │ └── test_rss.py ├── settings.py ├── socialnetwork │ ├── __init__.py │ ├── base.py │ ├── irc.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── ircd.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests │ │ ├── __init__.py │ │ └── test_socialnetwork.py │ └── twitter.py ├── static │ ├── .gitignore │ └── admin │ │ ├── css │ │ ├── base.css │ │ ├── changelists.css │ │ ├── dashboard.css │ │ ├── forms.css │ │ ├── ie.css │ │ ├── login.css │ │ ├── rtl.css │ │ └── widgets.css │ │ ├── img │ │ ├── changelist-bg.gif │ │ ├── changelist-bg_rtl.gif │ │ ├── chooser-bg.gif │ │ ├── chooser_stacked-bg.gif │ │ ├── default-bg-reverse.gif │ │ ├── default-bg.gif │ │ ├── deleted-overlay.gif │ │ ├── gis │ │ │ ├── move_vertex_off.png │ │ │ └── move_vertex_on.png │ │ ├── icon-no.gif │ │ ├── icon-unknown.gif │ │ ├── icon-yes.gif │ │ ├── icon_addlink.gif │ │ ├── icon_alert.gif │ │ ├── icon_calendar.gif │ │ ├── icon_changelink.gif │ │ ├── icon_clock.gif │ │ ├── icon_deletelink.gif │ │ ├── icon_error.gif │ │ ├── icon_searchbox.png │ │ ├── icon_success.gif │ │ ├── inline-delete-8bit.png │ │ ├── inline-delete.png │ │ ├── inline-restore-8bit.png │ │ ├── inline-restore.png │ │ ├── inline-splitter-bg.gif │ │ ├── nav-bg-grabber.gif │ │ ├── nav-bg-reverse.gif │ │ ├── nav-bg-selected.gif │ │ ├── nav-bg.gif │ │ ├── selector-icons.gif │ │ ├── selector-search.gif │ │ ├── sorting-icons.gif │ │ ├── tool-left.gif │ │ ├── tool-left_over.gif │ │ ├── tool-right.gif │ │ ├── tool-right_over.gif │ │ ├── tooltag-add.gif │ │ ├── tooltag-add_over.gif │ │ ├── tooltag-arrowright.gif │ │ └── tooltag-arrowright_over.gif │ │ └── js │ │ ├── LICENSE-JQUERY.txt │ │ ├── SelectBox.js │ │ ├── SelectFilter2.js │ │ ├── actions.js │ │ ├── actions.min.js │ │ ├── admin │ │ ├── DateTimeShortcuts.js │ │ ├── RelatedObjectLookups.js │ │ └── ordering.js │ │ ├── calendar.js │ │ ├── collapse.js │ │ ├── collapse.min.js │ │ ├── core.js │ │ ├── getElementsBySelector.js │ │ ├── inlines.js │ │ ├── inlines.min.js │ │ ├── jquery.init.js │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ ├── prepopulate.js │ │ ├── prepopulate.min.js │ │ ├── timeparse.js │ │ └── urlify.js ├── translate │ ├── __init__.py │ ├── admin.py │ ├── adminfilter.py │ ├── adminform.py │ ├── admininline.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_set_traduction.py │ │ └── __init__.py │ └── models.py ├── urls.py ├── utils │ ├── __init__.py │ ├── admin.py │ ├── authentication.py │ ├── handle_south.py │ ├── mock.py │ ├── set_current_timezone.py │ └── test.py └── wsgi.py ├── clean.sh ├── debian ├── README.Debian ├── changelog ├── compat ├── control ├── copyright ├── pydist-overrides ├── python-baobab.baobab-ircd.init ├── python-baobab.baobab-syncdb.init ├── python-baobab.baobab.cron.d ├── python-baobab.install ├── rules └── source │ └── format ├── gulpfile.js ├── misc └── hooks │ ├── README │ ├── post-merge │ ├── post-rebase │ ├── post-rewrite │ └── pre-commit ├── nginx └── sites-available │ ├── status.gandi.net │ └── status.gandi.net-ssl ├── npm-shrinkwrap.json ├── package.json ├── protractor.conf.js ├── pytest.ini ├── setup.py ├── status.template └── test.sh /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = */migrations/* 3 | */tests/* 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # Distribution / packaging 6 | build/ 7 | develop-eggs/ 8 | dist/ 9 | eggs/ 10 | lib/ 11 | lib64/ 12 | parts/ 13 | sdist/ 14 | var/ 15 | *.egg-info/ 16 | .installed.cfg 17 | *.egg 18 | 19 | # Editor stuff 20 | *~ 21 | .#* 22 | \#*# 23 | .*.swp 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | .tox/ 31 | .coverage 32 | .cache 33 | nosetests.xml 34 | coverage.xml 35 | test-prod-*/ 36 | 37 | # default (dev) db 38 | baobab/default.db 39 | 40 | # Node 41 | node_modules 42 | 43 | # Angular app 44 | *.ng/*.sw* 45 | *.ng/build/ 46 | *.ng/locale/templates 47 | *.ng/bin/ 48 | *.ng/node_modules/ 49 | *.ng/vendor/ 50 | *.ng/locale/static/*.json 51 | *.ng/app/spec/output 52 | *.ng/**/vendor 53 | 54 | # Generated assets 55 | baobab/front/templates/index.html 56 | baobab/static/public 57 | 58 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: 5 | - pip install -e '.[test]' 6 | - python setup.py develop 7 | - pip install pytest 8 | - pip install -U 'pytest-django<3.0.0' 9 | - pip install pytest-coverage 10 | - pip install coveralls 11 | script: 12 | - py.test --cov baobab 13 | after_success: 14 | - coveralls 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README 2 | include CHANGELOG 3 | recursive-include baobab/front/templates *.html 4 | -------------------------------------------------------------------------------- /baobab.front/assets/fonts/museo/Museo_Slab_500-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/museo/Museo_Slab_500-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/museo/Museo_Slab_500-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/museo/Museo_Slab_500-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/museo/Museo_Slab_500-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/museo/Museo_Slab_500-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Bold-webfont.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Bold-webfont.svg -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Bold-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-BoldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-BoldItalic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Italic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Regular-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/open-sans/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/open-sans/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Google Android License.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2008 The Android Open Source Project 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | ########## 16 | 17 | This directory contains the fonts for the platform. They are licensed 18 | under the Apache 2 license. 19 | -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Black-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Black-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Black-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Black-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Black-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Black-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BlackItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BlackItalic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BlackItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BlackItalic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BlackItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BlackItalic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Bold-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Bold-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Bold-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BoldCondensed-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BoldCondensed-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BoldCondensed-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BoldCondensed-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BoldCondensed-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BoldCondensed-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BoldCondensedItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BoldCondensedItalic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BoldCondensedItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BoldCondensedItalic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BoldCondensedItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BoldCondensedItalic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BoldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BoldItalic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Condensed-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Condensed-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Condensed-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Condensed-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Condensed-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Condensed-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-CondensedItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-CondensedItalic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-CondensedItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-CondensedItalic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-CondensedItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-CondensedItalic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Italic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Italic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Italic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Light-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Light-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Light-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-LightItalic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-LightItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-LightItalic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-LightItalic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Medium-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Medium-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Medium-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Medium-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Medium-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Medium-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-MediumItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-MediumItalic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-MediumItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-MediumItalic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-MediumItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-MediumItalic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Regular-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Regular-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Regular-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Thin-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Thin-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Thin-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Thin-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-Thin-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-Thin-webfont.woff -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-ThinItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-ThinItalic-webfont.eot -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-ThinItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-ThinItalic-webfont.ttf -------------------------------------------------------------------------------- /baobab.front/assets/fonts/roboto/Roboto-ThinItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/assets/fonts/roboto/Roboto-ThinItalic-webfont.woff -------------------------------------------------------------------------------- /baobab.front/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/favicon.ico -------------------------------------------------------------------------------- /baobab.front/images/header_contactsupport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/header_contactsupport.png -------------------------------------------------------------------------------- /baobab.front/images/header_rss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/header_rss.png -------------------------------------------------------------------------------- /baobab.front/images/header_twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/header_twitter.png -------------------------------------------------------------------------------- /baobab.front/images/logo_gandi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/logo_gandi.png -------------------------------------------------------------------------------- /baobab.front/images/status_incident.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/status_incident.png -------------------------------------------------------------------------------- /baobab.front/images/status_incident_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/status_incident_detail.png -------------------------------------------------------------------------------- /baobab.front/images/status_maintenance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/status_maintenance.png -------------------------------------------------------------------------------- /baobab.front/images/status_maintenance_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/status_maintenance_detail.png -------------------------------------------------------------------------------- /baobab.front/images/status_ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/status_ok.png -------------------------------------------------------------------------------- /baobab.front/images/status_ok_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab.front/images/status_ok_detail.png -------------------------------------------------------------------------------- /baobab.front/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Status - Gandi.net 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |

Gandi.net

27 |

system status

28 | 34 |

35 |

Status of services as of

36 |
37 | Show services status 38 |
39 |
40 | Click or scroll down for history 41 |
42 | 54 |
55 |
56 |
57 | 58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /baobab.front/js/.gitignore: -------------------------------------------------------------------------------- 1 | /templates.js 2 | -------------------------------------------------------------------------------- /baobab.front/js/api.js: -------------------------------------------------------------------------------- 1 | var Qajax = require('qajax'), 2 | moment = require('moment'); 3 | 4 | module.exports = { 5 | status: function() { 6 | return Qajax({url: '/api/status', headers:{'Accept': 'application/json'}}) 7 | .then(Qajax.filterSuccess) 8 | .then(Qajax.toJSON); 9 | }, 10 | services: function() { 11 | return Qajax({url: '/api/services', headers:{'Accept': 'application/json'}}) 12 | .then(Qajax.filterSuccess) 13 | .then(Qajax.toJSON); 14 | }, 15 | events: function(end_date, days) { 16 | var end = moment.utc(end_date), 17 | start = end 18 | .subtract(days, 'days') 19 | .startOf('day'); 20 | var args = { 21 | "date_start__gte": start.format('YYYY-MM-DD') 22 | }; 23 | return Qajax({url: '/api/events?' + Qajax.serialize(args), headers:{'Accept': 'application/json'}}) 24 | .then(Qajax.filterSuccess) 25 | .then(Qajax.toJSON) 26 | }, 27 | event: function(id) { 28 | return Qajax({url: '/api/events/' + id, headers:{'Accept': 'application/json'}}) 29 | .then(Qajax.filterSuccess) 30 | .then(Qajax.toJSON) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /baobab.front/js/elements/events.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'), 2 | api = require('../api'), 3 | router_utils = require('../router_utils'), 4 | templates = require('../templates'), 5 | $ = require('jquery-browserify'), 6 | moment = require('moment'); 7 | 8 | var target = $('main'); 9 | var maintenancesBox = $('header .maintenances'); 10 | 11 | var size = 240; 12 | 13 | function maintenances(events) { 14 | var futureEvents = events.filter(function(el) { 15 | return el.date_start.isAfter(); 16 | }).filter(function(el) { 17 | return el.category == "Maintenance"; 18 | }); 19 | 20 | if (futureEvents.length > 0) { 21 | maintenancesBox.html(templates['maintenances'](futureEvents)) 22 | .show(); 23 | } 24 | return events; 25 | } 26 | 27 | module.exports = Q.fcall(function() { 28 | var now = moment(); 29 | var days = 30; 30 | 31 | return api.events(now, days) 32 | .then(function (json) { 33 | return json.map(function(el) { 34 | el.date_start = moment(el.date_start); 35 | if (el.date_end) { 36 | el.date_end = moment(el.date_end); 37 | el.duration = moment.duration(el.date_start.diff(el.date_end)); 38 | } 39 | el.estimate_date_end = moment(el.estimate_date_end); 40 | return el; 41 | }); 42 | }) 43 | .then(maintenances) 44 | .then(function (json) { 45 | var events = {}; 46 | 47 | json.forEach(function (el) { 48 | date_end = el.date_end || el.estimate_date_end; 49 | // because of day change 50 | if (el.date_start < now && date_end > moment().endOf('day')) { 51 | date_end = moment(); 52 | if (el.date_end) { 53 | el.date_end = date_end; 54 | } 55 | el.estimate_date_end = date_end; 56 | } 57 | date_end = moment.utc(date_end).format('YYYY-MM-DD'); 58 | 59 | events[date_end] = events[date_end] || []; 60 | events[date_end].push(el); 61 | }); 62 | 63 | return events; 64 | }) 65 | .then(function(events) { 66 | var iter_date = moment.utc(now).startOf('day'); 67 | 68 | var content = []; 69 | 70 | for(var i = 0; i < days; i++) { 71 | day_events_tmp = events[iter_date.format('YYYY-MM-DD')] || [] 72 | day_events = day_events_tmp.map(function(el) { 73 | date_end = el.date_end || el.estimate_date_end; 74 | diff = date_end.diff(iter_date) / 1000 / 3600 / 24; // Results as percent of day 75 | el.pos = Math.round(size - diff*size) + 'px'; 76 | 77 | diff = date_end.diff(el.date_start) / 1000 / 3600 / 24; 78 | el.height = Math.round(diff*size) + 'px'; 79 | return el; 80 | }) 81 | 82 | day = templates['day']({ 83 | day: iter_date, 84 | events: day_events 85 | }) 86 | 87 | iter_date.subtract(1, 'days'); 88 | 89 | content.push(day); 90 | } 91 | 92 | target.html(content.join('')) 93 | .children('section').children('article').on('click', function(e) { 94 | router_utils.goto($(this).attr('data-href')); 95 | e.preventDefault(); 96 | }); 97 | 98 | }); 99 | }) 100 | 101 | -------------------------------------------------------------------------------- /baobab.front/js/elements/eventsfilter.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'), 2 | $ = require('jquery-browserify'); 3 | 4 | var target = $('main'); 5 | var categoryButton = $('#category'); 6 | var serviceButton = $('#service'); 7 | 8 | var selected = function(){ return this.selected; } 9 | 10 | function filterClassRegex(r) { 11 | return function(i, classname) { 12 | return classname.split(/\s+/g).filter(function(el) { 13 | return (new RegExp(r)).test(el)?el:''; 14 | }).join(' '); 15 | }; 16 | } 17 | 18 | function updateFilterCategory() { 19 | var newvalue = categoryButton.children().filter(selected).attr('value'); 20 | 21 | if (newvalue) { 22 | target.removeClass(filterClassRegex(/^cat-/)) 23 | .addClass('cat-'+newvalue); 24 | } else { 25 | target.removeClass(filterClassRegex(/^cat-/)); 26 | } 27 | } 28 | 29 | function updateFilterService() { 30 | var newvalue = serviceButton.children().filter(selected).attr('value'); 31 | 32 | if (newvalue) { 33 | target.removeClass(filterClassRegex(/^svc-/)) 34 | .addClass('svc-'+newvalue); 35 | } else { 36 | target.removeClass(filterClassRegex(/^svc-/)); 37 | } 38 | } 39 | 40 | function updateFilterType() { 41 | var category = categoryButton.children().filter(selected).attr('value'), 42 | service = serviceButton.children().filter(selected).attr('value'); 43 | 44 | if (category && service) { 45 | target.removeClass('filtered-category') 46 | .removeClass('filtered-service') 47 | .addClass('filtered-category-service'); 48 | } else if (category) { 49 | target.removeClass('filtered-service') 50 | .removeClass('filtered-category-service') 51 | .addClass('filtered-category'); 52 | } else if (service) { 53 | target.removeClass('filtered-category') 54 | .removeClass('filtered-category-service') 55 | .addClass('filtered-service'); 56 | } else { 57 | target.removeClass('filtered-category') 58 | .removeClass('filtered-service') 59 | .removeClass('filtered-category-service'); 60 | } 61 | 62 | } 63 | 64 | 65 | module.exports = Q.fcall(function() { 66 | categoryButton.on('change', updateFilterCategory); 67 | serviceButton.on('change', updateFilterService); 68 | 69 | categoryButton.on('change', updateFilterType); 70 | serviceButton.on('change', updateFilterType); 71 | }); 72 | 73 | -------------------------------------------------------------------------------- /baobab.front/js/elements/services.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'), 2 | api = require('../api'), 3 | templates = require('../templates'), 4 | $ = require('jquery-browserify'); 5 | 6 | var button = $('header > .button'); 7 | var target = $('.services'); 8 | var targetUp = $('.services-up'); 9 | var filterButton = $('#service'); 10 | 11 | function fillServicesDetails (json){ 12 | var content = ''; 13 | var contentUp = ''; 14 | 15 | for (var key in json) { 16 | if (json[key] != undefined && json[key].status != undefined) { 17 | element = templates['service']( 18 | $.extend({name: key}, json[key])); 19 | if (json[key].status != 'SUNNY') { 20 | content += element; 21 | } else { 22 | contentUp += element; 23 | } 24 | } 25 | } 26 | target.html(content); 27 | targetUp.html(contentUp); 28 | 29 | return json; 30 | } 31 | 32 | function fillFilterServices (json) { 33 | filterButton.html(templates['service-button'](json)); 34 | return json; 35 | } 36 | 37 | 38 | module.exports = api.services() 39 | .then(fillServicesDetails) 40 | .then(fillFilterServices) 41 | .then(function() { 42 | button.on('click', function() { 43 | targetUp.toggle() 44 | }); 45 | }); 46 | 47 | 48 | -------------------------------------------------------------------------------- /baobab.front/js/elements/status.js: -------------------------------------------------------------------------------- 1 | var api = require('../api'), 2 | $ = require('jquery-browserify'), 3 | templates = require('../templates'); 4 | 5 | 6 | var contents = { 7 | 'STORMY': 'Oops, we have problems', 8 | 'CLOUDY': 'We\'re currently working on our platform', 9 | 'FOGGY': 'Oops we have problems which are not impacting our services.', 10 | 'SUNNY': 'All services are up and running', 11 | }; 12 | 13 | var target = $('header > h3'); 14 | var button = $('header > a.button'); 15 | 16 | module.exports = api.status() 17 | .then(function(json) { 18 | target.text(contents[json.status]) 19 | .addClass('status-' + json.status.toLowerCase()); 20 | button.html(templates['service-status-button'](json)).show(); 21 | }); 22 | -------------------------------------------------------------------------------- /baobab.front/js/elements/statustime.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'), 2 | moment = require('moment'), 3 | $ = require('jquery-browserify'); 4 | 5 | 6 | var target = $('header > p > time'); 7 | 8 | module.exports = Q.fcall(function() { 9 | var now = moment(); 10 | target.attr('datetime', now.format()) 11 | .text(now.format('MMMM Do YYYY, h:mm a ZZ')) 12 | .parent().show(); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /baobab.front/js/main.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'), 2 | router = require('./router'), 3 | ie = require('./ie'), 4 | element_status = require('./elements/status'), 5 | element_statustime = require('./elements/statustime'), 6 | element_services = require('./elements/services'), 7 | element_events = require('./elements/events'), 8 | element_eventsfilter = require('./elements/eventsfilter'); 9 | 10 | Q.all([ 11 | ie, 12 | element_status, 13 | element_statustime, 14 | element_services, 15 | element_eventsfilter 16 | ]).then( 17 | router.init 18 | ).then( 19 | element_events 20 | ).done(); 21 | 22 | -------------------------------------------------------------------------------- /baobab.front/js/page/event.js: -------------------------------------------------------------------------------- 1 | var api = require('../api'), 2 | marked = require('marked'), 3 | moment = require('moment'), 4 | $ = require('jquery-browserify'), 5 | template = require('../templates'), 6 | router_utils = require('../router_utils'); 7 | 8 | var target = $('body > article'); 9 | 10 | 11 | marked.setOptions({ 12 | gfm: true 13 | }); 14 | 15 | module.exports = { 16 | show: function() { 17 | target.show(); 18 | }, 19 | hide: function (e) { 20 | target.hide(); 21 | if (e && e.preventDefault != undefined) e.preventDefault(); 22 | } 23 | } 24 | 25 | var self = module.exports; 26 | 27 | module.exports.unload = function() { 28 | self.hide(); 29 | } 30 | 31 | module.exports.load = function(event_id) { 32 | var short_format = 1000 * 60 * 60 * 18; // 18 hours 33 | function reldate(date) { 34 | if ( Math.abs(moment() - date) < short_format ) { 35 | return date.format('dddd, LT ZZ'); 36 | } else { 37 | return date.format('LLLL ZZ'); 38 | } 39 | }; 40 | 41 | api.event(event_id) 42 | .then(function(json) { 43 | json['marked'] = marked; 44 | 45 | json['date_start'] = moment(json['date_start']) 46 | json['estimate_date_end'] = moment(json['estimate_date_end']) 47 | 48 | for ( var elt in json['logs']) { 49 | json['logs'][elt]['date'] = moment(json['logs'][elt]['date']) 50 | } 51 | 52 | if (json['date_end']) json['date_end'] = moment(json['date_end']) 53 | 54 | var end_date = json.date_end || json.estimate_date_end; 55 | end_date = moment.utc(end_date); 56 | 57 | json['end_date'] = end_date; 58 | 59 | json['duration'] = moment.duration(json['date_start'].diff(end_date)); 60 | 61 | // Change date formating relative to current date 62 | json['reldate'] = reldate; 63 | 64 | target.html(template['event'](json)) 65 | .removeClass('maintenance') 66 | .removeClass('incident') 67 | .addClass(json.category.toLowerCase()); 68 | }).then(function() { 69 | $(document).on('keydown', function(e) { 70 | if (e.keyCode === 27) { // Escape 71 | $(document).unbind('keydown'); 72 | self.hide(); 73 | router_utils.goto('/timeline'); 74 | } 75 | }); 76 | }).then(function() { 77 | self.show(); 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /baobab.front/js/page/status.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery-browserify'); 2 | // event = require('./event'); 3 | 4 | var header = $('#container > header'); 5 | var target = $('#container'); 6 | 7 | module.exports = { 8 | load: function () { 9 | target.scrollTop(0); 10 | 11 | $(document).on('keydown', function(e) { 12 | if (e.keyCode == 34) { // Pagedown 13 | $(document).unbind('keydown'); 14 | target.scrollTop(header.height()); 15 | e.preventDefault(); 16 | } 17 | }); 18 | }, 19 | unload: function () { 20 | $(document).unbind('keydown'); 21 | } 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /baobab.front/js/page/timeline.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery-browserify'), 2 | event = require('./event'); 3 | 4 | var target = $('#container'); 5 | var status = $('#container > header'); 6 | 7 | module.exports = { 8 | load: function() { 9 | // Direct exit if old route was a subroute 10 | if (this.lastRoute && this.lastRoute.slice(0,2).join('/') == ['timeline', 'events'].join('/')) return; 11 | var height = status.height(); 12 | target.scrollTop(height); 13 | }, 14 | unload: function() { 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /baobab.front/js/router.js: -------------------------------------------------------------------------------- 1 | var router_utils = require('./router_utils'), 2 | status = require('./page/status'), 3 | timeline = require('./page/timeline'), 4 | event = require('./page/event'); 5 | 6 | module.exports = { 7 | init: function() { 8 | router_utils.init({ 9 | '/': { 10 | on: status.load, 11 | after: status.unload, 12 | '/timeline': { 13 | on: timeline.load, 14 | after: timeline.unload, 15 | '/events/:id': { 16 | on: event.load, 17 | after: event.unload 18 | } 19 | } 20 | } 21 | }); 22 | }, 23 | goto: router_utils.goto 24 | } 25 | -------------------------------------------------------------------------------- /baobab.front/js/router_utils.js: -------------------------------------------------------------------------------- 1 | var Router = require('director').Router, 2 | $ = require('jquery-browserify'); 3 | var self = module.exports; 4 | 5 | 6 | function sameOrigin(href) { 7 | var origin = location.protocol + '//' + location.hostname; 8 | if (location.port) origin += ':' + location.port; 9 | return href && (0 === href.indexOf(origin)); 10 | } 11 | 12 | function which(e) { 13 | e = e || window.event; 14 | return null === e.which 15 | ? e.button 16 | : e.which; 17 | } 18 | 19 | function onclick(e) { 20 | if (1 != which(e)) return; 21 | if (e.metaKey || e.ctrlKey || e.shiftKey) return; 22 | if (e.defaultPrevented) return; 23 | 24 | // ensure link 25 | var el = e.target; 26 | while (el && 'A' != el.nodeName) el = el.parentNode; 27 | if (!el || 'A' != el.nodeName) return; 28 | 29 | // ensure non-hash for the same path 30 | var link = el.getAttribute('href'); 31 | if (el.pathname == location.pathname && (el.hash || '#' == link)) return; 32 | 33 | // Check for mailto: in the href 34 | if (link && link.indexOf("mailto:") > -1) return; 35 | 36 | // check target 37 | if (el.target) return; 38 | 39 | // x-origin 40 | if (!sameOrigin(el.href)) return; 41 | 42 | // rebuild path 43 | var path = el.pathname + el.search + (el.hash || ''); 44 | 45 | // same page 46 | var orig = path + el.hash; 47 | 48 | module.exports.goto(orig); 49 | return false; 50 | } 51 | 52 | function registerClick() { 53 | $(window).on('click', onclick); 54 | } 55 | 56 | function storeLast() { 57 | self.router.lastRoute = self.router.getRoute(); 58 | } 59 | 60 | module.exports = { 61 | router: undefined, 62 | init: function(routes) { 63 | self.router = new Router(routes).configure({html5history: true, on: storeLast}) 64 | self.router.param('id', /([\\w\\-]+)/); 65 | self.router.init(); 66 | 67 | registerClick(); 68 | }, 69 | goto: function(url) { 70 | self.router.setRoute(url); 71 | } 72 | } 73 | self = module.exports; 74 | -------------------------------------------------------------------------------- /baobab.front/less/fonts.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "MuseoSlab500Regular"; 3 | src: url(assets/fonts/museo/Museo_Slab_500-webfont.eot); 4 | font-weight: 400; 5 | font-style: "normal"; 6 | src: url(assets/fonts/museo/Museo_Slab_500-webfont.eot?iefix) format('eot'), 7 | url(assets/fonts/museo/Museo_Slab_500-webfont.woff) format('woff'), 8 | url(assets/fonts/museo/Museo_Slab_500-webfont.ttf) format('truetype'), 9 | url(assets/fonts/museo/Museo_Slab_500-webfont.svg#MuseoSlab500Regular) format('svg'); 10 | } 11 | 12 | @font-face { 13 | font-family: 'Roboto'; 14 | font-style: normal; 15 | font-weight: 400; 16 | src: url(assets/fonts/roboto/Roboto-Light-webfont.eot?iefix) format('eot'), 17 | url(assets/fonts/roboto/Roboto-Light-webfont.woff) format('woff'), 18 | url(assets/fonts/roboto/Roboto-Light-webfont.ttf) format('truetype'), 19 | url(assets/fonts/roboto/Roboto-Light-webfont.svg#MuseoSlab500Regular) format('svg'); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /baobab.front/less/main.less: -------------------------------------------------------------------------------- 1 | @import 'reset.less'; 2 | @import 'resetie.less'; 3 | @import 'fonts.less'; 4 | 5 | html, body { 6 | height: 100%; 7 | width: 100%; 8 | } 9 | 10 | html { 11 | font-size: 62.5%; 12 | } 13 | 14 | @import 'status.less'; 15 | @import 'timeline.less'; 16 | @import 'event.less'; 17 | 18 | -------------------------------------------------------------------------------- /baobab.front/less/reset.less: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | -------------------------------------------------------------------------------- /baobab.front/less/resetie.less: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure, 2 | footer,header,hgroup,menu,nav,section { 3 | display:block; 4 | } 5 | -------------------------------------------------------------------------------- /baobab.front/tests/aliasifyConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | aliases: { 3 | "../api": "../tests/mocks/api" 4 | }, 5 | verbose: true 6 | } 7 | -------------------------------------------------------------------------------- /baobab.front/tests/integration/event.js: -------------------------------------------------------------------------------- 1 | describe('events', function() { 2 | function click(el) { 3 | return browser.driver.executeScript('return arguments[0].click();', el.getWebElement()); 4 | } 5 | 6 | it('should open a popup', function() { 7 | browser.driver.get('http://localhost:8000'); 8 | browser.ignoreSynchronization = true; // Non angular 9 | browser.driver.sleep(600); // see https://github.com/flatiron/director/blob/v1.2.2/lib/director/browser.js#L83 10 | 11 | var event1 = element.all(by.css('main section article p a')).first(); 12 | var popup = $('body>article'); 13 | 14 | event1.then(click); 15 | 16 | expect(popup.getAttribute('style')).toEqual('display: block;'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /baobab.front/tests/integration/service_details.js: -------------------------------------------------------------------------------- 1 | describe('service details', function() { 2 | it('should be open with two maintenance services', function() { 3 | browser.driver.get('http://localhost:8000'); 4 | browser.ignoreSynchronization = true; // Non angular 5 | 6 | var services = element(by.css('header .services')); 7 | 8 | expect(services.all(by.tagName('div')).count()).toEqual(2); 9 | 10 | // One of each type 11 | expect(services.all(by.css('div.status-cloudy')).count()).toEqual(1); 12 | expect(services.all(by.css('div.status-stormy')).count()).toEqual(1); 13 | }); 14 | it('should open/close', function() { 15 | browser.get('http://localhost:8000'); 16 | browser.ignoreSynchronization = true; // Non angular 17 | 18 | var button = element(by.css('header a.button')); 19 | var services = element(by.css('header .services-up')); 20 | 21 | button.click(); 22 | 23 | expect(services.getAttribute('style')).toEqual('display: block;'); 24 | 25 | button.click(); 26 | 27 | expect(services.getAttribute('style')).toEqual('display: none;'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /baobab.front/tests/integration/services.js: -------------------------------------------------------------------------------- 1 | describe('services', function() { 2 | 3 | it('should be listed in the services button', function() { 4 | browser.driver.get('http://localhost:8000'); 5 | browser.ignoreSynchronization = true; // Non angular 6 | 7 | var services = $('#service'); 8 | 9 | expect(services.all(by.tagName('option')).count()).toEqual(7+1); // 7 services + 1 default value 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /baobab.front/tests/integration/timeline.js: -------------------------------------------------------------------------------- 1 | describe('timeline', function() { 2 | function scrollTop(el) { 3 | return browser.driver.executeScript('return arguments[0].scrollTop;', el.getWebElement()); 4 | } 5 | function height(el) { 6 | return browser.driver.executeScript('return arguments[0].getBoundingClientRect().height;', el.getWebElement()); 7 | } 8 | 9 | it('should be scrolled when clicking on click', function() { 10 | browser.driver.get('http://localhost:8000'); 11 | browser.ignoreSynchronization = true; // Non angular 12 | 13 | var scroll = element(by.css('header a.more')); 14 | var headerHeight = $('header').then(height); 15 | 16 | expect($('#container').then(scrollTop)).toEqual(0); 17 | browser.driver.sleep(600); // see https://github.com/flatiron/director/blob/v1.2.2/lib/director/browser.js#L83 18 | 19 | scroll.click(); 20 | 21 | expect($('#container').then(scrollTop)).toEqual(headerHeight); 22 | }); 23 | 24 | it('should be scrolled on page down', function() { 25 | browser.driver.get('http://localhost:8000'); 26 | browser.ignoreSynchronization = true; // Non angular 27 | 28 | browser.driver.sleep(600); // see https://github.com/flatiron/director/blob/v1.2.2/lib/director/browser.js#L83 29 | 30 | $('body').sendKeys(protractor.Key.PAGE_DOWN); // PAGE DOWN 31 | 32 | var headerHeight = $('header').then(height); 33 | 34 | expect($('#container').then(scrollTop)).toEqual(headerHeight); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /baobab.front/tests/mocks/api.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'); 2 | 3 | module.exports = { 4 | status: function() { 5 | return Q.resolve({'status': 'CLOUDY'}); 6 | }, 7 | services: function() { 8 | return Q.resolve([ 9 | {"description": "IAAS", "name": "IAAS", "status": "CLOUDY"}, 10 | {"description": "PAAS", "name": "PAAS", "status": "SUNNY"}, 11 | {"description": "Site", "name": "Site", "status": "STORMY"}, 12 | {"description": "API", "name": "API", "status": "SUNNY"}, 13 | {"description": "SSL", "name": "SSL", "status": "SUNNY"}, 14 | {"description": "Domain", "name": "Domain", "status": "SUNNY"}, 15 | {"description": "Email", "name": "Email", "status": "SUNNY"} 16 | ]); 17 | }, 18 | events: function() { 19 | return Q.resolve([ 20 | {"category": "Maintenance", "date_end": "2014-10-16T17:51:14+00:00", "date_start": "2014-10-16T17:00:49+00:00", "duration": 100, "estimate_date_end": "2014-10-16T17:45:14+00:00", "id": 4, "services": ["Site", "API"], "title": "truc"}, 21 | {"category": "Maintenance", "date_end": "2014-10-11T13:51:14+00:00", "date_start": "2014-10-11T12:10:49+00:00", "duration": 100, "estimate_date_end": "2014-10-11T13:51:14+00:00", "id": 3, "services": ["Site", "API"], "title": "truc"}, 22 | {"category": "Maintenance", "date_end": "2014-10-11T12:50:31+00:00", "date_start": "2014-10-11T11:50:17+00:00", "duration": 60, "estimate_date_end": "2014-10-11T12:50:31+00:00", "id": 2, "services": ["IAAS"], "title": "machin"}, 23 | {"category": "Incident", "date_end": "2014-10-04T17:29:59+00:00", "date_start": "2014-10-04T14:29:59+00:00", "duration": 180, "estimate_date_end": "2014-10-05T00:29:59+00:00", "id": 1, "services": ["Site", "SSL"], "title": "asdasd"} 24 | ]); 25 | }, 26 | event: function(id) { 27 | switch(id) { 28 | case '1': 29 | return Q.resolve( 30 | {"category": "Incident", "date_end": "2014-10-04T17:29:59+00:00", "date_start": "2014-10-04T14:29:59+00:00", "duration": 180, "estimate_date_end": "2014-10-05T00:29:59+00:00", "id": 1, "logs": [], "services": ["Site", "SSL"], "summary": "# asdasd\r\n\r\ncoucou\r\n\r\n## coucou2\r\n \r\n`coucou`\r\n\r\n# coucou3\r\n\r\ncoucou ca va?\r\n\r\nparce que sinon\r\n\r\n", "title": "asdasd"} 31 | ); 32 | case '2': 33 | return Q.resolve( 34 | {"category": "Maintenance", "date_end": "2014-10-11T12:50:31+00:00", "date_start": "2014-10-11T11:50:17+00:00", "duration": 60, "estimate_date_end": "2014-10-11T12:50:31+00:00", "id": 2, "logs": [], "services": ["IAAS"], "summary": "", "title": "machin"} 35 | ); 36 | case '3': 37 | return Q.resolve( 38 | {"category": "Maintenance", "date_end": "2014-10-11T13:51:14+00:00", "date_start": "2014-10-11T12:10:49+00:00", "duration": 100, "estimate_date_end": "2014-10-11T13:51:14+00:00", "id": 3, "logs": [], "services": ["Site", "API"], "summary": "# h1\r\n\r\ntruc \r\n\r\n## h2\r\n\r\nalsdkjs\r\n\r\nalskjdlk\r\n\r\n### h3\r\n\r\n#### h4\r\n \r\n##### h5\r\n \r\n###### h6\r\n\r\n * foo\r\n * bar\r\n\r\n \r\n`code goes here`\r\n\r\n*italic*\r\n**bold**\r\n\r\n\r\nvery\r\n\r\nvery\r\n\r\nvery\r\nvery\r\n\r\nvery\r\n\r\nvery\r\nvery\r\n\r\nvery\r\n\r\nvery\r\nvery\r\n\r\nvery\r\n\r\nvery\r\nvery\r\n\r\nvery\r\n\r\nvery\r\n\r\n", "title": "truc"} 39 | ); 40 | case '4': 41 | return Q.resolve( 42 | {"category": "Maintenance", "date_end": "2014-10-16T17:51:14+00:00", "date_start": "2014-10-17T17:00:49+00:00", "duration": 100, "estimate_date_end": "2014-10-17T17:45:14+00:00", "id": 3, "logs": [], "services": ["Site", "API"], "summary": "# h1\r\n\r\ntruc \r\n\r\n## h2\r\n\r\nalsdkjs\r\n\r\nalskjdlk\r\n\r\n### h3\r\n\r\n#### h4\r\n \r\n##### h5\r\n \r\n###### h6\r\n\r\n * foo\r\n * bar\r\n\r\n \r\n`code goes here`\r\n\r\n*italic*\r\n**bold**\r\n\r\n\r\nvery\r\n\r\nvery\r\n\r\nvery\r\nvery\r\n\r\nvery\r\n\r\nvery\r\nvery\r\n\r\nvery\r\n\r\nvery\r\nvery\r\n\r\nvery\r\n\r\nvery\r\nvery\r\n\r\nvery\r\n\r\nvery\r\n\r\n", "title": "truc"} 43 | ); 44 | default: 45 | return Q.reject(""); 46 | } 47 | }, 48 | } 49 | -------------------------------------------------------------------------------- /baobab.front/views/day.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | {{~it.events :value}} 8 |
9 |

{{=value.title}} 10 | {{? value.date_start.isAfter()}} 11 | Will start 12 | {{??}} 13 | {{? value.date_end}} 14 | Lasted 15 | {{??}} 16 | Started 17 | {{?}} 18 | {{?}} 19 |

20 | 21 |
22 | {{~}} 23 |
24 | -------------------------------------------------------------------------------- /baobab.front/views/event.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 |

{{=it.title}}

11 | 12 | {{? it.date_start.isAfter() }} Will start {{??}} Started {{?}} 13 |
14 | {{? it.date_end}} 15 | Finished 16 | {{??}} 17 | Should finish 18 | {{?}} 19 | ()
20 |
21 | {{=it['marked'](it.summary)}} 22 |
23 | {{? it.logs.length > 0}} 24 | 32 | {{?}} 33 |
34 |
35 | -------------------------------------------------------------------------------- /baobab.front/views/maintenances.html: -------------------------------------------------------------------------------- 1 |

Scheduled operations

2 | {{~it :maintenance}} 3 |
4 |

From {{=maintenance.date_start.format('LLLL ZZ')}} to {{=maintenance.estimate_date_end.format('LLLL ZZ')}} for {{=maintenance.services.join(', ') }} - Details

5 |
6 | {{~}} 7 | -------------------------------------------------------------------------------- /baobab.front/views/service-button.html: -------------------------------------------------------------------------------- 1 | 2 | {{~it :value}} 3 | 4 | {{~}} 5 | -------------------------------------------------------------------------------- /baobab.front/views/service-status-button.html: -------------------------------------------------------------------------------- 1 | {{?it.status != 'SUNNY' && it.status != 'FOGGY'}} 2 | See other services' status 3 | {{??}} 4 | Show services status 5 | {{?}} 6 | -------------------------------------------------------------------------------- /baobab.front/views/service.html: -------------------------------------------------------------------------------- 1 |
2 |

{{=it.name}}

3 | Details 4 |
5 | -------------------------------------------------------------------------------- /baobab/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __version__ = '1.6' 4 | -------------------------------------------------------------------------------- /baobab/apirest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/apirest/__init__.py -------------------------------------------------------------------------------- /baobab/apirest/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mandatory to be able to do test 4 | """ 5 | -------------------------------------------------------------------------------- /baobab/apirest/services.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | API REST for baobab 4 | """ 5 | from baobab.backoffice.models import Service as BOService 6 | 7 | from baobab.apirest.modelresource import RawModelResource 8 | from baobab.apirest.status import StatusResource 9 | 10 | 11 | class ServicesResource(RawModelResource): 12 | 13 | def dehydrate(self, bundle): 14 | bundle.data['name'] = bundle.obj.get_name_display() 15 | bundle.data['status'] = bundle.obj.get_status_display() 16 | return bundle 17 | 18 | def build_schema(self): 19 | """ 20 | dehydrate overwrite some value need to manually fix the schema 21 | """ 22 | from baobab.apirest.urls import ApiUrls 23 | 24 | schema = super(ServicesResource, self).build_schema() 25 | schema['fields']['name']['type'] = 'string' 26 | schema['fields']['status'] = { 27 | 'type': 'related', 28 | 'schema': '/%s/%s/schema' % (ApiUrls.name, 29 | StatusResource._meta.resource_name) 30 | } 31 | return schema 32 | 33 | class Meta(RawModelResource.Meta): 34 | queryset = BOService.objects.all() 35 | excludes = ['id', ] 36 | -------------------------------------------------------------------------------- /baobab/apirest/status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | API REST for baobab 4 | """ 5 | from django.db.models import Q 6 | from django.utils.timezone import now 7 | 8 | from baobab.backoffice.models import (Event as BOEvent, 9 | Service as BOService) 10 | 11 | from baobab.apirest.modelresource import RawModelResource 12 | 13 | 14 | class StatusResource(RawModelResource): 15 | 16 | def alter_list_data_to_serialize(self, request, data_dict): 17 | """ 18 | dehydrate will be call for each BOEvent 19 | here we need to compute the result of each event thus the use of 20 | this method instead of dehydrate 21 | 22 | the rule are: 23 | INCIDENT with impact on services = STORMY 24 | INCIDENT with no impact on services = FOGGY 25 | MAINTENANCE = CLOUDY 26 | OTHER = SUNNY 27 | 28 | """ 29 | status = [BOService.SUNNY, ] 30 | for event in data_dict['objects']: # data_dict is a tastypie Bundle 31 | event = event.obj 32 | if event.category == BOEvent.INCIDENT: 33 | if event.services.exists(): 34 | status.append(BOService.STORMY) 35 | else: 36 | status.append(BOService.FOGGY) 37 | else: 38 | status.append(BOService.CLOUDY) 39 | return {'status': BOService.DICT_STATUS_CHOICES[max(status)]} 40 | 41 | def build_schema(self): 42 | """ 43 | due to the use of alter_list_data_to_serialize need to manually 44 | create the schema 45 | """ 46 | return { 47 | "allowed_detail_http_methods": [ 48 | None 49 | ], 50 | "allowed_list_http_methods": [ 51 | "get" 52 | ], 53 | "default_format": "application/json", 54 | "fields": { 55 | "status": { 56 | "help_text": "the status of Gandi's platform", 57 | "nullable": False, 58 | "readonly": True, 59 | "type": "string", 60 | "value": [{status[1]: status[2]} 61 | for status in BOService.STATUS_CHOICES] 62 | }, 63 | }, 64 | } 65 | 66 | def get_object_list(self, request): 67 | return super(StatusResource, self).get_object_list(request).filter( 68 | Q(date_end__isnull=True) | Q(date_end__gt=now), 69 | date_start__lte=now 70 | ) 71 | 72 | class Meta(RawModelResource.Meta): 73 | queryset = BOEvent.objects.all() 74 | detail_allowed_methods = [] # disable all request on specific resource 75 | -------------------------------------------------------------------------------- /baobab/apirest/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | django only look for test in app_name.tests.py or app_name.tests/__init__.py 5 | add the tests classes here to make them visible to django 6 | """ 7 | 8 | from .test_route import TestRoute 9 | from .test_status import TestStatus 10 | from .test_services import TestServices 11 | from .test_events import TestEvents 12 | from .test_utc_date import TestUTCDate 13 | from .test_schema import TestSchema 14 | -------------------------------------------------------------------------------- /baobab/apirest/tests/test_route.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | check that only the expected route are really defined 4 | """ 5 | 6 | from django.core.urlresolvers import RegexURLPattern, RegexURLResolver 7 | 8 | from baobab.utils.test import ResourceTestCase 9 | from baobab.apirest.urls import ApiUrls 10 | from baobab.apirest.tests.test_schema import TestSchema 11 | 12 | 13 | class TestRoute(ResourceTestCase): 14 | 15 | nb_route = 3 # status, services, events 16 | 17 | # XXX default route are: 18 | # list_view 19 | # list_view/set/{id} 20 | # schema 21 | # detail_view/{id} 22 | nb_default_route_by_ressource = 4 23 | 24 | # XXX default to 4, +2 to correctly handle the route events/{id} 25 | nb_route_for_event = 6 26 | 27 | def test_01_nb_of_route(self): 28 | urls = ApiUrls.get_urls() 29 | # check the nb of defined ressources should be the same as the 30 | # number of route + 1 done by tastypie 31 | self.assertEqual(len(urls), self.nb_route + 1) 32 | self.assertTrue(isinstance(urls.pop(0), RegexURLPattern)) 33 | 34 | event_url = urls.pop(0) 35 | self.assertTrue(isinstance(event_url, RegexURLResolver)) 36 | # XXX depend on the version of tastypie: only use one or both method: 37 | # prepend_urls/override_urls 38 | self.assertIn(len(event_url.url_patterns), 39 | [self.nb_route_for_event, self.nb_route_for_event + 2]) 40 | 41 | for url in urls: 42 | self.assertTrue(isinstance(url, RegexURLResolver)) 43 | self.assertEqual(len(url.url_patterns), 44 | self.nb_default_route_by_ressource) 45 | 46 | def test_02_nb_route_schema(self): 47 | self.assertEqual(len(TestSchema.routes), self.nb_route) 48 | -------------------------------------------------------------------------------- /baobab/apirest/tests/test_schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | check that only the expected route are really defined 4 | """ 5 | 6 | import json 7 | 8 | from django.core.urlresolvers import RegexURLPattern, RegexURLResolver 9 | 10 | from baobab.utils.test import ResourceTestCase 11 | from baobab.apirest.urls import ApiUrls 12 | 13 | 14 | class TestSchema(ResourceTestCase): 15 | 16 | routes = [ 17 | 'events', 18 | 'services', 19 | 'status', 20 | ] 21 | 22 | def test_route_json(self): 23 | resp = self.api_client.get('/api') 24 | self.assertValidJSONResponse(resp) 25 | for route in self.routes: 26 | resp = self.api_client.get('/api/%s/schema' % route) 27 | self.assertValidJSONResponse(resp) 28 | 29 | def test_route_html(self): 30 | resp = self.api_client.get('/api', format='html') 31 | self.assertHttpOK(resp) 32 | for route in self.routes: 33 | resp = self.api_client.get('/api/%s/schema' % route, format='html') 34 | self.assertHttpOK(resp) 35 | -------------------------------------------------------------------------------- /baobab/apirest/tests/test_services.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | 4 | from django.utils.timezone import now 5 | 6 | from baobab.utils.test import ResourceTestCase 7 | from baobab.backoffice.models import Service as BOService 8 | 9 | 10 | class TestServices(ResourceTestCase): 11 | fixtures = ['db_user', 'db_backoffice'] 12 | 13 | url = '/api/services' 14 | 15 | def test_01_get_list(self): 16 | resp = self.api_client.get(self.url) 17 | self.assertValidJSONResponse(resp) 18 | 19 | def test_02_number_of_elem(self): 20 | resp = self.api_client.get(self.url) 21 | data = self.deserialize(resp) 22 | self.assertEqual(len(data), len(BOService.SERVICE_CHOICES)) 23 | 24 | def test_03_key(self): 25 | resp = self.api_client.get(self.url) 26 | datas = self.deserialize(resp) 27 | for data in datas: 28 | self.assertKeys(data, ['name', 'description', 'status']) 29 | 30 | def test_04_name(self): 31 | resp = self.api_client.get(self.url) 32 | datas = self.deserialize(resp) 33 | names = [val for _, val in BOService.SERVICE_CHOICES] 34 | for data in datas: 35 | self.assertTrue(data['name'] in names) 36 | -------------------------------------------------------------------------------- /baobab/apirest/tests/test_status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import timedelta 4 | 5 | from django.utils.timezone import now 6 | 7 | from tastypie.http import HttpMethodNotAllowed 8 | 9 | from baobab.utils.test import ResourceTestCase 10 | from baobab.backoffice.models import Event as BOEvent 11 | 12 | 13 | class TestStatus(ResourceTestCase): 14 | fixtures = ['db_user', 'db_backoffice'] 15 | 16 | url = '/api/status' 17 | 18 | def _create_event(self, duration, services, category): 19 | event = BOEvent() 20 | event.date_start = now() 21 | event.duration = duration 22 | event.title = 'My Title: %d' % duration 23 | event.summary = '' 24 | event.category = category 25 | event.save() 26 | event.services.add(*services) 27 | 28 | def _end_event(event=event): 29 | event.date_end = now() 30 | event.save() 31 | 32 | event.end = _end_event 33 | return event 34 | 35 | def test_01_get_list(self): 36 | resp = self.api_client.get(self.url) 37 | self.assertValidJSONResponse(resp) 38 | 39 | def test_02_get_detail(self): 40 | resp = self.api_client.get('%s/1' % self.url) 41 | self.assertEqual(type(resp), HttpMethodNotAllowed) 42 | 43 | def test_03_OK(self): 44 | resp = self.api_client.get(self.url) 45 | data = self.deserialize(resp) 46 | self.assertEqual(data, {'status': u'SUNNY'}) 47 | 48 | def test_04_CLOUDY(self): 49 | event = self._create_event(10, [1, 2], BOEvent.MAINTENANCE) 50 | resp = self.api_client.get(self.url) 51 | data = self.deserialize(resp) 52 | self.assertEqual(data, {'status': u'CLOUDY'}) 53 | 54 | def test_05_STORMY(self): 55 | event = self._create_event(10, [1, 2], BOEvent.INCIDENT) 56 | resp = self.api_client.get(self.url) 57 | data = self.deserialize(resp) 58 | self.assertEqual(data, {'status': u'STORMY'}) 59 | 60 | def test_06_STORMY_CLOUDY(self): 61 | cloudy = self._create_event(10, [1, 2], BOEvent.MAINTENANCE) 62 | stormy = self._create_event(10, [3, 4], BOEvent.INCIDENT) 63 | resp = self.api_client.get(self.url) 64 | data = self.deserialize(resp) 65 | self.assertEqual(data, {'status': u'STORMY'}) 66 | stormy.end() 67 | resp = self.api_client.get(self.url) 68 | data = self.deserialize(resp) 69 | self.assertEqual(data, {'status': u'CLOUDY'}) 70 | 71 | def test_07_FOGGY(self): 72 | foggy = self._create_event(10, [], BOEvent.INCIDENT) 73 | resp = self.api_client.get(self.url) 74 | data = self.deserialize(resp) 75 | self.assertEqual(data, {'status': u'FOGGY'}) 76 | 77 | def test_08_STORMY_FOGGY(self): 78 | foggy = self._create_event(10, [], BOEvent.INCIDENT) 79 | stormy = self._create_event(10, [3, 4], BOEvent.INCIDENT) 80 | resp = self.api_client.get(self.url) 81 | data = self.deserialize(resp) 82 | self.assertEqual(data, {'status': u'STORMY'}) 83 | stormy.end() 84 | resp = self.api_client.get(self.url) 85 | data = self.deserialize(resp) 86 | self.assertEqual(data, {'status': u'FOGGY'}) 87 | 88 | def test_09_NOT_FOGGY(self): 89 | couldy = self._create_event(10, [], BOEvent.MAINTENANCE) 90 | resp = self.api_client.get(self.url) 91 | data = self.deserialize(resp) 92 | self.assertEqual(data, {'status': u'CLOUDY'}) 93 | 94 | def test_10_FOGGY_CLOUDY(self): 95 | foggy = self._create_event(10, [], BOEvent.INCIDENT) 96 | could = self._create_event(10, [], BOEvent.MAINTENANCE) 97 | resp = self.api_client.get(self.url) 98 | data = self.deserialize(resp) 99 | self.assertEqual(data, {'status': u'FOGGY'}) 100 | foggy.end() 101 | resp = self.api_client.get(self.url) 102 | data = self.deserialize(resp) 103 | self.assertEqual(data, {'status': u'CLOUDY'}) 104 | -------------------------------------------------------------------------------- /baobab/apirest/tests/test_utc_date.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import datetime 4 | import pytz 5 | 6 | from django.conf import settings 7 | from django.utils.timezone import is_naive, now 8 | 9 | from baobab.utils.test import TestCase 10 | from baobab.apirest.modelresource import MySerializer 11 | 12 | 13 | class TestUTCDate(TestCase): 14 | url = '/api/services' 15 | 16 | def test_01_native_date(self): 17 | date = datetime.datetime(2014, 5, 9, 18, 00) 18 | tz = pytz.timezone(settings.TIME_ZONE) 19 | self.assertTrue(is_naive(date)) 20 | api_date = MySerializer().format_datetime(date) 21 | date = date - tz.utcoffset(date) 22 | self.assertEqual(api_date, date.strftime('%Y-%m-%dT%H:%M:%S+00:00')) 23 | 24 | def test_02_default_tz(self): 25 | tz = pytz.timezone(settings.TIME_ZONE) 26 | date = datetime.datetime(2014, 5, 9, 18, 00, tzinfo=tz) 27 | self.assertFalse(is_naive(date)) 28 | api_date = MySerializer().format_datetime(date) 29 | date = date - tz.utcoffset(date) 30 | #date = date.astimezone(pytz.UTC) 31 | self.assertEqual(api_date, date.strftime('%Y-%m-%dT%H:%M:%S+00:00')) 32 | 33 | def test_03_utc_tz(self): 34 | date = datetime.datetime(2014, 5, 9, 18, 00, tzinfo=pytz.UTC) 35 | self.assertFalse(is_naive(date)) 36 | api_date = MySerializer().format_datetime(date) 37 | self.assertEqual(api_date, date.strftime('%Y-%m-%dT%H:%M:%S+00:00')) 38 | 39 | def test_04_another_tz(self): 40 | tz = pytz.timezone('US/Hawaii') 41 | date = datetime.datetime(2014, 5, 9, 18, 00, tzinfo=tz) 42 | self.assertFalse(is_naive(date)) 43 | api_date = MySerializer().format_datetime(date) 44 | date = date - tz.utcoffset(date) 45 | #date = date.astimezone(pytz.UTC) 46 | self.assertEqual(api_date, date.strftime('%Y-%m-%dT%H:%M:%S+00:00')) 47 | 48 | def test_05_no_microsecond(self): 49 | date = now() 50 | api_date = MySerializer().format_datetime(date) 51 | self.assertEqual(api_date, date.strftime('%Y-%m-%dT%H:%M:%S+00:00')) 52 | -------------------------------------------------------------------------------- /baobab/apirest/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | To define the url for the API 4 | """ 5 | 6 | import textwrap 7 | import json 8 | 9 | from django.http import HttpResponse 10 | 11 | from tastypie.api import Api 12 | from tastypie.exceptions import BadRequest 13 | from tastypie.utils import is_valid_jsonp_callback_value 14 | from tastypie.utils.mime import determine_format, build_content_type 15 | from tastypie.serializers import Serializer 16 | 17 | from baobab.apirest.events import EventsResource 18 | from baobab.apirest.services import ServicesResource 19 | from baobab.apirest.status import StatusResource 20 | 21 | 22 | class MyApi(Api): 23 | 24 | def top_level(self, request, api_name=None): 25 | """ 26 | this is a copy/past of the method Api.top_level 27 | the original method locally use a Serializer object which didn't 28 | implement the `to_html` method 29 | this function does exactly the same except it can server html content 30 | """ 31 | 32 | serializer = Serializer() 33 | available_resources = {} 34 | 35 | if api_name is None: 36 | api_name = self.api_name 37 | 38 | for name in sorted(self._registry.keys()): 39 | kwargs = { 40 | 'api_name': api_name, 41 | 'resource_name': name, 42 | } 43 | available_resources[name] = { 44 | 'list_endpoint': self._build_reverse_url("api_dispatch_list", 45 | kwargs=kwargs), 46 | 'schema': self._build_reverse_url("api_get_schema", 47 | kwargs=kwargs), 48 | } 49 | 50 | desired_format = determine_format(request, serializer) 51 | options = {} 52 | if 'text/html' in desired_format: 53 | serialized = textwrap.dedent(""" 54 | 55 | 56 | 57 | 76 | 77 | Api route 78 | 79 |
80 |

Available route

81 |
%s
82 |
83 | 84 | 85 | """) % json.dumps(available_resources, indent=4, sort_keys=True) 86 | else: 87 | if 'text/javascript' in desired_format: 88 | callback = request.GET.get('callback', 'callback') 89 | if not is_valid_jsonp_callback_value(callback): 90 | raise BadRequest('JSONP callback name is invalid.') 91 | options['callback'] = callback 92 | serialized = serializer.serialize(available_resources, 93 | desired_format, options) 94 | return HttpResponse(content=serialized, 95 | content_type=build_content_type(desired_format)) 96 | 97 | 98 | class ApiUrls(object): 99 | 100 | name = 'api' 101 | 102 | @classmethod 103 | def get_urls(cls): 104 | api_name = MyApi(api_name=cls.name) 105 | api_name.register(EventsResource()) 106 | api_name.register(ServicesResource()) 107 | api_name.register(StatusResource()) 108 | return api_name.urls 109 | -------------------------------------------------------------------------------- /baobab/backoffice/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/backoffice/__init__.py -------------------------------------------------------------------------------- /baobab/backoffice/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Define the form for django's admin 4 | """ 5 | import datetime 6 | import logging 7 | 8 | from django.contrib import admin 9 | 10 | from baobab.utils.admin import ModelAdmin 11 | 12 | from baobab.translate.models import (Lang as TranslateLang, 13 | EventData as TranslateEventData, 14 | EventLogData as TranslateEventLogData) 15 | 16 | from baobab.backoffice import models 17 | from baobab.backoffice.admininline import EventLogInline 18 | from baobab.backoffice.adminform import EventForm, EventLogForm 19 | 20 | LOG = logging.getLogger(__name__) 21 | logging.basicConfig(level=logging.WARNING) 22 | 23 | 24 | class EventAdmin(ModelAdmin): 25 | form = EventForm 26 | list_display = ['title_en', 'date_start', 'date_end', 'estimate_date_end', 27 | 'last_update', 'get_duration', 'first_user', 'last_user', 28 | 'category'] 29 | list_display_dynamic = { 30 | 'val_name': models.Service.SERVICE_CHOICES, 31 | 'filter': 'name', 32 | 'sub_obj': 'services', 33 | } 34 | inlines = [EventLogInline, ] 35 | 36 | search_fields = ['eventdatas__title'] 37 | 38 | def changelist_view(self, request, extra_context=None): 39 | # XXX to keep compatibility: should be remove in a next release 40 | try: 41 | return super(EventAdmin, self).changelist_view( 42 | request=request, extra_context=extra_context) 43 | except Exception as err: 44 | LOG.error('error when searching an event: %s', err) 45 | self.search_fields = ['title'] 46 | return super(EventAdmin, self).changelist_view( 47 | request=request, extra_context=extra_context) 48 | 49 | list_filter = ('services__name', 'date_start', 'last_update', 'category') 50 | fieldsets = [ 51 | ('Real duration of the event ()', 52 | {'description': ("it used your browser's clock and will be converted " 53 | 'to UTC based on your timezone'), 55 | 'fields': (('date_start', 'date_end'), )}), 56 | ('To define the estimated date end', {'fields': ('duration', )}), 57 | (None, {'fields': [('title_en', 'category'), ('summary_en', 'msg')]}), 58 | (None, {'fields': ('services', )}), 59 | ] 60 | 61 | def get_duration(self, obj): 62 | return datetime.timedelta(minutes=obj.duration) 63 | get_duration.short_description = 'duration' 64 | 65 | def title_en(self, obj): 66 | try: 67 | return obj.eventdatas.get(lang__iso='en').title 68 | except Exception as err: 69 | LOG.error("error when getting the event's title: %s", err) 70 | # XXX to keep compatibility: should be remove in a next release 71 | return obj.title 72 | 73 | def summary_en(self, obj): 74 | try: 75 | return obj.eventdatas.get(lang__iso='en').summary 76 | except Exception as err: 77 | LOG.error("error when getting the event's title: %s", err) 78 | # XXX to keep compatibility: should be remove in a next release 79 | return obj.summary 80 | 81 | def first_user(self, obj): 82 | if obj.eventlogs.count(): 83 | # XXX no earliest before django 1.6 84 | return obj.eventlogs.order_by('date')[0].user.username.capitalize() 85 | else: 86 | return 'Unknown' 87 | first_user.short_description = 'user (first one)' 88 | 89 | def last_user(self, obj): 90 | if obj.eventlogs.count(): 91 | return obj.eventlogs.latest('date').user.username.capitalize() 92 | else: 93 | return 'Unknown' 94 | last_user.short_description = 'user (last one)' 95 | 96 | 97 | class EventLogAdmin(ModelAdmin): 98 | 99 | form = EventLogForm 100 | 101 | list_display = ('event', 'comment_en', 'date', 'user') 102 | fields = ('event', 'comment_en') 103 | 104 | def comment_en(self, obj): 105 | try: 106 | return obj.eventlogdatas.get(lang__iso='en').comment 107 | except: 108 | # XXX to keep compatibility: should be remove in a next release 109 | return obj.comment 110 | 111 | 112 | class TimeZoneAdmin(ModelAdmin): 113 | list_display = ('timezone', ) 114 | fields = ('timezone', ) 115 | 116 | def queryset(self, request): 117 | qs = super(TimeZoneAdmin, self).queryset(request) 118 | return qs.filter(user=request.user) 119 | 120 | def has_add_permission(self, request): 121 | return False 122 | 123 | 124 | admin.site.register(models.Event, EventAdmin) 125 | admin.site.register(models.EventLog, EventLogAdmin) 126 | admin.site.register(models.TimeZone, TimeZoneAdmin) 127 | -------------------------------------------------------------------------------- /baobab/backoffice/admininline.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | all the inline class 5 | """ 6 | 7 | from baobab.utils.admin import TabularInline 8 | from baobab.backoffice import models 9 | from baobab.backoffice.adminform import EventLogForm 10 | 11 | 12 | class EventLogInline(TabularInline): 13 | 14 | """ 15 | Used by EventAdmin 16 | """ 17 | 18 | fields = ['comment_en', 'msg'] 19 | model = models.EventLog 20 | extra = 1 21 | form = EventLogForm 22 | ordering = ['date', ] 23 | -------------------------------------------------------------------------------- /baobab/backoffice/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/backoffice/management/__init__.py -------------------------------------------------------------------------------- /baobab/backoffice/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/backoffice/management/commands/__init__.py -------------------------------------------------------------------------------- /baobab/backoffice/management/commands/migrate_fixtures.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | HACK to apply new migrations to the base fixtures 4 | 5 | the base fixtures are the one in settings.FIXTURE_DIRS 6 | the name of the fixtures has to be of the form: db_app_name.json 7 | this means one file by app 8 | 9 | a mandatory file: db_south.json which keeps track of applied migrations 10 | 11 | the global var APP define on which app this hack should be applied 12 | """ 13 | 14 | import os 15 | import re 16 | from StringIO import StringIO 17 | import sys 18 | 19 | from django.conf import settings 20 | from django.core.management import call_command 21 | from django.core.management.base import BaseCommand 22 | 23 | from south.models import MigrationHistory 24 | 25 | from baobab.utils.handle_south import HandleSouth 26 | 27 | 28 | class Command(BaseCommand): 29 | regex = re.compile(r'^[0-9]{4}.*\.py$') 30 | fixtures = [] 31 | 32 | def write(self, file_, lines): 33 | if not isinstance(lines, list): 34 | lines = [lines, ] 35 | for line in lines: 36 | file_.write(line.rstrip(' \n')) 37 | file_.write('\n') 38 | 39 | def migrate(self, app): 40 | """ 41 | dump the new fixture remove the ending whitespce and add the missing \n 42 | """ 43 | 44 | print 'backup the new fixtures: %s' % app 45 | backup = sys.stdout 46 | file_ = open('%s/db_%s.json' % (settings.FIXTURE_DIRS[0], app), 'w') 47 | sys.stdout = StringIO() 48 | call_command('dumpdata', app, indent=2) 49 | sys.stdout.seek(0) 50 | self.write(file_, sys.stdout.readlines()) 51 | 52 | file_.close() 53 | sys.stdout.close() 54 | sys.stdout = backup 55 | 56 | def has_fixtures(self, app): 57 | if not self.fixtures: 58 | for rep in settings.FIXTURE_DIRS: 59 | self.fixtures.extend(map(lambda x: x.split('.', 1)[0], 60 | os.listdir(rep))) 61 | return 'db_%s' % app in self.fixtures 62 | 63 | def handle(self, *args, **options): 64 | if 'test' not in sys.argv or not settings.FIXTURE_DIRS: 65 | print 'ERROR: not in test env' 66 | exit(1) 67 | HandleSouth.disable() 68 | call_command('syncdb', interactive=False) 69 | HandleSouth.enable() 70 | # migration already done 71 | call_command('loaddata', 'db_south', verbosity='0') 72 | call_command('loaddata', 'db_user', verbosity='0') 73 | for app in HandleSouth.get_apps(): 74 | if not self.has_fixtures(app): 75 | continue 76 | print 'loading fixtures: %s' % app 77 | call_command('loaddata', 'db_%s' % app, verbosity='0') 78 | nb = MigrationHistory.objects.filter(app_name=app).count() 79 | files = os.listdir('%s/%s/migrations' % (settings.ROOT_DIR, app)) 80 | migrations = [migration for migration in files 81 | if self.regex.match(migration)] 82 | migrations.sort() 83 | for migration in migrations[nb:]: 84 | # XXX schemamigration will always be done by the syncdb command 85 | # they shouldn't be done again: e.g raise table not found 86 | # datamigration are 'manually' created unlike 87 | # schemamigration so a schemamigration should always 88 | # contain '_auto__' in its name 89 | if '_auto__' in migration: 90 | call_command('migrate', app, migration, fake=True) 91 | else: 92 | call_command('migrate', app, migration) 93 | self.migrate(app) 94 | # update the done migration 95 | self.migrate('south') 96 | -------------------------------------------------------------------------------- /baobab/backoffice/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/backoffice/migrations/__init__.py -------------------------------------------------------------------------------- /baobab/backoffice/modelmanager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | override the default manager 4 | """ 5 | from django.db import models 6 | from django.db.models.query import QuerySet 7 | 8 | 9 | class EventQuerySet(QuerySet): 10 | 11 | """ 12 | The admin will always order the result 13 | (see dkago/contrib/admin/views/main.py ling 260) 14 | 15 | It will keep the right order and avoid the admin to do a -pk order 16 | """ 17 | 18 | def order_by(self, *field_names): 19 | # XXX this is an 'impossible' filter, thus it still 20 | # allow an other ordering if needed/wanted 21 | if set(field_names) == set(['id', 'pk']): 22 | return self._clone() 23 | return super(EventQuerySet, self).order_by(*field_names) 24 | 25 | 26 | class EventManager(models.Manager): 27 | 28 | """ 29 | manager for the event model 30 | 31 | get_query_set will always be called which is not the case for the 32 | others function (like all, filter) when they are chained 33 | """ 34 | 35 | def get_query_set(self): 36 | return EventQuerySet(self.model, using=self._db).extra( 37 | select={'real_date_end': 'COALESCE(date_end, estimate_date_end)'}, 38 | order_by=['-real_date_end', '-date_start'], 39 | ) 40 | -------------------------------------------------------------------------------- /baobab/backoffice/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | django only look for test in app_name.tests.py or app_name.tests/__init__.py 5 | add the tests classes here to make them visible to django 6 | """ 7 | 8 | from .test_event import TestEvent 9 | from .test_status import TestStatus 10 | from .test_event_trigger import TestEventTrigger 11 | from .test_eventlog_trigger import TestEventLogTrigger 12 | -------------------------------------------------------------------------------- /baobab/backoffice/tests/test_event_trigger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | 4 | from django.utils.timezone import now as tz_now 5 | 6 | from baobab.utils.test import TestCase 7 | from baobab.utils.mock import MockSN 8 | from baobab.backoffice.models import Event 9 | from baobab.socialnetwork.models import Event as SnEvent 10 | 11 | 12 | class TestEventTrigger(TestCase): 13 | fixtures = ['db_user', 'db_backoffice'] 14 | 15 | def _create_event(self, now, end, category): 16 | event = Event() 17 | event.date_start = now 18 | event.date_end = end 19 | event.title = 'My Title' 20 | event.summary = '' 21 | event.category = category 22 | event.msg = 'a msg to publish' 23 | event.save() 24 | return event 25 | 26 | def test_01_event_incident_now(self): 27 | self.assertEqual(SnEvent.objects.count(), 0) 28 | 29 | now = tz_now().replace(microsecond=0) 30 | end = now + datetime.timedelta(minutes=10) 31 | event = self._create_event(now, end, Event.INCIDENT) 32 | 33 | self.assertEqual(SnEvent.objects.count(), 1) 34 | sn_event = SnEvent.objects.get(id=1) 35 | self.assertEqual(sn_event.event, event) 36 | self.assertEqual(sn_event.name, MockSN.name) 37 | self.assertEqual(sn_event.sn_id, '1') 38 | 39 | def test_02_event_incident_past(self): 40 | self.assertEqual(SnEvent.objects.count(), 0) 41 | 42 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=20) 43 | end = now - datetime.timedelta(minutes=10) 44 | event = self._create_event(now, end, Event.INCIDENT) 45 | 46 | self.assertEqual(SnEvent.objects.count(), 1) 47 | sn_event = SnEvent.objects.get(id=1) 48 | self.assertEqual(sn_event.event, event) 49 | self.assertEqual(sn_event.name, MockSN.name) 50 | self.assertEqual(sn_event.sn_id, '1') 51 | 52 | def test_03_event_incident_futre(self): 53 | self.assertEqual(SnEvent.objects.count(), 0) 54 | 55 | now = tz_now().replace(microsecond=0) + datetime.timedelta(minutes=10) 56 | end = now + datetime.timedelta(minutes=10) 57 | event = self._create_event(now, end, Event.INCIDENT) 58 | 59 | self.assertEqual(SnEvent.objects.count(), 1) 60 | sn_event = SnEvent.objects.get(id=1) 61 | self.assertEqual(sn_event.event, event) 62 | self.assertEqual(sn_event.name, MockSN.name) 63 | self.assertEqual(sn_event.sn_id, '1') 64 | 65 | def test_04_event_maintenace_now(self): 66 | self.assertEqual(SnEvent.objects.count(), 0) 67 | 68 | now = tz_now().replace(microsecond=0) 69 | end = now + datetime.timedelta(minutes=10) 70 | event = self._create_event(now, end, Event.MAINTENANCE) 71 | 72 | self.assertEqual(SnEvent.objects.count(), 1) 73 | sn_event = SnEvent.objects.get(id=1) 74 | self.assertEqual(sn_event.event, event) 75 | self.assertEqual(sn_event.name, MockSN.name) 76 | self.assertEqual(sn_event.sn_id, '1') 77 | 78 | def test_05_event_maintenace_past(self): 79 | self.assertEqual(SnEvent.objects.count(), 0) 80 | 81 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=20) 82 | end = now - datetime.timedelta(minutes=10) 83 | event = self._create_event(now, end, Event.MAINTENANCE) 84 | 85 | self.assertEqual(SnEvent.objects.count(), 1) 86 | sn_event = SnEvent.objects.get(id=1) 87 | self.assertEqual(sn_event.event, event) 88 | self.assertEqual(sn_event.name, MockSN.name) 89 | self.assertEqual(sn_event.sn_id, '1') 90 | 91 | def test_06_event_maintenace_futre(self): 92 | self.assertEqual(SnEvent.objects.count(), 0) 93 | 94 | now = tz_now().replace(microsecond=0) + datetime.timedelta(minutes=10) 95 | end = now + datetime.timedelta(minutes=10) 96 | event = self._create_event(now, end, Event.MAINTENANCE) 97 | 98 | self.assertEqual(SnEvent.objects.count(), 0) 99 | -------------------------------------------------------------------------------- /baobab/backoffice/tests/test_eventlog_trigger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | 4 | from django.utils.timezone import now as tz_now 5 | 6 | from baobab.utils.test import TestCase 7 | from baobab.utils.mock import MockSN 8 | 9 | from baobab.backoffice.models import Event, EventLog 10 | from baobab.socialnetwork.models import EventLog as SnEventLog 11 | 12 | 13 | class TestEventLogTrigger(TestCase): 14 | fixtures = ['db_user', 'db_backoffice'] 15 | 16 | def _create_eventlog(self, now, end, category): 17 | event = Event() 18 | event.date_start = now 19 | event.date_end = end 20 | event.title = 'My Title' 21 | event.summary = '' 22 | event.category = category 23 | event.msg = None # to not trigger the event on_event 24 | event.save() 25 | eventlog = EventLog() 26 | eventlog.user_id = 1 27 | eventlog.event = event 28 | eventlog.comment = '' 29 | eventlog.msg = 'a eventlog msg' 30 | eventlog.save() 31 | return eventlog 32 | 33 | def test_01_eventlog_incident_now(self): 34 | self.assertEqual(SnEventLog.objects.count(), 0) 35 | 36 | now = tz_now().replace(microsecond=0) 37 | end = now + datetime.timedelta(minutes=10) 38 | eventlog = self._create_eventlog(now, end, Event.INCIDENT) 39 | 40 | self.assertEqual(SnEventLog.objects.count(), 1) 41 | sn_eventlog = SnEventLog.objects.get(id=1) 42 | self.assertEqual(sn_eventlog.eventlog, eventlog) 43 | self.assertEqual(sn_eventlog.name, MockSN.name) 44 | self.assertEqual(sn_eventlog.sn_id, '1') 45 | 46 | def test_02_eventlog_incident_past(self): 47 | self.assertEqual(SnEventLog.objects.count(), 0) 48 | 49 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=20) 50 | end = now - datetime.timedelta(minutes=10) 51 | eventlog = self._create_eventlog(now, end, Event.INCIDENT) 52 | 53 | self.assertEqual(SnEventLog.objects.count(), 1) 54 | sn_eventlog = SnEventLog.objects.get(id=1) 55 | self.assertEqual(sn_eventlog.eventlog, eventlog) 56 | self.assertEqual(sn_eventlog.name, MockSN.name) 57 | self.assertEqual(sn_eventlog.sn_id, '1') 58 | 59 | def test_03_eventlog_incident_futre(self): 60 | self.assertEqual(SnEventLog.objects.count(), 0) 61 | 62 | now = tz_now().replace(microsecond=0) + datetime.timedelta(minutes=10) 63 | end = now + datetime.timedelta(minutes=10) 64 | eventlog = self._create_eventlog(now, end, Event.INCIDENT) 65 | 66 | self.assertEqual(SnEventLog.objects.count(), 1) 67 | sn_eventlog = SnEventLog.objects.get(id=1) 68 | self.assertEqual(sn_eventlog.eventlog, eventlog) 69 | self.assertEqual(sn_eventlog.name, MockSN.name) 70 | self.assertEqual(sn_eventlog.sn_id, '1') 71 | 72 | def test_04_eventlog_maintenace_now(self): 73 | self.assertEqual(SnEventLog.objects.count(), 0) 74 | 75 | now = tz_now().replace(microsecond=0) 76 | end = now + datetime.timedelta(minutes=10) 77 | eventlog = self._create_eventlog(now, end, Event.MAINTENANCE) 78 | 79 | self.assertEqual(SnEventLog.objects.count(), 1) 80 | sn_eventlog = SnEventLog.objects.get(id=1) 81 | self.assertEqual(sn_eventlog.eventlog, eventlog) 82 | self.assertEqual(sn_eventlog.name, MockSN.name) 83 | self.assertEqual(sn_eventlog.sn_id, '1') 84 | 85 | def test_05_eventlog_maintenace_past(self): 86 | self.assertEqual(SnEventLog.objects.count(), 0) 87 | 88 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=20) 89 | end = now - datetime.timedelta(minutes=10) 90 | eventlog = self._create_eventlog(now, end, Event.MAINTENANCE) 91 | 92 | self.assertEqual(SnEventLog.objects.count(), 1) 93 | sn_eventlog = SnEventLog.objects.get(id=1) 94 | self.assertEqual(sn_eventlog.eventlog, eventlog) 95 | self.assertEqual(sn_eventlog.name, MockSN.name) 96 | self.assertEqual(sn_eventlog.sn_id, '1') 97 | 98 | def test_06_eventlog_maintenace_futre(self): 99 | self.assertEqual(SnEventLog.objects.count(), 0) 100 | 101 | now = tz_now().replace(microsecond=0) + datetime.timedelta(minutes=10) 102 | end = now + datetime.timedelta(minutes=10) 103 | eventlog = self._create_eventlog(now, end, Event.MAINTENANCE) 104 | 105 | self.assertEqual(SnEventLog.objects.count(), 0) 106 | -------------------------------------------------------------------------------- /baobab/backoffice/tests/test_status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | 4 | from django.utils.timezone import now 5 | 6 | from baobab.utils.test import TestCase 7 | from baobab.backoffice.models import Event, Service 8 | 9 | 10 | class TestStatus(TestCase): 11 | fixtures = ['db_user', 'db_backoffice'] 12 | 13 | def _create_event(self, duration, services, category): 14 | event = Event() 15 | event.date_start = now() 16 | event.duration = duration 17 | event.title = 'My Title: %d' % duration 18 | event.summary = '' 19 | event.category = category 20 | event.save() 21 | event.services.add(*services) 22 | 23 | def _end_event(event=event): 24 | event.date_end = now() 25 | event.save() 26 | 27 | event.end = _end_event 28 | return event 29 | 30 | def _check_status(self, cloudy=[], stormy=[]): 31 | nb_cloudy = 0 32 | nb_stormy = 0 33 | for service in Service.objects.all(): 34 | if service.id in cloudy: 35 | self.assertEqual(service.status, Service.CLOUDY) 36 | self.assertEqual(service.get_status_display(), 'CLOUDY') 37 | nb_cloudy += 1 38 | elif service.id in stormy: 39 | self.assertEqual(service.status, Service.STORMY) 40 | self.assertEqual(service.get_status_display(), 'STORMY') 41 | nb_stormy += 1 42 | else: 43 | self.assertEqual(service.status, Service.SUNNY) 44 | self.assertEqual(service.get_status_display(), 'SUNNY') 45 | self.assertEqual(nb_cloudy, len(cloudy)) 46 | self.assertEqual(nb_stormy, len(stormy)) 47 | 48 | def test_01_SUNNY(self): 49 | for service in Service.objects.all(): 50 | self.assertEqual(service.status, Service.SUNNY) 51 | self.assertEqual(service.get_status_display(), 'SUNNY') 52 | 53 | def test_02_CLOUDY_1_service(self): 54 | cloudy = [1, ] 55 | event = self._create_event(10, cloudy, Event.MAINTENANCE) 56 | self._check_status(cloudy=cloudy) 57 | event.end() 58 | self._check_status([]) 59 | 60 | def test_03_CLOUDY_2_service(self): 61 | cloudy = [1, 3] 62 | event = self._create_event(10, cloudy, Event.MAINTENANCE) 63 | self._check_status(cloudy=cloudy) 64 | event.end() 65 | self._check_status([]) 66 | 67 | def test_04_STORMY_1_service(self): 68 | stormy = [1, ] 69 | event = self._create_event(10, stormy, Event.INCIDENT) 70 | self._check_status(stormy=stormy) 71 | event.end() 72 | self._check_status([]) 73 | 74 | def test_05_STORMY_2_service(self): 75 | stormy = [1, 3] 76 | event = self._create_event(10, stormy, Event.INCIDENT) 77 | self._check_status(stormy=stormy) 78 | event.end() 79 | self._check_status([]) 80 | 81 | def test_06_KO_2_event(self): 82 | cloudy = [1] 83 | stormy = [3] 84 | cloudy_event = self._create_event(60, cloudy, Event.MAINTENANCE) 85 | stormy_event = self._create_event(20, stormy, Event.INCIDENT) 86 | self._check_status(cloudy=cloudy, stormy=stormy) 87 | stormy_event.end() 88 | self._check_status(cloudy=cloudy) 89 | cloudy_event.end() 90 | self._check_status() 91 | 92 | def test_07_KO_2_event_same_service(self): 93 | cloudy = [1] 94 | stormy = [1] 95 | cloudy_event = self._create_event(60, cloudy, Event.MAINTENANCE) 96 | stormy_event = self._create_event(20, stormy, Event.INCIDENT) 97 | self._check_status(stormy=stormy) # stormy override stormy 98 | stormy_event.end() 99 | self._check_status(cloudy=cloudy) 100 | cloudy_event.end() 101 | self._check_status() 102 | 103 | def test_08_KO_2_event_2_service(self): 104 | cloudy = [1, 3, 4, 5] 105 | event1 = self._create_event(60, cloudy[0:2], Event.MAINTENANCE) 106 | event2 = self._create_event(20, cloudy[2:], Event.MAINTENANCE) 107 | self._check_status(cloudy) 108 | event2.end() 109 | self._check_status(cloudy[0:2]) 110 | event1.end() 111 | self._check_status([]) 112 | 113 | def test_09_estimate_date_end_in_the_past(self): 114 | cloudy = [1, 3, 4, 5] 115 | event = self._create_event(20, cloudy, Event.MAINTENANCE) 116 | event.date_start = now() + datetime.timedelta(minutes=-60) 117 | event.save() 118 | self._check_status(cloudy) 119 | event.end() 120 | self._check_status([]) 121 | -------------------------------------------------------------------------------- /baobab/bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/bin/__init__.py -------------------------------------------------------------------------------- /baobab/bin/cmd_baobab.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Like the manage.py in django's project with added/overridden command 5 | 6 | TODO all 'command' in this file should be move to a real django's command 7 | """ 8 | 9 | import os 10 | import sys 11 | import pytz 12 | 13 | from django.core.management import execute_from_command_line, call_command 14 | from django.conf import settings 15 | 16 | from baobab.utils.handle_south import HandleSouth 17 | 18 | 19 | def default_user(): 20 | from django.contrib.auth.models import User 21 | from baobab.backoffice.models import TimeZoneEnum 22 | argv = sys.argv 23 | 24 | if not User.objects.exists(): 25 | 26 | if not TimeZoneEnum.objects.exists(): 27 | print 'Creating the TimeZoneEnum ...' 28 | for timezone in pytz.common_timezones: 29 | TimeZoneEnum.objects.create(name=timezone) 30 | 31 | login = getattr(settings, 'DEFAULT_USER_LOGIN', None) 32 | pwd = getattr(settings, 'DEFAULT_USER_PWD', None) 33 | mail = getattr(settings, 'DEFAULT_USER_MAIL', None) 34 | if not mail: 35 | mail = '%s@gandi.net' % login 36 | if login and pwd: 37 | execute_from_command_line([argv[0], 38 | 'createsuperuser', 39 | '--username=%s' % login, 40 | '--email=%s' % mail, 41 | '--noinput']) 42 | user = User.objects.get(username=login) 43 | user.set_password(pwd) 44 | user.save() 45 | else: 46 | create_user = '' 47 | while create_user not in ['y', 'n']: 48 | create_user = raw_input('Do you want to create ' 49 | 'a user: y/n? ') 50 | print create_user 51 | if create_user == 'y': 52 | execute_from_command_line([argv[0], 'createsuperuser']) 53 | 54 | 55 | def main(): 56 | argv = sys.argv 57 | 58 | # to set DJANGO_SETTINGS_MODULE which is mandatory to use django 59 | for idx in range(len(sys.argv)): 60 | if sys.argv[idx].startswith('--settings'): 61 | setting = sys.argv.pop(idx) 62 | if '=' in setting: 63 | setting = setting.split('=', 1)[1] 64 | else: 65 | setting = sys.argv.pop(idx) 66 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', setting) 67 | break 68 | if 'DJANGO_SETTINGS_MODULE' not in os.environ: 69 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'baobab.settings') 70 | 71 | # XXX fixe me need the settings to be configure 72 | from baobab.cron import exec_cron 73 | 74 | if len(argv) < 2: 75 | execute_from_command_line(argv) 76 | sys.exit(0) 77 | 78 | if argv[1] == 'syncdb': 79 | # will create the db, run all migration, add a default user 80 | execute_from_command_line([argv[0], 81 | 'syncdb', 82 | '--migrate', 83 | '--noinput']) 84 | default_user() 85 | elif argv[1] == 'setup-dev': 86 | HandleSouth.disable() 87 | fixtures = [] 88 | for rep in settings.FIXTURE_DIRS: 89 | tmp = map(lambda x: x.split('.', 1)[0], os.listdir(rep)) 90 | fixtures.extend(filter(lambda x: x.startswith('db_'), tmp)) 91 | call_command('syncdb', interactive=False) 92 | default_user() 93 | print 'loading fixtures ...' 94 | call_command('loaddata', *fixtures, verbosity=0) 95 | elif argv[1] == 'test': 96 | apps = [name.split('.', 1)[1] for name in settings.INSTALLED_APPS 97 | if name.startswith('baobab.')] 98 | if not [arg for arg in sys.argv if arg.split('.')[0] in apps]: 99 | sys.argv.extend(apps) 100 | execute_from_command_line(sys.argv) 101 | elif len(argv) > 2 and argv[1] == 'migrate' and argv[2] == 'fixtures': 102 | sys.argv.append('test') 103 | execute_from_command_line([argv[0], 'migrate_fixtures']) 104 | elif not exec_cron(argv[1]): 105 | execute_from_command_line(argv) 106 | 107 | 108 | if __name__ == '__main__': 109 | main() 110 | -------------------------------------------------------------------------------- /baobab/conf/default.py: -------------------------------------------------------------------------------- 1 | """ 2 | use my local db not the dev one 3 | """ 4 | 5 | from os.path import abspath, dirname 6 | import sys 7 | 8 | HERE = dirname(abspath(__file__)) 9 | 10 | 11 | if 'test' not in sys.argv: 12 | DATABASES = { 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', 15 | 'NAME': '%s/default.db' % dirname(HERE), 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /baobab/cron/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | To create a cron you HAVE TO create a function call `cron_name_of_my_cron` 4 | and import it here. 5 | It will be directly available with the following command: 6 | $ baoaba name_of_my_cron 7 | 8 | XXX 9 | 10 | This should not be done like that but use to tools provided by Django: 11 | - create a module: app_name/management/commands/cron_name.py 12 | - create a class that sub-class BaseCommand 13 | - implement the `handle(self, *args, **options)` 14 | 15 | Pros: 16 | - have option to your command 17 | - the help command will list it 18 | - have it's own help 19 | 20 | Cons: 21 | - None (???) 22 | """ 23 | 24 | from .socialnetwork import cron_social_network 25 | from .close_event import cron_close_event 26 | 27 | 28 | def exec_cron(cron_name): 29 | cron = globals().get('cron_%s' % cron_name) 30 | if cron and callable(cron): 31 | cron() 32 | return True 33 | return False 34 | -------------------------------------------------------------------------------- /baobab/cron/close_event.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Close an Event with no date_end set and the duration has passed 4 | """ 5 | 6 | from datetime import datetime 7 | import pytz 8 | 9 | from baobab.backoffice.models import Event 10 | 11 | 12 | def cron_close_event(): 13 | now = datetime.now(pytz.timezone('UTC')) 14 | filters = { 15 | 'date_start__lte': now, 16 | 'estimate_date_end__lte': now, 17 | 'date_end__isnull': True, 18 | } 19 | for event in Event.objects.filter(**filters): 20 | event.date_end = event.estimate_date_end 21 | event.save() 22 | print 'End Event id: %i' % event.id 23 | -------------------------------------------------------------------------------- /baobab/cron/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mandatory to be able to do test 4 | """ 5 | -------------------------------------------------------------------------------- /baobab/cron/socialnetwork.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | to send the Event or EventLog to each define social network 4 | """ 5 | 6 | from datetime import datetime, timedelta 7 | import pytz 8 | 9 | from django.db.models import Q 10 | 11 | from baobab.backoffice.models import Event, EventLog 12 | from baobab.socialnetwork import SocialNetworks 13 | 14 | 15 | def cron_social_network(): 16 | now = datetime.now(pytz.timezone('UTC')) 17 | sn = SocialNetworks() 18 | 19 | filters = Q(date_start__lte=now) & ( 20 | Q(date_end__gte=now) | Q(date_end__isnull=True) 21 | ) 22 | for event in Event.objects.filter(filters): 23 | sn.publish(event) 24 | 25 | deltat = now - timedelta(minutes=5) 26 | filters = Q(date__lte=now) & ( 27 | Q(event__date_end__gte=deltat) | Q(event__date_end__isnull=True) 28 | ) 29 | for eventlog in EventLog.objects.filter(filters): 30 | sn.publish(eventlog) 31 | -------------------------------------------------------------------------------- /baobab/cron/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .test_sn import TestSN 4 | -------------------------------------------------------------------------------- /baobab/cron/tests/test_sn.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | 4 | from django.utils.timezone import now as tz_now 5 | 6 | from baobab.utils.test import TestCase 7 | from baobab.backoffice.models import Event, EventLog 8 | from baobab.socialnetwork.models import (Event as SnEvent, 9 | EventLog as SnEventLog) 10 | from baobab.utils.mock import MockSN 11 | 12 | from baobab.cron.socialnetwork import cron_social_network 13 | 14 | 15 | class TestSN(TestCase): 16 | fixtures = ['db_user', 'db_backoffice'] 17 | 18 | def _create_event(self, now, end=None): 19 | event = Event() 20 | event.date_start = now 21 | if not end: 22 | event.duration = 42 23 | event.date_end = end 24 | event.title = 'My Title' 25 | event.summary = '' 26 | event.category = Event.INCIDENT 27 | event.msg = 'a msg to publish' 28 | event.save() 29 | SnEvent.objects.all().delete() 30 | return event 31 | 32 | def test_01_event_not_started_with_end(self): 33 | now = tz_now().replace(microsecond=0) + datetime.timedelta(minutes=10) 34 | end = now + datetime.timedelta(minutes=10) 35 | event = self._create_event(now, end) 36 | cron_social_network() 37 | self.assertEqual(SnEvent.objects.count(), 0) 38 | 39 | def test_02_event_not_started_without_end(self): 40 | now = tz_now().replace(microsecond=0) + datetime.timedelta(minutes=10) 41 | event = self._create_event(now) 42 | cron_social_network() 43 | self.assertEqual(SnEvent.objects.count(), 0) 44 | 45 | def test_03_event_started_with_end(self): 46 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=10) 47 | end = now + datetime.timedelta(minutes=20) 48 | event = self._create_event(now, end) 49 | cron_social_network() 50 | self.assertEqual(SnEvent.objects.count(), 1) 51 | sn_event = SnEvent.objects.get(id=1) 52 | self.assertEqual(sn_event.event, event) 53 | self.assertEqual(sn_event.name, MockSN.name) 54 | self.assertEqual(sn_event.sn_id, '1') 55 | 56 | def test_04_event_started_without_end(self): 57 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=10) 58 | event = self._create_event(now) 59 | cron_social_network() 60 | self.assertEqual(SnEvent.objects.count(), 1) 61 | sn_event = SnEvent.objects.get(id=1) 62 | self.assertEqual(sn_event.event, event) 63 | self.assertEqual(sn_event.name, MockSN.name) 64 | self.assertEqual(sn_event.sn_id, '1') 65 | 66 | def test_05_event_finished(self): 67 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=20) 68 | end = now + datetime.timedelta(minutes=10) 69 | event = self._create_event(now, end) 70 | 71 | cron_social_network() 72 | self.assertEqual(SnEvent.objects.count(), 0) 73 | 74 | def _create_eventlog(self, now, end): 75 | eventlog = EventLog() 76 | eventlog.user_id = 1 77 | eventlog.event = self._create_event(now, end) 78 | eventlog.msg = 'My Title' 79 | eventlog.save() 80 | SnEventLog.objects.all().delete() 81 | return eventlog 82 | 83 | def test_06_eventlog_default(self): 84 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=10) 85 | end = now + datetime.timedelta(minutes=20) 86 | eventlog = self._create_eventlog(now, end) 87 | 88 | cron_social_network() 89 | self.assertEqual(SnEventLog.objects.count(), 1) 90 | sn_eventlog = SnEventLog.objects.get(id=1) 91 | self.assertEqual(sn_eventlog.eventlog, eventlog) 92 | self.assertEqual(sn_eventlog.name, MockSN.name) 93 | self.assertEqual(sn_eventlog.sn_id, '1') 94 | 95 | def test_07_eventlog_future(self): 96 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=10) 97 | end = now + datetime.timedelta(minutes=20) 98 | eventlog = self._create_eventlog(now, end) 99 | 100 | eventlog.date = end 101 | eventlog.save() 102 | SnEventLog.objects.all().delete() 103 | 104 | cron_social_network() 105 | self.assertEqual(SnEventLog.objects.count(), 0) 106 | 107 | def test_08_eventlog_finished(self): 108 | now = tz_now().replace(microsecond=0) - datetime.timedelta(minutes=20) 109 | end = now + datetime.timedelta(minutes=10) 110 | eventlog = self._create_eventlog(now, end) 111 | 112 | cron_social_network() 113 | self.assertEqual(SnEventLog.objects.count(), 0) 114 | -------------------------------------------------------------------------------- /baobab/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/fixtures/__init__.py -------------------------------------------------------------------------------- /baobab/fixtures/db_south.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "south.migrationhistory", 5 | "fields": { 6 | "applied": "2015-03-26T17:14:21.192Z", 7 | "app_name": "backoffice", 8 | "migration": "0001_initial" 9 | } 10 | }, 11 | { 12 | "pk": 2, 13 | "model": "south.migrationhistory", 14 | "fields": { 15 | "applied": "2015-03-26T17:14:21.298Z", 16 | "app_name": "backoffice", 17 | "migration": "0002_create_services_and_set_timezone_enm" 18 | } 19 | }, 20 | { 21 | "pk": 3, 22 | "model": "south.migrationhistory", 23 | "fields": { 24 | "applied": "2015-03-26T17:14:21.336Z", 25 | "app_name": "backoffice", 26 | "migration": "0003_auto__chg_field_event_title" 27 | } 28 | }, 29 | { 30 | "pk": 4, 31 | "model": "south.migrationhistory", 32 | "fields": { 33 | "applied": "2015-03-26T17:14:21.476Z", 34 | "app_name": "backoffice", 35 | "migration": "0004_auto__add_field_eventlog_msg_id__add_field_eventlog_msg__add_field_eve" 36 | } 37 | }, 38 | { 39 | "pk": 5, 40 | "model": "south.migrationhistory", 41 | "fields": { 42 | "applied": "2015-03-26T17:14:21.516Z", 43 | "app_name": "backoffice", 44 | "migration": "0005_auto__chg_field_event_msg" 45 | } 46 | }, 47 | { 48 | "pk": 6, 49 | "model": "south.migrationhistory", 50 | "fields": { 51 | "applied": "2015-03-26T17:14:21.600Z", 52 | "app_name": "backoffice", 53 | "migration": "0006_auto__chg_field_eventlog_msg_id__chg_field_event_msg_id" 54 | } 55 | }, 56 | { 57 | "pk": 7, 58 | "model": "south.migrationhistory", 59 | "fields": { 60 | "applied": "2015-03-26T17:14:21.760Z", 61 | "app_name": "translate", 62 | "migration": "0001_initial" 63 | } 64 | }, 65 | { 66 | "pk": 8, 67 | "model": "south.migrationhistory", 68 | "fields": { 69 | "applied": "2015-03-26T17:14:21.782Z", 70 | "app_name": "translate", 71 | "migration": "0002_set_traduction" 72 | } 73 | }, 74 | { 75 | "pk": 9, 76 | "model": "south.migrationhistory", 77 | "fields": { 78 | "applied": "2015-04-22T14:37:39.126Z", 79 | "app_name": "translate", 80 | "migration": "0001_initial" 81 | } 82 | }, 83 | { 84 | "pk": 10, 85 | "model": "south.migrationhistory", 86 | "fields": { 87 | "applied": "2015-04-22T14:37:39.189Z", 88 | "app_name": "translate", 89 | "migration": "0002_set_traduction" 90 | } 91 | }, 92 | { 93 | "pk": 11, 94 | "model": "south.migrationhistory", 95 | "fields": { 96 | "applied": "2015-04-23T11:39:08.262Z", 97 | "app_name": "backoffice", 98 | "migration": "0007_auto__add_field_event_last_update" 99 | } 100 | }, 101 | { 102 | "pk": 12, 103 | "model": "south.migrationhistory", 104 | "fields": { 105 | "applied": "2015-04-23T11:39:08.419Z", 106 | "app_name": "backoffice", 107 | "migration": "0008_set_last_update" 108 | } 109 | }, 110 | { 111 | "pk": 13, 112 | "model": "south.migrationhistory", 113 | "fields": { 114 | "applied": "2015-09-07T11:39:08.419Z", 115 | "app_name": "socialnetwork", 116 | "migration": "0001_initial" 117 | } 118 | }, 119 | { 120 | "pk": 14, 121 | "model": "south.migrationhistory", 122 | "fields": { 123 | "applied": "2015-09-07T09:24:47.875Z", 124 | "app_name": "backoffice", 125 | "migration": "0009_move_twitter_to_socialnetwork" 126 | } 127 | }, 128 | { 129 | "pk": 15, 130 | "model": "south.migrationhistory", 131 | "fields": { 132 | "applied": "2015-09-07T09:24:47.890Z", 133 | "app_name": "backoffice", 134 | "migration": "0010_auto__del_field_eventlog_msg_id__del_field_event_msg_id" 135 | } 136 | } 137 | ] 138 | -------------------------------------------------------------------------------- /baobab/fixtures/db_user.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "auth.user", 5 | "fields": { 6 | "username": "gandi", 7 | "first_name": "", 8 | "last_name": "", 9 | "is_active": true, 10 | "is_superuser": true, 11 | "is_staff": true, 12 | "last_login": "2014-04-03T15:52:14.498Z", 13 | "groups": [], 14 | "user_permissions": [], 15 | "password": "pbkdf2_sha256$10000$OkkpRZg793ab$5PRp94y15u9jJLxnDZN7xWrr+mVISTyaFZYjRj3vHbE=", 16 | "email": "devnull@gandi.net", 17 | "date_joined": "2014-04-03T15:52:03.596Z" 18 | } 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /baobab/front/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/front/__init__.py -------------------------------------------------------------------------------- /baobab/front/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /baobab/front/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from baobab.utils.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | 13 | def test_basic_addition(self): 14 | """ 15 | Tests that 1 + 1 always equals 2. 16 | """ 17 | self.assertEqual(1 + 1, 2) 18 | -------------------------------------------------------------------------------- /baobab/front/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.shortcuts import render 3 | from django.views.decorators.gzip import gzip_page 4 | from django.views.decorators.cache import cache_page 5 | from django.views.decorators.vary import vary_on_headers 6 | 7 | 8 | @gzip_page 9 | @cache_page(3600) 10 | @vary_on_headers('Accept-Encoding') 11 | def index(request): 12 | resp = render(request, 'index.html') 13 | resp['X-UA-Compatible'] = 'IE=edge,chrome=1' 14 | 15 | return resp 16 | -------------------------------------------------------------------------------- /baobab/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | from os.path import abspath, dirname 5 | import sys 6 | 7 | # Add the parent directory of 'manage.py' to the python path, so manage.py can 8 | # be run from any directory. From http://www.djangosnippets.org/snippets/281/ 9 | sys.path.insert(0, dirname(dirname(abspath(__file__)))) 10 | 11 | if __name__ == '__main__': 12 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'baobab.settings') 13 | 14 | from django.core.management import execute_from_command_line 15 | 16 | execute_from_command_line(sys.argv) 17 | -------------------------------------------------------------------------------- /baobab/middleware.py: -------------------------------------------------------------------------------- 1 | from urlparse import urlparse 2 | import re 3 | 4 | from django.http import HttpResponse 5 | 6 | ACCESS_CONTROL_ALLOW_ORIGIN = 'Access-Control-Allow-Origin' 7 | ACCESS_CONTROL_EXPOSE_HEADERS = 'Access-Control-Expose-Headers' 8 | ACCESS_CONTROL_ALLOW_CREDENTIALS = 'Access-Control-Allow-Credentials' 9 | ACCESS_CONTROL_ALLOW_HEADERS = 'Access-Control-Allow-Headers' 10 | ACCESS_CONTROL_ALLOW_METHODS = 'Access-Control-Allow-Methods' 11 | ACCESS_CONTROL_MAX_AGE = 'Access-Control-Max-Age' 12 | 13 | CORS_URLS_REGEX = r'^/api/.*$' 14 | CORS_ALLOW_METHODS = ['GET'] 15 | 16 | 17 | class CorsMiddleware(object): 18 | 19 | def process_request(self, request): 20 | ''' 21 | If CORS preflight header, then create an empty body response (200 OK) and return it 22 | 23 | Django won't bother calling any other request view/exception middleware along with 24 | the requested view; it will call any response middlewares 25 | ''' 26 | if (request.method == 'OPTIONS' and 27 | 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META): 28 | response = HttpResponse() 29 | return response 30 | return None 31 | 32 | def process_response(self, request, response): 33 | ''' 34 | Add the respective CORS headers 35 | ''' 36 | origin = request.META.get('HTTP_ORIGIN') 37 | if origin: 38 | # todo: check hostname from db instead 39 | url = urlparse(origin) 40 | 41 | if not re.match(CORS_URLS_REGEX, request.path): 42 | return response 43 | 44 | response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*" 45 | response[ACCESS_CONTROL_ALLOW_METHODS] = ', '.join( 46 | CORS_ALLOW_METHODS) 47 | 48 | return response 49 | -------------------------------------------------------------------------------- /baobab/rss/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /baobab/rss/feed.py: -------------------------------------------------------------------------------- 1 | from django.contrib.syndication.views import Feed 2 | from django.utils.feedgenerator import Atom1Feed 3 | from baobab.backoffice.models import Event, EventLog 4 | from django.utils.timezone import now 5 | import markdown 6 | 7 | 8 | class RssStatusFeed(Feed): 9 | title = 'Gandi.net Status RSS Feed' 10 | link = '/rss/' 11 | description = 'Get information about Gandi platform status' 12 | 13 | def items(self): 14 | return Event.objects.order_by('-last_update')[:50] 15 | 16 | def item_title(self, item): 17 | title = item.title 18 | nb_logs = item.eventlogs.count() 19 | state = None 20 | if item.category == item.MAINTENANCE and item.date_start < now(): 21 | if not item.date_end or item.date_end > now(): 22 | state = 'STARTED' 23 | if nb_logs > 0: 24 | state = 'UPDATE %d' % nb_logs 25 | if item.date_end and item.date_end <= now(): 26 | state = 'FINISHED' 27 | if state: 28 | title = '[%s] %s' % (state, title) 29 | return title 30 | 31 | def item_description(self, item): 32 | if item.services.exists(): 33 | services = [] 34 | for service in item.services.all(): 35 | services.append(service.get_name_display()) 36 | services = ', '.join(service for service in services) 37 | else: 38 | services = 'No service impacted' 39 | description = '**Type : %s**
' % item.get_category_display() 40 | description += '**Services : %s**
' % services 41 | description += '**Last update : %s**
' % str( 42 | item.last_update.strftime("%Y-%m-%d %X %z")) 43 | description += '**Date start : %s**
' % (item.date_start) 44 | if item.date_end: 45 | description += '**Date end : %s**
' % (item.date_end) 46 | elif item.estimate_date_end: 47 | description += '**Estimated date end : %s**
' % ( 48 | item.estimate_date_end) 49 | if item.eventlogs.exists(): 50 | for eventlog in item.eventlogs.all(): 51 | description += '

Update on %s :
' % str( 52 | eventlog.date) 53 | description += markdown.markdown(eventlog.comment) 54 | description += '


' 55 | description += markdown.markdown('#### Initial message') 56 | description += markdown.markdown( 57 | item.eventdatas.get(lang__iso='en').summary) 58 | return markdown.markdown(description) 59 | 60 | def item_pubdate(self, item): 61 | return item.last_update 62 | 63 | def item_link(self, item): 64 | event_id = item.id 65 | nb_logs = item.eventlogs.count() 66 | if nb_logs > 0: 67 | # Add a GET params unused on frontend side due to RSS client cache 68 | last_item = item.eventlogs.all()[0] 69 | # Ugly hack to update the link if the date_end only is updated 70 | if item.date_end: 71 | if last_item.date < item.date_end: 72 | last_item.id += 1 73 | event_id = "%d?evnt_id=%d" % (event_id, last_item.id) 74 | elif item.date_end: 75 | event_id = "%d?evnt_id=1" % event_id 76 | return '/timeline/events/%s' % event_id 77 | 78 | 79 | class AtomStatusFeed(RssStatusFeed): 80 | feed_type = Atom1Feed 81 | title = 'Gandi.net Status Atom Feed' 82 | link = '/atom/' 83 | subtitle = RssStatusFeed.description 84 | -------------------------------------------------------------------------------- /baobab/rss/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mandatory to be able to do test 4 | """ 5 | -------------------------------------------------------------------------------- /baobab/rss/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | django only look for test in app_name.tests.py or app_name.tests/__init__.py 5 | add the tests classes here to make them visible to django 6 | """ 7 | 8 | from .test_rss import TestRss 9 | -------------------------------------------------------------------------------- /baobab/socialnetwork/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | based module to handle all social network 4 | """ 5 | 6 | from .base import SocialNetworks 7 | from .twitter import Twitter 8 | from .irc import IRC 9 | -------------------------------------------------------------------------------- /baobab/socialnetwork/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/socialnetwork/management/__init__.py -------------------------------------------------------------------------------- /baobab/socialnetwork/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/socialnetwork/management/commands/__init__.py -------------------------------------------------------------------------------- /baobab/socialnetwork/management/commands/ircd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | daemon irc 4 | """ 5 | 6 | from django.core.management.base import BaseCommand 7 | 8 | from baobab.socialnetwork.irc import IRCDaemon 9 | 10 | 11 | class Command(BaseCommand): 12 | 13 | help = "A IRC daemon" 14 | 15 | def handle(self, *args, **options): 16 | irc = IRCDaemon() 17 | irc.run() 18 | -------------------------------------------------------------------------------- /baobab/socialnetwork/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/socialnetwork/migrations/__init__.py -------------------------------------------------------------------------------- /baobab/socialnetwork/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Define the database model to know if a given Event/Eventlog 4 | has been sent to a given social network 5 | """ 6 | 7 | from django.db import models 8 | 9 | 10 | class Event(models.Model): 11 | 12 | event = models.ForeignKey('backoffice.Event') 13 | name = models.CharField(max_length=10) 14 | sn_id = models.CharField(max_length=30) 15 | 16 | class Meta: 17 | unique_together = ('event', 'name') 18 | 19 | 20 | class EventLog(models.Model): 21 | 22 | eventlog = models.ForeignKey('backoffice.EventLog') 23 | name = models.CharField(max_length=10) 24 | sn_id = models.CharField(max_length=30) 25 | 26 | class Meta: 27 | unique_together = ('eventlog', 'name') 28 | -------------------------------------------------------------------------------- /baobab/socialnetwork/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | django only look for test in app_name.tests.py or app_name.tests/__init__.py 5 | add the tests classes here to make them visible to django 6 | """ 7 | 8 | from .test_socialnetwork import TestSocialNetwork 9 | -------------------------------------------------------------------------------- /baobab/static/.gitignore: -------------------------------------------------------------------------------- 1 | assets 2 | images 3 | main*.css 4 | /js 5 | 6 | *.gz 7 | rev-manifest.json 8 | -------------------------------------------------------------------------------- /baobab/static/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | } 25 | 26 | ul.actionlist li.changelink { 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | -o-text-overflow: ellipsis; 30 | } -------------------------------------------------------------------------------- /baobab/static/admin/css/ie.css: -------------------------------------------------------------------------------- 1 | /* IE 6 & 7 */ 2 | 3 | /* Proper fixed width for dashboard in IE6 */ 4 | 5 | .dashboard #content { 6 | *width: 768px; 7 | } 8 | 9 | .dashboard #content-main { 10 | *width: 535px; 11 | } 12 | 13 | /* IE 6 ONLY */ 14 | 15 | /* Keep header from flowing off the page */ 16 | 17 | #container { 18 | _position: static; 19 | } 20 | 21 | /* Put the right sidebars back on the page */ 22 | 23 | .colMS #content-related { 24 | _margin-right: 0; 25 | _margin-left: 10px; 26 | _position: static; 27 | } 28 | 29 | /* Put the left sidebars back on the page */ 30 | 31 | .colSM #content-related { 32 | _margin-right: 10px; 33 | _margin-left: -115px; 34 | _position: static; 35 | } 36 | 37 | .form-row { 38 | _height: 1%; 39 | } 40 | 41 | /* Fix right margin for changelist filters in IE6 */ 42 | 43 | #changelist-filter ul { 44 | _margin-right: -10px; 45 | } 46 | 47 | /* IE ignores min-height, but treats height as if it were min-height */ 48 | 49 | .change-list .filtered { 50 | _height: 400px; 51 | } 52 | 53 | /* IE doesn't know alpha transparency in PNGs */ 54 | 55 | .inline-deletelink { 56 | background: transparent url(../img/inline-delete-8bit.png) no-repeat; 57 | } 58 | 59 | /* IE7 doesn't support inline-block */ 60 | .change-list ul.toplinks li { 61 | zoom: 1; 62 | *display: inline; 63 | } -------------------------------------------------------------------------------- /baobab/static/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #eee; 5 | } 6 | 7 | .login #container { 8 | background: white; 9 | border: 1px solid #ccc; 10 | width: 28em; 11 | min-width: 300px; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-top: 100px; 15 | } 16 | 17 | .login #content-main { 18 | width: 100%; 19 | } 20 | 21 | .login form { 22 | margin-top: 1em; 23 | } 24 | 25 | .login .form-row { 26 | padding: 4px 0; 27 | float: left; 28 | width: 100%; 29 | } 30 | 31 | .login .form-row label { 32 | float: left; 33 | width: 9em; 34 | padding-right: 0.5em; 35 | line-height: 2em; 36 | text-align: right; 37 | font-size: 1em; 38 | color: #333; 39 | } 40 | 41 | .login .form-row #id_username, .login .form-row #id_password { 42 | width: 14em; 43 | } 44 | 45 | .login span.help { 46 | font-size: 10px; 47 | display: block; 48 | } 49 | 50 | .login .submit-row { 51 | clear: both; 52 | padding: 1em 0 0 9.4em; 53 | } 54 | 55 | .login .password-reset-link { 56 | text-align: center; 57 | } 58 | -------------------------------------------------------------------------------- /baobab/static/admin/img/changelist-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/changelist-bg.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/changelist-bg_rtl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/changelist-bg_rtl.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/chooser-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/chooser-bg.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/chooser_stacked-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/chooser_stacked-bg.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/default-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/default-bg-reverse.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/default-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/default-bg.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/deleted-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/deleted-overlay.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/gis/move_vertex_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/gis/move_vertex_off.png -------------------------------------------------------------------------------- /baobab/static/admin/img/gis/move_vertex_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/gis/move_vertex_on.png -------------------------------------------------------------------------------- /baobab/static/admin/img/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon-no.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon-unknown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon-unknown.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon-yes.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon_addlink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon_addlink.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon_alert.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon_calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon_calendar.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon_changelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon_changelink.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon_clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon_clock.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon_deletelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon_deletelink.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon_error.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/icon_searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon_searchbox.png -------------------------------------------------------------------------------- /baobab/static/admin/img/icon_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/icon_success.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/inline-delete-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/inline-delete-8bit.png -------------------------------------------------------------------------------- /baobab/static/admin/img/inline-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/inline-delete.png -------------------------------------------------------------------------------- /baobab/static/admin/img/inline-restore-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/inline-restore-8bit.png -------------------------------------------------------------------------------- /baobab/static/admin/img/inline-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/inline-restore.png -------------------------------------------------------------------------------- /baobab/static/admin/img/inline-splitter-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/inline-splitter-bg.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/nav-bg-grabber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/nav-bg-grabber.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/nav-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/nav-bg-reverse.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/nav-bg-selected.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/nav-bg-selected.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/nav-bg.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/selector-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/selector-icons.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/selector-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/selector-search.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/sorting-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/sorting-icons.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/tool-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/tool-left.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/tool-left_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/tool-left_over.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/tool-right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/tool-right.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/tool-right_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/tool-right_over.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/tooltag-add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/tooltag-add.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/tooltag-add_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/tooltag-add_over.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/tooltag-arrowright.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/tooltag-arrowright.gif -------------------------------------------------------------------------------- /baobab/static/admin/img/tooltag-arrowright_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/static/admin/img/tooltag-arrowright_over.gif -------------------------------------------------------------------------------- /baobab/static/admin/js/LICENSE-JQUERY.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 John Resig, http://jquery.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /baobab/static/admin/js/SelectBox.js: -------------------------------------------------------------------------------- 1 | var SelectBox = { 2 | cache: new Object(), 3 | init: function(id) { 4 | var box = document.getElementById(id); 5 | var node; 6 | SelectBox.cache[id] = new Array(); 7 | var cache = SelectBox.cache[id]; 8 | for (var i = 0; (node = box.options[i]); i++) { 9 | cache.push({value: node.value, text: node.text, displayed: 1}); 10 | } 11 | }, 12 | redisplay: function(id) { 13 | // Repopulate HTML select box from cache 14 | var box = document.getElementById(id); 15 | box.options.length = 0; // clear all options 16 | for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) { 17 | var node = SelectBox.cache[id][i]; 18 | if (node.displayed) { 19 | box.options[box.options.length] = new Option(node.text, node.value, false, false); 20 | } 21 | } 22 | }, 23 | filter: function(id, text) { 24 | // Redisplay the HTML select box, displaying only the choices containing ALL 25 | // the words in text. (It's an AND search.) 26 | var tokens = text.toLowerCase().split(/\s+/); 27 | var node, token; 28 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 29 | node.displayed = 1; 30 | for (var j = 0; (token = tokens[j]); j++) { 31 | if (node.text.toLowerCase().indexOf(token) == -1) { 32 | node.displayed = 0; 33 | } 34 | } 35 | } 36 | SelectBox.redisplay(id); 37 | }, 38 | delete_from_cache: function(id, value) { 39 | var node, delete_index = null; 40 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 41 | if (node.value == value) { 42 | delete_index = i; 43 | break; 44 | } 45 | } 46 | var j = SelectBox.cache[id].length - 1; 47 | for (var i = delete_index; i < j; i++) { 48 | SelectBox.cache[id][i] = SelectBox.cache[id][i+1]; 49 | } 50 | SelectBox.cache[id].length--; 51 | }, 52 | add_to_cache: function(id, option) { 53 | SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); 54 | }, 55 | cache_contains: function(id, value) { 56 | // Check if an item is contained in the cache 57 | var node; 58 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 59 | if (node.value == value) { 60 | return true; 61 | } 62 | } 63 | return false; 64 | }, 65 | move: function(from, to) { 66 | var from_box = document.getElementById(from); 67 | var to_box = document.getElementById(to); 68 | var option; 69 | for (var i = 0; (option = from_box.options[i]); i++) { 70 | if (option.selected && SelectBox.cache_contains(from, option.value)) { 71 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 72 | SelectBox.delete_from_cache(from, option.value); 73 | } 74 | } 75 | SelectBox.redisplay(from); 76 | SelectBox.redisplay(to); 77 | }, 78 | move_all: function(from, to) { 79 | var from_box = document.getElementById(from); 80 | var to_box = document.getElementById(to); 81 | var option; 82 | for (var i = 0; (option = from_box.options[i]); i++) { 83 | if (SelectBox.cache_contains(from, option.value)) { 84 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 85 | SelectBox.delete_from_cache(from, option.value); 86 | } 87 | } 88 | SelectBox.redisplay(from); 89 | SelectBox.redisplay(to); 90 | }, 91 | sort: function(id) { 92 | SelectBox.cache[id].sort( function(a, b) { 93 | a = a.text.toLowerCase(); 94 | b = b.text.toLowerCase(); 95 | try { 96 | if (a > b) return 1; 97 | if (a < b) return -1; 98 | } 99 | catch (e) { 100 | // silently fail on IE 'unknown' exception 101 | } 102 | return 0; 103 | } ); 104 | }, 105 | select_all: function(id) { 106 | var box = document.getElementById(id); 107 | for (var i = 0; i < box.options.length; i++) { 108 | box.options[i].selected = 'selected'; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /baobab/static/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.actions=function(n){var b=a.extend({},a.fn.actions.defaults,n),e=a(this),g=false,k=function(c){c?i():j();a(e).attr("checked",c).parent().parent().toggleClass(b.selectedClass,c)},f=function(){var c=a(e).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},true));a(b.allToggle).attr("checked",function(){if(c==e.length){value=true;i()}else{value=false;l()}return value})},i= 2 | function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},j=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},l=function(){j();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show(); 3 | a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass);f();a(b.acrossInput).val()==1&&m()});a(b.allToggle).show().click(function(){k(a(this).attr("checked"));f()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).attr("checked",false);l();k(0);f()});lastChecked=null;a(e).click(function(c){if(!c)c=window.event;var d=c.target? 4 | c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&c.shiftKey===true){var h=false;a(lastChecked).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(e).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))h=h?false:true;h&&a(this).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);lastChecked=d;f()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){g= 5 | true});a('form#changelist-form button[name="index"]').click(function(){if(g)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').click(function(){var c=false;a("div.actions select option:selected").each(function(){if(a(this).val())c=true});if(c)return g?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")): 6 | confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); 7 | -------------------------------------------------------------------------------- /baobab/static/admin/js/admin/RelatedObjectLookups.js: -------------------------------------------------------------------------------- 1 | // Handles related-objects functionality: lookup link for raw_id_fields 2 | // and Add Another links. 3 | 4 | function html_unescape(text) { 5 | // Unescape a string that was escaped using django.utils.html.escape. 6 | text = text.replace(/</g, '<'); 7 | text = text.replace(/>/g, '>'); 8 | text = text.replace(/"/g, '"'); 9 | text = text.replace(/'/g, "'"); 10 | text = text.replace(/&/g, '&'); 11 | return text; 12 | } 13 | 14 | // IE doesn't accept periods or dashes in the window name, but the element IDs 15 | // we use to generate popup window names may contain them, therefore we map them 16 | // to allowed characters in a reversible way so that we can locate the correct 17 | // element when the popup window is dismissed. 18 | function id_to_windowname(text) { 19 | text = text.replace(/\./g, '__dot__'); 20 | text = text.replace(/\-/g, '__dash__'); 21 | return text; 22 | } 23 | 24 | function windowname_to_id(text) { 25 | text = text.replace(/__dot__/g, '.'); 26 | text = text.replace(/__dash__/g, '-'); 27 | return text; 28 | } 29 | 30 | function showRelatedObjectLookupPopup(triggeringLink) { 31 | var name = triggeringLink.id.replace(/^lookup_/, ''); 32 | name = id_to_windowname(name); 33 | var href; 34 | if (triggeringLink.href.search(/\?/) >= 0) { 35 | href = triggeringLink.href + '&pop=1'; 36 | } else { 37 | href = triggeringLink.href + '?pop=1'; 38 | } 39 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 40 | win.focus(); 41 | return false; 42 | } 43 | 44 | function dismissRelatedLookupPopup(win, chosenId) { 45 | var name = windowname_to_id(win.name); 46 | var elem = document.getElementById(name); 47 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 48 | elem.value += ',' + chosenId; 49 | } else { 50 | document.getElementById(name).value = chosenId; 51 | } 52 | win.close(); 53 | } 54 | 55 | function showAddAnotherPopup(triggeringLink) { 56 | var name = triggeringLink.id.replace(/^add_/, ''); 57 | name = id_to_windowname(name); 58 | href = triggeringLink.href 59 | if (href.indexOf('?') == -1) { 60 | href += '?_popup=1'; 61 | } else { 62 | href += '&_popup=1'; 63 | } 64 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 65 | win.focus(); 66 | return false; 67 | } 68 | 69 | function dismissAddAnotherPopup(win, newId, newRepr) { 70 | // newId and newRepr are expected to have previously been escaped by 71 | // django.utils.html.escape. 72 | newId = html_unescape(newId); 73 | newRepr = html_unescape(newRepr); 74 | var name = windowname_to_id(win.name); 75 | var elem = document.getElementById(name); 76 | if (elem) { 77 | var elemName = elem.nodeName.toUpperCase(); 78 | if (elemName == 'SELECT') { 79 | var o = new Option(newRepr, newId); 80 | elem.options[elem.options.length] = o; 81 | o.selected = true; 82 | } else if (elemName == 'INPUT') { 83 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 84 | elem.value += ',' + newId; 85 | } else { 86 | elem.value = newId; 87 | } 88 | } 89 | } else { 90 | var toId = name + "_to"; 91 | elem = document.getElementById(toId); 92 | var o = new Option(newRepr, newId); 93 | SelectBox.add_to_cache(toId, o); 94 | SelectBox.redisplay(toId); 95 | } 96 | win.close(); 97 | } 98 | -------------------------------------------------------------------------------- /baobab/static/admin/js/admin/ordering.js: -------------------------------------------------------------------------------- 1 | addEvent(window, 'load', reorder_init); 2 | 3 | var lis; 4 | var top = 0; 5 | var left = 0; 6 | var height = 30; 7 | 8 | function reorder_init() { 9 | lis = document.getElementsBySelector('ul#orderthese li'); 10 | var input = document.getElementsBySelector('input[name=order_]')[0]; 11 | setOrder(input.value.split(',')); 12 | input.disabled = true; 13 | draw(); 14 | // Now initialize the dragging behavior 15 | var limit = (lis.length - 1) * height; 16 | for (var i = 0; i < lis.length; i++) { 17 | var li = lis[i]; 18 | var img = document.getElementById('handle'+li.id); 19 | li.style.zIndex = 1; 20 | Drag.init(img, li, left + 10, left + 10, top + 10, top + 10 + limit); 21 | li.onDragStart = startDrag; 22 | li.onDragEnd = endDrag; 23 | img.style.cursor = 'move'; 24 | } 25 | } 26 | 27 | function submitOrderForm() { 28 | var inputOrder = document.getElementsBySelector('input[name=order_]')[0]; 29 | inputOrder.value = getOrder(); 30 | inputOrder.disabled=false; 31 | } 32 | 33 | function startDrag() { 34 | this.style.zIndex = '10'; 35 | this.className = 'dragging'; 36 | } 37 | 38 | function endDrag(x, y) { 39 | this.style.zIndex = '1'; 40 | this.className = ''; 41 | // Work out how far along it has been dropped, using x co-ordinate 42 | var oldIndex = this.index; 43 | var newIndex = Math.round((y - 10 - top) / height); 44 | // 'Snap' to the correct position 45 | this.style.top = (10 + top + newIndex * height) + 'px'; 46 | this.index = newIndex; 47 | moveItem(oldIndex, newIndex); 48 | } 49 | 50 | function moveItem(oldIndex, newIndex) { 51 | // Swaps two items, adjusts the index and left co-ord for all others 52 | if (oldIndex == newIndex) { 53 | return; // Nothing to swap; 54 | } 55 | var direction, lo, hi; 56 | if (newIndex > oldIndex) { 57 | lo = oldIndex; 58 | hi = newIndex; 59 | direction = -1; 60 | } else { 61 | direction = 1; 62 | hi = oldIndex; 63 | lo = newIndex; 64 | } 65 | var lis2 = new Array(); // We will build the new order in this array 66 | for (var i = 0; i < lis.length; i++) { 67 | if (i < lo || i > hi) { 68 | // Position of items not between the indexes is unaffected 69 | lis2[i] = lis[i]; 70 | continue; 71 | } else if (i == newIndex) { 72 | lis2[i] = lis[oldIndex]; 73 | continue; 74 | } else { 75 | // Item is between the two indexes - move it along 1 76 | lis2[i] = lis[i - direction]; 77 | } 78 | } 79 | // Re-index everything 80 | reIndex(lis2); 81 | lis = lis2; 82 | draw(); 83 | // document.getElementById('hiddenOrder').value = getOrder(); 84 | document.getElementsBySelector('input[name=order_]')[0].value = getOrder(); 85 | } 86 | 87 | function reIndex(lis) { 88 | for (var i = 0; i < lis.length; i++) { 89 | lis[i].index = i; 90 | } 91 | } 92 | 93 | function draw() { 94 | for (var i = 0; i < lis.length; i++) { 95 | var li = lis[i]; 96 | li.index = i; 97 | li.style.position = 'absolute'; 98 | li.style.left = (10 + left) + 'px'; 99 | li.style.top = (10 + top + (i * height)) + 'px'; 100 | } 101 | } 102 | 103 | function getOrder() { 104 | var order = new Array(lis.length); 105 | for (var i = 0; i < lis.length; i++) { 106 | order[i] = lis[i].id.substring(1, 100); 107 | } 108 | return order.join(','); 109 | } 110 | 111 | function setOrder(id_list) { 112 | /* Set the current order to match the lsit of IDs */ 113 | var temp_lis = new Array(); 114 | for (var i = 0; i < id_list.length; i++) { 115 | var id = 'p' + id_list[i]; 116 | temp_lis[temp_lis.length] = document.getElementById(id); 117 | } 118 | reIndex(temp_lis); 119 | lis = temp_lis; 120 | draw(); 121 | } 122 | 123 | function addEvent(elm, evType, fn, useCapture) 124 | // addEvent and removeEvent 125 | // cross-browser event handling for IE5+, NS6 and Mozilla 126 | // By Scott Andrew 127 | { 128 | if (elm.addEventListener){ 129 | elm.addEventListener(evType, fn, useCapture); 130 | return true; 131 | } else if (elm.attachEvent){ 132 | var r = elm.attachEvent("on"+evType, fn); 133 | return r; 134 | } else { 135 | elm['on'+evType] = fn; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /baobab/static/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(document).ready(function() { 3 | // Add anchor tag for Show/Hide link 4 | $("fieldset.collapse").each(function(i, elem) { 5 | // Don't hide if fields in this fieldset have errors 6 | if ($(elem).find("div.errors").length == 0) { 7 | $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") + 9 | ')'); 10 | } 11 | }); 12 | // Add toggle to anchor tag 13 | $("fieldset.collapse a.collapse-toggle").toggle( 14 | function() { // Show 15 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); 16 | return false; 17 | }, 18 | function() { // Hide 19 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); 20 | return false; 21 | } 22 | ); 23 | }); 24 | })(django.jQuery); 25 | -------------------------------------------------------------------------------- /baobab/static/admin/js/collapse.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){a(b).find("div.errors").length==0&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").toggle(function(){a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]);return false},function(){a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", 2 | [a(this).attr("id")]);return false})})})(django.jQuery); 3 | -------------------------------------------------------------------------------- /baobab/static/admin/js/inlines.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),c=b(this),d=c.parent(),i=function(a,e,g){var d=RegExp("("+e+"-(\\d+|__prefix__))"),e=e+"-"+g;b(a).attr("for")&&b(a).attr("for",b(a).attr("for").replace(d,e));a.id&&(a.id=a.id.replace(d,e));a.name&&(a.name=a.name.replace(d,e))},f=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off"),g=parseInt(f.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off"),f=""===e.val()||0'+a.addText+""),h=d.find("tr:last a")):(c.filter(":last").after('
'+a.addText+"
"),h=c.filter(":last").next().find("a"));h.click(function(d){d.preventDefault();var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),d=b("#"+a.prefix+ 3 | "-empty"),c=d.clone(true);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);c.is("tr")?c.children(":last").append('
'+a.deleteText+"
"):c.is("ul")||c.is("ol")?c.append('
  • '+a.deleteText+"
  • "):c.children(":first").append(''+a.deleteText+"");c.find("*").each(function(){i(this, 4 | a.prefix,f.val())});c.insertBefore(b(d));b(f).val(parseInt(f.val(),10)+1);g=g+1;e.val()!==""&&e.val()-f.val()<=0&&h.parent().hide();c.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();g=g-1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(e.val()===""||e.val()-d.length>0)&&h.parent().show();for(var c=0,f=d.length;c 0) { 25 | values.push($(field).val()); 26 | } 27 | }) 28 | field.val(URLify(values.join(' '), maxLength)); 29 | }; 30 | 31 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); 32 | }); 33 | }; 34 | })(django.jQuery); 35 | -------------------------------------------------------------------------------- /baobab/static/admin/js/prepopulate.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.prepopulate=function(d,g){return this.each(function(){var b=a(this);b.data("_changed",false);b.change(function(){b.data("_changed",true)});var c=function(){if(b.data("_changed")!=true){var e=[];a.each(d,function(h,f){a(f).val().length>0&&e.push(a(f).val())});b.val(URLify(e.join(" "),g))}};a(d.join(",")).keyup(c).change(c).focus(c)})}})(django.jQuery); 2 | -------------------------------------------------------------------------------- /baobab/static/admin/js/timeparse.js: -------------------------------------------------------------------------------- 1 | var timeParsePatterns = [ 2 | // 9 3 | { re: /^\d{1,2}$/i, 4 | handler: function(bits) { 5 | if (bits[0].length == 1) { 6 | return '0' + bits[0] + ':00'; 7 | } else { 8 | return bits[0] + ':00'; 9 | } 10 | } 11 | }, 12 | // 13:00 13 | { re: /^\d{2}[:.]\d{2}$/i, 14 | handler: function(bits) { 15 | return bits[0].replace('.', ':'); 16 | } 17 | }, 18 | // 9:00 19 | { re: /^\d[:.]\d{2}$/i, 20 | handler: function(bits) { 21 | return '0' + bits[0].replace('.', ':'); 22 | } 23 | }, 24 | // 3 am / 3 a.m. / 3am 25 | { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i, 26 | handler: function(bits) { 27 | var hour = parseInt(bits[1]); 28 | if (hour == 12) { 29 | hour = 0; 30 | } 31 | if (bits[2].toLowerCase() == 'p') { 32 | if (hour == 12) { 33 | hour = 0; 34 | } 35 | return (hour + 12) + ':00'; 36 | } else { 37 | if (hour < 10) { 38 | return '0' + hour + ':00'; 39 | } else { 40 | return hour + ':00'; 41 | } 42 | } 43 | } 44 | }, 45 | // 3.30 am / 3:15 a.m. / 3.00am 46 | { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i, 47 | handler: function(bits) { 48 | var hour = parseInt(bits[1]); 49 | var mins = parseInt(bits[2]); 50 | if (mins < 10) { 51 | mins = '0' + mins; 52 | } 53 | if (hour == 12) { 54 | hour = 0; 55 | } 56 | if (bits[3].toLowerCase() == 'p') { 57 | if (hour == 12) { 58 | hour = 0; 59 | } 60 | return (hour + 12) + ':' + mins; 61 | } else { 62 | if (hour < 10) { 63 | return '0' + hour + ':' + mins; 64 | } else { 65 | return hour + ':' + mins; 66 | } 67 | } 68 | } 69 | }, 70 | // noon 71 | { re: /^no/i, 72 | handler: function(bits) { 73 | return '12:00'; 74 | } 75 | }, 76 | // midnight 77 | { re: /^mid/i, 78 | handler: function(bits) { 79 | return '00:00'; 80 | } 81 | } 82 | ]; 83 | 84 | function parseTimeString(s) { 85 | for (var i = 0; i < timeParsePatterns.length; i++) { 86 | var re = timeParsePatterns[i].re; 87 | var handler = timeParsePatterns[i].handler; 88 | var bits = re.exec(s); 89 | if (bits) { 90 | return handler(bits); 91 | } 92 | } 93 | return s; 94 | } 95 | -------------------------------------------------------------------------------- /baobab/translate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/translate/__init__.py -------------------------------------------------------------------------------- /baobab/translate/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Define the form for django's admin 4 | """ 5 | 6 | from django.contrib import admin 7 | 8 | from baobab.utils.admin import ModelAdmin 9 | 10 | from baobab.translate.models import Lang, Event, EventLog 11 | from baobab.translate.admininline import EventDataInline, EventLogDataInline 12 | from baobab.translate.adminfilter import (EventCompleteFilter, 13 | EventLogCompleteFilter) 14 | 15 | 16 | class EventAdmin(ModelAdmin): 17 | fields = ['title_en', ] 18 | readonly_fields = ['title_en', ] 19 | list_display = ['title_en', 'complete_event', 'complete_log'] 20 | list_display_dynamic = { 21 | 'val_name': Lang.objects.exclude(iso='en').values_list('iso', 'name'), 22 | 'filter': 'lang__iso', 23 | 'sub_obj': 'eventdatas', 24 | } 25 | inlines = [EventDataInline, ] 26 | list_filter = [EventCompleteFilter, ] 27 | search_fields = ['eventdatas__title'] 28 | 29 | def title_en(self, obj): 30 | return obj.eventdatas.get(lang__iso='en').title 31 | title_en.short_description = 'Title' 32 | 33 | def complete_event(self, obj): 34 | if obj.eventdatas.count() == Lang.objects.count(): 35 | return 'True' 36 | return 'False' 37 | complete_event.allow_tags = True 38 | 39 | def complete_log(self, obj): 40 | for eventlog in obj.eventlogs.all(): 41 | if eventlog.eventlogdatas.count() != Lang.objects.count(): 42 | return 'False' 43 | return 'True' 44 | complete_log.allow_tags = True 45 | 46 | # XXX for safety 47 | def has_delete_permission(self, request, obj=None): 48 | return False 49 | 50 | def has_add_permission(self, request, obj=None): 51 | return False 52 | 53 | def has_change_permission(self, request, obj=None): 54 | if request.user.groups.filter(name='translate').exists(): 55 | return True 56 | return super(EventAdmin, self).has_change_permission(request, obj=obj) 57 | 58 | 59 | class EventLogAdmin(ModelAdmin): 60 | fields = ['comment_en', ] 61 | readonly_fields = ['comment_en', ] 62 | list_display = ['title_en', 'comment_en', 'complete'] 63 | list_display_dynamic = { 64 | 'val_name': Lang.objects.exclude(iso='en').values_list('iso', 'name'), 65 | 'filter': 'lang__iso', 66 | 'sub_obj': 'eventlogdatas', 67 | } 68 | inlines = [EventLogDataInline, ] 69 | list_filter = [EventLogCompleteFilter, ] 70 | search_fields = ['event__eventdatas__title'] 71 | 72 | def title_en(self, obj): 73 | return obj.event.eventdatas.get(lang__iso='en').title 74 | title_en.short_description = 'Title' 75 | 76 | def comment_en(self, obj): 77 | return obj.eventlogdatas.get(lang__iso='en').comment 78 | comment_en.short_description = 'Comment' 79 | 80 | def complete(self, obj): 81 | if obj.eventlogdatas.count() == Lang.objects.count(): 82 | return 'True' 83 | return 'False' 84 | complete.allow_tags = True 85 | 86 | # XXX for safety 87 | def has_delete_permission(self, request, obj=None): 88 | return False 89 | 90 | def has_add_permission(self, request, obj=None): 91 | return False 92 | 93 | def has_change_permission(self, request, obj=None): 94 | if request.user.groups.filter(name='translate').exists(): 95 | return True 96 | return super(EventLogAdmin, self).has_change_permission(request, 97 | obj=obj) 98 | 99 | 100 | admin.site.register(Event, EventAdmin) 101 | admin.site.register(EventLog, EventLogAdmin) 102 | -------------------------------------------------------------------------------- /baobab/translate/adminfilter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Define some filter that can't be auto generated by django 5 | """ 6 | 7 | from django.db.models import Q, F, Count 8 | from django.contrib.admin import SimpleListFilter 9 | 10 | from baobab.translate.models import Lang, Event 11 | 12 | 13 | class EventCompleteFilter(SimpleListFilter): 14 | title = 'Complete' 15 | parameter_name = 'complete' 16 | 17 | def lookups(self, request, model_admin): 18 | return ( 19 | ('true', 'True'), 20 | ('false', 'False'), 21 | ) 22 | 23 | def queryset(self, request, queryset): 24 | if not self.value(): 25 | return queryset 26 | nb_lang = Lang.objects.count() 27 | queryset = queryset.annotate(sum=Count('eventdatas')) 28 | events = queryset.filter(sum=nb_lang) 29 | ids = [] 30 | for event in events: 31 | annotate = {'sum': Count('eventlogdatas__lang')} 32 | nb_logdatas = event.eventlogs.annotate(**annotate).values('sum') 33 | if not all(map(lambda e: e['sum'] == nb_lang, nb_logdatas)): 34 | continue 35 | ids.append(event.id) 36 | if self.value() == 'true': 37 | return Event.objects.filter(id__in=ids) 38 | return Event.objects.exclude(id__in=ids) 39 | 40 | 41 | class EventLogCompleteFilter(SimpleListFilter): 42 | title = 'CompleteLog' 43 | parameter_name = 'completelog' 44 | 45 | def lookups(self, request, model_admin): 46 | return ( 47 | ('true', 'True'), 48 | ('false', 'False'), 49 | ) 50 | 51 | def queryset(self, request, queryset): 52 | if not self.value(): 53 | return queryset 54 | nb_lang = Lang.objects.count() 55 | annotate = {'sum': Count('eventlogdatas__lang')} 56 | queryset = queryset.annotate(**annotate) 57 | if self.value() == 'true': 58 | return queryset.filter(sum=nb_lang) 59 | return queryset.filter(sum__lt=nb_lang) 60 | -------------------------------------------------------------------------------- /baobab/translate/adminform.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Re-define some default admin form 5 | """ 6 | from django.forms import ModelForm 7 | 8 | from baobab.translate import models 9 | 10 | 11 | class EventDataForm(ModelForm): 12 | 13 | def save(self, commit=True): 14 | if not self.instance: 15 | self.instance.user = self.request.user 16 | return super(EventDataForm, self).save(commit) 17 | 18 | class Meta: 19 | model = models.EventData 20 | 21 | 22 | class EventLogDataForm(ModelForm): 23 | 24 | def save(self, commit=True): 25 | if self.instance: 26 | self.instance.user = self.request.user 27 | return super(EventLogDataForm, self).save(commit) 28 | 29 | class Meta: 30 | model = models.EventLogData 31 | -------------------------------------------------------------------------------- /baobab/translate/admininline.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | all the inline class 5 | """ 6 | 7 | from baobab.utils.admin import TabularInline 8 | from baobab.translate import models 9 | from baobab.translate.adminform import EventDataForm, EventLogDataForm 10 | 11 | 12 | class EventDataInline(TabularInline): 13 | form = EventDataForm 14 | model = models.EventData 15 | fields = ['lang', 'title', 'summary'] 16 | extra = models.Lang.objects.count() 17 | max_num = extra 18 | 19 | # XXX for safety 20 | def has_delete_permission(self, request, obj=None): 21 | return False 22 | 23 | 24 | class EventLogDataInline(TabularInline): 25 | form = EventLogDataForm 26 | model = models.EventLogData 27 | fields = ['lang', 'comment'] 28 | extra = models.Lang.objects.count() 29 | max_num = extra 30 | 31 | # XXX for safety 32 | def has_delete_permission(self, request, obj=None): 33 | return False 34 | -------------------------------------------------------------------------------- /baobab/translate/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/translate/migrations/__init__.py -------------------------------------------------------------------------------- /baobab/translate/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Define the database model to translate the comment (Event/EventLog) 4 | """ 5 | 6 | from django.contrib.auth.models import User 7 | 8 | from django.db import models 9 | 10 | from baobab.backoffice.models import (Event as BackOfficeEvent, 11 | EventLog as BackOfficeEventLog) 12 | 13 | 14 | class Lang(models.Model): 15 | 16 | name = models.CharField(max_length=200, unique=True) 17 | iso = models.CharField(max_length=5, unique=True) 18 | 19 | def __unicode__(self): 20 | return self.name 21 | 22 | 23 | # XXX entris in this table are auto-genered by the admin 24 | # see: baobab.backoffice.adminform.EventForm.save 25 | # XXX this should probably be replace with a `django.dispatch.receiver` 26 | class EventData(models.Model): 27 | 28 | event = models.ForeignKey('backoffice.Event', related_name='eventdatas') 29 | lang = models.ForeignKey('Lang', related_name='eventdatas') 30 | title = models.CharField(max_length=255, 31 | help_text='A short description of the event') 32 | summary = models.TextField(null=True, blank=True, default=None) 33 | user = models.ForeignKey(User, related_name='eventdatas', null=True, 34 | default=None) 35 | 36 | class Meta: 37 | unique_together = ('event', 'lang') 38 | 39 | 40 | # XXX entris in this table are auto-genered by the admin 41 | # see: baobab.backoffice.adminform.EventLogForm.save 42 | # XXX this should probably be replace with a `django.dispatch.receiver` 43 | class EventLogData(models.Model): 44 | 45 | eventlog = models.ForeignKey('backoffice.EventLog', 46 | related_name='eventlogdatas') 47 | lang = models.ForeignKey('Lang', related_name='eventlogdatas') 48 | comment = models.TextField() 49 | user = models.ForeignKey(User, related_name='eventlogdatas', null=True, 50 | default=None) 51 | 52 | class Meta: 53 | unique_together = ('eventlog', 'lang') 54 | 55 | 56 | # XXX can't have twice the same model in the admin so the following 57 | # are just fake models classes to be able to have them twice 58 | 59 | class Event(BackOfficeEvent): 60 | 61 | class Meta: 62 | proxy = True 63 | 64 | 65 | class EventLog(BackOfficeEventLog): 66 | 67 | class Meta: 68 | proxy = True 69 | -------------------------------------------------------------------------------- /baobab/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf import settings 3 | from django.conf.urls import patterns, include, url 4 | from django.views.static import serve 5 | 6 | from django.contrib import admin 7 | admin.autodiscover() 8 | 9 | from baobab.apirest.urls import ApiUrls as ApiRestApiUrls 10 | from baobab.rss.feed import RssStatusFeed, AtomStatusFeed 11 | 12 | 13 | # XXX why it's not using the middleware GZipMiddleware ???? 14 | def _serve_gzip(request): 15 | if not settings.SERVE_GZIP: 16 | return False 17 | if 'HTTP_ACCEPT_ENCODING' in request.META: 18 | encodings = request.META.get('HTTP_ACCEPT_ENCODING') 19 | encodings = [encoding.strip() for encoding in encodings.split(',')] 20 | return 'gzip' in encodings or 'deflate' in encodings 21 | return False 22 | 23 | 24 | def baobab_serve(request, path, document_root=None, show_indexes=False): 25 | if not settings.INFINITE_CACHE: 26 | return serve(request, path, document_root, show_indexes) 27 | else: 28 | if _serve_gzip(request): 29 | resp = serve(request, path + '.gz', document_root, show_indexes) 30 | resp['Content-Encoding'] = 'gzip' 31 | else: 32 | resp = serve(request, path, document_root, show_indexes) 33 | resp['Cache-Control'] = 'public, max-age=%d' % (3600 * 24 * 365) 34 | resp['Vary'] = 'Accept-Encoding' 35 | return resp 36 | 37 | # back-end URL 38 | urlpatterns = patterns( 39 | '', 40 | url(r'^admin/', include(admin.site.urls)), 41 | url(r'^admin', include(admin.site.urls)), 42 | url(r'', include(ApiRestApiUrls.get_urls())), 43 | ) 44 | 45 | # static content URL 46 | urlpatterns += patterns( 47 | '', 48 | url(r'^static/(?P.*)$', baobab_serve, 49 | {'document_root': settings.MEDIA_ROOT}), 50 | url(r'^(favicon.ico)$', 'django.views.static.serve', 51 | {'document_root': settings.MEDIA_ROOT}), 52 | ) 53 | 54 | # RSS site URL 55 | 56 | urlpatterns += patterns( 57 | '', 58 | url(r'^rss/$', RssStatusFeed(), name='rss'), 59 | url(r'^atom/$', AtomStatusFeed(), name='atom'), 60 | ) 61 | 62 | # web site URL 63 | urlpatterns += patterns( 64 | '', 65 | url(r'^.*$', 'baobab.front.views.index'), 66 | ) 67 | -------------------------------------------------------------------------------- /baobab/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi/baobab/7d4671bda7c9eb3dff4702c784e9fc0f5fb59b63/baobab/utils/__init__.py -------------------------------------------------------------------------------- /baobab/utils/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | sub class the django Admin class: 4 | * to have dynamic columns 5 | * to set the request in the form, which contain the user 6 | * remove the delete action if you don't have the delete right 7 | """ 8 | 9 | from django.contrib.admin import (ModelAdmin as DjangoModelAdmin, 10 | StackedInline as DjangoStackedInline, 11 | TabularInline as DjangoTabularInline) 12 | 13 | 14 | class ModelAdmin(DjangoModelAdmin): 15 | 16 | """ 17 | sub classes need to define a dict `list_display_dynamic` to have dynamic 18 | columns with True/False value 19 | - val_name key is a list of tuple: 20 | - first elem will be used as the conditional value 21 | - second elem will be used as the columns name 22 | - filter: a string to define on which filed the obj should be filtered 23 | - sub_obj: a string to define a sub object 24 | """ 25 | 26 | def __init__(self, *args, **kargs): 27 | cls = self.__class__ 28 | if hasattr(cls, 'list_display_dynamic'): 29 | val_name = cls.list_display_dynamic['val_name'] 30 | filter_ = cls.list_display_dynamic['filter'] 31 | if cls.list_display_dynamic['sub_obj']: 32 | sub_objs = cls.list_display_dynamic['sub_obj'].split('.') 33 | else: 34 | sub_objs = [] 35 | for val, name in val_name: 36 | 37 | def _columns(self, obj, val=val): 38 | for sub_obj in sub_objs: 39 | obj = getattr(obj, sub_obj) 40 | if obj.filter(**{filter_: val}).exists(): 41 | return 'True' 42 | return 'False' 43 | _columns.short_description = name 44 | _columns.allow_tags = True 45 | 46 | setattr(cls, name, _columns) 47 | cls.list_display.append(name) 48 | super(ModelAdmin, self).__init__(*args, **kargs) 49 | 50 | def get_form(self, request, *args, **kwargs): 51 | form = super(ModelAdmin, self).get_form(request, *args, **kwargs) 52 | form.request = request 53 | return form 54 | 55 | def get_actions(self, request): 56 | actions = super(ModelAdmin, self).get_actions(request) 57 | if not self.has_delete_permission(request): 58 | if 'delete_selected' in actions: 59 | del actions['delete_selected'] 60 | return actions 61 | 62 | 63 | class StackedInline(DjangoStackedInline): 64 | 65 | def get_formset(self, request, obj=None, **kwargs): 66 | formset = super(DjangoStackedInline, self).get_formset(request, 67 | obj=obj, 68 | **kwargs) 69 | formset.form.request = request 70 | return formset 71 | 72 | 73 | class TabularInline(DjangoTabularInline): 74 | 75 | def get_formset(self, request, obj=None, **kwargs): 76 | formset = super(DjangoTabularInline, self).get_formset(request, 77 | obj=obj, 78 | **kwargs) 79 | formset.form.request = request 80 | return formset 81 | -------------------------------------------------------------------------------- /baobab/utils/authentication.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from oauth2 import Consumer, Client, Token 4 | from httplib2 import ProxyInfo 5 | from httplib2.socks import PROXY_TYPE_HTTP 6 | 7 | from django.conf import settings 8 | 9 | 10 | class Authentication(object): 11 | 12 | def __init__(self, consumer_key, consumer_secret, token_key, token_secret): 13 | consumer = Consumer(key=consumer_key, secret=consumer_secret) 14 | token = Token(key=token_key, secret=token_secret) 15 | 16 | proxy_info = None 17 | if hasattr(settings, 'PROXY_HOST') and \ 18 | hasattr(settings, 'PROXY_PORT'): 19 | proxy_info = ProxyInfo( 20 | proxy_type=PROXY_TYPE_HTTP, 21 | proxy_host=settings.PROXY_HOST, 22 | proxy_port=settings.PROXY_PORT) 23 | self.client = Client( 24 | consumer=consumer, 25 | token=token, 26 | proxy_info=proxy_info) 27 | 28 | def __enter__(self): 29 | return self 30 | 31 | def __exit__(self, exc_type, exc_val, exc_tb): 32 | pass 33 | -------------------------------------------------------------------------------- /baobab/utils/handle_south.py: -------------------------------------------------------------------------------- 1 | """ 2 | to easily disable the south migration 3 | 4 | this is useful to create a local database 5 | and it's also use by the command: 6 | $ baobab migrate fixture 7 | """ 8 | 9 | from django.conf import settings 10 | from django.utils.datastructures import SortedDict 11 | 12 | 13 | class HandleSouth(object): 14 | 15 | apps = None 16 | 17 | @classmethod 18 | def get_apps(cls): 19 | if not cls.apps: 20 | app = filter(lambda x: x.startswith('baobab.'), 21 | settings.INSTALLED_APPS) 22 | app = map(lambda x: x.rsplit('.', 1)[1], app) 23 | ignore = ['ignore'] * len(app) 24 | cls.apps = SortedDict(zip(app, ignore)) 25 | return cls.apps 26 | 27 | @classmethod 28 | def disable(cls): 29 | settings.SOUTH_MIGRATION_MODULES = cls.get_apps() 30 | 31 | @classmethod 32 | def enable(cls): 33 | if getattr(settings, 'SOUTH_MIGRATION_MODULES'): 34 | del settings.SOUTH_MIGRATION_MODULES 35 | -------------------------------------------------------------------------------- /baobab/utils/mock.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from baobab.socialnetwork.base import SocialNetworkBase 4 | 5 | 6 | class MockLOG(object): 7 | 8 | def __init__(self): 9 | self._info = [] 10 | self._error = [] 11 | 12 | def info(self, msg, *args): 13 | self._info.append(msg % args) 14 | 15 | def error(self, msg, *args): 16 | self._error.append(msg % args) 17 | 18 | 19 | class MockSN(SocialNetworkBase): 20 | name = 'MockSN' 21 | 22 | def __init__(self): 23 | self._msg = [] 24 | 25 | @classmethod 26 | def is_configured(cls): 27 | True 28 | 29 | @classmethod 30 | def get_max_char(cls, default): 31 | return 42 32 | 33 | def publish(self, msg, url): 34 | self._msg.append((msg, url)) 35 | return len(self._msg) 36 | 37 | def __enter__(self): 38 | return self 39 | 40 | def __exit__(self, exc_type, exc_val, exc_tb): 41 | pass 42 | 43 | 44 | def mock_get_field_by_name(): 45 | class Field(object): 46 | max_length = 42 47 | return (Field(), ) 48 | -------------------------------------------------------------------------------- /baobab/utils/set_current_timezone.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytz 4 | 5 | from django.conf import settings 6 | from django.utils import timezone 7 | 8 | 9 | class TimezoneMiddleware(object): 10 | 11 | def process_request(self, request): 12 | if request.user.is_authenticated(): 13 | timezone.activate(pytz.timezone(str(request.user.timezone))) 14 | else: 15 | timezone.activate(settings.TIME_ZONE) 16 | -------------------------------------------------------------------------------- /baobab/utils/test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | from mock import patch 5 | 6 | from django.test import TestCase as DjangoTestCase 7 | from tastypie.test import ResourceTestCase as TastypieResourceTestCase 8 | 9 | from baobab.utils.mock import MockSN, MockLOG 10 | 11 | 12 | class TestCase(DjangoTestCase): 13 | 14 | def setUp(self, *args, **kwargs): 15 | super(TestCase, self).setUp(*args, **kwargs) 16 | self._mock = [] 17 | 18 | cls_path = 'baobab.socialnetwork.base.LOG' 19 | self._mock.append(patch(cls_path, new_callable=MockLOG)) 20 | self.log = self._mock[0].start() 21 | 22 | cls_path = 'baobab.socialnetwork.base.SocialNetworkBase.__subclasses__' 23 | self._mock.append(patch(cls_path, return_value=[MockSN, ])) 24 | self._mock[1].start() 25 | 26 | def tearDown(self, *args, **kwargs): 27 | super(TestCase, self).tearDown(*args, **kwargs) 28 | for mock in self._mock: 29 | mock.stop() 30 | 31 | 32 | class ResourceTestCase(TastypieResourceTestCase): 33 | 34 | def setUp(self, *args, **kwargs): 35 | super(ResourceTestCase, self).setUp(*args, **kwargs) 36 | self._mock = [] 37 | 38 | cls_path = 'baobab.socialnetwork.base.LOG' 39 | self._mock.append(patch(cls_path, new_callable=MockLOG)) 40 | self.log = self._mock[0].start() 41 | 42 | cls_path = 'baobab.socialnetwork.base.SocialNetworkBase.__subclasses__' 43 | self._mock.append(patch(cls_path, return_value=[MockSN, ])) 44 | self._mock[1].start() 45 | 46 | def tearDown(self, *args, **kwargs): 47 | super(ResourceTestCase, self).tearDown(*args, **kwargs) 48 | for mock in self._mock: 49 | mock.stop() 50 | -------------------------------------------------------------------------------- /baobab/wsgi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | WSGI config for baobab project. 4 | 5 | This module contains the WSGI application used by Django's development server 6 | and any production WSGI deployments. It should expose a module-level variable 7 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 8 | this application via the ``WSGI_APPLICATION`` setting. 9 | 10 | Usually you will have the standard Django WSGI application here, but it also 11 | might make sense to replace the whole Django WSGI application with a custom one 12 | that later delegates to the Django one. For example, you could introduce WSGI 13 | middleware here, or combine a Django application with an application of another 14 | framework. 15 | 16 | """ 17 | import os 18 | 19 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "baobab.settings") 20 | 21 | # This application object is used by any WSGI server configured to use this 22 | # file. This includes Django's development server, if the WSGI_APPLICATION 23 | # setting points here. 24 | from django.core.wsgi import get_wsgi_application 25 | application = get_wsgi_application() 26 | 27 | # Apply WSGI middleware here. 28 | # from helloworld.wsgi import HelloWorldApplication 29 | # application = HelloWorldApplication(application) 30 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf baobab.egg-info build baobab.ng/build node_modules baobab.ng/node_modules 4 | dh_clean 5 | -------------------------------------------------------------------------------- /debian/README.Debian: -------------------------------------------------------------------------------- 1 | Packaging for baobab project used as base for 2 | https://status.gandi.net/ 3 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: baobab 2 | Priority: optional 3 | Section: python 4 | Maintainer: Gandi 5 | Build-Depends: debhelper (>= 9), 6 | python (>= 2.7), 7 | npm (>= 1.3.24), 8 | quilt (>= 0.60-2), 9 | nodejs-legacy (>= 0.10.20), 10 | nodejs (>= 0.10.20), 11 | nodejs (<< 4.0), 12 | git-core, 13 | dh-python, 14 | ca-certificates, 15 | build-essential, 16 | libpng-dev, 17 | automake, autoconf, 18 | nasm, 19 | realpath | coreutils (>= 8.23) 20 | Build-Depends-Indep: python-setuptools, 21 | make (>= 3.81) | dpkg-dev (>= 1.16), 22 | Standards-Version: 3.9.4 23 | X-Python-Version: >= 2.7 24 | 25 | Package: python-baobab 26 | Architecture: all 27 | Depends: ${misc:Depends}, 28 | ${python:Depends}, 29 | libjs-jquery, 30 | gunicorn (>= 0.14.5) | httpd-wsgi, 31 | nginx-light | libapache2-mpm-worker | httpd, 32 | python-all, 33 | python-pastedeploy, 34 | devscripts, 35 | libparse-debcontrol-perl 36 | Provides: ${python:Provides} 37 | Description: Gandi status website 38 | Status website about Gandi services and communication project. 39 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Files: * 2 | Copyright: © 2013-2015 Gandi S.A.S. 3 | License: GPL-3 4 | 5 | License: GPL-3 6 | See /usr/share/common-licenses/GPL-3 7 | 8 | -------------------------------------------------------------------------------- /debian/pydist-overrides: -------------------------------------------------------------------------------- 1 | Django python-django; PEP386 2 | South python-django-south; PEP386 3 | django-tastypie python-tastypie; PEP386 4 | pytz python-tz; PEP386 5 | oauth2 python-oauth2; PEP386 6 | irc python-irc; PEP386 7 | -------------------------------------------------------------------------------- /debian/python-baobab.baobab-ircd.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | ### BEGIN INIT INFO 4 | # Provides: baobab-ircd 5 | # Required-Start: $network $local_fs $remote_fs $syslog 6 | # Required-Stop: $remote_fs 7 | # Should-Start: 8 | # Should-Stop: 9 | # Default-Start: 2 3 4 5 10 | # Default-Stop: 0 1 6 11 | # Short-Description: init-Script for baobab-ircd 12 | # Description: Start a client irc for baobab to send data to it. 13 | ### END INIT INFO 14 | 15 | NAME=baobab-ircd 16 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 17 | CMD=/usr/bin/baobab 18 | RUNDIR=/run/$NAME 19 | PIDFILE="${RUNDIR}/${NAME}.pid" 20 | CONFFILE="/etc/gandi/baobab.py" 21 | OPTIONS="ircd" 22 | USER='www-data' 23 | GROUP='www-data' 24 | SOCKET=`grep '^IRC_SOCKET_PATH' /etc/gandi/baobab.py | sed -e "s,^IRC_SOCKET_PATH\s*=\s*,,g" -e "s,[\'\"],,g"` 25 | 26 | . /lib/lsb/init-functions 27 | 28 | if [ ! -e $CONFFILE ]; then 29 | log_failure_msg "$CONFFILE not found." 30 | exit 1 31 | fi 32 | 33 | [ ! -f $CMD ] || CMD=`which baobab` 34 | 35 | case "$1" in 36 | start) 37 | if [ ! -d "$RUNDIR" ]; then 38 | install -d -m 0750 -o "$USER" -g "$GROUP" "$RUNDIR" 39 | else 40 | chown $USER:$GROUP "$RUNDIR" 41 | fi 42 | log_begin_msg "Starting $NAME" 43 | export PYTHONPATH="/etc" DJANGO_SETTINGS_MODULE="gandi.baobab" 44 | start-stop-daemon --start --quiet \ 45 | --chuid $USER:$GROUP \ 46 | --make-pidfile --pidfile $PIDFILE \ 47 | --background \ 48 | --exec $CMD -- $OPTIONS 49 | ;; 50 | stop) 51 | start-stop-daemon --stop --quiet \ 52 | --pidfile $PIDFILE \ 53 | --user $USER 54 | rm -rf "$RUNDIR" 55 | ;; 56 | status|force-reload|restart|try-restart) 57 | # nothing 58 | ;; 59 | *) 60 | log_warning_msg "Usage: /etc/init.d/$NAME start|stop" 61 | exit 1 62 | ;; 63 | esac 64 | 65 | exit 0 66 | 67 | # vim:ts=4:sw=4: 68 | -------------------------------------------------------------------------------- /debian/python-baobab.baobab-syncdb.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | ### BEGIN INIT INFO 4 | # Provides: baobab-syncdb 5 | # Required-Start: $network $local_fs $remote_fs $syslog 6 | # Required-Stop: $remote_fs 7 | # Should-Start: 8 | # Should-Stop: 9 | # Default-Start: 2 3 4 5 10 | # Default-Stop: 0 1 6 11 | # Short-Description: init-Script for baobab database sync 12 | # Description: Run when the VM start and sync the database of the baobab running. 13 | ### END INIT INFO 14 | 15 | NAME=baobab-syncdb 16 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 17 | CMD=/usr/bin/baobab 18 | OPTIONS="syncdb" 19 | CONFFILE="/etc/gandi/baobab.py" 20 | 21 | . /lib/lsb/init-functions 22 | 23 | if [ ! -e $CONFFILE ]; then 24 | log_failure_msg "$CONFFILE not found." 25 | exit 1 26 | fi 27 | 28 | [ ! -f $CMD ] || CMD=`which baobab` 29 | 30 | case "$1" in 31 | start) 32 | log_begin_msg "Starting syncdb process:" "$NAME" 33 | export PYTHONPATH='/etc/' 34 | export DJANGO_SETTINGS_MODULE='gandi.baobab' 35 | if $CMD $OPTIONS | logger -t $NAME; then 36 | log_end_msg 0 37 | else 38 | log_end_msg 1 39 | exit 1 40 | fi 41 | ;; 42 | status|stop|force-reload|restart|try-restart) 43 | # nothing 44 | ;; 45 | *) 46 | log_warning_msg "Usage: /etc/init.d/$NAME start" 47 | exit 1 48 | ;; 49 | esac 50 | 51 | exit 0 52 | 53 | # vim:ts=4:sw=4: 54 | -------------------------------------------------------------------------------- /debian/python-baobab.baobab.cron.d: -------------------------------------------------------------------------------- 1 | */5 * * * * www-data PYTHONPATH=/etc/ DJANGO_SETTINGS_MODULE=gandi.baobab /usr/bin/baobab social_network 2>&1| logger -t baobab 2 | */5 * * * * www-data PYTHONPATH=/etc/ DJANGO_SETTINGS_MODULE=gandi.baobab /usr/bin/baobab close_event 2>&1| logger -t baobab 3 | -------------------------------------------------------------------------------- /debian/python-baobab.install: -------------------------------------------------------------------------------- 1 | nginx/sites-available/status.gandi.net etc/nginx/sites-available 2 | nginx/sites-available/status.gandi.net-ssl etc/nginx/sites-available 3 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | export DH_VERBOSE=0 6 | export NPM_CONFIG_STRICT_SSL=false 7 | export NODE_ENV=production 8 | 9 | # XXX TOFIX this is dirty 10 | export NPM_CONFIG_CACHE=$(shell mktemp -d --suffix=baobab) 11 | export HOME=$(NPM_CONFIG_CACHE) 12 | 13 | # default value for npm registry 14 | 15 | 16 | %: 17 | dh $@ --with=python2 18 | 19 | override_dh_builddeb: 20 | dh_builddeb 21 | if [ ! -z $$NPM_CONFIG_CACHE ] && [ -d $$NPM_CONFIG_CACHE ]; then \ 22 | rm -rf $$NPM_CONFIG_CACHE; \ 23 | fi 24 | if [ `expr match $$HOME /tmp` = 4 ] && [ -d $$HOME ]; then \ 25 | rm -rf $$HOME; \ 26 | fi 27 | 28 | override_dh_auto_install: 29 | install -d -m 0755 debian/python-baobab/usr/share/baobab/ 30 | install -d -m 0755 $(NPM_CONFIG_CACHE) 31 | 32 | if [ ! -z $$NPM_CONFIG_REGISTRY ]; then \ 33 | cp npm-shrinkwrap.json npm-shrinkwrap.json.backup; \ 34 | sed -i -e "s,https://registry.npmjs.org,$$NPM_CONFIG_REGISTRY,g" \ 35 | npm-shrinkwrap.json; \ 36 | fi 37 | 38 | npm config set progress false 39 | npm install 40 | npm run release 41 | cp -rf baobab/static/* debian/python-baobab/usr/share/baobab/ 42 | dh_auto_install 43 | find debian/python-baobab -type f -iname "*.js" -exec chmod 0644 {} \; 44 | 45 | if [ -e npm-shrinkwrap.json.backup ]; then \ 46 | mv -f npm-shrinkwrap.json.backup npm-shrinkwrap.json ; \ 47 | fi 48 | 49 | override_dh_clean: 50 | dh_clean 51 | rm -rf build 52 | rm -rf baobab.ng/build 53 | rm -rf baobab.egg-info build baobab/__init__.pyc 54 | if [ ! -z $$NPM_CONFIG_CACHE ] && [ -d $$NPM_CONFIG_CACHE ]; then \ 55 | rm -rf $$NPM_CONFIG_CACHE; \ 56 | fi 57 | if [ `expr match $$HOME /tmp` = 4 ] && [ -d $$HOME ]; then \ 58 | rm -rf $$HOME; \ 59 | fi 60 | 61 | override_dh_installcron: 62 | dh_installcron --name=baobab 63 | 64 | override_dh_installinit: 65 | dh_installinit --no-restart-on-upgrade --no-start --name=baobab-syncdb 66 | dh_installinit --no-restart-on-upgrade --no-start --name=baobab-ircd 67 | 68 | .PHONY: override_dh_auto_install, override_dh_clean, override_dh_installcron, override_dh_builddeb, override_dh_instalinit 69 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /misc/hooks/README: -------------------------------------------------------------------------------- 1 | some git hooks can help you with south 2 | - https://gist.github.com/gurglet/1780139 3 | - https://github.com/agiliq/compass 4 | - https://github.com/MoiTux/compass (a fork of the previous one) 5 | 6 | or use the hooks file in this directory it will just just inform you 7 | that you have some new/old migrations 8 | -------------------------------------------------------------------------------- /misc/hooks/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #In most case the order of the south migration doesn't really maters 3 | #just add --merge to fix the warning 4 | #In case the same model has change it might be better to 're-do' 5 | #a new migration, also having a real order for the migration can 6 | #be better for the MEP 7 | 8 | 9 | ROOT=$(git rev-parse --show-toplevel) 10 | 11 | CMD="python ${ROOT}/baobab/bin/cmd_baobab.py" 12 | 13 | for dir in $(find "${ROOT}" -name migrations) 14 | do 15 | nb_file=$(ls ${dir}/*.py | wc -l) 16 | nb_file=$(expr $nb_file - 1) # need to remove 1 for __init__.py 17 | last_idx=$(ls -r ${dir}/*.py | head -2 | tail -1 | cut -d _ -f 1) 18 | last_idx=${last_idx##*/} # remove all path to just get the number 19 | if [ $nb_file -ne $last_idx ] 20 | then 21 | echo '' 22 | echo ' *** Might be great to re-order the migration ' \ 23 | 'or redo them entirely ***' 24 | echo '' 25 | fi 26 | done 27 | 28 | has_migration=$(${CMD} migrate --list 2>&1 | grep '( )') 29 | 30 | if [ ! -z "${has_migration}" ] 31 | then 32 | echo 'New migration need to be apply !!!' 33 | fi 34 | -------------------------------------------------------------------------------- /misc/hooks/post-rebase: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # might be call by git pull --rebase -> so call post-merge 3 | echo '--> post-rebase' 4 | ROOT=$(git rev-parse --show-toplevel) 5 | ${ROOT}/.git/hooks/post-merge 6 | -------------------------------------------------------------------------------- /misc/hooks/post-rewrite: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # might be call by git pull --rebase -> so call post-merge 3 | echo '--> post-rewrite' 4 | ROOT=$(git rev-parse --show-toplevel) 5 | ${ROOT}/.git/hooks/post-merge 6 | -------------------------------------------------------------------------------- /misc/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # in case of a fail merge (CONFLICT) post-merge isn't call 3 | # since a commit is done so force its call 4 | 5 | ROOT=$(git rev-parse --show-toplevel) 6 | 7 | if [ -e "${ROOT}/.git/MERGE_HEAD" ] 8 | then 9 | ${ROOT}/.git/hooks/post-merge 10 | fi 11 | -------------------------------------------------------------------------------- /nginx/sites-available/status.gandi.net: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80 default_server ipv6only=on; 4 | server_tokens off; 5 | return 301 https://$host$request_uri; 6 | } 7 | -------------------------------------------------------------------------------- /nginx/sites-available/status.gandi.net-ssl: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443; 3 | listen [::]:443 default_server ipv6only=on; 4 | 5 | ssl on; 6 | ssl_certificate /etc/ssl/certs/status.gandi.net.all.crt; 7 | ssl_certificate_key /etc/ssl/private/status.gandi.net.key; 8 | ssl_dhparam /etc/ssl/dh_params.pem; 9 | 10 | ssl_session_timeout 5m; 11 | 12 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 13 | ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK'; 14 | ssl_prefer_server_ciphers on; 15 | 16 | index index.html index.htm; 17 | 18 | server_name status.gandi.net; 19 | 20 | access_log /var/log/nginx/status.gandi.net-access.log; 21 | error_log /var/log/nginx/status.gandi.net-error.log debug; 22 | 23 | gzip on; 24 | 25 | autoindex off; 26 | 27 | autoindex_exact_size off; 28 | autoindex_localtime on; 29 | 30 | location ~ ^/static/admin { 31 | root /usr/share/pyshared/django/contrib/admin/; 32 | } 33 | 34 | location ~ ^/static/(.*)$ { 35 | alias /usr/share/baobab/$1; 36 | } 37 | 38 | location = /nginx_status { 39 | stub_status on; 40 | access_log off; 41 | allow 127.0.0.1; 42 | deny all; 43 | } 44 | 45 | location / { 46 | proxy_pass_header Server; 47 | proxy_set_header Host $http_host; 48 | proxy_redirect off; 49 | proxy_set_header X-Real-IP $remote_addr; 50 | proxy_set_header X-Scheme $scheme; 51 | proxy_connect_timeout 300; 52 | proxy_read_timeout 200; 53 | proxy_pass http://localhost:8008/; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Gandi", 3 | "name": "baobab", 4 | "version": "0.0.1", 5 | "description": "Status page for Gandi.net services", 6 | "homepage": "https://status.gandi.net", 7 | "licenses": "All Rights Reserved", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/gandi/baobab.git" 11 | }, 12 | "dependencies": { 13 | "director": "^1.2.3", 14 | "gulp": "^3.8.8", 15 | "gulp-browserify": "^0.5.0", 16 | "gulp-clean": "^0.3.1", 17 | "gulp-concat": "^2.4.1", 18 | "gulp-dotify": "^0.1.2", 19 | "gulp-gzip": "0.0.8", 20 | "gulp-header": "^1.1.1", 21 | "gulp-htmlmin": "^0.2.0", 22 | "gulp-imagemin": "^1.0.1", 23 | "gulp-inject": "^1.0.2", 24 | "gulp-less": "^1.3.6", 25 | "gulp-rev": "^1.1.0", 26 | "gulp-rev-css-url": "0.0.8", 27 | "gulp-uglify": "^1.0.1", 28 | "html5shiv": "^3.7.3", 29 | "imagemin-pngquant": "^4.0.0", 30 | "jquery-browserify": "^1.8.1", 31 | "marked": "^0.3.2", 32 | "moment": "^2.8.3", 33 | "polly-phil": "^0.1.0", 34 | "q": "^1.0.1", 35 | "qajax": "git+https://github.com/baloo/qajax.git#patch-1", 36 | "run-sequence": "^1.0.1" 37 | }, 38 | "devDependencies": { 39 | "aliasify": "^1.4.0", 40 | "gulp-protractor": "0.0.11", 41 | "protractor": "^1.3.1" 42 | }, 43 | "scripts": { 44 | "release": "node_modules/.bin/gulp release", 45 | "test": "node_modules/.bin/gulp test", 46 | "dev": "node_modules/.bin/gulp dev" 47 | }, 48 | "aliasify": "./baobab.front/tests/aliasifyConfig.js" 49 | } 50 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | seleniumAddress: 'http://localhost:4444/wd/hub', 3 | specs: ['baobab.front/tests/integration/*.js'] 4 | }; 5 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE=baobab.settings 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import re 7 | from setuptools import setup, find_packages 8 | 9 | name = 'baobab' 10 | 11 | sys.path.append(os.path.abspath(os.path.dirname(__file__))) 12 | 13 | here = os.path.abspath(os.path.dirname(__file__)) 14 | README = open(os.path.join(here, 'README.md')).read() 15 | CHANGES = open(os.path.join(here, 'CHANGELOG')).read() 16 | 17 | with open(os.path.join(here, name, '__init__.py')) as v_file: 18 | version = re.compile(r".*__version__ = '(.*?)'", 19 | re.S).match(v_file.read()).group(1) 20 | 21 | # django-tastypie < 0.10.0 if django < 1.5 22 | # Django 1.6: 23 | # - the way to do test has changed and might not be compatible 24 | # - BooleanField no longer default to False: 25 | # - need to add explicit default value in the models 26 | # - or always set a value for all the fields 27 | # Django 1.7: South is already integrated 28 | # Should be compatilbe util 'irc<10' but will also need 'jaraco.util<10.8' 29 | # WARNING: the db has to handle the COALESCE function 30 | requires = ['Django<1.6', 31 | 'South<=0.8.4', 32 | 'django-tastypie<0.10.0', 33 | 'pytz', 34 | 'oauth2', 35 | 'markdown', 36 | 'irc<8.9.1' 37 | ] 38 | 39 | extras_require = { 40 | 'test': ['mock' ], 41 | } 42 | 43 | tests_requires = requires + extras_require['test'] 44 | 45 | setup( 46 | name=name, 47 | version=version, 48 | description="Gandi's status web site", 49 | long_description=README + '\n\n' + CHANGES, 50 | classifiers=[ 51 | 'Development Status :: 5 - Production/Stable', 52 | 'Intended Audience :: Developers', 53 | 'Intended Audience :: System Administrators', 54 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 55 | 'Operating System :: Unix', 56 | 'Programming Language :: Python :: 2.7', 57 | 'Framework :: Django :: 1.4', 58 | 'Framework :: Django :: 1.5', 59 | ], 60 | author='Gandi', 61 | author_email='feedback@gandi.net', 62 | url='http://gandi.net/', 63 | license='GPL-3', 64 | include_package_data=True, 65 | install_requires=requires, 66 | extras_require=extras_require, 67 | tests_require=tests_requires, 68 | packages=find_packages(), 69 | entry_points={ 70 | 'console_scripts': [ 71 | 'baobab = baobab.bin.cmd_baobab:main', 72 | ], 73 | } 74 | ) 75 | --------------------------------------------------------------------------------