├── static ├── media │ ├── loading.gif │ └── mimic-III.png ├── bootstrap │ ├── css │ │ ├── footer.css │ │ ├── carousel.css │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap-theme.min.css.map │ │ └── bootstrap-theme.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ └── npm.js ├── fonts │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── js │ ├── analytics.js │ ├── sb-admin-2.js │ ├── custom.js │ ├── custom_dashboard.js │ ├── FileSaver.js │ ├── jquery.twbsPagination.js │ └── bootbox.js └── css │ ├── metisMenu.min.css │ ├── custom.css │ └── sb-admin-2.css ├── wsgi-sample.py ├── requirements.txt ├── wsgi.py ├── wsgi_querybuilder.ini ├── templates ├── base.html ├── user.html ├── css_js.html ├── dashboard.html └── login.html ├── README.md ├── config_sample.py ├── LICENSE ├── .gitignore ├── MIMIC_Table_desc.py ├── database.dump ├── Postgres.py └── manage.py /static/media/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/QueryBuilder-AWS/master/static/media/loading.gif -------------------------------------------------------------------------------- /static/media/mimic-III.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/QueryBuilder-AWS/master/static/media/mimic-III.png -------------------------------------------------------------------------------- /static/bootstrap/css/footer.css: -------------------------------------------------------------------------------- 1 | footer { 2 | background-color: #555; 3 | color: white; 4 | padding: 15px; 5 | } 6 | -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/QueryBuilder-AWS/master/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/QueryBuilder-AWS/master/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/QueryBuilder-AWS/master/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /wsgi-sample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | 5 | sys.path.append('/PATH/TO/Flask/') 6 | 7 | from manage import app 8 | -------------------------------------------------------------------------------- /static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/QueryBuilder-AWS/master/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/QueryBuilder-AWS/master/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Click==7.0 2 | Flask==1.0.2 3 | itsdangerous==1.1.0 4 | Jinja2==2.10.1 5 | MarkupSafe==1.1.0 6 | psycopg2==2.7.7 7 | Werkzeug==0.15.6 8 | requests==2.22.0 9 | -------------------------------------------------------------------------------- /static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/QueryBuilder-AWS/master/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIT-LCP/QueryBuilder-AWS/master/static/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # 3 | sys.path.append('/var/www/vhosts/querybuilder-lcp.mit.edu/Flask/') 4 | 5 | from manage import app 6 | from manage import app as application 7 | 8 | #if __name__ == "__main__": 9 | # app.run() 10 | # application.run() 11 | -------------------------------------------------------------------------------- /static/js/analytics.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 5 | 6 | ga('create', 'UA-87592301-2', 'auto'); 7 | ga('send', 'pageview'); -------------------------------------------------------------------------------- /wsgi_querybuilder.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | 3 | need-app = true 4 | wsgi-file = /PATH/TO/Flask/wsgi.py 5 | 6 | plugins = python3 7 | 8 | callable = app 9 | master = true 10 | processes = 5 11 | gid = publicusers 12 | 13 | socket = /etc/uwsgi.sockets/querybuilder_uwsgi.sock 14 | venv = /PATH/TO/Flask/python_env 15 | 16 | logto = /var/log/uwsgi/%n.log 17 | 18 | chmod-socket = 666 19 | die-on-term = true 20 | 21 | vacuum = true 22 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Query Builder 6 | 7 | {% include 'css_js.html' %} 8 | 9 | {% block local_css_js %} {% endblock %} 10 | 11 | 12 | 13 |
14 | {% block content %} {% endblock %} 15 |
16 | 17 | {% block base_js %} {% endblock %} 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /static/bootstrap/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Querybuilder 2 | 3 | This is the QueryBuilder build page. In order for this to work you must allow apache or NGINX to use a sucket with uwsgi. 4 | 5 | First one has to install and activate the python virtual enviroment. 6 | ``` 7 | virtualenv -p python3 venv 8 | source venv/bin/activate 9 | pip install -r requirements.txt 10 | ``` 11 | 12 | Once you do a git clone, you have to create the preview 100 rows for the databases you will be using. For that run the script `python MIMIC_Table_desc.py`. 13 | 14 | You need to set the credentials on the config.py, and create the database for the query history using the `database.dump` file. 15 | 16 | You can also run this as a stand alone flask application with `python manage.py`. 17 | 18 | -------------------------------------------------------------------------------- /config_sample.py: -------------------------------------------------------------------------------- 1 | class Config: 2 | def __init__(self, name = None, user = None, passwd = None): 3 | self.user = "username" 4 | self.passwd = "password" 5 | self.name = "QueryBuilder" 6 | 7 | def getUser(self): 8 | return self.user 9 | 10 | def getPassword(self): 11 | return self.passwd 12 | 13 | def getDBName(self): 14 | return self.name 15 | 16 | class DBConfig: 17 | def __init__(self, name = None, user = None, passwd = None): 18 | self.user = "mimicusername" 19 | self.passwd = "mimicpassword" 20 | self.name = "mimic" 21 | 22 | def getUser(self): 23 | return self.user 24 | 25 | def getPassword(self): 26 | return self.passwd 27 | 28 | def getDBName(self): 29 | return self.name 30 | -------------------------------------------------------------------------------- /static/css/metisMenu.min.css: -------------------------------------------------------------------------------- 1 | /* 2 | * metismenu - v1.1.3 3 | * Easy menu jQuery plugin for Twitter Bootstrap 3 4 | * https://github.com/onokumus/metisMenu 5 | * 6 | * Made by Osman Nuri Okumus 7 | * Under MIT License 8 | */ 9 | 10 | .arrow { 11 | float: right; 12 | line-height: 1.42857 13 | } 14 | .glyphicon.arrow:before { 15 | content: "\e079" 16 | } 17 | .active>a>.glyphicon.arrow:before { 18 | content: "\e114" 19 | } 20 | .fa.arrow:before { 21 | content: "\f104" 22 | } 23 | .active>a>.fa.arrow:before { 24 | content: "\f107" 25 | } 26 | .plus-times { 27 | float: right 28 | } 29 | .fa.plus-times:before { 30 | content: "\f067" 31 | } 32 | .active>a>.fa.plus-times { 33 | filter: progid: DXImageTransform.Microsoft.BasicImage(rotation=1); 34 | -webkit-transform: rotate(45deg); 35 | -moz-transform: rotate(45deg); 36 | -ms-transform: rotate(45deg); 37 | -o-transform: rotate(45deg); 38 | transform: rotate(45deg) 39 | } 40 | .plus-minus { 41 | float: right 42 | } 43 | .fa.plus-minus:before { 44 | content: "\f067" 45 | } 46 | .active>a>.fa.plus-minus:before { 47 | content: "\f068" 48 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MIT Laboratory for Computational Physiology 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /static/js/sb-admin-2.js: -------------------------------------------------------------------------------- 1 | //Loads the correct sidebar on window load, 2 | //collapses the sidebar on window resize. 3 | // Sets the min-height of #page-wrapper to window size 4 | $(function() { 5 | $(window).bind("load resize", function() { 6 | topOffset = 50; 7 | width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width; 8 | if (width < 768) { 9 | $('div.navbar-collapse').addClass('collapse'); 10 | topOffset = 100; // 2-row-menu 11 | } else { 12 | $('div.navbar-collapse').removeClass('collapse'); 13 | } 14 | 15 | height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1; 16 | height = height - topOffset; 17 | if (height < 1) height = 1; 18 | if (height > topOffset) { 19 | $("#page-wrapper").css("min-height", (height) + "px"); 20 | } 21 | }); 22 | 23 | var url = window.location; 24 | var element = $('ul.nav a').filter(function() { 25 | return this.href == url || url.href.indexOf(this.href) == 0; 26 | }).addClass('active').parent().parent().addClass('in').parent(); 27 | if (element.is('li')) { 28 | element.addClass('active'); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /static/css/custom.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | 3 | height: 100%; 4 | width: 100%; 5 | 6 | } 7 | .header { 8 | background-color: #dff0d8 9 | } 10 | 11 | .aside { 12 | position: fixed; 13 | height: 95%; 14 | overflow-y: scroll; 15 | padding-left:0px; 16 | } 17 | 18 | .bit-right { 19 | padding-right:30px; 20 | } 21 | 22 | .query-table { 23 | overflow-y:auto !important; 24 | height:50vh !important; 25 | padding-top: 15px; 26 | } 27 | 28 | .login_content { 29 | font-size : 120%; 30 | } 31 | 32 | 33 | .nav_head { 34 | margin-bottom: 0 !important; 35 | border-radius: 0 !important; 36 | } 37 | 38 | #sidebar-wrapper { 39 | position: fixed; 40 | height: 100%; 41 | overflow-y: auto; 42 | z-index: 1000; 43 | transition: all 0.4s ease 0s; 44 | } 45 | 46 | .side_nav{ 47 | padding-left: 0; 48 | } 49 | 50 | .full_row{ 51 | width: 100%; 52 | } 53 | 54 | .table_list { 55 | border: 1px solid ; 56 | } 57 | 58 | /*#wrapper { 59 | padding-left: 0; 60 | } 61 | 62 | #wrapper.active { 63 | position: relative; 64 | left: 250px; 65 | } 66 | */ 67 | /*#wrapper { 68 | padding-left: 250px; 69 | transition: all 0.4s ease 0s; 70 | } 71 | */ 72 | 73 | /*.user { 74 | font-size:18px; 75 | color: #9d9d9d;; 76 | }*/ 77 | 78 | /*#sidebar-wrapper { 79 | left: 0; 80 | } 81 | */ 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | env3/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | 92 | initial_testing.pyc 93 | *.pyc 94 | config.py 95 | wsgi.py 96 | templates/tables/* 97 | -------------------------------------------------------------------------------- /static/js/custom.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 5 | 6 | ga('create', 'UA-87592301-2', 'auto'); 7 | ga('send', 'pageview'); 8 | 9 | function addTab(evt, TabName) {//TAB_Content 10 | var oInput = document.getElementById('TabManager'),oChild; 11 | for(i = 0; i < oInput.childNodes.length; i++){ 12 | oInput.childNodes[i].className = ""; 13 | } 14 | document.getElementById(TabName.toLowerCase()).style.display = 'block'; 15 | document.getElementById(TabName.toLowerCase()).className = 'active'; 16 | document.getElementById('Query_tab1').className = ''; 17 | 18 | var coInput = document.getElementById('TAB_Content'),oChild; 19 | for(i = 0; i < coInput.childNodes.length; i++){ 20 | coInput.childNodes[i].className = 'tab-pane fade'; 21 | } 22 | document.getElementById(TabName.toUpperCase()).className = 'tab-pane fade in active'; 23 | // 24 | } 25 | 26 | function Remove_Tab(ID, event){ 27 | $('#'+ID.toLowerCase()).removeClass('active'); 28 | $('#'+ID.toLowerCase()).hide(); 29 | $('#'+ID.toUpperCase()).removeClass('in'); 30 | $('#'+ID.toUpperCase()).removeClass('active'); 31 | var oInput = document.getElementById('TabManager'),oChild; 32 | for(i = 0; i < oInput.childNodes.length; i++){ 33 | oInput.childNodes[i].className = ""; 34 | } 35 | var coInput = document.getElementById('TAB_Content'),oChild; 36 | for(i = 0; i < coInput.childNodes.length; i++){ 37 | coInput.childNodes[i].className = 'tab-pane fade'; 38 | } 39 | $('#Query_tab1').addClass('active'); 40 | $('#QUERY_TAB').addClass('in active'); 41 | event.stopPropagation(); 42 | } 43 | -------------------------------------------------------------------------------- /templates/user.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}User query history{% endblock %} 4 | 5 | {% block content %} 6 | {% if Error %} 7 | 8 | {% endif %} 9 | 24 | 25 |
26 |
27 |
28 |
29 |
30 |
31 |

User information

32 |
    33 |
  • Email: {{session['Email']}}
  • 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |

Last {{ Query_History | length }} queries

43 |
    44 | {% for query in Query_History %} 45 |
  • {{ query }}
  • 46 | {% endfor %} 47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 | {% endblock %} 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /templates/css_js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /static/bootstrap/css/carousel.css: -------------------------------------------------------------------------------- 1 | /* GLOBAL STYLES 2 | -------------------------------------------------- */ 3 | /* Padding below the footer and lighter body text */ 4 | 5 | body { 6 | padding-top: 3rem; 7 | padding-bottom: 3rem; 8 | color: #5a5a5a; 9 | } 10 | 11 | 12 | /* CUSTOMIZE THE CAROUSEL 13 | -------------------------------------------------- */ 14 | 15 | /* Carousel base class */ 16 | .carousel { 17 | margin-bottom: 4rem; 18 | } 19 | /* Since positioning the image, we need to help out the caption */ 20 | .carousel-caption { 21 | z-index: 10; 22 | bottom: 3rem; 23 | } 24 | 25 | /* Declare heights because of positioning of img element */ 26 | .carousel-item { 27 | height: 32rem; 28 | background-color: #777; 29 | } 30 | .carousel-item > img { 31 | position: absolute; 32 | top: 0; 33 | left: 0; 34 | min-width: 100%; 35 | height: 32rem; 36 | } 37 | 38 | 39 | /* MARKETING CONTENT 40 | -------------------------------------------------- */ 41 | 42 | /* Center align the text within the three columns below the carousel */ 43 | .marketing .col-lg-4 { 44 | margin-bottom: 1.5rem; 45 | text-align: center; 46 | } 47 | .marketing h2 { 48 | font-weight: normal; 49 | } 50 | .marketing .col-lg-4 p { 51 | margin-right: .75rem; 52 | margin-left: .75rem; 53 | } 54 | 55 | 56 | /* Featurettes 57 | ------------------------- */ 58 | 59 | .featurette-divider { 60 | margin: 5rem 0; /* Space out the Bootstrap
more */ 61 | } 62 | 63 | /* Thin out the marketing headings */ 64 | .featurette-heading { 65 | font-weight: 300; 66 | line-height: 1; 67 | letter-spacing: -.05rem; 68 | } 69 | 70 | 71 | /* RESPONSIVE CSS 72 | -------------------------------------------------- */ 73 | 74 | @media (min-width: 40em) { 75 | /* Bump up size of carousel content */ 76 | .carousel-caption p { 77 | margin-bottom: 1.25rem; 78 | font-size: 1.25rem; 79 | line-height: 1.4; 80 | } 81 | 82 | .featurette-heading { 83 | font-size: 50px; 84 | } 85 | } 86 | 87 | @media (min-width: 62em) { 88 | .featurette-heading { 89 | margin-top: 7rem; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /static/js/custom_dashboard.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | tab_list = ['q_home'] 3 | 4 | $("#Export").click(function(){ 5 | $("#loading").show(); 6 | 7 | x = document.getElementById("query").value; 8 | if(x != null){ 9 | $.ajax({ 10 | url : "/download_random_query", 11 | type : "POST", 12 | data : {Query : x}, 13 | success : function(File) { 14 | 15 | $("#loading").hide(); 16 | var NewFile = new Blob([File], {type: "text/csv;charset='utf-8'"}); 17 | saveAs(NewFile, "Result.csv"); 18 | 19 | } 20 | }); 21 | } 22 | }); 23 | 24 | $("#Execute").click(function(){ 25 | 26 | $('#result-content').html(''); 27 | $('#result-content2').html(''); 28 | $('#result-error').html(''); 29 | x = document.getElementById("query").value; 30 | $("#loading").show(); 31 | 32 | if(x != null){ 33 | var query = $.ajax({ 34 | url : "/random_query", 35 | type : "POST", 36 | data : {Query : x}, 37 | success : function(response) { 38 | $("#loading").hide(); 39 | Content = response['result']; 40 | if (Content.length > 2){ 41 | result = Content[0]; 42 | content2 = Content[1]; 43 | write = Content[2]; 44 | $('#result-content').html(result); 45 | $('#result-content2').html(content2); 46 | $('#result-error').html(''); 47 | } 48 | else{ 49 | Error_Query = Content[0]; 50 | $('#result-error').html(Error_Query); 51 | $('#result-content').html(''); 52 | $('#result-content2').html(''); 53 | } 54 | } 55 | }); 56 | } 57 | }); 58 | }); 59 | 60 | function Get_File(filename, TabName){ 61 | $.ajax({ 62 | url : "get_file", 63 | type : "POST", 64 | data : { Filename : filename }, 65 | success : function(File) { 66 | if ( $.inArray(TabName, tab_list) == -1 ) { 67 | $("#page-content-wrapper").append(File); 68 | tab_list.push(TabName); 69 | $('#TabManager').append(""); 70 | } 71 | } 72 | }); 73 | } 74 | 75 | function eventFire(el, etype){ 76 | if (el.fireEvent) { 77 | el.fireEvent('on' + etype); 78 | } else { 79 | var evObj = document.createEvent('Events'); 80 | evObj.initEvent(etype, true, false); 81 | el.dispatchEvent(evObj); 82 | } 83 | } 84 | 85 | function Remove_Tab(ID, event){ 86 | 87 | var index = tab_list.indexOf(ID); 88 | 89 | if (index > -1) { 90 | tab_list.splice(index, 1); 91 | } 92 | document.getElementById("LI_"+ID).remove(); 93 | document.getElementById(ID).remove(); 94 | eventFire(document.getElementById('Query_tab'), 'click'); 95 | $('#q_home').addClass(' active'); 96 | if ( !document.getElementById("q_home").classList.contains('active') ){ 97 | document.getElementById("q_home").classList.add('active'); 98 | } 99 | 100 | event.stopPropagation(); 101 | } 102 | -------------------------------------------------------------------------------- /MIMIC_Table_desc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | from Postgres import Database 4 | 5 | SCHEMA = 'mimiciii' 6 | DB_CONN = Database() 7 | TABLES = DB_CONN.get_tables(SCHEMA) 8 | DB_CONN.set_schema(SCHEMA) 9 | 10 | 11 | # table_description = get_table_description() 12 | # table_dict = format_tables_to_html(table_description) 13 | # print(table_dict['admissions']) 14 | # write_to_file(table_dict) 15 | 16 | 17 | def get_table_description(): 18 | """ 19 | Get all the names of the tables and exclude the partitioned chartevents 20 | Once the names of the tables are defined, get a description of the table 21 | 22 | Returns an array of arrays with the table properties 23 | E.g. 24 | ['admissions', 1, 'row_id', 'integer', 'YES'], 25 | ['admissions', 2, 'subject_id', 'integer', 'YES'], 26 | ... 27 | """ 28 | table_description = [] 29 | for index, line in enumerate(TABLES): 30 | info = '' 31 | if not line[0].startswith("chartevents_"): 32 | info = DB_CONN.describe_tables(line[0]) 33 | for indx, item in enumerate(info): 34 | info[indx] = list(item) 35 | info[indx].insert(0, line[0]) 36 | if info != '': 37 | table_description.append(info) 38 | return table_description 39 | 40 | 41 | def format_tables_to_html(description): 42 | """ 43 | Format the table description into a tab and adds a 100 lines preview into 44 | another tab. 45 | 46 | This is added to a dictionary which the index is the name of the table. 47 | The deictionary is returned to be saved into a file. 48 | """ 49 | table_to_save = {} 50 | for table in description: 51 | temp = "" 52 | temp += "
".format(table[0][0].upper()) 53 | temp += """""".format(table[0][0].upper(), table[0][0].upper()) 57 | temp += "
" 58 | temp += "
".format(table[0][0].upper()) 59 | temp += """ 60 | 61 | """ 62 | for column in table: 63 | temp += """ 64 | 65 | """.format(column[1], column[2], column[3], column[4]) 66 | temp += """
IDCOLUMN_NAMEDATA_TYPENULLABLE
{0}{1}{2}{3}
67 |
68 | \n""".format( 69 | table[0][0].upper()) 70 | desc, title, error = DB_CONN.get_table_preview(table[0][0]) 71 | for item1 in title: 72 | temp += "".format(item1) 73 | temp += "\n" 74 | for item2 in desc: 75 | temp += "\n" 76 | for line2 in item2: 77 | temp += "".format(line2) 78 | temp += "\n" 79 | temp += "
{0}
{0}
" 80 | table_to_save[table[0][0]] = temp 81 | return table_to_save 82 | 83 | 84 | def write_to_file(tables): 85 | """ 86 | Write the description and preview of a table to a sample file. 87 | """ 88 | for item in tables.keys(): 89 | file = open('templates/tables/{}_table_desc.html'.format(item), 'w') 90 | file.write(tables[item]) 91 | file.close() 92 | -------------------------------------------------------------------------------- /templates/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Login{% endblock %} 4 | 5 | {% block local_css_js %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% endblock %} 25 | 26 | {% block content %} 27 | 28 | {% if Error %} {% endif %} 29 | 30 | 31 | 46 |
47 | 48 |
49 |
50 | 55 |
56 | 57 |
58 | 63 |
64 |
65 | 66 | Execute Query 67 | Export Results 69 |
70 |
71 | 72 |
73 |
74 |
75 |
76 | 77 |
78 |
79 |
80 |
81 | {% endblock %} 82 | 83 | {% block base_js %} 84 | 85 | 86 | 87 | {% endblock %} 88 | -------------------------------------------------------------------------------- /database.dump: -------------------------------------------------------------------------------- 1 | -- 2 | -- PostgreSQL database dump 3 | -- 4 | 5 | -- Dumped from database version 9.6.12 6 | -- Dumped by pg_dump version 10.7 7 | 8 | SET statement_timeout = 0; 9 | SET lock_timeout = 0; 10 | SET idle_in_transaction_session_timeout = 0; 11 | SET client_encoding = 'UTF8'; 12 | SET standard_conforming_strings = on; 13 | SELECT pg_catalog.set_config('search_path', '', false); 14 | SET check_function_bodies = false; 15 | SET client_min_messages = warning; 16 | SET row_security = off; 17 | 18 | -- 19 | -- Name: Builder; Type: SCHEMA; Schema: -; Owner: qbuilder 20 | -- 21 | 22 | CREATE SCHEMA "Builder"; 23 | 24 | 25 | ALTER SCHEMA "Builder" OWNER TO qbuilder; 26 | 27 | -- 28 | -- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: 29 | -- 30 | 31 | CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; 32 | 33 | 34 | -- 35 | -- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: 36 | -- 37 | 38 | COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; 39 | 40 | 41 | SET default_tablespace = ''; 42 | 43 | SET default_with_oids = false; 44 | 45 | -- 46 | -- Name: Old_Users; Type: TABLE; Schema: Builder; Owner: mimicuser 47 | -- 48 | 49 | CREATE TABLE "Builder"."Old_Users" ( 50 | "ID" integer, 51 | "Name" character varying(60), 52 | "Last" character varying(30), 53 | "Email" character varying(50), 54 | "Username" character varying(50), 55 | "Date" timestamp without time zone, 56 | "LastLogin" date, 57 | "Works" character varying(50), 58 | "Admin" smallint, 59 | "Organization" text, 60 | "Department" text, 61 | "City" text, 62 | "State" text, 63 | "Country" text, 64 | "URL" text, 65 | "Pic" text, 66 | "Cert" text, 67 | "Institution" text, 68 | "Date_Approved" text, 69 | "General_Research_Area" text 70 | ); 71 | 72 | 73 | ALTER TABLE "Builder"."Old_Users" OWNER TO mimicuser; 74 | 75 | -- 76 | -- Name: Queries; Type: TABLE; Schema: Builder; Owner: qbuilder 77 | -- 78 | 79 | CREATE TABLE "Builder"."Queries" ( 80 | "ID" integer NOT NULL, 81 | "UID" bigint, 82 | "Query" character varying(2044) NOT NULL, 83 | "Date" timestamp without time zone DEFAULT now(), 84 | email text 85 | ); 86 | 87 | 88 | ALTER TABLE "Builder"."Queries" OWNER TO qbuilder; 89 | 90 | -- 91 | -- Name: Queries_ID_seq; Type: SEQUENCE; Schema: Builder; Owner: qbuilder 92 | -- 93 | 94 | CREATE SEQUENCE "Builder"."Queries_ID_seq" 95 | START WITH 1 96 | INCREMENT BY 1 97 | NO MINVALUE 98 | NO MAXVALUE 99 | CACHE 1; 100 | 101 | 102 | ALTER TABLE "Builder"."Queries_ID_seq" OWNER TO qbuilder; 103 | 104 | -- 105 | -- Name: Queries_ID_seq; Type: SEQUENCE OWNED BY; Schema: Builder; Owner: qbuilder 106 | -- 107 | 108 | ALTER SEQUENCE "Builder"."Queries_ID_seq" OWNED BY "Builder"."Queries"."ID"; 109 | 110 | 111 | -- 112 | -- Name: Sessions; Type: TABLE; Schema: Builder; Owner: qbuilder 113 | -- 114 | 115 | CREATE TABLE "Builder"."Sessions" ( 116 | "UID" integer, 117 | "SID" bigint NOT NULL, 118 | "Date" timestamp without time zone DEFAULT now(), 119 | email text 120 | ); 121 | 122 | 123 | ALTER TABLE "Builder"."Sessions" OWNER TO qbuilder; 124 | 125 | -- 126 | -- Name: Users; Type: TABLE; Schema: Builder; Owner: qbuilder 127 | -- 128 | 129 | CREATE TABLE "Builder"."Users" ( 130 | "ID" integer NOT NULL, 131 | "Name" character varying(60) NOT NULL, 132 | "Last" character varying(30), 133 | "Email" character varying(50) NOT NULL, 134 | "Username" character varying(50), 135 | "Date" timestamp without time zone DEFAULT now(), 136 | "LastLogin" date, 137 | "Works" character varying(50), 138 | "Admin" smallint, 139 | "Organization" text, 140 | "Department" text, 141 | "City" text, 142 | "State" text, 143 | "Country" text, 144 | "URL" text, 145 | "Pic" text, 146 | "Cert" text, 147 | "Institution" text, 148 | "Date_Approved" text, 149 | "General_Research_Area" text 150 | ); 151 | 152 | 153 | ALTER TABLE "Builder"."Users" OWNER TO qbuilder; 154 | 155 | -- 156 | -- Name: Users_ID_seq; Type: SEQUENCE; Schema: Builder; Owner: qbuilder 157 | -- 158 | 159 | CREATE SEQUENCE "Builder"."Users_ID_seq" 160 | START WITH 1 161 | INCREMENT BY 1 162 | NO MINVALUE 163 | NO MAXVALUE 164 | CACHE 1; 165 | 166 | 167 | ALTER TABLE "Builder"."Users_ID_seq" OWNER TO qbuilder; 168 | 169 | -- 170 | -- Name: Users_ID_seq; Type: SEQUENCE OWNED BY; Schema: Builder; Owner: qbuilder 171 | -- 172 | 173 | ALTER SEQUENCE "Builder"."Users_ID_seq" OWNED BY "Builder"."Users"."ID"; 174 | 175 | 176 | -- 177 | -- Name: Queries ID; Type: DEFAULT; Schema: Builder; Owner: qbuilder 178 | -- 179 | 180 | ALTER TABLE ONLY "Builder"."Queries" ALTER COLUMN "ID" SET DEFAULT nextval('"Builder"."Queries_ID_seq"'::regclass); 181 | 182 | 183 | -- 184 | -- Name: Users ID; Type: DEFAULT; Schema: Builder; Owner: qbuilder 185 | -- 186 | 187 | ALTER TABLE ONLY "Builder"."Users" ALTER COLUMN "ID" SET DEFAULT nextval('"Builder"."Users_ID_seq"'::regclass); 188 | 189 | 190 | -- 191 | -- Name: Users Users_pkey; Type: CONSTRAINT; Schema: Builder; Owner: qbuilder 192 | -- 193 | 194 | ALTER TABLE ONLY "Builder"."Users" 195 | ADD CONSTRAINT "Users_pkey" PRIMARY KEY ("ID"); 196 | 197 | 198 | -- 199 | -- Name: Queries unique_ID; Type: CONSTRAINT; Schema: Builder; Owner: qbuilder 200 | -- 201 | 202 | ALTER TABLE ONLY "Builder"."Queries" 203 | ADD CONSTRAINT "unique_ID" PRIMARY KEY ("ID"); 204 | 205 | 206 | -- 207 | -- Name: Sessions unique_SID; Type: CONSTRAINT; Schema: Builder; Owner: qbuilder 208 | -- 209 | 210 | ALTER TABLE ONLY "Builder"."Sessions" 211 | ADD CONSTRAINT "unique_SID" UNIQUE ("SID"); 212 | 213 | 214 | -- 215 | -- Name: SCHEMA "Builder"; Type: ACL; Schema: -; Owner: qbuilder 216 | -- 217 | 218 | GRANT ALL ON SCHEMA "Builder" TO mimicuser; 219 | 220 | 221 | -- 222 | -- PostgreSQL database dump complete 223 | -- 224 | -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Login{% endblock %} 4 | 5 | {% block content %} 6 |

Query Builder v1.2

7 | 8 | {% if Error %} 9 | 10 | {% endif %} 11 | 12 |
13 |
14 |
15 | 17 |
19 |
20 |
21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 |
33 | 34 |
35 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |

MIMIC III on Google cloud - BigQuery

55 |

56 | MIMIC III has been added to google cloud and is accesible by the tool "BigQuery". This is managed by the google group "mimic-users". For access add your google email address to your PhysioNet account and request access above the files section of the PhysioNet page https://physionet.org/content/mimiciii/1.4/#files. 57 |
58 |
59 |

60 | 74 |
75 |
76 |
77 |

Issue Tracker

78 | 79 | 80 |
81 |
82 |

83 |
84 |
85 |
86 |

MIMIC-III Critical Care Database

87 | 88 | 89 | 90 |
    91 |
  • it is freely available to researchers worldwide
  • 92 |
  • it encompasses a diverse and very large population of ICU patients
  • 93 |
  • it contains high temporal resolution data including lab results, electronic documentation, and bedside monitor trends and waveforms.
  • 94 |
95 |
96 |
97 | Mimic Flow Chart 98 |
99 |
100 |
101 |
102 | 103 | {% endblock %} 104 | -------------------------------------------------------------------------------- /static/js/FileSaver.js: -------------------------------------------------------------------------------- 1 | /* FileSaver.js 2 | * A saveAs() FileSaver implementation. 3 | * 1.3.2 4 | * 2016-06-16 18:25:19 5 | * 6 | * By Eli Grey, http://eligrey.com 7 | * License: MIT 8 | * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md 9 | */ 10 | 11 | /*global self */ 12 | /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ 13 | 14 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 15 | 16 | var saveAs = saveAs || (function(view) { 17 | "use strict"; 18 | // IE <10 is explicitly unsupported 19 | if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { 20 | return; 21 | } 22 | var 23 | doc = view.document 24 | // only get URL when necessary in case Blob.js hasn't overridden it yet 25 | , get_URL = function() { 26 | return view.URL || view.webkitURL || view; 27 | } 28 | , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") 29 | , can_use_save_link = "download" in save_link 30 | , click = function(node) { 31 | var event = new MouseEvent("click"); 32 | node.dispatchEvent(event); 33 | } 34 | , is_safari = /constructor/i.test(view.HTMLElement) 35 | , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) 36 | , throw_outside = function(ex) { 37 | (view.setImmediate || view.setTimeout)(function() { 38 | throw ex; 39 | }, 0); 40 | } 41 | , force_saveable_type = "application/octet-stream" 42 | // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to 43 | , arbitrary_revoke_timeout = 1000 * 40 // in ms 44 | , revoke = function(file) { 45 | var revoker = function() { 46 | if (typeof file === "string") { // file is an object URL 47 | get_URL().revokeObjectURL(file); 48 | } else { // file is a File 49 | file.remove(); 50 | } 51 | }; 52 | setTimeout(revoker, arbitrary_revoke_timeout); 53 | } 54 | , dispatch = function(filesaver, event_types, event) { 55 | event_types = [].concat(event_types); 56 | var i = event_types.length; 57 | while (i--) { 58 | var listener = filesaver["on" + event_types[i]]; 59 | if (typeof listener === "function") { 60 | try { 61 | listener.call(filesaver, event || filesaver); 62 | } catch (ex) { 63 | throw_outside(ex); 64 | } 65 | } 66 | } 67 | } 68 | , auto_bom = function(blob) { 69 | // prepend BOM for UTF-8 XML and text/* types (including HTML) 70 | // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF 71 | if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { 72 | return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); 73 | } 74 | return blob; 75 | } 76 | , FileSaver = function(blob, name, no_auto_bom) { 77 | if (!no_auto_bom) { 78 | blob = auto_bom(blob); 79 | } 80 | // First try a.download, then web filesystem, then object URLs 81 | var 82 | filesaver = this 83 | , type = blob.type 84 | , force = type === force_saveable_type 85 | , object_url 86 | , dispatch_all = function() { 87 | dispatch(filesaver, "writestart progress write writeend".split(" ")); 88 | } 89 | // on any filesys errors revert to saving with object URLs 90 | , fs_error = function() { 91 | if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { 92 | // Safari doesn't allow downloading of blob urls 93 | var reader = new FileReader(); 94 | reader.onloadend = function() { 95 | var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); 96 | var popup = view.open(url, '_blank'); 97 | if(!popup) view.location.href = url; 98 | url=undefined; // release reference before dispatching 99 | filesaver.readyState = filesaver.DONE; 100 | dispatch_all(); 101 | }; 102 | reader.readAsDataURL(blob); 103 | filesaver.readyState = filesaver.INIT; 104 | return; 105 | } 106 | // don't create more object URLs than needed 107 | if (!object_url) { 108 | object_url = get_URL().createObjectURL(blob); 109 | } 110 | if (force) { 111 | view.location.href = object_url; 112 | } else { 113 | var opened = view.open(object_url, "_blank"); 114 | if (!opened) { 115 | // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html 116 | view.location.href = object_url; 117 | } 118 | } 119 | filesaver.readyState = filesaver.DONE; 120 | dispatch_all(); 121 | revoke(object_url); 122 | } 123 | ; 124 | filesaver.readyState = filesaver.INIT; 125 | 126 | if (can_use_save_link) { 127 | object_url = get_URL().createObjectURL(blob); 128 | setTimeout(function() { 129 | save_link.href = object_url; 130 | save_link.download = name; 131 | click(save_link); 132 | dispatch_all(); 133 | revoke(object_url); 134 | filesaver.readyState = filesaver.DONE; 135 | }); 136 | return; 137 | } 138 | 139 | fs_error(); 140 | } 141 | , FS_proto = FileSaver.prototype 142 | , saveAs = function(blob, name, no_auto_bom) { 143 | return new FileSaver(blob, name || blob.name || "download", no_auto_bom); 144 | } 145 | ; 146 | // IE 10+ (native saveAs) 147 | if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { 148 | return function(blob, name, no_auto_bom) { 149 | name = name || blob.name || "download"; 150 | 151 | if (!no_auto_bom) { 152 | blob = auto_bom(blob); 153 | } 154 | return navigator.msSaveOrOpenBlob(blob, name); 155 | }; 156 | } 157 | 158 | FS_proto.abort = function(){}; 159 | FS_proto.readyState = FS_proto.INIT = 0; 160 | FS_proto.WRITING = 1; 161 | FS_proto.DONE = 2; 162 | 163 | FS_proto.error = 164 | FS_proto.onwritestart = 165 | FS_proto.onprogress = 166 | FS_proto.onwrite = 167 | FS_proto.onabort = 168 | FS_proto.onerror = 169 | FS_proto.onwriteend = 170 | null; 171 | 172 | return saveAs; 173 | }( 174 | typeof self !== "undefined" && self 175 | || typeof window !== "undefined" && window 176 | || this.content 177 | )); 178 | // `self` is undefined in Firefox for Android content script context 179 | // while `this` is nsIContentFrameMessageManager 180 | // with an attribute `content` that corresponds to the window 181 | 182 | if (typeof module !== "undefined" && module.exports) { 183 | module.exports.saveAs = saveAs; 184 | } else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) { 185 | define([], function() { 186 | return saveAs; 187 | }); 188 | } 189 | -------------------------------------------------------------------------------- /static/css/sb-admin-2.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com) 3 | * Code licensed under the Apache License v2.0. 4 | * For details, see http://www.apache.org/licenses/LICENSE-2.0. 5 | */ 6 | 7 | body { 8 | background-color: #f8f8f8; 9 | height: 100%; 10 | } 11 | 12 | #wrapper { 13 | width: 100%; 14 | } 15 | 16 | #page-wrapper { 17 | padding: 0 15px; 18 | min-height: 568px; 19 | background-color: #ffffff; 20 | } 21 | 22 | @media(min-width:768px) { 23 | #page-wrapper { 24 | position: inherit; 25 | margin: 0 0 0 240px; 26 | border-left: 1px #DFF0D8; 27 | } 28 | } 29 | 30 | .navbar-top-links { 31 | margin-right: 0; 32 | } 33 | 34 | .navbar-top-links li { 35 | display: inline-block; 36 | } 37 | 38 | .navbar-top-links li:last-child { 39 | margin-right: 15px; 40 | } 41 | 42 | .navbar-top-links li a { 43 | padding: 15px; 44 | min-height: 50px; 45 | } 46 | 47 | .navbar-top-links .dropdown-menu li { 48 | display: block; 49 | } 50 | 51 | .navbar-top-links .dropdown-menu li:last-child { 52 | margin-right: 0; 53 | } 54 | 55 | .navbar-top-links .dropdown-menu li a { 56 | padding: 3px 20px; 57 | min-height: 0; 58 | } 59 | 60 | .navbar-top-links .dropdown-menu li a div { 61 | white-space: normal; 62 | } 63 | 64 | .navbar-top-links .dropdown-messages, 65 | .navbar-top-links .dropdown-tasks, 66 | .navbar-top-links .dropdown-alerts { 67 | width: 310px; 68 | min-width: 0; 69 | } 70 | 71 | .navbar-top-links .dropdown-messages { 72 | margin-left: 5p 73 | x; 74 | } 75 | 76 | .navbar-top-links .dropdown-tasks { 77 | margin-left: -59px; 78 | } 79 | 80 | .navbar-top-links .dropdown-alerts { 81 | margin-left: -123px; 82 | } 83 | 84 | .navbar-top-links .dropdown-user { 85 | right: 0; 86 | left: auto; 87 | } 88 | 89 | .sidebar .sidebar-nav.navbar-collapse { 90 | padding-right: 0; 91 | padding-left: 0; 92 | height:auto; 93 | max-height:95vh; 94 | overflow:auto; 95 | overflow-y: auto; 96 | overflow-x: hidden; 97 | } 98 | 99 | .sidebar .sidebar-search { 100 | padding: 15px; 101 | } 102 | 103 | .sidebar ul li { 104 | border-bottom: 1px solid #e7e7e7; 105 | } 106 | 107 | .sidebar ul li a.active { 108 | background-color: #eee; 109 | } 110 | 111 | .sidebar .arrow { 112 | float: right; 113 | } 114 | 115 | .sidebar .fa.arrow:before { 116 | content: "\f104"; 117 | } 118 | 119 | .sidebar .active>a>.fa.arrow:before { 120 | content: "\f107"; 121 | } 122 | 123 | .sidebar .nav-second-level li, 124 | .sidebar .nav-third-level li { 125 | border-bottom: 0!important; 126 | } 127 | 128 | .sidebar .nav-second-level li a { 129 | padding-left: 37px; 130 | } 131 | 132 | .sidebar .nav-third-level li a { 133 | padding-left: 52px; 134 | } 135 | 136 | @media(min-width:768px) { 137 | .sidebar { 138 | z-index: 1; 139 | position: absolute; 140 | width: 240px; 141 | margin-top: 47px; 142 | } 143 | 144 | .navbar-top-links .dropdown-messages, 145 | .navbar-top-links .dropdown-tasks, 146 | .navbar-top-links .dropdown-alerts { 147 | margin-left: auto; 148 | } 149 | } 150 | 151 | .btn-outline { 152 | color: inherit; 153 | background-color: transparent; 154 | transition: all .5s; 155 | } 156 | 157 | .btn-primary.btn-outline { 158 | color: #428bca; 159 | } 160 | 161 | .btn-success.btn-outline { 162 | color: #5cb85c; 163 | } 164 | 165 | .btn-info.btn-outline { 166 | color: #5bc0de; 167 | } 168 | 169 | .btn-warning.btn-outline { 170 | color: #f0ad4e; 171 | } 172 | 173 | .btn-danger.btn-outline { 174 | color: #d9534f; 175 | } 176 | 177 | .btn-primary.btn-outline:hover, 178 | .btn-success.btn-outline:hover, 179 | .btn-info.btn-outline:hover, 180 | .btn-warning.btn-outline:hover, 181 | .btn-danger.btn-outline:hover { 182 | color: #fff; 183 | } 184 | 185 | .chat { 186 | margin: 0; 187 | padding: 0; 188 | list-style: none; 189 | } 190 | 191 | .chat li { 192 | margin-bottom: 10px; 193 | padding-bottom: 5px; 194 | border-bottom: 1px dotted #999; 195 | } 196 | 197 | .chat li.left .chat-body { 198 | margin-left: 60px; 199 | } 200 | 201 | .chat li.right .chat-body { 202 | margin-right: 60px; 203 | } 204 | 205 | .chat li .chat-body p { 206 | margin: 0; 207 | } 208 | 209 | .panel .slidedown .glyphicon, 210 | .chat .glyphicon { 211 | margin-right: 5px; 212 | } 213 | 214 | .chat-panel .panel-body { 215 | height: 350px; 216 | overflow-y: scroll; 217 | } 218 | 219 | .login-panel { 220 | margin-top: 25%; 221 | } 222 | 223 | .flot-chart { 224 | display: block; 225 | height: 400px; 226 | } 227 | 228 | .flot-chart-content { 229 | width: 100%; 230 | height: 100%; 231 | } 232 | 233 | .dataTables_wrapper { 234 | position: relative; 235 | clear: both; 236 | } 237 | 238 | table.dataTable thead .sorting, 239 | table.dataTable thead .sorting_asc, 240 | table.dataTable thead .sorting_desc, 241 | table.dataTable thead .sorting_asc_disabled, 242 | table.dataTable thead .sorting_desc_disabled { 243 | background: 0 0; 244 | } 245 | 246 | table.dataTable thead .sorting_asc:after { 247 | content: "\f0de"; 248 | float: right; 249 | font-family: fontawesome; 250 | } 251 | 252 | table.dataTable thead .sorting_desc:after { 253 | content: "\f0dd"; 254 | float: right; 255 | font-family: fontawesome; 256 | } 257 | 258 | table.dataTable thead .sorting:after { 259 | content: "\f0dc"; 260 | float: right; 261 | font-family: fontawesome; 262 | color: rgba(50,50,50,.5); 263 | } 264 | 265 | .btn-circle { 266 | width: 30px; 267 | height: 30px; 268 | padding: 6px 0; 269 | border-radius: 15px; 270 | text-align: center; 271 | font-size: 12px; 272 | line-height: 1.428571429; 273 | } 274 | 275 | .btn-circle.btn-lg { 276 | width: 50px; 277 | height: 50px; 278 | padding: 10px 16px; 279 | border-radius: 25px; 280 | font-size: 18px; 281 | line-height: 1.33; 282 | } 283 | 284 | .btn-circle.btn-xl { 285 | width: 70px; 286 | height: 70px; 287 | padding: 10px 16px; 288 | border-radius: 35px; 289 | font-size: 24px; 290 | line-height: 1.33; 291 | } 292 | 293 | .show-grid [class^=col-] { 294 | padding-top: 10px; 295 | padding-bottom: 10px; 296 | border: 1px solid #ddd; 297 | background-color: #eee!important; 298 | } 299 | 300 | .show-grid { 301 | margin: 15px 0; 302 | } 303 | 304 | .huge { 305 | font-size: 40px; 306 | } 307 | 308 | .panel-green { 309 | border-color: #5cb85c; 310 | } 311 | 312 | .panel-green .panel-heading { 313 | border-color: #5cb85c; 314 | color: #fff; 315 | background-color: #5cb85c; 316 | } 317 | 318 | .panel-green a { 319 | color: #5cb85c; 320 | } 321 | 322 | .panel-green a:hover { 323 | color: #3d8b3d; 324 | } 325 | 326 | .panel-red { 327 | border-color: #d9534f; 328 | } 329 | 330 | .panel-red .panel-heading { 331 | border-color: #d9534f; 332 | color: #fff; 333 | background-color: #d9534f; 334 | } 335 | 336 | .panel-red a { 337 | color: #d9534f; 338 | } 339 | 340 | .panel-red a:hover { 341 | color: #b52b27; 342 | } 343 | 344 | .panel-yellow { 345 | border-color: #f0ad4e; 346 | } 347 | 348 | .panel-yellow .panel-heading { 349 | border-color: #f0ad4e; 350 | color: #fff; 351 | background-color: #f0ad4e; 352 | } 353 | 354 | .panel-yellow a { 355 | color: #f0ad4e; 356 | } 357 | 358 | .panel-yellow a:hover { 359 | color: #df8a13; 360 | } 361 | 362 | -------------------------------------------------------------------------------- /Postgres.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | from sys import stderr as http_logger 4 | from psycopg2 import connect, Error 5 | from io import StringIO 6 | from config import DatabaseConfig, Config 7 | 8 | 9 | class UserModel: 10 | """ 11 | Store a user query history 12 | """ 13 | def __init__(self): 14 | db_info = Config() 15 | try: 16 | self.con = connect("dbname={0} user={1} host={2} password={3}\ 17 | ".format(db_info.get_db(), db_info.get_user(), 18 | db_info.get_host(), db_info.get_password())) 19 | self.cur = self.con.cursor() 20 | except Error as error: 21 | http_logger.write("Error connecting to the user database: {0}\ 22 | ".format(error)) 23 | 24 | def __del__(self): 25 | try: 26 | self.con.close() 27 | except Error as error: 28 | http_logger.write("Error deleting connection of the user to the \ 29 | DB: {0}".format(error)) 30 | 31 | def get_queries_from_email(self, email): 32 | """ 33 | Return the user querie history 34 | """ 35 | try: 36 | self.cur.execute("""SELECT "Query" FROM "Builder"."Queries" WHERE 37 | "email" = '%s' order by "Date" desc limit 10""" % email) 38 | return self.cur.fetchall() 39 | except Error as error: 40 | http_logger.write("\t*** ERROR *** Error getting the last \ 41 | queriess,\nEmail: {0}\nError: {1}\n".format( 42 | email, error)) 43 | return False 44 | 45 | def record_query(self, email, query): 46 | """ 47 | Record the query used by the user 48 | """ 49 | try: 50 | if "'" in query: 51 | query = query.replace("'", "''") 52 | self.cur.execute("""INSERT INTO "Builder"."Queries" ("email", 53 | "Query") VALUES ('%s', '%s')""" % (email, query)) 54 | self.con.commit() 55 | return True 56 | except Error as error: 57 | http_logger.write("\t*** ERROR *** Error inserting the query,\n\ 58 | Email: {0}\nQuery: {1}\nError: {2}\n".format(email, query, 59 | error)) 60 | return False 61 | 62 | 63 | class Database(): 64 | """ 65 | MIMIC database 66 | """ 67 | def __init__(self): 68 | db_info = DatabaseConfig() 69 | try: 70 | self.con = connect("dbname={0} user={1} host={2} password={3} \ 71 | options='-c statement_timeout=15min'".format( 72 | db_info.get_db(), db_info.get_user(), 73 | db_info.get_host(), db_info.get_password())) 74 | self.cur = self.con.cursor() 75 | except Error as error: 76 | if self.con: 77 | self.con.rollback() 78 | http_logger.write("Error connecting: {0}".format(error)) 79 | 80 | def __del__(self): 81 | try: 82 | self.con.close() 83 | except Error as error: 84 | http_logger.write("Error disconnecting: {0}".format(error)) 85 | 86 | def get_tables(self, db_name): 87 | """ 88 | Get table names from postgres using a schema 89 | """ 90 | try: 91 | self.cur.execute("SELECT table_name FROM \ 92 | information_schema.tables WHERE table_schema = '%s' ORDER BY \ 93 | table_name" % db_name) 94 | return self.cur.fetchall() 95 | except Error as error: 96 | http_logger.write("Error with tables: {0}".format(error)) 97 | return False 98 | 99 | def describe_tables(self, table): 100 | """ 101 | Get table's information 102 | """ 103 | try: 104 | self.cur.execute("SELECT ordinal_position, column_name, \ 105 | data_type, is_nullable FROM information_schema.columns WHERE \ 106 | table_name ='%s'" % table) 107 | return self.cur.fetchall() 108 | except Error as error: 109 | http_logger.write("Error describing tables: {0}".format(error)) 110 | return False 111 | 112 | def random_query(self, query): 113 | """ 114 | Execute a random query done by a user 115 | """ 116 | try: 117 | http_logger.write("Query -- : {0}".format(query)) 118 | if "information_schema" in query or "pg_" in query: 119 | return [["Bad Query"], ], ("Bad Query",), False 120 | try: 121 | self.cur.execute("%s" % query) 122 | except Error as error: 123 | http_logger.write("Error describing tables: {0}".format(error)) 124 | return False, False, error 125 | listing = [] 126 | if self.cur.description is not None: 127 | for item in self.cur.description: 128 | listing.append(item[0]) 129 | 130 | result = self.cur.fetchmany(500) 131 | 132 | return result, listing, False 133 | except Error as error: 134 | http_logger.write("Error describing tables: %s" % error) 135 | return False, False, error 136 | 137 | def get_table_preview(self, table): 138 | """ 139 | Get 100 lines of preview from DB 140 | """ 141 | try: 142 | query = "SELECT * FROM " + table + " LIMIT 100" 143 | self.cur.execute(query) 144 | listing = [] 145 | if self.cur.description is not None: 146 | for item in self.cur.description: 147 | listing.append(item[0]) 148 | return self.cur.fetchmany(100), listing, False 149 | except Error as error: 150 | http_logger.write("Error describing tables: %s" % error) 151 | return False, False, error 152 | 153 | def random_query_download(self, query): 154 | """ 155 | Execute a random query done by a user to download 156 | """ 157 | try: 158 | if "information_schema" in query or "pg_" in query: 159 | return [["Bad Query"], ], ("Bad Query",), False 160 | text_stream = StringIO() 161 | if query.rstrip()[-1] == ';': 162 | query = query.rstrip()[:-1] 163 | copy_query = "COPY ({}) TO STDOUT WITH CSV HEADER DELIMITER ','\ 164 | ".format(query) 165 | self.cur.copy_expert(copy_query, text_stream) 166 | tmp = 0 167 | temp = u'' 168 | text_stream.seek(0) 169 | while tmp < 5000: 170 | try: 171 | temp += next(text_stream) 172 | tmp += 1 173 | except: 174 | tmp = 5000 175 | return temp, False 176 | except Error as error: 177 | http_logger.write("\t*** ERROR *** Error executing a random query \ 178 | to download.\nQuery: {0}\nError: {1}".format( 179 | query, error)) 180 | return False, error 181 | 182 | def set_schema(self, schema): 183 | """ 184 | Set the database schema 185 | """ 186 | try: 187 | self.cur.execute("SET search_path TO " + schema) 188 | self.cur.execute("SET statement_timeout = '15min'") 189 | self.con.commit() 190 | except Error as error: 191 | http_logger.write("Error setting schema: {0}".format(error)) 192 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | from datetime import datetime 4 | from functools import wraps 5 | from os import getcwd 6 | from re import search 7 | 8 | from requests.auth import HTTPBasicAuth 9 | from requests import get, codes 10 | from flask import (Flask, render_template, redirect, session, request, jsonify, 11 | current_app, url_for) 12 | 13 | from Postgres import UserModel, Database 14 | 15 | app = Flask(__name__) 16 | app.debug = True 17 | app.secret_key = 'mf}7WwDxTmpULrYgSWzfxutj|zDo1n(h+abvEa&$aM)?O$8R>qUtE)?CyOQ)*6YwADN!IHLy+TE^K1>>8^riVxe**JBH!+N' 18 | 19 | 20 | def login_required(function): 21 | """ 22 | Wrapper function to force login 23 | """ 24 | @wraps(function) 25 | def wrap(*args, **kwargs): 26 | # if user is not logged in, redirect to login page 27 | if ('Email' not in session) or ('URL' not in session): 28 | return redirect(url_for('login')) 29 | return function(*args, **kwargs) 30 | return wrap 31 | 32 | 33 | def auth(email, passwd): 34 | """ 35 | Authentication function against mimiciii 36 | """ 37 | url = 'https://physionet.org/files/mimiciii/1.4/' 38 | headers = {'User-Agent': 'Wget/1.18', 'Application': 'QueryBuilder'} 39 | response = get(url, auth=HTTPBasicAuth(email, passwd), headers=headers) 40 | 41 | if response.status_code == codes.ok: 42 | app.logger.info("User {} loggin complete".format(email)) 43 | elif response.status_code == codes.forbidden: 44 | app.logger.info("User {} does not have access to MIMIC".format(email)) 45 | elif response.status_code == codes.unauthorized: 46 | app.logger.info("Invalid credentials for {}".format(email)) 47 | else: 48 | app.logger.info("Error logging in user {0}\nReason {0}\n {1}".format( 49 | email, response.reason, response.status_code)) 50 | response.raise_for_status() 51 | return response.status_code 52 | 53 | 54 | @app.route("/", methods=['POST', 'GET']) 55 | @app.route("/index.html", methods=['POST', 'GET']) 56 | @app.route("/index.cgi", methods=['POST', 'GET']) 57 | @app.route("/index", methods=['POST', 'GET']) 58 | @app.route("/login.html", methods=['POST', 'GET']) 59 | @app.route("/login.cgi", methods=['POST', 'GET']) 60 | @app.route("/login", methods=['POST', 'GET']) 61 | def login(): 62 | """ 63 | Login view 64 | """ 65 | if request.method == 'POST': 66 | email = request.form.get('Email', None).lower() 67 | passwd = request.form.get('Password', None) 68 | now = datetime.now() 69 | if email and passwd and valid_email(email): 70 | app.logger.info('{} is trying to log into querybuilder.'.format( 71 | email)) 72 | code = auth(email, passwd) 73 | if code in ['200', 200]: 74 | app.logger.info('Generating session for - {}.'.format(email)) 75 | session["Email"] = email 76 | session["Date"] = now 77 | session['URL'] = "https://querybuilder-lcp.mit.edu" 78 | return redirect('dashboard') 79 | app.logger.info('{1}: Incorrect password or username: {0}'.format( 80 | email, code)) 81 | return render_template('login.html', Error='Incorrect username or password, please try again.') 82 | elif not valid_email(email): 83 | app.logger.info('Invalid email {}'.format(email)) 84 | return render_template('login.html', Error='Invalid email') 85 | return render_template('login.html') 86 | 87 | 88 | @app.route("/dashboard.cgi") 89 | @app.route("/dashboard") 90 | @login_required 91 | def dashboard(): 92 | """ 93 | Querybuilder dashboard 94 | """ 95 | email = session['Email'] 96 | 97 | table_names = ['ADMISSIONS', 'CALLOUT', 'CAREGIVERS', 'CHARTEVENTS', 98 | 'CPTEVENTS', 'D_CPT', 'D_ICD_DIAGNOSES', 'D_ICD_PROCEDURES', 99 | 'D_ITEMS', 'D_LABITEMS', 'DATETIMEEVENTS', 'DIAGNOSES_ICD', 100 | 'DRGCODES', 'ICUSTAYS', 'INPUTEVENTS_CV', 'INPUTEVENTS_MV', 101 | 'LABEVENTS', 'MICROBIOLOGYEVENTS', 'NOTEEVENTS', 102 | 'OUTPUTEVENTS', 'PATIENTS', 'PRESCRIPTIONS', 103 | 'PROCEDUREEVENTS_MV', 'PROCEDURES_ICD', 'SERVICES', 104 | 'TRANSFERS'] 105 | files = ['admissions_table_desc.html', 'callout_table_desc.html', 106 | 'caregivers_table_desc.html', 'chartevents_table_desc.html', 107 | 'cptevents_table_desc.html', 'd_cpt_table_desc.html', 108 | 'd_icd_diagnoses_table_desc.html', 109 | 'd_icd_procedures_table_desc.html', 'd_items_table_desc.html', 110 | 'd_labitems_table_desc.html', 'datetimeevents_table_desc.html', 111 | 'diagnoses_icd_table_desc.html', 'drgcodes_table_desc.html', 112 | 'icustays_table_desc.html', 'inputevents_cv_table_desc.html', 113 | 'inputevents_mv_table_desc.html', 'labevents_table_desc.html', 114 | 'microbiologyevents_table_desc.html', 115 | 'noteevents_table_desc.html', 'outputevents_table_desc.html', 116 | 'patients_table_desc.html', 'prescriptions_table_desc.html', 117 | 'procedureevents_mv_table_desc.html', 118 | 'procedures_icd_table_desc.html', 'services_table_desc.html', 119 | 'transfers_table_desc.html'] 120 | 121 | app.logger.info('User - {} is in the dashboard.'.format(email)) 122 | return render_template('dashboard.html', Tables=table_names, User=email, 123 | Files=files) 124 | 125 | 126 | @app.route("/get_file", methods=['POST', 'GET']) 127 | @login_required 128 | def get_file(): 129 | """ 130 | Render table description file 131 | """ 132 | if 'Email' not in session and 'URL' not in session: 133 | return redirect('login') 134 | 135 | app.logger.info(getcwd()) 136 | filename = request.form.get('Filename', None) 137 | return current_app.open_resource('templates/tables/'+filename).read() 138 | 139 | 140 | @app.route("/User") 141 | @login_required 142 | def user(): 143 | """ 144 | User query history page 145 | """ 146 | email = session['Email'] 147 | query = [] 148 | query_history = UserModel().get_queries_from_email(email) 149 | for item in query_history: 150 | query.append(item[0]) 151 | return render_template('user.html', Query_History=query, User=email) 152 | 153 | 154 | @app.route("/random_query", methods=['POST', 'GET']) 155 | @login_required 156 | def random_query(): 157 | """ 158 | Run a random query 159 | """ 160 | query = request.form.get('Query', None) 161 | app.logger.info('User: {0} ran this query:\n{1}\n'.format( 162 | session['Email'], query)) 163 | 164 | data = Database() 165 | data.set_schema('mimiciii') 166 | UserModel().record_query(session['Email'], query) 167 | result, title, error = data.random_query(query) 168 | if error: 169 | app.logger.info('There was an error in the query:\n{}\n'.format( 170 | error)) 171 | query_error = """
172 |
173 | An error was encountered from the query
174 |

Your query was:

{}

175 |

The error is:

""".format( 176 | query.rstrip()) 177 | for line in str(error).split("\n"): 178 | query_error += "

{}

".format(line) 179 | query_error += """
180 |
""" 181 | return jsonify(result=[query_error]) 182 | 183 | max_rows = 50 184 | content = "" 185 | counter = 0 186 | head = "" 187 | foot = "" 188 | for item in title: 189 | head += "{}".format(item) 190 | foot += "{}".format(item) 191 | head += "" 192 | foot += "" 193 | body = "" 194 | for idx, item in enumerate(result): 195 | body += "" 196 | for row in item: 197 | body += "{}".format(row) 198 | body += "" 199 | if ((idx + 1) % max_rows == 0) or (idx + 1 == len(result)): 200 | counter+=1 201 | body += "" 202 | if counter == 1: 203 | content += "
\ 204 | {1}{2}{3}
".format(counter, head, foot, body) 205 | else: 206 | content += "".format(counter, head, foot, body) 208 | body = "" 209 | if len(result) == 0: 210 | counter += 1 211 | content += "
\ 212 | {1}
".format(counter, head) 213 | 214 | write = "" 215 | if len(result) == 1: 216 | write = "Showing only 1 result." 217 | elif len(result) == 500: 218 | write = "Showing only 500 results. The rest were omitted" 219 | elif len(result) == 0: 220 | write = "Showing 0 results." 221 | else: 222 | write = "Showing only {} results.".format(len(result)) 223 | 224 | content2 = """ 225 | """ 243 | return jsonify(result=[content, content2, write]) 244 | 245 | 246 | @app.route("/download_random_query", methods=['POST', 'GET']) 247 | @login_required 248 | def download_random_query(): 249 | """ 250 | Download a random query 251 | """ 252 | query = request.form.get('Query', None) 253 | db_connection = Database() 254 | db_connection.set_schema('mimiciii') 255 | result, error = db_connection.random_query_download(query.rstrip()) 256 | app.logger.info('User - {0} ran this query to download:\n{1}\n'.format( 257 | session['Email'], query)) 258 | if error: 259 | app.logger.info('There was an error in the query:\n{}\n'.format( 260 | error)) 261 | return error 262 | return result 263 | 264 | 265 | @app.route("/logout") 266 | @app.route("/logout.cgi") 267 | def logout(): 268 | """ 269 | Logout function 270 | """ 271 | session.clear() 272 | return redirect('login') 273 | 274 | 275 | def valid_email(email): 276 | """ 277 | Validates emails 278 | """ 279 | regex = r'\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$' 280 | 281 | if not search(regex, email): 282 | app.logger.info("Invalid email: {}".format(email)) 283 | return False 284 | return True 285 | 286 | 287 | if __name__ == "__main__": 288 | app.jinja_env.auto_reload = True 289 | app.run(host='0.0.0.0', port=8084, threaded=True, debug=True) 290 | -------------------------------------------------------------------------------- /static/js/jquery.twbsPagination.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery pagination plugin v1.4.2 3 | * http://josecebe.github.io/twbs-pagination/ 4 | * 5 | * Copyright 2014-2018, Eugene Simakin 6 | * Released under Apache 2.0 license 7 | * http://apache.org/licenses/LICENSE-2.0.html 8 | */ 9 | (function ($, window, document, undefined) { 10 | 11 | 'use strict'; 12 | 13 | var old = $.fn.twbsPagination; 14 | 15 | // PROTOTYPE AND CONSTRUCTOR 16 | 17 | var TwbsPagination = function (element, options) { 18 | this.$element = $(element); 19 | this.options = $.extend({}, $.fn.twbsPagination.defaults, options); 20 | 21 | if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) { 22 | throw new Error('Start page option is incorrect'); 23 | } 24 | 25 | this.options.totalPages = parseInt(this.options.totalPages); 26 | if (isNaN(this.options.totalPages)) { 27 | throw new Error('Total pages option is not correct!'); 28 | } 29 | 30 | this.options.visiblePages = parseInt(this.options.visiblePages); 31 | if (isNaN(this.options.visiblePages)) { 32 | throw new Error('Visible pages option is not correct!'); 33 | } 34 | 35 | if (this.options.beforePageClick instanceof Function) { 36 | this.$element.first().on('beforePage', this.options.beforePageClick); 37 | } 38 | 39 | if (this.options.onPageClick instanceof Function) { 40 | this.$element.first().on('page', this.options.onPageClick); 41 | } 42 | 43 | // hide if only one page exists 44 | if (this.options.hideOnlyOnePage && this.options.totalPages == 1) { 45 | if (this.options.initiateStartPageClick) { 46 | this.$element.trigger('page', 1); 47 | } 48 | return this; 49 | } 50 | 51 | if (this.options.href) { 52 | this.options.startPage = this.getPageFromQueryString(); 53 | if (!this.options.startPage) { 54 | this.options.startPage = 1; 55 | } 56 | } 57 | 58 | var tagName = (typeof this.$element.prop === 'function') ? 59 | this.$element.prop('tagName') : this.$element.attr('tagName'); 60 | 61 | if (tagName === 'UL') { 62 | this.$listContainer = this.$element; 63 | } else { 64 | var elements = this.$element; 65 | var $newListContainer = $([]); 66 | elements.each(function(index) { 67 | var $newElem = $(""); 68 | $(this).append($newElem); 69 | $newListContainer.push($newElem[0]); 70 | }); 71 | this.$listContainer = $newListContainer; 72 | this.$element = $newListContainer; 73 | } 74 | 75 | this.$listContainer.addClass(this.options.paginationClass); 76 | 77 | if (this.options.initiateStartPageClick) { 78 | this.show(this.options.startPage); 79 | } else { 80 | this.currentPage = this.options.startPage; 81 | this.render(this.getPages(this.options.startPage)); 82 | this.setupEvents(); 83 | } 84 | 85 | return this; 86 | }; 87 | 88 | TwbsPagination.prototype = { 89 | 90 | constructor: TwbsPagination, 91 | 92 | destroy: function () { 93 | this.$element.empty(); 94 | this.$element.removeData('twbs-pagination'); 95 | this.$element.off('page'); 96 | 97 | return this; 98 | }, 99 | 100 | show: function (page) { 101 | if (page < 1 || page > this.options.totalPages) { 102 | throw new Error('Page is incorrect.'); 103 | } 104 | this.currentPage = page; 105 | 106 | this.$element.trigger('beforePage', page); 107 | 108 | var pages = this.getPages(page); 109 | this.render(pages); 110 | this.setupEvents(); 111 | 112 | this.$element.trigger('page', page); 113 | 114 | return pages; 115 | }, 116 | 117 | enable: function () { 118 | this.show(this.currentPage); 119 | }, 120 | 121 | disable: function () { 122 | var _this = this; 123 | this.$listContainer.off('click').on('click', 'li', function (evt) { 124 | evt.preventDefault(); 125 | }); 126 | this.$listContainer.children().each(function () { 127 | var $this = $(this); 128 | if (!$this.hasClass(_this.options.activeClass)) { 129 | $(this).addClass(_this.options.disabledClass); 130 | } 131 | }); 132 | }, 133 | 134 | buildListItems: function (pages) { 135 | var listItems = []; 136 | 137 | if (this.options.first) { 138 | listItems.push(this.buildItem('first', 1)); 139 | } 140 | 141 | if (this.options.prev) { 142 | var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1; 143 | listItems.push(this.buildItem('prev', prev)); 144 | } 145 | 146 | for (var i = 0; i < pages.numeric.length; i++) { 147 | listItems.push(this.buildItem('page', pages.numeric[i])); 148 | } 149 | 150 | if (this.options.next) { 151 | var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages; 152 | listItems.push(this.buildItem('next', next)); 153 | } 154 | 155 | if (this.options.last) { 156 | listItems.push(this.buildItem('last', this.options.totalPages)); 157 | } 158 | 159 | return listItems; 160 | }, 161 | 162 | buildItem: function (type, page) { 163 | var $itemContainer = $('
  • '), 164 | $itemContent = $(''), 165 | itemText = this.options[type] ? this.makeText(this.options[type], page) : page; 166 | 167 | $itemContainer.addClass(this.options[type + 'Class']); 168 | $itemContainer.data('page', page); 169 | $itemContainer.data('page-type', type); 170 | $itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText)); 171 | 172 | return $itemContainer; 173 | }, 174 | 175 | getPages: function (currentPage) { 176 | var pages = []; 177 | 178 | var half = Math.floor(this.options.visiblePages / 2); 179 | var start = currentPage - half + 1 - this.options.visiblePages % 2; 180 | var end = currentPage + half; 181 | 182 | var visiblePages = this.options.visiblePages; 183 | if (visiblePages > this.options.totalPages) { 184 | visiblePages = this.options.totalPages; 185 | } 186 | 187 | // handle boundary case 188 | if (start <= 0) { 189 | start = 1; 190 | end = visiblePages; 191 | } 192 | if (end > this.options.totalPages) { 193 | start = this.options.totalPages - visiblePages + 1; 194 | end = this.options.totalPages; 195 | } 196 | 197 | var itPage = start; 198 | while (itPage <= end) { 199 | pages.push(itPage); 200 | itPage++; 201 | } 202 | 203 | return {"currentPage": currentPage, "numeric": pages}; 204 | }, 205 | 206 | render: function (pages) { 207 | var _this = this; 208 | this.$listContainer.children().remove(); 209 | var items = this.buildListItems(pages); 210 | $.each(items, function(key, item){ 211 | _this.$listContainer.append(item); 212 | }); 213 | 214 | this.$listContainer.children().each(function () { 215 | var $this = $(this), 216 | pageType = $this.data('page-type'); 217 | 218 | switch (pageType) { 219 | case 'page': 220 | if ($this.data('page') === pages.currentPage) { 221 | $this.addClass(_this.options.activeClass); 222 | } 223 | break; 224 | case 'first': 225 | $this.toggleClass(_this.options.disabledClass, pages.currentPage === 1); 226 | break; 227 | case 'last': 228 | $this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages); 229 | break; 230 | case 'prev': 231 | $this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1); 232 | break; 233 | case 'next': 234 | $this.toggleClass(_this.options.disabledClass, 235 | !_this.options.loop && pages.currentPage === _this.options.totalPages); 236 | break; 237 | default: 238 | break; 239 | } 240 | 241 | }); 242 | }, 243 | 244 | setupEvents: function () { 245 | var _this = this; 246 | this.$listContainer.off('click').on('click', 'li', function (evt) { 247 | var $this = $(this); 248 | if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) { 249 | return false; 250 | } 251 | // Prevent click event if href is not set. 252 | !_this.options.href && evt.preventDefault(); 253 | _this.show(parseInt($this.data('page'))); 254 | }); 255 | }, 256 | 257 | changeTotalPages: function(totalPages, currentPage) { 258 | this.options.totalPages = totalPages; 259 | return this.show(currentPage); 260 | }, 261 | 262 | makeHref: function (page) { 263 | return this.options.href ? this.generateQueryString(page) : "#"; 264 | }, 265 | 266 | makeText: function (text, page) { 267 | return text.replace(this.options.pageVariable, page) 268 | .replace(this.options.totalPagesVariable, this.options.totalPages) 269 | }, 270 | 271 | getPageFromQueryString: function (searchStr) { 272 | var search = this.getSearchString(searchStr), 273 | regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'), 274 | page = regex.exec(search); 275 | if (!page || !page[2]) { 276 | return null; 277 | } 278 | page = decodeURIComponent(page[2]); 279 | page = parseInt(page); 280 | if (isNaN(page)) { 281 | return null; 282 | } 283 | return page; 284 | }, 285 | 286 | generateQueryString: function (pageNumber, searchStr) { 287 | var search = this.getSearchString(searchStr), 288 | regex = new RegExp(this.options.pageVariable + '=*[^&#]*'); 289 | if (!search) return ''; 290 | return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber); 291 | }, 292 | 293 | getSearchString: function (searchStr) { 294 | var search = searchStr || window.location.search; 295 | if (search === '') { 296 | return null; 297 | } 298 | if (search.indexOf('?') === 0) search = search.substr(1); 299 | return search; 300 | }, 301 | 302 | getCurrentPage: function () { 303 | return this.currentPage; 304 | }, 305 | 306 | getTotalPages: function () { 307 | return this.options.totalPages; 308 | } 309 | }; 310 | 311 | // PLUGIN DEFINITION 312 | 313 | $.fn.twbsPagination = function (option) { 314 | var args = Array.prototype.slice.call(arguments, 1); 315 | var methodReturn; 316 | 317 | var $this = $(this); 318 | var data = $this.data('twbs-pagination'); 319 | var options = typeof option === 'object' ? option : {}; 320 | 321 | if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) )); 322 | if (typeof option === 'string') methodReturn = data[ option ].apply(data, args); 323 | 324 | return ( methodReturn === undefined ) ? $this : methodReturn; 325 | }; 326 | 327 | $.fn.twbsPagination.defaults = { 328 | totalPages: 1, 329 | startPage: 1, 330 | visiblePages: 5, 331 | initiateStartPageClick: true, 332 | hideOnlyOnePage: false, 333 | href: false, 334 | pageVariable: '{{page}}', 335 | totalPagesVariable: '{{total_pages}}', 336 | page: null, 337 | first: 'First', 338 | prev: 'Previous', 339 | next: 'Next', 340 | last: 'Last', 341 | loop: false, 342 | beforePageClick: null, 343 | onPageClick: null, 344 | paginationClass: 'pagination', 345 | nextClass: 'page-item next', 346 | prevClass: 'page-item prev', 347 | lastClass: 'page-item last', 348 | firstClass: 'page-item first', 349 | pageClass: 'page-item', 350 | activeClass: 'active', 351 | disabledClass: 'disabled', 352 | anchorClass: 'page-link' 353 | }; 354 | 355 | $.fn.twbsPagination.Constructor = TwbsPagination; 356 | 357 | $.fn.twbsPagination.noConflict = function () { 358 | $.fn.twbsPagination = old; 359 | return this; 360 | }; 361 | 362 | $.fn.twbsPagination.version = "1.4.2"; 363 | 364 | })(window.jQuery, window, document); 365 | -------------------------------------------------------------------------------- /static/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ -------------------------------------------------------------------------------- /static/bootstrap/css/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} -------------------------------------------------------------------------------- /static/bootstrap/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | .btn-default, 7 | .btn-primary, 8 | .btn-success, 9 | .btn-info, 10 | .btn-warning, 11 | .btn-danger { 12 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 13 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 14 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | } 16 | .btn-default:active, 17 | .btn-primary:active, 18 | .btn-success:active, 19 | .btn-info:active, 20 | .btn-warning:active, 21 | .btn-danger:active, 22 | .btn-default.active, 23 | .btn-primary.active, 24 | .btn-success.active, 25 | .btn-info.active, 26 | .btn-warning.active, 27 | .btn-danger.active { 28 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 29 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | } 31 | .btn-default.disabled, 32 | .btn-primary.disabled, 33 | .btn-success.disabled, 34 | .btn-info.disabled, 35 | .btn-warning.disabled, 36 | .btn-danger.disabled, 37 | .btn-default[disabled], 38 | .btn-primary[disabled], 39 | .btn-success[disabled], 40 | .btn-info[disabled], 41 | .btn-warning[disabled], 42 | .btn-danger[disabled], 43 | fieldset[disabled] .btn-default, 44 | fieldset[disabled] .btn-primary, 45 | fieldset[disabled] .btn-success, 46 | fieldset[disabled] .btn-info, 47 | fieldset[disabled] .btn-warning, 48 | fieldset[disabled] .btn-danger { 49 | -webkit-box-shadow: none; 50 | box-shadow: none; 51 | } 52 | .btn-default .badge, 53 | .btn-primary .badge, 54 | .btn-success .badge, 55 | .btn-info .badge, 56 | .btn-warning .badge, 57 | .btn-danger .badge { 58 | text-shadow: none; 59 | } 60 | .btn:active, 61 | .btn.active { 62 | background-image: none; 63 | } 64 | .btn-default { 65 | text-shadow: 0 1px 0 #fff; 66 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 67 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 68 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 69 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 70 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 71 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 72 | background-repeat: repeat-x; 73 | border-color: #dbdbdb; 74 | border-color: #ccc; 75 | } 76 | .btn-default:hover, 77 | .btn-default:focus { 78 | background-color: #e0e0e0; 79 | background-position: 0 -15px; 80 | } 81 | .btn-default:active, 82 | .btn-default.active { 83 | background-color: #e0e0e0; 84 | border-color: #dbdbdb; 85 | } 86 | .btn-default.disabled, 87 | .btn-default[disabled], 88 | fieldset[disabled] .btn-default, 89 | .btn-default.disabled:hover, 90 | .btn-default[disabled]:hover, 91 | fieldset[disabled] .btn-default:hover, 92 | .btn-default.disabled:focus, 93 | .btn-default[disabled]:focus, 94 | fieldset[disabled] .btn-default:focus, 95 | .btn-default.disabled.focus, 96 | .btn-default[disabled].focus, 97 | fieldset[disabled] .btn-default.focus, 98 | .btn-default.disabled:active, 99 | .btn-default[disabled]:active, 100 | fieldset[disabled] .btn-default:active, 101 | .btn-default.disabled.active, 102 | .btn-default[disabled].active, 103 | fieldset[disabled] .btn-default.active { 104 | background-color: #e0e0e0; 105 | background-image: none; 106 | } 107 | .btn-primary { 108 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 109 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 110 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 111 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 112 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 113 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 114 | background-repeat: repeat-x; 115 | border-color: #245580; 116 | } 117 | .btn-primary:hover, 118 | .btn-primary:focus { 119 | background-color: #265a88; 120 | background-position: 0 -15px; 121 | } 122 | .btn-primary:active, 123 | .btn-primary.active { 124 | background-color: #265a88; 125 | border-color: #245580; 126 | } 127 | .btn-primary.disabled, 128 | .btn-primary[disabled], 129 | fieldset[disabled] .btn-primary, 130 | .btn-primary.disabled:hover, 131 | .btn-primary[disabled]:hover, 132 | fieldset[disabled] .btn-primary:hover, 133 | .btn-primary.disabled:focus, 134 | .btn-primary[disabled]:focus, 135 | fieldset[disabled] .btn-primary:focus, 136 | .btn-primary.disabled.focus, 137 | .btn-primary[disabled].focus, 138 | fieldset[disabled] .btn-primary.focus, 139 | .btn-primary.disabled:active, 140 | .btn-primary[disabled]:active, 141 | fieldset[disabled] .btn-primary:active, 142 | .btn-primary.disabled.active, 143 | .btn-primary[disabled].active, 144 | fieldset[disabled] .btn-primary.active { 145 | background-color: #265a88; 146 | background-image: none; 147 | } 148 | .btn-success { 149 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 150 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 151 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 152 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 153 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 154 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 155 | background-repeat: repeat-x; 156 | border-color: #3e8f3e; 157 | } 158 | .btn-success:hover, 159 | .btn-success:focus { 160 | background-color: #419641; 161 | background-position: 0 -15px; 162 | } 163 | .btn-success:active, 164 | .btn-success.active { 165 | background-color: #419641; 166 | border-color: #3e8f3e; 167 | } 168 | .btn-success.disabled, 169 | .btn-success[disabled], 170 | fieldset[disabled] .btn-success, 171 | .btn-success.disabled:hover, 172 | .btn-success[disabled]:hover, 173 | fieldset[disabled] .btn-success:hover, 174 | .btn-success.disabled:focus, 175 | .btn-success[disabled]:focus, 176 | fieldset[disabled] .btn-success:focus, 177 | .btn-success.disabled.focus, 178 | .btn-success[disabled].focus, 179 | fieldset[disabled] .btn-success.focus, 180 | .btn-success.disabled:active, 181 | .btn-success[disabled]:active, 182 | fieldset[disabled] .btn-success:active, 183 | .btn-success.disabled.active, 184 | .btn-success[disabled].active, 185 | fieldset[disabled] .btn-success.active { 186 | background-color: #419641; 187 | background-image: none; 188 | } 189 | .btn-info { 190 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 191 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 192 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 193 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 194 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 195 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 196 | background-repeat: repeat-x; 197 | border-color: #28a4c9; 198 | } 199 | .btn-info:hover, 200 | .btn-info:focus { 201 | background-color: #2aabd2; 202 | background-position: 0 -15px; 203 | } 204 | .btn-info:active, 205 | .btn-info.active { 206 | background-color: #2aabd2; 207 | border-color: #28a4c9; 208 | } 209 | .btn-info.disabled, 210 | .btn-info[disabled], 211 | fieldset[disabled] .btn-info, 212 | .btn-info.disabled:hover, 213 | .btn-info[disabled]:hover, 214 | fieldset[disabled] .btn-info:hover, 215 | .btn-info.disabled:focus, 216 | .btn-info[disabled]:focus, 217 | fieldset[disabled] .btn-info:focus, 218 | .btn-info.disabled.focus, 219 | .btn-info[disabled].focus, 220 | fieldset[disabled] .btn-info.focus, 221 | .btn-info.disabled:active, 222 | .btn-info[disabled]:active, 223 | fieldset[disabled] .btn-info:active, 224 | .btn-info.disabled.active, 225 | .btn-info[disabled].active, 226 | fieldset[disabled] .btn-info.active { 227 | background-color: #2aabd2; 228 | background-image: none; 229 | } 230 | .btn-warning { 231 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 232 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 233 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 234 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 235 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 236 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 237 | background-repeat: repeat-x; 238 | border-color: #e38d13; 239 | } 240 | .btn-warning:hover, 241 | .btn-warning:focus { 242 | background-color: #eb9316; 243 | background-position: 0 -15px; 244 | } 245 | .btn-warning:active, 246 | .btn-warning.active { 247 | background-color: #eb9316; 248 | border-color: #e38d13; 249 | } 250 | .btn-warning.disabled, 251 | .btn-warning[disabled], 252 | fieldset[disabled] .btn-warning, 253 | .btn-warning.disabled:hover, 254 | .btn-warning[disabled]:hover, 255 | fieldset[disabled] .btn-warning:hover, 256 | .btn-warning.disabled:focus, 257 | .btn-warning[disabled]:focus, 258 | fieldset[disabled] .btn-warning:focus, 259 | .btn-warning.disabled.focus, 260 | .btn-warning[disabled].focus, 261 | fieldset[disabled] .btn-warning.focus, 262 | .btn-warning.disabled:active, 263 | .btn-warning[disabled]:active, 264 | fieldset[disabled] .btn-warning:active, 265 | .btn-warning.disabled.active, 266 | .btn-warning[disabled].active, 267 | fieldset[disabled] .btn-warning.active { 268 | background-color: #eb9316; 269 | background-image: none; 270 | } 271 | .btn-danger { 272 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 273 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 274 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 275 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 276 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 277 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 278 | background-repeat: repeat-x; 279 | border-color: #b92c28; 280 | } 281 | .btn-danger:hover, 282 | .btn-danger:focus { 283 | background-color: #c12e2a; 284 | background-position: 0 -15px; 285 | } 286 | .btn-danger:active, 287 | .btn-danger.active { 288 | background-color: #c12e2a; 289 | border-color: #b92c28; 290 | } 291 | .btn-danger.disabled, 292 | .btn-danger[disabled], 293 | fieldset[disabled] .btn-danger, 294 | .btn-danger.disabled:hover, 295 | .btn-danger[disabled]:hover, 296 | fieldset[disabled] .btn-danger:hover, 297 | .btn-danger.disabled:focus, 298 | .btn-danger[disabled]:focus, 299 | fieldset[disabled] .btn-danger:focus, 300 | .btn-danger.disabled.focus, 301 | .btn-danger[disabled].focus, 302 | fieldset[disabled] .btn-danger.focus, 303 | .btn-danger.disabled:active, 304 | .btn-danger[disabled]:active, 305 | fieldset[disabled] .btn-danger:active, 306 | .btn-danger.disabled.active, 307 | .btn-danger[disabled].active, 308 | fieldset[disabled] .btn-danger.active { 309 | background-color: #c12e2a; 310 | background-image: none; 311 | } 312 | .thumbnail, 313 | .img-thumbnail { 314 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 315 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 316 | } 317 | .dropdown-menu > li > a:hover, 318 | .dropdown-menu > li > a:focus { 319 | background-color: #e8e8e8; 320 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 321 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 322 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 323 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 324 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 325 | background-repeat: repeat-x; 326 | } 327 | .dropdown-menu > .active > a, 328 | .dropdown-menu > .active > a:hover, 329 | .dropdown-menu > .active > a:focus { 330 | background-color: #2e6da4; 331 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 332 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 333 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 334 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 336 | background-repeat: repeat-x; 337 | } 338 | .navbar-default { 339 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 340 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 342 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 344 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 345 | background-repeat: repeat-x; 346 | border-radius: 4px; 347 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 348 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 349 | } 350 | .navbar-default .navbar-nav > .open > a, 351 | .navbar-default .navbar-nav > .active > a { 352 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 353 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 354 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 355 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 356 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 357 | background-repeat: repeat-x; 358 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 359 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 360 | } 361 | .navbar-brand, 362 | .navbar-nav > li > a { 363 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 364 | } 365 | .navbar-inverse { 366 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 367 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 368 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 369 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 370 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 371 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 372 | background-repeat: repeat-x; 373 | border-radius: 4px; 374 | } 375 | .navbar-inverse .navbar-nav > .open > a, 376 | .navbar-inverse .navbar-nav > .active > a { 377 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 378 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 379 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 380 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 382 | background-repeat: repeat-x; 383 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 384 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 385 | } 386 | .navbar-inverse .navbar-brand, 387 | .navbar-inverse .navbar-nav > li > a { 388 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 389 | } 390 | .navbar-static-top, 391 | .navbar-fixed-top, 392 | .navbar-fixed-bottom { 393 | border-radius: 0; 394 | } 395 | @media (max-width: 767px) { 396 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 397 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 398 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 399 | color: #fff; 400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 405 | background-repeat: repeat-x; 406 | } 407 | } 408 | .alert { 409 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 410 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 411 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 412 | } 413 | .alert-success { 414 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 415 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 416 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 418 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 419 | background-repeat: repeat-x; 420 | border-color: #b2dba1; 421 | } 422 | .alert-info { 423 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 424 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 425 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 427 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 428 | background-repeat: repeat-x; 429 | border-color: #9acfea; 430 | } 431 | .alert-warning { 432 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 433 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 437 | background-repeat: repeat-x; 438 | border-color: #f5e79e; 439 | } 440 | .alert-danger { 441 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 442 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 443 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 444 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 445 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 446 | background-repeat: repeat-x; 447 | border-color: #dca7a7; 448 | } 449 | .progress { 450 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 451 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 453 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 455 | background-repeat: repeat-x; 456 | } 457 | .progress-bar { 458 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 459 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 461 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 463 | background-repeat: repeat-x; 464 | } 465 | .progress-bar-success { 466 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 467 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 469 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 471 | background-repeat: repeat-x; 472 | } 473 | .progress-bar-info { 474 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 475 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 476 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 477 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 478 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 479 | background-repeat: repeat-x; 480 | } 481 | .progress-bar-warning { 482 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 483 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 484 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 485 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 486 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 487 | background-repeat: repeat-x; 488 | } 489 | .progress-bar-danger { 490 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 491 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 492 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 493 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 494 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 495 | background-repeat: repeat-x; 496 | } 497 | .progress-bar-striped { 498 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 499 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 500 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 501 | } 502 | .list-group { 503 | border-radius: 4px; 504 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 505 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 506 | } 507 | .list-group-item.active, 508 | .list-group-item.active:hover, 509 | .list-group-item.active:focus { 510 | text-shadow: 0 -1px 0 #286090; 511 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 512 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 513 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 514 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 515 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 516 | background-repeat: repeat-x; 517 | border-color: #2b669a; 518 | } 519 | .list-group-item.active .badge, 520 | .list-group-item.active:hover .badge, 521 | .list-group-item.active:focus .badge { 522 | text-shadow: none; 523 | } 524 | .panel { 525 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 526 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 527 | } 528 | .panel-default > .panel-heading { 529 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 530 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 531 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 532 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 533 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 534 | background-repeat: repeat-x; 535 | } 536 | .panel-primary > .panel-heading { 537 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 538 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 539 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 540 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 541 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 542 | background-repeat: repeat-x; 543 | } 544 | .panel-success > .panel-heading { 545 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 546 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 547 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 548 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 549 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 550 | background-repeat: repeat-x; 551 | } 552 | .panel-info > .panel-heading { 553 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 554 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 555 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 556 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 557 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 558 | background-repeat: repeat-x; 559 | } 560 | .panel-warning > .panel-heading { 561 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 562 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 563 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 564 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 565 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 566 | background-repeat: repeat-x; 567 | } 568 | .panel-danger > .panel-heading { 569 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 570 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 571 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 572 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 573 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 574 | background-repeat: repeat-x; 575 | } 576 | .well { 577 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 578 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 579 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 580 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 582 | background-repeat: repeat-x; 583 | border-color: #dcdcdc; 584 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 585 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 586 | } 587 | /*# sourceMappingURL=bootstrap-theme.css.map */ 588 | -------------------------------------------------------------------------------- /static/js/bootbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootbox.js [master branch] 3 | * 4 | * http://bootboxjs.com/license.txt 5 | */ 6 | 7 | // @see https://github.com/makeusabrew/bootbox/issues/180 8 | // @see https://github.com/makeusabrew/bootbox/issues/186 9 | (function (root, factory) { 10 | 11 | "use strict"; 12 | if (typeof define === "function" && define.amd) { 13 | // AMD. Register as an anonymous module. 14 | define(["jquery"], factory); 15 | } else if (typeof exports === "object") { 16 | // Node. Does not work with strict CommonJS, but 17 | // only CommonJS-like environments that support module.exports, 18 | // like Node. 19 | module.exports = factory(require("jquery")); 20 | } else { 21 | // Browser globals (root is window) 22 | root.bootbox = factory(root.jQuery); 23 | } 24 | 25 | }(this, function init($, undefined) { 26 | 27 | "use strict"; 28 | 29 | // the base DOM structure needed to create a modal 30 | var templates = { 31 | dialog: 32 | "", 39 | header: 40 | "", 43 | footer: 44 | "", 45 | closeButton: 46 | "", 47 | form: 48 | "
    ", 49 | inputs: { 50 | text: 51 | "", 52 | textarea: 53 | "", 54 | email: 55 | "", 56 | select: 57 | "", 58 | checkbox: 59 | "
    ", 60 | date: 61 | "", 62 | time: 63 | "", 64 | number: 65 | "", 66 | password: 67 | "" 68 | } 69 | }; 70 | 71 | var defaults = { 72 | // default language 73 | locale: "en", 74 | // show backdrop or not. Default to static so user has to interact with dialog 75 | backdrop: "static", 76 | // animate the modal in/out 77 | animate: true, 78 | // additional class string applied to the top level dialog 79 | className: null, 80 | // whether or not to include a close button 81 | closeButton: true, 82 | // show the dialog immediately by default 83 | show: true, 84 | // dialog container 85 | container: "body" 86 | }; 87 | 88 | // our public object; augmented after our private API 89 | var exports = {}; 90 | 91 | /** 92 | * @private 93 | */ 94 | function _t(key) { 95 | var locale = locales[defaults.locale]; 96 | return locale ? locale[key] : locales.en[key]; 97 | } 98 | 99 | function processCallback(e, dialog, callback) { 100 | e.stopPropagation(); 101 | e.preventDefault(); 102 | 103 | // by default we assume a callback will get rid of the dialog, 104 | // although it is given the opportunity to override this 105 | 106 | // so, if the callback can be invoked and it *explicitly returns false* 107 | // then we'll set a flag to keep the dialog active... 108 | var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false; 109 | 110 | // ... otherwise we'll bin it 111 | if (!preserveDialog) { 112 | dialog.modal("hide"); 113 | } 114 | } 115 | 116 | function getKeyLength(obj) { 117 | // @TODO defer to Object.keys(x).length if available? 118 | var k, t = 0; 119 | for (k in obj) { 120 | t ++; 121 | } 122 | return t; 123 | } 124 | 125 | function each(collection, iterator) { 126 | var index = 0; 127 | $.each(collection, function(key, value) { 128 | iterator(key, value, index++); 129 | }); 130 | } 131 | 132 | function sanitize(options) { 133 | var buttons; 134 | var total; 135 | 136 | if (typeof options !== "object") { 137 | throw new Error("Please supply an object of options"); 138 | } 139 | 140 | if (!options.message) { 141 | throw new Error("Please specify a message"); 142 | } 143 | 144 | // make sure any supplied options take precedence over defaults 145 | options = $.extend({}, defaults, options); 146 | 147 | if (!options.buttons) { 148 | options.buttons = {}; 149 | } 150 | 151 | buttons = options.buttons; 152 | 153 | total = getKeyLength(buttons); 154 | 155 | each(buttons, function(key, button, index) { 156 | 157 | if ($.isFunction(button)) { 158 | // short form, assume value is our callback. Since button 159 | // isn't an object it isn't a reference either so re-assign it 160 | button = buttons[key] = { 161 | callback: button 162 | }; 163 | } 164 | 165 | // before any further checks make sure by now button is the correct type 166 | if ($.type(button) !== "object") { 167 | throw new Error("button with key " + key + " must be an object"); 168 | } 169 | 170 | if (!button.label) { 171 | // the lack of an explicit label means we'll assume the key is good enough 172 | button.label = key; 173 | } 174 | 175 | if (!button.className) { 176 | if (total <= 2 && index === total-1) { 177 | // always add a primary to the main option in a two-button dialog 178 | button.className = "btn-primary"; 179 | } else { 180 | button.className = "btn-default"; 181 | } 182 | } 183 | }); 184 | 185 | return options; 186 | } 187 | 188 | /** 189 | * map a flexible set of arguments into a single returned object 190 | * if args.length is already one just return it, otherwise 191 | * use the properties argument to map the unnamed args to 192 | * object properties 193 | * so in the latter case: 194 | * mapArguments(["foo", $.noop], ["message", "callback"]) 195 | * -> { message: "foo", callback: $.noop } 196 | */ 197 | function mapArguments(args, properties) { 198 | var argn = args.length; 199 | var options = {}; 200 | 201 | if (argn < 1 || argn > 2) { 202 | throw new Error("Invalid argument length"); 203 | } 204 | 205 | if (argn === 2 || typeof args[0] === "string") { 206 | options[properties[0]] = args[0]; 207 | options[properties[1]] = args[1]; 208 | } else { 209 | options = args[0]; 210 | } 211 | 212 | return options; 213 | } 214 | 215 | /** 216 | * merge a set of default dialog options with user supplied arguments 217 | */ 218 | function mergeArguments(defaults, args, properties) { 219 | return $.extend( 220 | // deep merge 221 | true, 222 | // ensure the target is an empty, unreferenced object 223 | {}, 224 | // the base options object for this type of dialog (often just buttons) 225 | defaults, 226 | // args could be an object or array; if it's an array properties will 227 | // map it to a proper options object 228 | mapArguments( 229 | args, 230 | properties 231 | ) 232 | ); 233 | } 234 | 235 | /** 236 | * this entry-level method makes heavy use of composition to take a simple 237 | * range of inputs and return valid options suitable for passing to bootbox.dialog 238 | */ 239 | function mergeDialogOptions(className, labels, properties, args) { 240 | // build up a base set of dialog properties 241 | var baseOptions = { 242 | className: "bootbox-" + className, 243 | buttons: createLabels.apply(null, labels) 244 | }; 245 | 246 | // ensure the buttons properties generated, *after* merging 247 | // with user args are still valid against the supplied labels 248 | return validateButtons( 249 | // merge the generated base properties with user supplied arguments 250 | mergeArguments( 251 | baseOptions, 252 | args, 253 | // if args.length > 1, properties specify how each arg maps to an object key 254 | properties 255 | ), 256 | labels 257 | ); 258 | } 259 | 260 | /** 261 | * from a given list of arguments return a suitable object of button labels 262 | * all this does is normalise the given labels and translate them where possible 263 | * e.g. "ok", "confirm" -> { ok: "OK, cancel: "Annuleren" } 264 | */ 265 | function createLabels() { 266 | var buttons = {}; 267 | 268 | for (var i = 0, j = arguments.length; i < j; i++) { 269 | var argument = arguments[i]; 270 | var key = argument.toLowerCase(); 271 | var value = argument.toUpperCase(); 272 | 273 | buttons[key] = { 274 | label: _t(value) 275 | }; 276 | } 277 | 278 | return buttons; 279 | } 280 | 281 | function validateButtons(options, buttons) { 282 | var allowedButtons = {}; 283 | each(buttons, function(key, value) { 284 | allowedButtons[value] = true; 285 | }); 286 | 287 | each(options.buttons, function(key) { 288 | if (allowedButtons[key] === undefined) { 289 | throw new Error("button key " + key + " is not allowed (options are " + buttons.join("\n") + ")"); 290 | } 291 | }); 292 | 293 | return options; 294 | } 295 | 296 | exports.alert = function() { 297 | var options; 298 | 299 | options = mergeDialogOptions("alert", ["ok"], ["message", "callback"], arguments); 300 | 301 | if (options.callback && !$.isFunction(options.callback)) { 302 | throw new Error("alert requires callback property to be a function when provided"); 303 | } 304 | 305 | /** 306 | * overrides 307 | */ 308 | options.buttons.ok.callback = options.onEscape = function() { 309 | if ($.isFunction(options.callback)) { 310 | return options.callback.call(this); 311 | } 312 | return true; 313 | }; 314 | 315 | return exports.dialog(options); 316 | }; 317 | 318 | exports.confirm = function() { 319 | var options; 320 | 321 | options = mergeDialogOptions("confirm", ["cancel", "confirm"], ["message", "callback"], arguments); 322 | 323 | /** 324 | * overrides; undo anything the user tried to set they shouldn't have 325 | */ 326 | options.buttons.cancel.callback = options.onEscape = function() { 327 | return options.callback.call(this, false); 328 | }; 329 | 330 | options.buttons.confirm.callback = function() { 331 | return options.callback.call(this, true); 332 | }; 333 | 334 | // confirm specific validation 335 | if (!$.isFunction(options.callback)) { 336 | throw new Error("confirm requires a callback"); 337 | } 338 | 339 | return exports.dialog(options); 340 | }; 341 | 342 | exports.prompt = function() { 343 | var options; 344 | var defaults; 345 | var dialog; 346 | var form; 347 | var input; 348 | var shouldShow; 349 | var inputOptions; 350 | 351 | // we have to create our form first otherwise 352 | // its value is undefined when gearing up our options 353 | // @TODO this could be solved by allowing message to 354 | // be a function instead... 355 | form = $(templates.form); 356 | 357 | // prompt defaults are more complex than others in that 358 | // users can override more defaults 359 | // @TODO I don't like that prompt has to do a lot of heavy 360 | // lifting which mergeDialogOptions can *almost* support already 361 | // just because of 'value' and 'inputType' - can we refactor? 362 | defaults = { 363 | className: "bootbox-prompt", 364 | buttons: createLabels("cancel", "confirm"), 365 | value: "", 366 | inputType: "text" 367 | }; 368 | 369 | options = validateButtons( 370 | mergeArguments(defaults, arguments, ["title", "callback"]), 371 | ["cancel", "confirm"] 372 | ); 373 | 374 | // capture the user's show value; we always set this to false before 375 | // spawning the dialog to give us a chance to attach some handlers to 376 | // it, but we need to make sure we respect a preference not to show it 377 | shouldShow = (options.show === undefined) ? true : options.show; 378 | 379 | /** 380 | * overrides; undo anything the user tried to set they shouldn't have 381 | */ 382 | options.message = form; 383 | 384 | options.buttons.cancel.callback = options.onEscape = function() { 385 | return options.callback.call(this, null); 386 | }; 387 | 388 | options.buttons.confirm.callback = function() { 389 | var value; 390 | 391 | switch (options.inputType) { 392 | case "text": 393 | case "textarea": 394 | case "email": 395 | case "select": 396 | case "date": 397 | case "time": 398 | case "number": 399 | case "password": 400 | value = input.val(); 401 | break; 402 | 403 | case "checkbox": 404 | var checkedItems = input.find("input:checked"); 405 | 406 | // we assume that checkboxes are always multiple, 407 | // hence we default to an empty array 408 | value = []; 409 | 410 | each(checkedItems, function(_, item) { 411 | value.push($(item).val()); 412 | }); 413 | break; 414 | } 415 | 416 | return options.callback.call(this, value); 417 | }; 418 | 419 | options.show = false; 420 | 421 | // prompt specific validation 422 | if (!options.title) { 423 | throw new Error("prompt requires a title"); 424 | } 425 | 426 | if (!$.isFunction(options.callback)) { 427 | throw new Error("prompt requires a callback"); 428 | } 429 | 430 | if (!templates.inputs[options.inputType]) { 431 | throw new Error("invalid prompt type"); 432 | } 433 | 434 | // create the input based on the supplied type 435 | input = $(templates.inputs[options.inputType]); 436 | 437 | switch (options.inputType) { 438 | case "text": 439 | case "textarea": 440 | case "email": 441 | case "date": 442 | case "time": 443 | case "number": 444 | case "password": 445 | input.val(options.value); 446 | break; 447 | 448 | case "select": 449 | var groups = {}; 450 | inputOptions = options.inputOptions || []; 451 | 452 | if (!$.isArray(inputOptions)) { 453 | throw new Error("Please pass an array of input options"); 454 | } 455 | 456 | if (!inputOptions.length) { 457 | throw new Error("prompt with select requires options"); 458 | } 459 | 460 | each(inputOptions, function(_, option) { 461 | 462 | // assume the element to attach to is the input... 463 | var elem = input; 464 | 465 | if (option.value === undefined || option.text === undefined) { 466 | throw new Error("given options in wrong format"); 467 | } 468 | 469 | // ... but override that element if this option sits in a group 470 | 471 | if (option.group) { 472 | // initialise group if necessary 473 | if (!groups[option.group]) { 474 | groups[option.group] = $("").attr("label", option.group); 475 | } 476 | 477 | elem = groups[option.group]; 478 | } 479 | 480 | elem.append(""); 481 | }); 482 | 483 | each(groups, function(_, group) { 484 | input.append(group); 485 | }); 486 | 487 | // safe to set a select's value as per a normal input 488 | input.val(options.value); 489 | break; 490 | 491 | case "checkbox": 492 | var values = $.isArray(options.value) ? options.value : [options.value]; 493 | inputOptions = options.inputOptions || []; 494 | 495 | if (!inputOptions.length) { 496 | throw new Error("prompt with checkbox requires options"); 497 | } 498 | 499 | if (!inputOptions[0].value || !inputOptions[0].text) { 500 | throw new Error("given options in wrong format"); 501 | } 502 | 503 | // checkboxes have to nest within a containing element, so 504 | // they break the rules a bit and we end up re-assigning 505 | // our 'input' element to this container instead 506 | input = $("
    "); 507 | 508 | each(inputOptions, function(_, option) { 509 | var checkbox = $(templates.inputs[options.inputType]); 510 | 511 | checkbox.find("input").attr("value", option.value); 512 | checkbox.find("label").append(option.text); 513 | 514 | // we've ensured values is an array so we can always iterate over it 515 | each(values, function(_, value) { 516 | if (value === option.value) { 517 | checkbox.find("input").prop("checked", true); 518 | } 519 | }); 520 | 521 | input.append(checkbox); 522 | }); 523 | break; 524 | } 525 | 526 | // @TODO provide an attributes option instead 527 | // and simply map that as keys: vals 528 | if (options.placeholder) { 529 | input.attr("placeholder", options.placeholder); 530 | } 531 | 532 | if (options.pattern) { 533 | input.attr("pattern", options.pattern); 534 | } 535 | 536 | if (options.maxlength) { 537 | input.attr("maxlength", options.maxlength); 538 | } 539 | 540 | // now place it in our form 541 | form.append(input); 542 | 543 | form.on("submit", function(e) { 544 | e.preventDefault(); 545 | // Fix for SammyJS (or similar JS routing library) hijacking the form post. 546 | e.stopPropagation(); 547 | // @TODO can we actually click *the* button object instead? 548 | // e.g. buttons.confirm.click() or similar 549 | dialog.find(".btn-primary").click(); 550 | }); 551 | 552 | dialog = exports.dialog(options); 553 | 554 | // clear the existing handler focusing the submit button... 555 | dialog.off("shown.bs.modal"); 556 | 557 | // ...and replace it with one focusing our input, if possible 558 | dialog.on("shown.bs.modal", function() { 559 | // need the closure here since input isn't 560 | // an object otherwise 561 | input.focus(); 562 | }); 563 | 564 | if (shouldShow === true) { 565 | dialog.modal("show"); 566 | } 567 | 568 | return dialog; 569 | }; 570 | 571 | exports.dialog = function(options) { 572 | options = sanitize(options); 573 | 574 | var dialog = $(templates.dialog); 575 | var innerDialog = dialog.find(".modal-dialog"); 576 | var body = dialog.find(".modal-body"); 577 | var buttons = options.buttons; 578 | var buttonStr = ""; 579 | var callbacks = { 580 | onEscape: options.onEscape 581 | }; 582 | 583 | if ($.fn.modal === undefined) { 584 | throw new Error( 585 | "$.fn.modal is not defined; please double check you have included " + 586 | "the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ " + 587 | "for more details." 588 | ); 589 | } 590 | 591 | each(buttons, function(key, button) { 592 | 593 | // @TODO I don't like this string appending to itself; bit dirty. Needs reworking 594 | // can we just build up button elements instead? slower but neater. Then button 595 | // can just become a template too 596 | buttonStr += ""; 597 | callbacks[key] = button.callback; 598 | }); 599 | 600 | body.find(".bootbox-body").html(options.message); 601 | 602 | if (options.animate === true) { 603 | dialog.addClass("fade"); 604 | } 605 | 606 | if (options.className) { 607 | dialog.addClass(options.className); 608 | } 609 | 610 | if (options.size === "large") { 611 | innerDialog.addClass("modal-lg"); 612 | } else if (options.size === "small") { 613 | innerDialog.addClass("modal-sm"); 614 | } 615 | 616 | if (options.title) { 617 | body.before(templates.header); 618 | } 619 | 620 | if (options.closeButton) { 621 | var closeButton = $(templates.closeButton); 622 | 623 | if (options.title) { 624 | dialog.find(".modal-header").prepend(closeButton); 625 | } else { 626 | closeButton.css("margin-top", "-10px").prependTo(body); 627 | } 628 | } 629 | 630 | if (options.title) { 631 | dialog.find(".modal-title").html(options.title); 632 | } 633 | 634 | if (buttonStr.length) { 635 | body.after(templates.footer); 636 | dialog.find(".modal-footer").html(buttonStr); 637 | } 638 | 639 | 640 | /** 641 | * Bootstrap event listeners; used handle extra 642 | * setup & teardown required after the underlying 643 | * modal has performed certain actions 644 | */ 645 | 646 | dialog.on("hidden.bs.modal", function(e) { 647 | // ensure we don't accidentally intercept hidden events triggered 648 | // by children of the current dialog. We shouldn't anymore now BS 649 | // namespaces its events; but still worth doing 650 | if (e.target === this) { 651 | dialog.remove(); 652 | } 653 | }); 654 | 655 | /* 656 | dialog.on("show.bs.modal", function() { 657 | // sadly this doesn't work; show is called *just* before 658 | // the backdrop is added so we'd need a setTimeout hack or 659 | // otherwise... leaving in as would be nice 660 | if (options.backdrop) { 661 | dialog.next(".modal-backdrop").addClass("bootbox-backdrop"); 662 | } 663 | }); 664 | */ 665 | 666 | dialog.on("shown.bs.modal", function() { 667 | dialog.find(".btn-primary:first").focus(); 668 | }); 669 | 670 | /** 671 | * Bootbox event listeners; experimental and may not last 672 | * just an attempt to decouple some behaviours from their 673 | * respective triggers 674 | */ 675 | 676 | if (options.backdrop !== "static") { 677 | // A boolean true/false according to the Bootstrap docs 678 | // should show a dialog the user can dismiss by clicking on 679 | // the background. 680 | // We always only ever pass static/false to the actual 681 | // $.modal function because with `true` we can't trap 682 | // this event (the .modal-backdrop swallows it) 683 | // However, we still want to sort of respect true 684 | // and invoke the escape mechanism instead 685 | dialog.on("click.dismiss.bs.modal", function(e) { 686 | // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop 687 | // moved *inside* the outer dialog rather than *alongside* it 688 | if (dialog.children(".modal-backdrop").length) { 689 | e.currentTarget = dialog.children(".modal-backdrop").get(0); 690 | } 691 | 692 | if (e.target !== e.currentTarget) { 693 | return; 694 | } 695 | 696 | dialog.trigger("escape.close.bb"); 697 | }); 698 | } 699 | 700 | dialog.on("escape.close.bb", function(e) { 701 | if (callbacks.onEscape) { 702 | processCallback(e, dialog, callbacks.onEscape); 703 | } 704 | }); 705 | 706 | /** 707 | * Standard jQuery event listeners; used to handle user 708 | * interaction with our dialog 709 | */ 710 | 711 | dialog.on("click", ".modal-footer button", function(e) { 712 | var callbackKey = $(this).data("bb-handler"); 713 | 714 | processCallback(e, dialog, callbacks[callbackKey]); 715 | }); 716 | 717 | dialog.on("click", ".bootbox-close-button", function(e) { 718 | // onEscape might be falsy but that's fine; the fact is 719 | // if the user has managed to click the close button we 720 | // have to close the dialog, callback or not 721 | processCallback(e, dialog, callbacks.onEscape); 722 | }); 723 | 724 | dialog.on("keyup", function(e) { 725 | if (e.which === 27) { 726 | dialog.trigger("escape.close.bb"); 727 | } 728 | }); 729 | 730 | // the remainder of this method simply deals with adding our 731 | // dialogent to the DOM, augmenting it with Bootstrap's modal 732 | // functionality and then giving the resulting object back 733 | // to our caller 734 | 735 | $(options.container).append(dialog); 736 | 737 | dialog.modal({ 738 | backdrop: options.backdrop ? "static": false, 739 | keyboard: false, 740 | show: false 741 | }); 742 | 743 | if (options.show) { 744 | dialog.modal("show"); 745 | } 746 | 747 | // @TODO should we return the raw element here or should 748 | // we wrap it in an object on which we can expose some neater 749 | // methods, e.g. var d = bootbox.alert(); d.hide(); instead 750 | // of d.modal("hide"); 751 | 752 | /* 753 | function BBDialog(elem) { 754 | this.elem = elem; 755 | } 756 | 757 | BBDialog.prototype = { 758 | hide: function() { 759 | return this.elem.modal("hide"); 760 | }, 761 | show: function() { 762 | return this.elem.modal("show"); 763 | } 764 | }; 765 | */ 766 | 767 | return dialog; 768 | 769 | }; 770 | 771 | exports.setDefaults = function() { 772 | var values = {}; 773 | 774 | if (arguments.length === 2) { 775 | // allow passing of single key/value... 776 | values[arguments[0]] = arguments[1]; 777 | } else { 778 | // ... and as an object too 779 | values = arguments[0]; 780 | } 781 | 782 | $.extend(defaults, values); 783 | }; 784 | 785 | exports.hideAll = function() { 786 | $(".bootbox").modal("hide"); 787 | 788 | return exports; 789 | }; 790 | 791 | 792 | /** 793 | * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are 794 | * unlikely to be required. If this gets too large it can be split out into separate JS files. 795 | */ 796 | var locales = { 797 | ar : { 798 | OK : "موافق", 799 | CANCEL : "الغاء", 800 | CONFIRM : "تأكيد" 801 | }, 802 | bg_BG : { 803 | OK : "Ок", 804 | CANCEL : "Отказ", 805 | CONFIRM : "Потвърждавам" 806 | }, 807 | br : { 808 | OK : "OK", 809 | CANCEL : "Cancelar", 810 | CONFIRM : "Sim" 811 | }, 812 | cs : { 813 | OK : "OK", 814 | CANCEL : "Zrušit", 815 | CONFIRM : "Potvrdit" 816 | }, 817 | da : { 818 | OK : "OK", 819 | CANCEL : "Annuller", 820 | CONFIRM : "Accepter" 821 | }, 822 | de : { 823 | OK : "OK", 824 | CANCEL : "Abbrechen", 825 | CONFIRM : "Akzeptieren" 826 | }, 827 | el : { 828 | OK : "Εντάξει", 829 | CANCEL : "Ακύρωση", 830 | CONFIRM : "Επιβεβαίωση" 831 | }, 832 | en : { 833 | OK : "OK", 834 | CANCEL : "Cancel", 835 | CONFIRM : "OK" 836 | }, 837 | es : { 838 | OK : "OK", 839 | CANCEL : "Cancelar", 840 | CONFIRM : "Aceptar" 841 | }, 842 | et : { 843 | OK : "OK", 844 | CANCEL : "Katkesta", 845 | CONFIRM : "OK" 846 | }, 847 | fa : { 848 | OK : "قبول", 849 | CANCEL : "لغو", 850 | CONFIRM : "تایید" 851 | }, 852 | fi : { 853 | OK : "OK", 854 | CANCEL : "Peruuta", 855 | CONFIRM : "OK" 856 | }, 857 | fr : { 858 | OK : "OK", 859 | CANCEL : "Annuler", 860 | CONFIRM : "Confirmer" 861 | }, 862 | he : { 863 | OK : "אישור", 864 | CANCEL : "ביטול", 865 | CONFIRM : "אישור" 866 | }, 867 | hu : { 868 | OK : "OK", 869 | CANCEL : "Mégsem", 870 | CONFIRM : "Megerősít" 871 | }, 872 | hr : { 873 | OK : "OK", 874 | CANCEL : "Odustani", 875 | CONFIRM : "Potvrdi" 876 | }, 877 | id : { 878 | OK : "OK", 879 | CANCEL : "Batal", 880 | CONFIRM : "OK" 881 | }, 882 | it : { 883 | OK : "OK", 884 | CANCEL : "Annulla", 885 | CONFIRM : "Conferma" 886 | }, 887 | ja : { 888 | OK : "OK", 889 | CANCEL : "キャンセル", 890 | CONFIRM : "確認" 891 | }, 892 | lt : { 893 | OK : "Gerai", 894 | CANCEL : "Atšaukti", 895 | CONFIRM : "Patvirtinti" 896 | }, 897 | lv : { 898 | OK : "Labi", 899 | CANCEL : "Atcelt", 900 | CONFIRM : "Apstiprināt" 901 | }, 902 | nl : { 903 | OK : "OK", 904 | CANCEL : "Annuleren", 905 | CONFIRM : "Accepteren" 906 | }, 907 | no : { 908 | OK : "OK", 909 | CANCEL : "Avbryt", 910 | CONFIRM : "OK" 911 | }, 912 | pl : { 913 | OK : "OK", 914 | CANCEL : "Anuluj", 915 | CONFIRM : "Potwierdź" 916 | }, 917 | pt : { 918 | OK : "OK", 919 | CANCEL : "Cancelar", 920 | CONFIRM : "Confirmar" 921 | }, 922 | ru : { 923 | OK : "OK", 924 | CANCEL : "Отмена", 925 | CONFIRM : "Применить" 926 | }, 927 | sq : { 928 | OK : "OK", 929 | CANCEL : "Anulo", 930 | CONFIRM : "Prano" 931 | }, 932 | sv : { 933 | OK : "OK", 934 | CANCEL : "Avbryt", 935 | CONFIRM : "OK" 936 | }, 937 | th : { 938 | OK : "ตกลง", 939 | CANCEL : "ยกเลิก", 940 | CONFIRM : "ยืนยัน" 941 | }, 942 | tr : { 943 | OK : "Tamam", 944 | CANCEL : "İptal", 945 | CONFIRM : "Onayla" 946 | }, 947 | zh_CN : { 948 | OK : "OK", 949 | CANCEL : "取消", 950 | CONFIRM : "确认" 951 | }, 952 | zh_TW : { 953 | OK : "OK", 954 | CANCEL : "取消", 955 | CONFIRM : "確認" 956 | } 957 | }; 958 | 959 | exports.addLocale = function(name, values) { 960 | $.each(["OK", "CANCEL", "CONFIRM"], function(_, v) { 961 | if (!values[v]) { 962 | throw new Error("Please supply a translation for '" + v + "'"); 963 | } 964 | }); 965 | 966 | locales[name] = { 967 | OK: values.OK, 968 | CANCEL: values.CANCEL, 969 | CONFIRM: values.CONFIRM 970 | }; 971 | 972 | return exports; 973 | }; 974 | 975 | exports.removeLocale = function(name) { 976 | delete locales[name]; 977 | 978 | return exports; 979 | }; 980 | 981 | exports.setLocale = function(name) { 982 | return exports.setDefaults("locale", name); 983 | }; 984 | 985 | exports.init = function(_$) { 986 | return init(_$ || $); 987 | }; 988 | 989 | return exports; 990 | })); 991 | --------------------------------------------------------------------------------