├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── collected └── index.html ├── conf └── config.inc.php ├── css ├── bootstrap-combobox.css ├── bootstrap-responsive.css └── bootstrap.css ├── img ├── favicon.ico └── raingauge_drops_small.png ├── index.php ├── js ├── bootstrap-collapse.js ├── bootstrap-combobox.js ├── bootstrap-dropdown.js ├── bootstrap-tab.js ├── bootstrap-typeahead.js ├── flot │ ├── jquery.colorhelpers.js │ ├── jquery.colorhelpers.min.js │ ├── jquery.flot.crosshair.js │ ├── jquery.flot.crosshair.min.js │ ├── jquery.flot.fillbetween.js │ ├── jquery.flot.fillbetween.min.js │ ├── jquery.flot.image.js │ ├── jquery.flot.image.min.js │ ├── jquery.flot.js │ ├── jquery.flot.min.js │ ├── jquery.flot.navigate.js │ ├── jquery.flot.navigate.min.js │ ├── jquery.flot.pie.js │ ├── jquery.flot.pie.min.js │ ├── jquery.flot.resize.js │ ├── jquery.flot.resize.min.js │ ├── jquery.flot.selection.js │ ├── jquery.flot.selection.min.js │ ├── jquery.flot.stack.js │ ├── jquery.flot.stack.min.js │ ├── jquery.flot.symbol.js │ ├── jquery.flot.symbol.min.js │ ├── jquery.flot.threshold.js │ └── jquery.flot.threshold.min.js ├── jquery-1.7.1.min.js ├── jquery-ui-1.8.16.custom.min.js ├── jquery-ui-sliderAccess.js ├── jquery-ui-timepicker-addon.js ├── jquery.flot.js ├── jquery.ui.core.js ├── jquery.ui.datepicker.js ├── jquery.ui.widget.js └── lang-sql.js ├── lib ├── Helpers.php ├── Loader.php ├── RainGauge.php └── RainGaugeModel.php ├── scripts ├── index.html ├── pt-stalk-raingauge ├── pt-stalk_2.1.1.patch ├── pt-stalk_2.1.2.patch ├── raingauge_package_and_send.sh ├── raingauge_rc ├── raingauge_service └── raingauge_triggers.sh ├── vagrant ├── Vagrantfile └── environment │ └── puppet │ ├── manifests │ └── site.pp │ └── modules │ ├── profile │ ├── files │ │ ├── bashrc │ │ ├── id_rsa.pub │ │ ├── my-master.cnf │ │ └── mysql_grants.sql │ └── manifests │ │ └── base.pp │ └── role │ └── manifests │ └── raingauge.pp └── views ├── footer.php ├── header.php ├── index.php ├── navbar.php ├── sample.php ├── server.php ├── server_list_samples.php ├── server_list_samples_json.php └── upload_test.php /.gitignore: -------------------------------------------------------------------------------- 1 | collected/* 2 | !collected/index.html 3 | *~ 4 | conf/config2.inc.php 5 | index2.php 6 | *.swp 7 | *.swo 8 | /.idea/ 9 | /vagrant/.vagrant/ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | script: make test 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | All contributions are welcome to this project. 4 | 5 | ## Contributor License Agreement 6 | 7 | Before a contribution can be merged into this project, please fill out the Contributor License Agreement (CLA) located at: 8 | 9 | http://box.github.io/cla 10 | 11 | To learn more about CLAs and why they are important to open source projects, please see the [Wikipedia entry](http://en.wikipedia.org/wiki/Contributor_License_Agreement). 12 | 13 | ## How to contribute 14 | 15 | * **File an issue** - if you found a bug, want to request an enhancement, or want to implement something (bug fix or feature). 16 | * **Send a pull request** - if you want to contribute code. Please be sure to file an issue first. 17 | 18 | ## Pull request best practices 19 | 20 | We want to accept your pull requests. Please follow these steps: 21 | 22 | ### Step 1: File an issue 23 | 24 | Before writing any code, please file an issue stating the problem you want to solve or the feature you want to implement. This allows us to give you feedback before you spend any time writing code. There may be a known limitation that can't be addressed, or a bug that has already been fixed in a different way. The issue allows us to communicate and figure out if it's worth your time to write a bunch of code for the project. 25 | 26 | ### Step 2: Fork this repository in GitHub 27 | 28 | This will create your own copy of our repository. 29 | 30 | ### Step 3: Add the upstream source 31 | 32 | The upstream source is the project under the Box organization on GitHub. To add an upstream source for this project, type: 33 | 34 | ``` 35 | git remote add upstream git@github.com:Box/RainGauge.git 36 | ``` 37 | 38 | This will come in useful later. 39 | 40 | ### Step 4: Create a feature branch 41 | 42 | Create a branch with a descriptive name, such as `add-search`. 43 | 44 | ### Step 5: Push your feature branch to your fork 45 | 46 | As you develop code, continue to push code to your remote feature branch. Please make sure to include the issue number you're addressing in your commit message, such as: 47 | 48 | ``` 49 | git commit -m "Adding search (fixes #123)" 50 | ``` 51 | 52 | This helps us out by allowing us to track which issue your commit relates to. 53 | 54 | Keep a separate feature branch for each issue you want to address. 55 | 56 | ### Step 6: Rebase 57 | 58 | Before sending a pull request, rebase against upstream, such as: 59 | 60 | ``` 61 | git fetch upstream 62 | git rebase upstream/master 63 | ``` 64 | 65 | This will add your changes on top of what's already in upstream, minimizing merge issues. 66 | 67 | ### Step 7: Run the tests 68 | 69 | Make sure that all tests are passing before submitting a pull request. 70 | 71 | ### Step 8: Send the pull request 72 | 73 | Send the pull request from your feature branch to us. Be sure to include a description that lets us know what work you did. 74 | 75 | Keep in mind that we like to see one issue addressed per pull request, as this helps keep our git history clean and we can more easily track down issues. 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | "License" shall mean the terms and conditions for use, reproduction, 9 | and distribution as defined by Sections 1 through 9 of this document. 10 | "Licensor" shall mean the copyright owner or entity authorized by 11 | the copyright owner that is granting the License. 12 | "Legal Entity" shall mean the union of the acting entity and all 13 | other entities that control, are controlled by, or are under common 14 | control with that entity. For the purposes of this definition, 15 | "control" means (i) the power, direct or indirect, to cause the 16 | direction or management of such entity, whether by contract or 17 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 18 | outstanding shares, or (iii) beneficial ownership of such entity. 19 | "You" (or "Your") shall mean an individual or Legal Entity 20 | exercising permissions granted by this License. 21 | "Source" form shall mean the preferred form for making modifications, 22 | including but not limited to software source code, documentation 23 | source, and configuration files. 24 | "Object" form shall mean any form resulting from mechanical 25 | transformation or translation of a Source form, including but 26 | not limited to compiled object code, generated documentation, 27 | and conversions to other media types. 28 | "Work" shall mean the work of authorship, whether in Source or 29 | Object form, made available under the License, as indicated by a 30 | copyright notice that is included in or attached to the work 31 | (an example is provided in the Appendix below). 32 | "Derivative Works" shall mean any work, whether in Source or Object 33 | form, that is based on (or derived from) the Work and for which the 34 | editorial revisions, annotations, elaborations, or other modifications 35 | represent, as a whole, an original work of authorship. For the purposes 36 | of this License, Derivative Works shall not include works that remain 37 | separable from, or merely link (or bind by name) to the interfaces of, 38 | the Work and Derivative Works thereof. 39 | "Contribution" shall mean any work of authorship, including 40 | the original version of the Work and any modifications or additions 41 | to that Work or Derivative Works thereof, that is intentionally 42 | submitted to Licensor for inclusion in the Work by the copyright owner 43 | or by an individual or Legal Entity authorized to submit on behalf of 44 | the copyright owner. For the purposes of this definition, "submitted" 45 | means any form of electronic, verbal, or written communication sent 46 | to the Licensor or its representatives, including but not limited to 47 | communication on electronic mailing lists, source code control systems, 48 | and issue tracking systems that are managed by, or on behalf of, the 49 | Licensor for the purpose of discussing and improving the Work, but 50 | excluding communication that is conspicuously marked or otherwise 51 | designated in writing by the copyright owner as "Not a Contribution." 52 | "Contributor" shall mean Licensor and any individual or Legal Entity 53 | on behalf of whom a Contribution has been received by Licensor and 54 | subsequently incorporated within the Work. 55 | 56 | 2. Grant of Copyright License. Subject to the terms and conditions of 57 | this License, each Contributor hereby grants to You a perpetual, 58 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 59 | copyright license to reproduce, prepare Derivative Works of, 60 | publicly display, publicly perform, sublicense, and distribute the 61 | Work and such Derivative Works in Source or Object form. 62 | 63 | 3. Grant of Patent License. Subject to the terms and conditions of 64 | this License, each Contributor hereby grants to You a perpetual, 65 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 66 | (except as stated in this section) patent license to make, have made, 67 | use, offer to sell, sell, import, and otherwise transfer the Work, 68 | where such license applies only to those patent claims licensable 69 | by such Contributor that are necessarily infringed by their 70 | Contribution(s) alone or by combination of their Contribution(s) 71 | with the Work to which such Contribution(s) was submitted. If You 72 | institute patent litigation against any entity (including a 73 | cross-claim or counterclaim in a lawsuit) alleging that the Work 74 | or a Contribution incorporated within the Work constitutes direct 75 | or contributory patent infringement, then any patent licenses 76 | granted to You under this License for that Work shall terminate 77 | as of the date such litigation is filed. 78 | 79 | 4. Redistribution. You may reproduce and distribute copies of the 80 | Work or Derivative Works thereof in any medium, with or without 81 | modifications, and in Source or Object form, provided that You 82 | meet the following conditions: 83 | 84 | (a) You must give any other recipients of the Work or 85 | Derivative Works a copy of this License; and 86 | 87 | (b) You must cause any modified files to carry prominent notices 88 | stating that You changed the files; and 89 | 90 | (c) You must retain, in the Source form of any Derivative Works 91 | that You distribute, all copyright, patent, trademark, and 92 | attribution notices from the Source form of the Work, 93 | excluding those notices that do not pertain to any part of 94 | the Derivative Works; and 95 | 96 | (d) If the Work includes a "NOTICE" text file as part of its 97 | distribution, then any Derivative Works that You distribute must 98 | include a readable copy of the attribution notices contained 99 | within such NOTICE file, excluding those notices that do not 100 | pertain to any part of the Derivative Works, in at least one 101 | of the following places: within a NOTICE text file distributed 102 | as part of the Derivative Works; within the Source form or 103 | documentation, if provided along with the Derivative Works; or, 104 | within a display generated by the Derivative Works, if and 105 | wherever such third-party notices normally appear. The contents 106 | of the NOTICE file are for informational purposes only and 107 | do not modify the License. You may add Your own attribution 108 | notices within Derivative Works that You distribute, alongside 109 | or as an addendum to the NOTICE text from the Work, provided 110 | that such additional attribution notices cannot be construed 111 | as modifying the License. 112 | 113 | You may add Your own copyright statement to Your modifications and 114 | may provide additional or different license terms and conditions 115 | for use, reproduction, or distribution of Your modifications, or 116 | for any such Derivative Works as a whole, provided Your use, 117 | reproduction, and distribution of the Work otherwise complies with 118 | the conditions stated in this License. 119 | 120 | 5. Submission of Contributions. Unless You explicitly state otherwise, 121 | any Contribution intentionally submitted for inclusion in the Work 122 | by You to the Licensor shall be under the terms and conditions of 123 | this License, without any additional terms or conditions. 124 | Notwithstanding the above, nothing herein shall supersede or modify 125 | the terms of any separate license agreement you may have executed 126 | with Licensor regarding such Contributions. 127 | 128 | 6. Trademarks. This License does not grant permission to use the trade 129 | names, trademarks, service marks, or product names of the Licensor, 130 | except as required for reasonable and customary use in describing the 131 | origin of the Work and reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. Unless required by applicable law or 134 | agreed to in writing, Licensor provides the Work (and each 135 | Contributor provides its Contributions) on an "AS IS" BASIS, 136 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 137 | implied, including, without limitation, any warranties or conditions 138 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 139 | PARTICULAR PURPOSE. You are solely responsible for determining the 140 | appropriateness of using or redistributing the Work and assume any 141 | risks associated with Your exercise of permissions under this License. 142 | 143 | 8. Limitation of Liability. In no event and under no legal theory, 144 | whether in tort (including negligence), contract, or otherwise, 145 | unless required by applicable law (such as deliberate and grossly 146 | negligent acts) or agreed to in writing, shall any Contributor be 147 | liable to You for damages, including any direct, indirect, special, 148 | incidental, or consequential damages of any character arising as a 149 | result of this License or out of the use or inability to use the 150 | Work (including but not limited to damages for loss of goodwill, 151 | work stoppage, computer failure or malfunction, or any and all 152 | other commercial damages or losses), even if such Contributor 153 | has been advised of the possibility of such damages. 154 | 155 | 9. Accepting Warranty or Additional Liability. While redistributing 156 | the Work or Derivative Works thereof, You may choose to offer, 157 | and charge a fee for, acceptance of support, warranty, indemnity, 158 | or other liability obligations and/or rights consistent with this 159 | License. However, in accepting such obligations, You may act only 160 | on Your own behalf and on Your sole responsibility, not on behalf 161 | of any other Contributor, and only if You agree to indemnify, 162 | defend, and hold each Contributor harmless for any liability 163 | incurred by, or claims asserted against, such Contributor by reason 164 | of your accepting any such warranty or additional liability. 165 | 166 | END OF TERMS AND CONDITIONS 167 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @echo "Running tests" 3 | # Put actual test command here 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Box Rain Gauge 4 | ============== 5 | [![Project Status](http://opensource.box.com/badges/maintenance.svg)](http://opensource.box.com/badges) 6 | [![Travis](https://img.shields.io/travis/box/RainGauge.svg?maxAge=2592000)](https://travis-ci.org/box/RainGauge) 7 | [![Join the chat at https://gitter.im/box/Anemometer](https://badges.gitter.im/box/RainGauge.svg)](https://gitter.im/box/RainGauge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 8 | 9 | 10 | Rain Gauge is a tool to simplify the process of collecting detailed information from mysql database servers when specific conditions are triggered. Collections are packaged and centralised in one place, with a convenient web interface to let you explore the data easily. This tool uses a modified version of the percona toolkit script pt-stalk to handle collecting data from remote servers. 11 | 12 | ## Quickstart 13 | 14 | Installation consists of two parts: setting up the web interface, and setting up the collector. 15 | 16 | ### Setting up the web interface 17 | 18 | Installation of the web interface is super simple! Just clone the RainGauge project in the document root of your webserver: 19 | 20 | git clone git://github.com/box/RainGauge.git 21 | 22 | There are a few things to configure in the conf/config.inc.php file if you want, but those aren't necessary 23 | 24 | ### Installing the collection scripts 25 | 26 | The collection scripts are a bit more involved. You'll want to create a separate user account in mysql. You'll also need to customise a couple of the scripts to put in the address of the web server where you installed the web interface, and change and options you may want to send to pt-stalk. Then you'll install a service and start it. 27 | 28 | #### Install the scripts 29 | 30 | cp RainGauge/scripts/raingauge_package_and_send.sh /usr/bin/ 31 | cp RainGauge/scripts/pt-stalk-raingauge /usr/bin/ 32 | 33 | #### Set up the package and send script 34 | 35 | vi /usr/bin/raingauge_package_and_send.sh 36 | 37 | Now change SERVER='' to be the web server where you installed the interface. The collection script will do a http post to copy collected data to a central location 38 | 39 | #### Set up a new database user in mysql 40 | 41 | mysql -uroot -e "GRANT PROCESS, SUPER ON *.* TO 'raingauge'@'localhost' IDENTIFIED BY 'SuperSecurePass'" 42 | 43 | #### Add the raingauge service 44 | 45 | cp RainGauge/scripts/raingauge_rc /etc/raingauge_rc 46 | cp RainGauge/scripts/raingauge_service /etc/init.d/raingauge 47 | 48 | #### Edit the rc file to set up options 49 | 50 | vi /etc/raingauge_rc 51 | 52 | Edit the following line to use the same password you created for the mysql user: 53 | 54 | PT_MYSQL_PASS='' 55 | 56 | #### Start the service 57 | 58 | sudo service raingauge start 59 | 60 | #### Install a cleanup cron 61 | 62 | You will probably want to clean up old collections after a while, try 2 days to start: 63 | 64 | [[ -d /www/RainGauge/collected/ ]] && find /www/RainGauge/collected/ -mindepth 1 -mtime +2 -exec rm -rf {} \; 65 | 66 | 67 | ## Copyright and License 68 | 69 | Copyright 2014 Box, Inc. All rights reserved. 70 | 71 | Licensed under the Apache License, Version 2.0 (the "License"); 72 | you may not use this file except in compliance with the License. 73 | You may obtain a copy of the License at 74 | 75 | http://www.apache.org/licenses/LICENSE-2.0 76 | 77 | Unless required by applicable law or agreed to in writing, software 78 | distributed under the License is distributed on an "AS IS" BASIS, 79 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 80 | See the License for the specific language governing permissions and 81 | limitations under the License. 82 | -------------------------------------------------------------------------------- /collected/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box/RainGauge/6ed95786bfa90b7f400145f54433220d621618f3/collected/index.html -------------------------------------------------------------------------------- /conf/config.inc.php: -------------------------------------------------------------------------------- 1 | array( 'Bytes_sent', 'Bytes_received' ), 10 | 'Threads' => array( 'Threads_connected', 'Threads_running'), 11 | 'DML' => array( 'Questions', 'Com_select', 'Com_insert','Com_update','Com_delete', 'Com_replace'), 12 | 'Select_Types' => array( 'Select_full_join', 'Select_full_range_join','Select_range','Select_range_check','Select_scan'), 13 | 'Os_Waits' => array( 'Innodb_mutex_os_waits', 'Innodb_x_lock_os_waits', 'Innodb_s_lock_os_waits'), 14 | 'LSN' => array( 'Innodb_lsn_current', 'Innodb_lsn_flushed', 'Innodb_lsn_last_checkpoint'), 15 | 'Log' => array( 'Innodb_log_writes','Innodb_log_write_requests', 'Innodb_log_waits'), 16 | 'Pages' => array( 'Innodb_pages_created', 'Innodb_pages_read','Innodb_pages_written'), 17 | 'Rows' => array( 'Innodb_rows_read','Innodb_rows_updated','Innodb_rows_inserted','Innodb_rows_deleted'), 18 | 'Pending' => array( 'Innodb_data_pending_fsyncs', 'Innodb_data_pending_writes','Innodb_data_pending_reads'), 19 | 'Binlog' => array( 'binlog_commits', 'binlog_group_commits'), 20 | ); 21 | 22 | function extract_trigger($hostname, $port, $filename) { 23 | $dir = dirname($filename); 24 | $cmd = "tar -xf $filename -C $dir --wildcards --no-anchored '*-trigger-*'"; 25 | exec($cmd); 26 | } 27 | 28 | $conf['on_file_upload'] = 'extract_trigger'; 29 | ?> 30 | -------------------------------------------------------------------------------- /css/bootstrap-combobox.css: -------------------------------------------------------------------------------- 1 | .combobox-container { 2 | margin-bottom: 5px; 3 | *zoom: 1; 4 | } 5 | .combobox-container:before, .combobox-container:after { 6 | display: table; 7 | content: ""; 8 | } 9 | .combobox-container:after { 10 | clear: both; 11 | } 12 | .combobox-container input, .combobox-container .uneditable-input { 13 | -webkit-border-radius: 0 3px 3px 0; 14 | -moz-border-radius: 0 3px 3px 0; 15 | border-radius: 0 3px 3px 0; 16 | } 17 | .combobox-container input:focus, .combobox-container .uneditable-input:focus { 18 | position: relative; 19 | z-index: 2; 20 | } 21 | .combobox-container .uneditable-input { 22 | border-left-color: #ccc; 23 | } 24 | .combobox-container .add-on { 25 | float: left; 26 | display: block; 27 | width: auto; 28 | min-width: 16px; 29 | height: 18px; 30 | margin-right: -1px; 31 | padding: 4px 5px; 32 | font-weight: normal; 33 | line-height: 18px; 34 | color: #999999; 35 | text-align: center; 36 | text-shadow: 0 1px 0 #ffffff; 37 | background-color: #f5f5f5; 38 | border: 1px solid #ccc; 39 | -webkit-border-radius: 3px 0 0 3px; 40 | -moz-border-radius: 3px 0 0 3px; 41 | border-radius: 3px 0 0 3px; 42 | } 43 | .combobox-container .active { 44 | background-color: #a9dba9; 45 | border-color: #46a546; 46 | } 47 | .combobox-container input, .combobox-container .uneditable-input { 48 | float: left; 49 | -webkit-border-radius: 3px 0 0 3px; 50 | -moz-border-radius: 3px 0 0 3px; 51 | border-radius: 3px 0 0 3px; 52 | } 53 | .combobox-container .uneditable-input { 54 | border-left-color: #eee; 55 | border-right-color: #ccc; 56 | } 57 | .combobox-container .add-on { 58 | margin-right: 0; 59 | margin-left: -1px; 60 | -webkit-border-radius: 0 3px 3px 0; 61 | -moz-border-radius: 0 3px 3px 0; 62 | border-radius: 0 3px 3px 0; 63 | } 64 | .combobox-container input:first-child { 65 | *margin-left: -160px; 66 | } 67 | .combobox-container input:first-child + .add-on { 68 | *margin-left: -21px; 69 | } 70 | .combobox-container select { 71 | display: inline-block; 72 | width: 0; 73 | height: 0; 74 | border: 0; 75 | padding: 0; 76 | margin: 0; 77 | float: left; 78 | text-indent: -99999px; 79 | *text-indent: 0; 80 | } 81 | .combobox-selected .combobox-clear { 82 | display: inline-block; 83 | } 84 | .combobox-selected .caret { 85 | display: none; 86 | } 87 | .combobox-clear { 88 | display: none; 89 | width: 14px; 90 | height: 14px; 91 | line-height: 14px; 92 | vertical-align: top; 93 | opacity: 0.3; 94 | filter: alpha(opacity=30); 95 | } 96 | .dropdown:hover .combobox-clear, .open.dropdown .combobox-clear { 97 | opacity: 1; 98 | filter: alpha(opacity=100); 99 | } 100 | .btn .combobox-clear { 101 | margin-top: 1px; 102 | margin-left: 1px; 103 | } 104 | .btn:hover .combobox-clear, .open.btn-group .combobox-clear { 105 | opacity: 1; 106 | filter: alpha(opacity=100); 107 | } 108 | .typeahead-long { 109 | max-height: 300px; 110 | overflow-y: auto; 111 | } 112 | -------------------------------------------------------------------------------- /css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.0.0 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | .hidden { 11 | display: none; 12 | visibility: hidden; 13 | } 14 | @media (max-width: 480px) { 15 | .nav-collapse { 16 | -webkit-transform: translate3d(0, 0, 0); 17 | } 18 | .page-header h1 small { 19 | display: block; 20 | line-height: 18px; 21 | } 22 | input[class*="span"], 23 | select[class*="span"], 24 | textarea[class*="span"], 25 | .uneditable-input { 26 | display: block; 27 | width: 100%; 28 | height: 28px; 29 | /* Make inputs at least the height of their button counterpart */ 30 | 31 | /* Makes inputs behave like true block-level elements */ 32 | 33 | -webkit-box-sizing: border-box; 34 | /* Older Webkit */ 35 | 36 | -moz-box-sizing: border-box; 37 | /* Older FF */ 38 | 39 | -ms-box-sizing: border-box; 40 | /* IE8 */ 41 | 42 | box-sizing: border-box; 43 | /* CSS3 spec*/ 44 | 45 | } 46 | .input-prepend input[class*="span"], .input-append input[class*="span"] { 47 | width: auto; 48 | } 49 | input[type="checkbox"], input[type="radio"] { 50 | border: 1px solid #ccc; 51 | } 52 | .form-horizontal .control-group > label { 53 | float: none; 54 | width: auto; 55 | padding-top: 0; 56 | text-align: left; 57 | } 58 | .form-horizontal .controls { 59 | margin-left: 0; 60 | } 61 | .form-horizontal .control-list { 62 | padding-top: 0; 63 | } 64 | .form-horizontal .form-actions { 65 | padding-left: 10px; 66 | padding-right: 10px; 67 | } 68 | .modal { 69 | position: absolute; 70 | top: 10px; 71 | left: 10px; 72 | right: 10px; 73 | width: auto; 74 | margin: 0; 75 | } 76 | .modal.fade.in { 77 | top: auto; 78 | } 79 | .modal-header .close { 80 | padding: 10px; 81 | margin: -10px; 82 | } 83 | .carousel-caption { 84 | position: static; 85 | } 86 | } 87 | @media (max-width: 768px) { 88 | .container { 89 | width: auto; 90 | padding: 0 20px; 91 | } 92 | .row-fluid { 93 | width: 100%; 94 | } 95 | .row { 96 | margin-left: 0; 97 | } 98 | .row > [class*="span"], .row-fluid > [class*="span"] { 99 | float: none; 100 | display: block; 101 | width: auto; 102 | margin: 0; 103 | } 104 | } 105 | @media (min-width: 768px) and (max-width: 980px) { 106 | .row { 107 | margin-left: -20px; 108 | *zoom: 1; 109 | } 110 | .row:before, .row:after { 111 | display: table; 112 | content: ""; 113 | } 114 | .row:after { 115 | clear: both; 116 | } 117 | [class*="span"] { 118 | float: left; 119 | margin-left: 20px; 120 | } 121 | .span1 { 122 | width: 42px; 123 | } 124 | .span2 { 125 | width: 104px; 126 | } 127 | .span3 { 128 | width: 166px; 129 | } 130 | .span4 { 131 | width: 228px; 132 | } 133 | .span5 { 134 | width: 290px; 135 | } 136 | .span6 { 137 | width: 352px; 138 | } 139 | .span7 { 140 | width: 414px; 141 | } 142 | .span8 { 143 | width: 476px; 144 | } 145 | .span9 { 146 | width: 538px; 147 | } 148 | .span10 { 149 | width: 600px; 150 | } 151 | .span11 { 152 | width: 662px; 153 | } 154 | .span12, .container { 155 | width: 724px; 156 | } 157 | .offset1 { 158 | margin-left: 82px; 159 | } 160 | .offset2 { 161 | margin-left: 144px; 162 | } 163 | .offset3 { 164 | margin-left: 206px; 165 | } 166 | .offset4 { 167 | margin-left: 268px; 168 | } 169 | .offset5 { 170 | margin-left: 330px; 171 | } 172 | .offset6 { 173 | margin-left: 392px; 174 | } 175 | .offset7 { 176 | margin-left: 454px; 177 | } 178 | .offset8 { 179 | margin-left: 516px; 180 | } 181 | .offset9 { 182 | margin-left: 578px; 183 | } 184 | .offset10 { 185 | margin-left: 640px; 186 | } 187 | .offset11 { 188 | margin-left: 702px; 189 | } 190 | .row-fluid { 191 | width: 100%; 192 | *zoom: 1; 193 | } 194 | .row-fluid:before, .row-fluid:after { 195 | display: table; 196 | content: ""; 197 | } 198 | .row-fluid:after { 199 | clear: both; 200 | } 201 | .row-fluid > [class*="span"] { 202 | float: left; 203 | margin-left: 2.762430939%; 204 | } 205 | .row-fluid > [class*="span"]:first-child { 206 | margin-left: 0; 207 | } 208 | .row-fluid .span1 { 209 | width: 5.801104972%; 210 | } 211 | .row-fluid .span2 { 212 | width: 14.364640883%; 213 | } 214 | .row-fluid .span3 { 215 | width: 22.928176794%; 216 | } 217 | .row-fluid .span4 { 218 | width: 31.491712705%; 219 | } 220 | .row-fluid .span5 { 221 | width: 40.055248616%; 222 | } 223 | .row-fluid .span6 { 224 | width: 48.618784527%; 225 | } 226 | .row-fluid .span7 { 227 | width: 57.182320438000005%; 228 | } 229 | .row-fluid .span8 { 230 | width: 65.74585634900001%; 231 | } 232 | .row-fluid .span9 { 233 | width: 74.30939226%; 234 | } 235 | .row-fluid .span10 { 236 | width: 82.87292817100001%; 237 | } 238 | .row-fluid .span11 { 239 | width: 91.436464082%; 240 | } 241 | .row-fluid .span12 { 242 | width: 99.999999993%; 243 | } 244 | input.span1, textarea.span1, .uneditable-input.span1 { 245 | width: 32px; 246 | } 247 | input.span2, textarea.span2, .uneditable-input.span2 { 248 | width: 94px; 249 | } 250 | input.span3, textarea.span3, .uneditable-input.span3 { 251 | width: 156px; 252 | } 253 | input.span4, textarea.span4, .uneditable-input.span4 { 254 | width: 218px; 255 | } 256 | input.span5, textarea.span5, .uneditable-input.span5 { 257 | width: 280px; 258 | } 259 | input.span6, textarea.span6, .uneditable-input.span6 { 260 | width: 342px; 261 | } 262 | input.span7, textarea.span7, .uneditable-input.span7 { 263 | width: 404px; 264 | } 265 | input.span8, textarea.span8, .uneditable-input.span8 { 266 | width: 466px; 267 | } 268 | input.span9, textarea.span9, .uneditable-input.span9 { 269 | width: 528px; 270 | } 271 | input.span10, textarea.span10, .uneditable-input.span10 { 272 | width: 590px; 273 | } 274 | input.span11, textarea.span11, .uneditable-input.span11 { 275 | width: 652px; 276 | } 277 | input.span12, textarea.span12, .uneditable-input.span12 { 278 | width: 714px; 279 | } 280 | } 281 | @media (max-width: 980px) { 282 | body { 283 | padding-top: 0; 284 | } 285 | .navbar-fixed-top { 286 | position: static; 287 | margin-bottom: 18px; 288 | } 289 | .navbar-fixed-top .navbar-inner { 290 | padding: 5px; 291 | } 292 | .navbar .container { 293 | width: auto; 294 | padding: 0; 295 | } 296 | .navbar .brand { 297 | padding-left: 10px; 298 | padding-right: 10px; 299 | margin: 0 0 0 -5px; 300 | } 301 | .navbar .nav-collapse { 302 | clear: left; 303 | } 304 | .navbar .nav { 305 | float: none; 306 | margin: 0 0 9px; 307 | } 308 | .navbar .nav > li { 309 | float: none; 310 | } 311 | .navbar .nav > li > a { 312 | margin-bottom: 2px; 313 | } 314 | .navbar .nav > .divider-vertical { 315 | display: none; 316 | } 317 | .navbar .nav > li > a, .navbar .dropdown-menu a { 318 | padding: 6px 15px; 319 | font-weight: bold; 320 | color: #999999; 321 | -webkit-border-radius: 3px; 322 | -moz-border-radius: 3px; 323 | border-radius: 3px; 324 | } 325 | .navbar .dropdown-menu li + li a { 326 | margin-bottom: 2px; 327 | } 328 | .navbar .nav > li > a:hover, .navbar .dropdown-menu a:hover { 329 | background-color: #222222; 330 | } 331 | .navbar .dropdown-menu { 332 | position: static; 333 | top: auto; 334 | left: auto; 335 | float: none; 336 | display: block; 337 | max-width: none; 338 | margin: 0 15px; 339 | padding: 0; 340 | background-color: transparent; 341 | border: none; 342 | -webkit-border-radius: 0; 343 | -moz-border-radius: 0; 344 | border-radius: 0; 345 | -webkit-box-shadow: none; 346 | -moz-box-shadow: none; 347 | box-shadow: none; 348 | } 349 | .navbar .dropdown-menu:before, .navbar .dropdown-menu:after { 350 | display: none; 351 | } 352 | .navbar .dropdown-menu .divider { 353 | display: none; 354 | } 355 | .navbar-form, .navbar-search { 356 | float: none; 357 | padding: 9px 15px; 358 | margin: 9px 0; 359 | border-top: 1px solid #222222; 360 | border-bottom: 1px solid #222222; 361 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 362 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 363 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 364 | } 365 | .navbar .nav.pull-right { 366 | float: none; 367 | margin-left: 0; 368 | } 369 | .navbar-static .navbar-inner { 370 | padding-left: 10px; 371 | padding-right: 10px; 372 | } 373 | .btn-navbar { 374 | display: block; 375 | } 376 | .nav-collapse { 377 | overflow: hidden; 378 | height: 0; 379 | } 380 | } 381 | @media (min-width: 980px) { 382 | .nav-collapse.collapse { 383 | height: auto !important; 384 | } 385 | } 386 | @media (min-width: 1200px) { 387 | .row { 388 | margin-left: -30px; 389 | *zoom: 1; 390 | } 391 | .row:before, .row:after { 392 | display: table; 393 | content: ""; 394 | } 395 | .row:after { 396 | clear: both; 397 | } 398 | [class*="span"] { 399 | float: left; 400 | margin-left: 30px; 401 | } 402 | .span1 { 403 | width: 70px; 404 | } 405 | .span2 { 406 | width: 170px; 407 | } 408 | .span3 { 409 | width: 270px; 410 | } 411 | .span4 { 412 | width: 370px; 413 | } 414 | .span5 { 415 | width: 470px; 416 | } 417 | .span6 { 418 | width: 570px; 419 | } 420 | .span7 { 421 | width: 670px; 422 | } 423 | .span8 { 424 | width: 770px; 425 | } 426 | .span9 { 427 | width: 870px; 428 | } 429 | .span10 { 430 | width: 970px; 431 | } 432 | .span11 { 433 | width: 1070px; 434 | } 435 | .span12, .container { 436 | width: 1170px; 437 | } 438 | .offset1 { 439 | margin-left: 130px; 440 | } 441 | .offset2 { 442 | margin-left: 230px; 443 | } 444 | .offset3 { 445 | margin-left: 330px; 446 | } 447 | .offset4 { 448 | margin-left: 430px; 449 | } 450 | .offset5 { 451 | margin-left: 530px; 452 | } 453 | .offset6 { 454 | margin-left: 630px; 455 | } 456 | .offset7 { 457 | margin-left: 730px; 458 | } 459 | .offset8 { 460 | margin-left: 830px; 461 | } 462 | .offset9 { 463 | margin-left: 930px; 464 | } 465 | .offset10 { 466 | margin-left: 1030px; 467 | } 468 | .offset11 { 469 | margin-left: 1130px; 470 | } 471 | .row-fluid { 472 | width: 100%; 473 | *zoom: 1; 474 | } 475 | .row-fluid:before, .row-fluid:after { 476 | display: table; 477 | content: ""; 478 | } 479 | .row-fluid:after { 480 | clear: both; 481 | } 482 | .row-fluid > [class*="span"] { 483 | float: left; 484 | margin-left: 2.564102564%; 485 | } 486 | .row-fluid > [class*="span"]:first-child { 487 | margin-left: 0; 488 | } 489 | .row-fluid .span1 { 490 | width: 5.982905983%; 491 | } 492 | .row-fluid .span2 { 493 | width: 14.529914530000001%; 494 | } 495 | .row-fluid .span3 { 496 | width: 23.076923077%; 497 | } 498 | .row-fluid .span4 { 499 | width: 31.623931624%; 500 | } 501 | .row-fluid .span5 { 502 | width: 40.170940171000005%; 503 | } 504 | .row-fluid .span6 { 505 | width: 48.717948718%; 506 | } 507 | .row-fluid .span7 { 508 | width: 57.264957265%; 509 | } 510 | .row-fluid .span8 { 511 | width: 65.81196581200001%; 512 | } 513 | .row-fluid .span9 { 514 | width: 74.358974359%; 515 | } 516 | .row-fluid .span10 { 517 | width: 82.905982906%; 518 | } 519 | .row-fluid .span11 { 520 | width: 91.45299145300001%; 521 | } 522 | .row-fluid .span12 { 523 | width: 100%; 524 | } 525 | input.span1, textarea.span1, .uneditable-input.span1 { 526 | width: 60px; 527 | } 528 | input.span2, textarea.span2, .uneditable-input.span2 { 529 | width: 160px; 530 | } 531 | input.span3, textarea.span3, .uneditable-input.span3 { 532 | width: 260px; 533 | } 534 | input.span4, textarea.span4, .uneditable-input.span4 { 535 | width: 360px; 536 | } 537 | input.span5, textarea.span5, .uneditable-input.span5 { 538 | width: 460px; 539 | } 540 | input.span6, textarea.span6, .uneditable-input.span6 { 541 | width: 560px; 542 | } 543 | input.span7, textarea.span7, .uneditable-input.span7 { 544 | width: 660px; 545 | } 546 | input.span8, textarea.span8, .uneditable-input.span8 { 547 | width: 760px; 548 | } 549 | input.span9, textarea.span9, .uneditable-input.span9 { 550 | width: 860px; 551 | } 552 | input.span10, textarea.span10, .uneditable-input.span10 { 553 | width: 960px; 554 | } 555 | input.span11, textarea.span11, .uneditable-input.span11 { 556 | width: 1060px; 557 | } 558 | input.span12, textarea.span12, .uneditable-input.span12 { 559 | width: 1160px; 560 | } 561 | .thumbnails { 562 | margin-left: -30px; 563 | } 564 | .thumbnails > li { 565 | margin-left: 30px; 566 | } 567 | } 568 | -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box/RainGauge/6ed95786bfa90b7f400145f54433220d621618f3/img/favicon.ico -------------------------------------------------------------------------------- /img/raingauge_drops_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box/RainGauge/6ed95786bfa90b7f400145f54433220d621618f3/img/raingauge_drops_small.png -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | , Geoffrey Anderson 7 | * @created 2012-05-15 8 | * @license Apache 2.0 license. See LICENSE document for more info 9 | **/ 10 | 11 | set_include_path( get_include_path() . PATH_SEPARATOR . "./lib"); 12 | require "Helpers.php"; 13 | require "RainGauge.php"; 14 | 15 | error_reporting(E_ERROR); 16 | $action = isset($_GET['action']) ? $_GET['action'] : 'index'; 17 | 18 | $conf = array(); 19 | include "conf/config.inc.php"; 20 | if (empty($conf)) 21 | { 22 | $action = 'noconfig'; 23 | } 24 | 25 | $controller = new RainGauge($conf); 26 | if (is_callable(array($controller, $action ))) 27 | { 28 | $controller->$action(); 29 | } 30 | else 31 | { 32 | print "Invalid action ($action)"; 33 | } 34 | 35 | ?> 36 | -------------------------------------------------------------------------------- /js/bootstrap-collapse.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-collapse.js v2.0.1 3 | * http://twitter.github.com/bootstrap/javascript.html#collapse 4 | * ============================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | !function( $ ){ 21 | 22 | "use strict" 23 | 24 | var Collapse = function ( element, options ) { 25 | this.$element = $(element) 26 | this.options = $.extend({}, $.fn.collapse.defaults, options) 27 | 28 | if (this.options["parent"]) { 29 | this.$parent = $(this.options["parent"]) 30 | } 31 | 32 | this.options.toggle && this.toggle() 33 | } 34 | 35 | Collapse.prototype = { 36 | 37 | constructor: Collapse 38 | 39 | , dimension: function () { 40 | var hasWidth = this.$element.hasClass('width') 41 | return hasWidth ? 'width' : 'height' 42 | } 43 | 44 | , show: function () { 45 | var dimension = this.dimension() 46 | , scroll = $.camelCase(['scroll', dimension].join('-')) 47 | , actives = this.$parent && this.$parent.find('.in') 48 | , hasData 49 | 50 | if (actives && actives.length) { 51 | hasData = actives.data('collapse') 52 | actives.collapse('hide') 53 | hasData || actives.data('collapse', null) 54 | } 55 | 56 | this.$element[dimension](0) 57 | this.transition('addClass', 'show', 'shown') 58 | this.$element[dimension](this.$element[0][scroll]) 59 | 60 | } 61 | 62 | , hide: function () { 63 | var dimension = this.dimension() 64 | this.reset(this.$element[dimension]()) 65 | this.transition('removeClass', 'hide', 'hidden') 66 | this.$element[dimension](0) 67 | } 68 | 69 | , reset: function ( size ) { 70 | var dimension = this.dimension() 71 | 72 | this.$element 73 | .removeClass('collapse') 74 | [dimension](size || 'auto') 75 | [0].offsetWidth 76 | 77 | this.$element.addClass('collapse') 78 | } 79 | 80 | , transition: function ( method, startEvent, completeEvent ) { 81 | var that = this 82 | , complete = function () { 83 | if (startEvent == 'show') that.reset() 84 | that.$element.trigger(completeEvent) 85 | } 86 | 87 | this.$element 88 | .trigger(startEvent) 89 | [method]('in') 90 | 91 | $.support.transition && this.$element.hasClass('collapse') ? 92 | this.$element.one($.support.transition.end, complete) : 93 | complete() 94 | } 95 | 96 | , toggle: function () { 97 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 98 | } 99 | 100 | } 101 | 102 | /* COLLAPSIBLE PLUGIN DEFINITION 103 | * ============================== */ 104 | 105 | $.fn.collapse = function ( option ) { 106 | return this.each(function () { 107 | var $this = $(this) 108 | , data = $this.data('collapse') 109 | , options = typeof option == 'object' && option 110 | if (!data) $this.data('collapse', (data = new Collapse(this, options))) 111 | if (typeof option == 'string') data[option]() 112 | }) 113 | } 114 | 115 | $.fn.collapse.defaults = { 116 | toggle: true 117 | } 118 | 119 | $.fn.collapse.Constructor = Collapse 120 | 121 | 122 | /* COLLAPSIBLE DATA-API 123 | * ==================== */ 124 | 125 | $(function () { 126 | $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { 127 | var $this = $(this), href 128 | , target = $this.attr('data-target') 129 | || e.preventDefault() 130 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 131 | , option = $(target).data('collapse') ? 'toggle' : $this.data() 132 | $(target).collapse(option) 133 | }) 134 | }) 135 | 136 | }( window.jQuery ); -------------------------------------------------------------------------------- /js/bootstrap-combobox.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-combobox.js v0.9.0 3 | * ============================================================= 4 | * Copyright 2012 Daniel Farrell 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * ============================================================ */ 18 | 19 | !function( $ ) { 20 | 21 | "use strict" 22 | 23 | var Combobox = function ( element, options ) { 24 | this.options = $.extend({}, $.fn.combobox.defaults, options) 25 | this.$container = this.setup(element) 26 | this.$element = this.$container.find('input') 27 | this.$button = this.$container.find('.dropdown-toggle') 28 | this.$target = this.$container.find('select') 29 | this.matcher = this.options.matcher || this.matcher 30 | this.sorter = this.options.sorter || this.sorter 31 | this.highlighter = this.options.highlighter || this.highlighter 32 | this.$menu = $(this.options.menu).appendTo('body') 33 | this.source = this.parse() 34 | this.options.items = this.source.length 35 | this.shown = false 36 | this.listen() 37 | } 38 | 39 | /* NOTE: COMBOBOX EXTENDS BOOTSTRAP-TYPEAHEAD.js 40 | ========================================== */ 41 | 42 | Combobox.prototype = $.extend({}, $.fn.typeahead.Constructor.prototype, { 43 | 44 | constructor: Combobox 45 | 46 | , setup: function (element) { 47 | var select = $(element) 48 | , combobox = $(this.options.template) 49 | select.before(combobox) 50 | select.detach() 51 | combobox.prepend(select) 52 | return combobox 53 | } 54 | 55 | , parse: function () { 56 | var map = {} 57 | , source = [] 58 | , selected = false 59 | this.$element.siblings('select').find('option').each(function() { 60 | var option = $(this) 61 | map[option.html()] = option.val() 62 | source.push(option.html()) 63 | if(option.attr('selected')) selected = option.html() 64 | }) 65 | this.map = map 66 | if (selected) { 67 | this.$element.val(selected) 68 | this.$container.addClass('combobox-selected') 69 | } 70 | return source 71 | } 72 | 73 | , toggle: function () { 74 | if (this.$container.hasClass('combobox-selected')) { 75 | this.$target.val('') 76 | this.$element.val('') 77 | this.$container.removeClass('combobox-selected') 78 | } else { 79 | if (this.shown) { 80 | this.hide() 81 | } else { 82 | this.lookup() 83 | } 84 | } 85 | } 86 | 87 | // modified typeahead function adding container and target handling 88 | , select: function () { 89 | var val = this.$menu.find('.active').attr('data-value') 90 | this.$element.val(val) 91 | this.$container.addClass('combobox-selected') 92 | this.$target.val(this.map[val]) 93 | this.$target.trigger('change') 94 | return this.hide() 95 | } 96 | 97 | // modified typeahead function removing the blank handling 98 | , lookup: function (event) { 99 | var that = this 100 | , items 101 | , q 102 | 103 | this.query = this.$element.val() 104 | 105 | items = $.grep(this.source, function (item) { 106 | if (that.matcher(item)) return item 107 | }) 108 | 109 | items = this.sorter(items) 110 | 111 | if (!items.length) { 112 | return this.shown ? this.hide() : this 113 | } 114 | 115 | return this.render(items.slice(0, this.options.items)).show() 116 | } 117 | 118 | // modified typeahead function adding button handling 119 | , listen: function () { 120 | this.$element 121 | .on('blur', $.proxy(this.blur, this)) 122 | .on('keypress', $.proxy(this.keypress, this)) 123 | .on('keyup', $.proxy(this.keyup, this)) 124 | 125 | if ($.browser.webkit || $.browser.msie) { 126 | this.$element.on('keydown', $.proxy(this.keypress, this)) 127 | } 128 | 129 | this.$menu 130 | .on('click', $.proxy(this.click, this)) 131 | .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) 132 | 133 | this.$button 134 | .on('click', $.proxy(this.toggle, this)) 135 | } 136 | 137 | }) 138 | 139 | /* COMBOBOX PLUGIN DEFINITION 140 | * =========================== */ 141 | 142 | $.fn.combobox = function ( option ) { 143 | return this.each(function () { 144 | var $this = $(this) 145 | , data 146 | , options = typeof option == 'object' && option 147 | $this.data('combobox', (data = new Combobox(this, options))) 148 | if (typeof option == 'string') data[option]() 149 | }) 150 | } 151 | 152 | $.fn.combobox.defaults = { 153 | template: '
' 154 | , menu: '' 155 | , item: '
  • ' 156 | } 157 | 158 | $.fn.combobox.Constructor = Combobox 159 | 160 | }( window.jQuery ) 161 | -------------------------------------------------------------------------------- /js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v2.0.1 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdowns 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* DROPDOWN CLASS DEFINITION 26 | * ========================= */ 27 | 28 | var toggle = '[data-toggle="dropdown"]' 29 | , Dropdown = function ( element ) { 30 | var $el = $(element).on('click.dropdown.data-api', this.toggle) 31 | $('html').on('click.dropdown.data-api', function () { 32 | $el.parent().removeClass('open') 33 | }) 34 | } 35 | 36 | Dropdown.prototype = { 37 | 38 | constructor: Dropdown 39 | 40 | , toggle: function ( e ) { 41 | var $this = $(this) 42 | , selector = $this.attr('data-target') 43 | , $parent 44 | , isActive 45 | 46 | if (!selector) { 47 | selector = $this.attr('href') 48 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 49 | } 50 | 51 | $parent = $(selector) 52 | $parent.length || ($parent = $this.parent()) 53 | 54 | isActive = $parent.hasClass('open') 55 | 56 | clearMenus() 57 | !isActive && $parent.toggleClass('open') 58 | 59 | return false 60 | } 61 | 62 | } 63 | 64 | function clearMenus() { 65 | $(toggle).parent().removeClass('open') 66 | } 67 | 68 | 69 | /* DROPDOWN PLUGIN DEFINITION 70 | * ========================== */ 71 | 72 | $.fn.dropdown = function ( option ) { 73 | return this.each(function () { 74 | var $this = $(this) 75 | , data = $this.data('dropdown') 76 | if (!data) $this.data('dropdown', (data = new Dropdown(this))) 77 | if (typeof option == 'string') data[option].call($this) 78 | }) 79 | } 80 | 81 | $.fn.dropdown.Constructor = Dropdown 82 | 83 | 84 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 85 | * =================================== */ 86 | 87 | $(function () { 88 | $('html').on('click.dropdown.data-api', clearMenus) 89 | $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) 90 | }) 91 | 92 | }( window.jQuery ); -------------------------------------------------------------------------------- /js/bootstrap-tab.js: -------------------------------------------------------------------------------- 1 | /* ======================================================== 2 | * bootstrap-tab.js v2.0.1 3 | * http://twitter.github.com/bootstrap/javascript.html#tabs 4 | * ======================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ======================================================== */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* TAB CLASS DEFINITION 26 | * ==================== */ 27 | 28 | var Tab = function ( element ) { 29 | this.element = $(element) 30 | } 31 | 32 | Tab.prototype = { 33 | 34 | constructor: Tab 35 | 36 | , show: function () { 37 | var $this = this.element 38 | , $ul = $this.closest('ul:not(.dropdown-menu)') 39 | , selector = $this.attr('data-target') 40 | , previous 41 | , $target 42 | 43 | if (!selector) { 44 | selector = $this.attr('href') 45 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 46 | } 47 | 48 | if ( $this.parent('li').hasClass('active') ) return 49 | 50 | previous = $ul.find('.active a').last()[0] 51 | 52 | $this.trigger({ 53 | type: 'show' 54 | , relatedTarget: previous 55 | }) 56 | 57 | $target = $(selector) 58 | 59 | this.activate($this.parent('li'), $ul) 60 | this.activate($target, $target.parent(), function () { 61 | $this.trigger({ 62 | type: 'shown' 63 | , relatedTarget: previous 64 | }) 65 | }) 66 | } 67 | 68 | , activate: function ( element, container, callback) { 69 | var $active = container.find('> .active') 70 | , transition = callback 71 | && $.support.transition 72 | && $active.hasClass('fade') 73 | 74 | function next() { 75 | $active 76 | .removeClass('active') 77 | .find('> .dropdown-menu > .active') 78 | .removeClass('active') 79 | 80 | element.addClass('active') 81 | 82 | if (transition) { 83 | element[0].offsetWidth // reflow for transition 84 | element.addClass('in') 85 | } else { 86 | element.removeClass('fade') 87 | } 88 | 89 | if ( element.parent('.dropdown-menu') ) { 90 | element.closest('li.dropdown').addClass('active') 91 | } 92 | 93 | callback && callback() 94 | } 95 | 96 | transition ? 97 | $active.one($.support.transition.end, next) : 98 | next() 99 | 100 | $active.removeClass('in') 101 | } 102 | } 103 | 104 | 105 | /* TAB PLUGIN DEFINITION 106 | * ===================== */ 107 | 108 | $.fn.tab = function ( option ) { 109 | return this.each(function () { 110 | var $this = $(this) 111 | , data = $this.data('tab') 112 | if (!data) $this.data('tab', (data = new Tab(this))) 113 | if (typeof option == 'string') data[option]() 114 | }) 115 | } 116 | 117 | $.fn.tab.Constructor = Tab 118 | 119 | 120 | /* TAB DATA-API 121 | * ============ */ 122 | 123 | $(function () { 124 | $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { 125 | e.preventDefault() 126 | $(this).tab('show') 127 | }) 128 | }) 129 | 130 | }( window.jQuery ); -------------------------------------------------------------------------------- /js/bootstrap-typeahead.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-typeahead.js v2.0.1 3 | * http://twitter.github.com/bootstrap/javascript.html#typeahead 4 | * ============================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | !function( $ ){ 21 | 22 | "use strict" 23 | 24 | var Typeahead = function ( element, options ) { 25 | this.$element = $(element) 26 | this.options = $.extend({}, $.fn.typeahead.defaults, options) 27 | this.matcher = this.options.matcher || this.matcher 28 | this.sorter = this.options.sorter || this.sorter 29 | this.highlighter = this.options.highlighter || this.highlighter 30 | this.$menu = $(this.options.menu).appendTo('body') 31 | this.source = this.options.source 32 | this.shown = false 33 | this.listen() 34 | } 35 | 36 | Typeahead.prototype = { 37 | 38 | constructor: Typeahead 39 | 40 | , select: function () { 41 | var val = this.$menu.find('.active').attr('data-value') 42 | this.$element.val(val) 43 | return this.hide() 44 | } 45 | 46 | , show: function () { 47 | var pos = $.extend({}, this.$element.offset(), { 48 | height: this.$element[0].offsetHeight 49 | }) 50 | 51 | this.$menu.css({ 52 | top: pos.top + pos.height 53 | , left: pos.left 54 | }) 55 | 56 | this.$menu.show() 57 | this.shown = true 58 | return this 59 | } 60 | 61 | , hide: function () { 62 | this.$menu.hide() 63 | this.shown = false 64 | return this 65 | } 66 | 67 | , lookup: function (event) { 68 | var that = this 69 | , items 70 | , q 71 | 72 | this.query = this.$element.val() 73 | 74 | if (!this.query) { 75 | return this.shown ? this.hide() : this 76 | } 77 | 78 | items = $.grep(this.source, function (item) { 79 | if (that.matcher(item)) return item 80 | }) 81 | 82 | items = this.sorter(items) 83 | 84 | if (!items.length) { 85 | return this.shown ? this.hide() : this 86 | } 87 | 88 | return this.render(items.slice(0, this.options.items)).show() 89 | } 90 | 91 | , matcher: function (item) { 92 | return ~item.toLowerCase().indexOf(this.query.toLowerCase()) 93 | } 94 | 95 | , sorter: function (items) { 96 | var beginswith = [] 97 | , caseSensitive = [] 98 | , caseInsensitive = [] 99 | , item 100 | 101 | while (item = items.shift()) { 102 | if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item) 103 | else if (~item.indexOf(this.query)) caseSensitive.push(item) 104 | else caseInsensitive.push(item) 105 | } 106 | 107 | return beginswith.concat(caseSensitive, caseInsensitive) 108 | } 109 | 110 | , highlighter: function (item) { 111 | return item.replace(new RegExp('(' + this.query + ')', 'ig'), function ($1, match) { 112 | return '' + match + '' 113 | }) 114 | } 115 | 116 | , render: function (items) { 117 | var that = this 118 | 119 | items = $(items).map(function (i, item) { 120 | i = $(that.options.item).attr('data-value', item) 121 | i.find('a').html(that.highlighter(item)) 122 | return i[0] 123 | }) 124 | 125 | items.first().addClass('active') 126 | this.$menu.html(items) 127 | return this 128 | } 129 | 130 | , next: function (event) { 131 | var active = this.$menu.find('.active').removeClass('active') 132 | , next = active.next() 133 | 134 | if (!next.length) { 135 | next = $(this.$menu.find('li')[0]) 136 | } 137 | 138 | next.addClass('active') 139 | } 140 | 141 | , prev: function (event) { 142 | var active = this.$menu.find('.active').removeClass('active') 143 | , prev = active.prev() 144 | 145 | if (!prev.length) { 146 | prev = this.$menu.find('li').last() 147 | } 148 | 149 | prev.addClass('active') 150 | } 151 | 152 | , listen: function () { 153 | this.$element 154 | .on('blur', $.proxy(this.blur, this)) 155 | .on('keypress', $.proxy(this.keypress, this)) 156 | .on('keyup', $.proxy(this.keyup, this)) 157 | 158 | if ($.browser.webkit || $.browser.msie) { 159 | this.$element.on('keydown', $.proxy(this.keypress, this)) 160 | } 161 | 162 | this.$menu 163 | .on('click', $.proxy(this.click, this)) 164 | .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) 165 | } 166 | 167 | , keyup: function (e) { 168 | e.stopPropagation() 169 | e.preventDefault() 170 | 171 | switch(e.keyCode) { 172 | case 40: // down arrow 173 | case 38: // up arrow 174 | break 175 | 176 | case 9: // tab 177 | case 13: // enter 178 | if (!this.shown) return 179 | this.select() 180 | break 181 | 182 | case 27: // escape 183 | this.hide() 184 | break 185 | 186 | default: 187 | this.lookup() 188 | } 189 | 190 | } 191 | 192 | , keypress: function (e) { 193 | e.stopPropagation() 194 | if (!this.shown) return 195 | 196 | switch(e.keyCode) { 197 | case 9: // tab 198 | case 13: // enter 199 | case 27: // escape 200 | e.preventDefault() 201 | break 202 | 203 | case 38: // up arrow 204 | e.preventDefault() 205 | this.prev() 206 | break 207 | 208 | case 40: // down arrow 209 | e.preventDefault() 210 | this.next() 211 | break 212 | } 213 | } 214 | 215 | , blur: function (e) { 216 | var that = this 217 | e.stopPropagation() 218 | e.preventDefault() 219 | setTimeout(function () { that.hide() }, 150) 220 | } 221 | 222 | , click: function (e) { 223 | e.stopPropagation() 224 | e.preventDefault() 225 | this.select() 226 | } 227 | 228 | , mouseenter: function (e) { 229 | this.$menu.find('.active').removeClass('active') 230 | $(e.currentTarget).addClass('active') 231 | } 232 | 233 | } 234 | 235 | 236 | /* TYPEAHEAD PLUGIN DEFINITION 237 | * =========================== */ 238 | 239 | $.fn.typeahead = function ( option ) { 240 | return this.each(function () { 241 | var $this = $(this) 242 | , data = $this.data('typeahead') 243 | , options = typeof option == 'object' && option 244 | if (!data) $this.data('typeahead', (data = new Typeahead(this, options))) 245 | if (typeof option == 'string') data[option]() 246 | }) 247 | } 248 | 249 | $.fn.typeahead.defaults = { 250 | source: [] 251 | , items: 8 252 | , menu: '' 253 | , item: '
  • ' 254 | } 255 | 256 | $.fn.typeahead.Constructor = Typeahead 257 | 258 | 259 | /* TYPEAHEAD DATA-API 260 | * ================== */ 261 | 262 | $(function () { 263 | $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { 264 | var $this = $(this) 265 | if ($this.data('typeahead')) return 266 | e.preventDefault() 267 | $this.typeahead($this.data()) 268 | }) 269 | }) 270 | 271 | }( window.jQuery ); -------------------------------------------------------------------------------- /js/flot/jquery.colorhelpers.js: -------------------------------------------------------------------------------- 1 | /* Plugin for jQuery for working with colors. 2 | * 3 | * Version 1.1. 4 | * 5 | * Inspiration from jQuery color animation plugin by John Resig. 6 | * 7 | * Released under the MIT license by Ole Laursen, October 2009. 8 | * 9 | * Examples: 10 | * 11 | * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() 12 | * var c = $.color.extract($("#mydiv"), 'background-color'); 13 | * console.log(c.r, c.g, c.b, c.a); 14 | * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" 15 | * 16 | * Note that .scale() and .add() return the same modified object 17 | * instead of making a new one. 18 | * 19 | * V. 1.1: Fix error handling so e.g. parsing an empty string does 20 | * produce a color rather than just crashing. 21 | */ 22 | 23 | (function($) { 24 | $.color = {}; 25 | 26 | // construct color object with some convenient chainable helpers 27 | $.color.make = function (r, g, b, a) { 28 | var o = {}; 29 | o.r = r || 0; 30 | o.g = g || 0; 31 | o.b = b || 0; 32 | o.a = a != null ? a : 1; 33 | 34 | o.add = function (c, d) { 35 | for (var i = 0; i < c.length; ++i) 36 | o[c.charAt(i)] += d; 37 | return o.normalize(); 38 | }; 39 | 40 | o.scale = function (c, f) { 41 | for (var i = 0; i < c.length; ++i) 42 | o[c.charAt(i)] *= f; 43 | return o.normalize(); 44 | }; 45 | 46 | o.toString = function () { 47 | if (o.a >= 1.0) { 48 | return "rgb("+[o.r, o.g, o.b].join(",")+")"; 49 | } else { 50 | return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")"; 51 | } 52 | }; 53 | 54 | o.normalize = function () { 55 | function clamp(min, value, max) { 56 | return value < min ? min: (value > max ? max: value); 57 | } 58 | 59 | o.r = clamp(0, parseInt(o.r), 255); 60 | o.g = clamp(0, parseInt(o.g), 255); 61 | o.b = clamp(0, parseInt(o.b), 255); 62 | o.a = clamp(0, o.a, 1); 63 | return o; 64 | }; 65 | 66 | o.clone = function () { 67 | return $.color.make(o.r, o.b, o.g, o.a); 68 | }; 69 | 70 | return o.normalize(); 71 | } 72 | 73 | // extract CSS color property from element, going up in the DOM 74 | // if it's "transparent" 75 | $.color.extract = function (elem, css) { 76 | var c; 77 | do { 78 | c = elem.css(css).toLowerCase(); 79 | // keep going until we find an element that has color, or 80 | // we hit the body 81 | if (c != '' && c != 'transparent') 82 | break; 83 | elem = elem.parent(); 84 | } while (!$.nodeName(elem.get(0), "body")); 85 | 86 | // catch Safari's way of signalling transparent 87 | if (c == "rgba(0, 0, 0, 0)") 88 | c = "transparent"; 89 | 90 | return $.color.parse(c); 91 | } 92 | 93 | // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"), 94 | // returns color object, if parsing failed, you get black (0, 0, 95 | // 0) out 96 | $.color.parse = function (str) { 97 | var res, m = $.color.make; 98 | 99 | // Look for rgb(num,num,num) 100 | if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)) 101 | return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10)); 102 | 103 | // Look for rgba(num,num,num,num) 104 | if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) 105 | return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4])); 106 | 107 | // Look for rgb(num%,num%,num%) 108 | if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)) 109 | return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55); 110 | 111 | // Look for rgba(num%,num%,num%,num) 112 | if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) 113 | return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4])); 114 | 115 | // Look for #a0b1c2 116 | if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)) 117 | return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)); 118 | 119 | // Look for #fff 120 | if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)) 121 | return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16)); 122 | 123 | // Otherwise, we're most likely dealing with a named color 124 | var name = $.trim(str).toLowerCase(); 125 | if (name == "transparent") 126 | return m(255, 255, 255, 0); 127 | else { 128 | // default to black 129 | res = lookupColors[name] || [0, 0, 0]; 130 | return m(res[0], res[1], res[2]); 131 | } 132 | } 133 | 134 | var lookupColors = { 135 | aqua:[0,255,255], 136 | azure:[240,255,255], 137 | beige:[245,245,220], 138 | black:[0,0,0], 139 | blue:[0,0,255], 140 | brown:[165,42,42], 141 | cyan:[0,255,255], 142 | darkblue:[0,0,139], 143 | darkcyan:[0,139,139], 144 | darkgrey:[169,169,169], 145 | darkgreen:[0,100,0], 146 | darkkhaki:[189,183,107], 147 | darkmagenta:[139,0,139], 148 | darkolivegreen:[85,107,47], 149 | darkorange:[255,140,0], 150 | darkorchid:[153,50,204], 151 | darkred:[139,0,0], 152 | darksalmon:[233,150,122], 153 | darkviolet:[148,0,211], 154 | fuchsia:[255,0,255], 155 | gold:[255,215,0], 156 | green:[0,128,0], 157 | indigo:[75,0,130], 158 | khaki:[240,230,140], 159 | lightblue:[173,216,230], 160 | lightcyan:[224,255,255], 161 | lightgreen:[144,238,144], 162 | lightgrey:[211,211,211], 163 | lightpink:[255,182,193], 164 | lightyellow:[255,255,224], 165 | lime:[0,255,0], 166 | magenta:[255,0,255], 167 | maroon:[128,0,0], 168 | navy:[0,0,128], 169 | olive:[128,128,0], 170 | orange:[255,165,0], 171 | pink:[255,192,203], 172 | purple:[128,0,128], 173 | violet:[128,0,128], 174 | red:[255,0,0], 175 | silver:[192,192,192], 176 | white:[255,255,255], 177 | yellow:[255,255,0] 178 | }; 179 | })(jQuery); 180 | -------------------------------------------------------------------------------- /js/flot/jquery.colorhelpers.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.color={};b.color.make=function(f,e,c,d){var h={};h.r=f||0;h.g=e||0;h.b=c||0;h.a=d!=null?d:1;h.add=function(k,j){for(var g=0;g=1){return"rgb("+[h.r,h.g,h.b].join(",")+")"}else{return"rgba("+[h.r,h.g,h.b,h.a].join(",")+")"}};h.normalize=function(){function g(j,k,i){return ki?i:k)}h.r=g(0,parseInt(h.r),255);h.g=g(0,parseInt(h.g),255);h.b=g(0,parseInt(h.b),255);h.a=g(0,h.a,1);return h};h.clone=function(){return b.color.make(h.r,h.b,h.g,h.a)};return h.normalize()};b.color.extract=function(e,d){var f;do{f=e.css(d).toLowerCase();if(f!=""&&f!="transparent"){break}e=e.parent()}while(!b.nodeName(e.get(0),"body"));if(f=="rgba(0, 0, 0, 0)"){f="transparent"}return b.color.parse(f)};b.color.parse=function(f){var e,c=b.color.make;if(e=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(f)){return c(parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10))}if(e=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(f)){return c(parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10),parseFloat(e[4]))}if(e=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(f)){return c(parseFloat(e[1])*2.55,parseFloat(e[2])*2.55,parseFloat(e[3])*2.55)}if(e=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(f)){return c(parseFloat(e[1])*2.55,parseFloat(e[2])*2.55,parseFloat(e[3])*2.55,parseFloat(e[4]))}if(e=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(f)){return c(parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16))}if(e=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(f)){return c(parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16))}var d=b.trim(f).toLowerCase();if(d=="transparent"){return c(255,255,255,0)}else{e=a[d]||[0,0,0];return c(e[0],e[1],e[2])}};var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); -------------------------------------------------------------------------------- /js/flot/jquery.flot.crosshair.js: -------------------------------------------------------------------------------- 1 | /* 2 | Flot plugin for showing crosshairs, thin lines, when the mouse hovers 3 | over the plot. 4 | 5 | crosshair: { 6 | mode: null or "x" or "y" or "xy" 7 | color: color 8 | lineWidth: number 9 | } 10 | 11 | Set the mode to one of "x", "y" or "xy". The "x" mode enables a 12 | vertical crosshair that lets you trace the values on the x axis, "y" 13 | enables a horizontal crosshair and "xy" enables them both. "color" is 14 | the color of the crosshair (default is "rgba(170, 0, 0, 0.80)"), 15 | "lineWidth" is the width of the drawn lines (default is 1). 16 | 17 | The plugin also adds four public methods: 18 | 19 | - setCrosshair(pos) 20 | 21 | Set the position of the crosshair. Note that this is cleared if 22 | the user moves the mouse. "pos" is in coordinates of the plot and 23 | should be on the form { x: xpos, y: ypos } (you can use x2/x3/... 24 | if you're using multiple axes), which is coincidentally the same 25 | format as what you get from a "plothover" event. If "pos" is null, 26 | the crosshair is cleared. 27 | 28 | - clearCrosshair() 29 | 30 | Clear the crosshair. 31 | 32 | - lockCrosshair(pos) 33 | 34 | Cause the crosshair to lock to the current location, no longer 35 | updating if the user moves the mouse. Optionally supply a position 36 | (passed on to setCrosshair()) to move it to. 37 | 38 | Example usage: 39 | var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; 40 | $("#graph").bind("plothover", function (evt, position, item) { 41 | if (item) { 42 | // Lock the crosshair to the data point being hovered 43 | myFlot.lockCrosshair({ x: item.datapoint[0], y: item.datapoint[1] }); 44 | } 45 | else { 46 | // Return normal crosshair operation 47 | myFlot.unlockCrosshair(); 48 | } 49 | }); 50 | 51 | - unlockCrosshair() 52 | 53 | Free the crosshair to move again after locking it. 54 | */ 55 | 56 | (function ($) { 57 | var options = { 58 | crosshair: { 59 | mode: null, // one of null, "x", "y" or "xy", 60 | color: "rgba(170, 0, 0, 0.80)", 61 | lineWidth: 1 62 | } 63 | }; 64 | 65 | function init(plot) { 66 | // position of crosshair in pixels 67 | var crosshair = { x: -1, y: -1, locked: false }; 68 | 69 | plot.setCrosshair = function setCrosshair(pos) { 70 | if (!pos) 71 | crosshair.x = -1; 72 | else { 73 | var o = plot.p2c(pos); 74 | crosshair.x = Math.max(0, Math.min(o.left, plot.width())); 75 | crosshair.y = Math.max(0, Math.min(o.top, plot.height())); 76 | } 77 | 78 | plot.triggerRedrawOverlay(); 79 | }; 80 | 81 | plot.clearCrosshair = plot.setCrosshair; // passes null for pos 82 | 83 | plot.lockCrosshair = function lockCrosshair(pos) { 84 | if (pos) 85 | plot.setCrosshair(pos); 86 | crosshair.locked = true; 87 | } 88 | 89 | plot.unlockCrosshair = function unlockCrosshair() { 90 | crosshair.locked = false; 91 | } 92 | 93 | function onMouseOut(e) { 94 | if (crosshair.locked) 95 | return; 96 | 97 | if (crosshair.x != -1) { 98 | crosshair.x = -1; 99 | plot.triggerRedrawOverlay(); 100 | } 101 | } 102 | 103 | function onMouseMove(e) { 104 | if (crosshair.locked) 105 | return; 106 | 107 | if (plot.getSelection && plot.getSelection()) { 108 | crosshair.x = -1; // hide the crosshair while selecting 109 | return; 110 | } 111 | 112 | var offset = plot.offset(); 113 | crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); 114 | crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); 115 | plot.triggerRedrawOverlay(); 116 | } 117 | 118 | plot.hooks.bindEvents.push(function (plot, eventHolder) { 119 | if (!plot.getOptions().crosshair.mode) 120 | return; 121 | 122 | eventHolder.mouseout(onMouseOut); 123 | eventHolder.mousemove(onMouseMove); 124 | }); 125 | 126 | plot.hooks.drawOverlay.push(function (plot, ctx) { 127 | var c = plot.getOptions().crosshair; 128 | if (!c.mode) 129 | return; 130 | 131 | var plotOffset = plot.getPlotOffset(); 132 | 133 | ctx.save(); 134 | ctx.translate(plotOffset.left, plotOffset.top); 135 | 136 | if (crosshair.x != -1) { 137 | ctx.strokeStyle = c.color; 138 | ctx.lineWidth = c.lineWidth; 139 | ctx.lineJoin = "round"; 140 | 141 | ctx.beginPath(); 142 | if (c.mode.indexOf("x") != -1) { 143 | ctx.moveTo(crosshair.x, 0); 144 | ctx.lineTo(crosshair.x, plot.height()); 145 | } 146 | if (c.mode.indexOf("y") != -1) { 147 | ctx.moveTo(0, crosshair.y); 148 | ctx.lineTo(plot.width(), crosshair.y); 149 | } 150 | ctx.stroke(); 151 | } 152 | ctx.restore(); 153 | }); 154 | 155 | plot.hooks.shutdown.push(function (plot, eventHolder) { 156 | eventHolder.unbind("mouseout", onMouseOut); 157 | eventHolder.unbind("mousemove", onMouseMove); 158 | }); 159 | } 160 | 161 | $.plot.plugins.push({ 162 | init: init, 163 | options: options, 164 | name: 'crosshair', 165 | version: '1.0' 166 | }); 167 | })(jQuery); 168 | -------------------------------------------------------------------------------- /js/flot/jquery.flot.crosshair.min.js: -------------------------------------------------------------------------------- 1 | (function(b){var a={crosshair:{mode:null,color:"rgba(170, 0, 0, 0.80)",lineWidth:1}};function c(h){var j={x:-1,y:-1,locked:false};h.setCrosshair=function e(l){if(!l){j.x=-1}else{var k=h.p2c(l);j.x=Math.max(0,Math.min(k.left,h.width()));j.y=Math.max(0,Math.min(k.top,h.height()))}h.triggerRedrawOverlay()};h.clearCrosshair=h.setCrosshair;h.lockCrosshair=function f(k){if(k){h.setCrosshair(k)}j.locked=true};h.unlockCrosshair=function g(){j.locked=false};function d(k){if(j.locked){return}if(j.x!=-1){j.x=-1;h.triggerRedrawOverlay()}}function i(k){if(j.locked){return}if(h.getSelection&&h.getSelection()){j.x=-1;return}var l=h.offset();j.x=Math.max(0,Math.min(k.pageX-l.left,h.width()));j.y=Math.max(0,Math.min(k.pageY-l.top,h.height()));h.triggerRedrawOverlay()}h.hooks.bindEvents.push(function(l,k){if(!l.getOptions().crosshair.mode){return}k.mouseout(d);k.mousemove(i)});h.hooks.drawOverlay.push(function(m,k){var n=m.getOptions().crosshair;if(!n.mode){return}var l=m.getPlotOffset();k.save();k.translate(l.left,l.top);if(j.x!=-1){k.strokeStyle=n.color;k.lineWidth=n.lineWidth;k.lineJoin="round";k.beginPath();if(n.mode.indexOf("x")!=-1){k.moveTo(j.x,0);k.lineTo(j.x,m.height())}if(n.mode.indexOf("y")!=-1){k.moveTo(0,j.y);k.lineTo(m.width(),j.y)}k.stroke()}k.restore()});h.hooks.shutdown.push(function(l,k){k.unbind("mouseout",d);k.unbind("mousemove",i)})}b.plot.plugins.push({init:c,options:a,name:"crosshair",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /js/flot/jquery.flot.fillbetween.js: -------------------------------------------------------------------------------- 1 | /* 2 | Flot plugin for computing bottoms for filled line and bar charts. 3 | 4 | The case: you've got two series that you want to fill the area 5 | between. In Flot terms, you need to use one as the fill bottom of the 6 | other. You can specify the bottom of each data point as the third 7 | coordinate manually, or you can use this plugin to compute it for you. 8 | 9 | In order to name the other series, you need to give it an id, like this 10 | 11 | var dataset = [ 12 | { data: [ ... ], id: "foo" } , // use default bottom 13 | { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom 14 | ]; 15 | 16 | $.plot($("#placeholder"), dataset, { line: { show: true, fill: true }}); 17 | 18 | As a convenience, if the id given is a number that doesn't appear as 19 | an id in the series, it is interpreted as the index in the array 20 | instead (so fillBetween: 0 can also mean the first series). 21 | 22 | Internally, the plugin modifies the datapoints in each series. For 23 | line series, extra data points might be inserted through 24 | interpolation. Note that at points where the bottom line is not 25 | defined (due to a null point or start/end of line), the current line 26 | will show a gap too. The algorithm comes from the jquery.flot.stack.js 27 | plugin, possibly some code could be shared. 28 | */ 29 | 30 | (function ($) { 31 | var options = { 32 | series: { fillBetween: null } // or number 33 | }; 34 | 35 | function init(plot) { 36 | function findBottomSeries(s, allseries) { 37 | var i; 38 | for (i = 0; i < allseries.length; ++i) { 39 | if (allseries[i].id == s.fillBetween) 40 | return allseries[i]; 41 | } 42 | 43 | if (typeof s.fillBetween == "number") { 44 | i = s.fillBetween; 45 | 46 | if (i < 0 || i >= allseries.length) 47 | return null; 48 | 49 | return allseries[i]; 50 | } 51 | 52 | return null; 53 | } 54 | 55 | function computeFillBottoms(plot, s, datapoints) { 56 | if (s.fillBetween == null) 57 | return; 58 | 59 | var other = findBottomSeries(s, plot.getData()); 60 | if (!other) 61 | return; 62 | 63 | var ps = datapoints.pointsize, 64 | points = datapoints.points, 65 | otherps = other.datapoints.pointsize, 66 | otherpoints = other.datapoints.points, 67 | newpoints = [], 68 | px, py, intery, qx, qy, bottom, 69 | withlines = s.lines.show, 70 | withbottom = ps > 2 && datapoints.format[2].y, 71 | withsteps = withlines && s.lines.steps, 72 | fromgap = true, 73 | i = 0, j = 0, l; 74 | 75 | while (true) { 76 | if (i >= points.length) 77 | break; 78 | 79 | l = newpoints.length; 80 | 81 | if (points[i] == null) { 82 | // copy gaps 83 | for (m = 0; m < ps; ++m) 84 | newpoints.push(points[i + m]); 85 | i += ps; 86 | } 87 | else if (j >= otherpoints.length) { 88 | // for lines, we can't use the rest of the points 89 | if (!withlines) { 90 | for (m = 0; m < ps; ++m) 91 | newpoints.push(points[i + m]); 92 | } 93 | i += ps; 94 | } 95 | else if (otherpoints[j] == null) { 96 | // oops, got a gap 97 | for (m = 0; m < ps; ++m) 98 | newpoints.push(null); 99 | fromgap = true; 100 | j += otherps; 101 | } 102 | else { 103 | // cases where we actually got two points 104 | px = points[i]; 105 | py = points[i + 1]; 106 | qx = otherpoints[j]; 107 | qy = otherpoints[j + 1]; 108 | bottom = 0; 109 | 110 | if (px == qx) { 111 | for (m = 0; m < ps; ++m) 112 | newpoints.push(points[i + m]); 113 | 114 | //newpoints[l + 1] += qy; 115 | bottom = qy; 116 | 117 | i += ps; 118 | j += otherps; 119 | } 120 | else if (px > qx) { 121 | // we got past point below, might need to 122 | // insert interpolated extra point 123 | if (withlines && i > 0 && points[i - ps] != null) { 124 | intery = py + (points[i - ps + 1] - py) * (qx - px) / (points[i - ps] - px); 125 | newpoints.push(qx); 126 | newpoints.push(intery) 127 | for (m = 2; m < ps; ++m) 128 | newpoints.push(points[i + m]); 129 | bottom = qy; 130 | } 131 | 132 | j += otherps; 133 | } 134 | else { // px < qx 135 | if (fromgap && withlines) { 136 | // if we come from a gap, we just skip this point 137 | i += ps; 138 | continue; 139 | } 140 | 141 | for (m = 0; m < ps; ++m) 142 | newpoints.push(points[i + m]); 143 | 144 | // we might be able to interpolate a point below, 145 | // this can give us a better y 146 | if (withlines && j > 0 && otherpoints[j - otherps] != null) 147 | bottom = qy + (otherpoints[j - otherps + 1] - qy) * (px - qx) / (otherpoints[j - otherps] - qx); 148 | 149 | //newpoints[l + 1] += bottom; 150 | 151 | i += ps; 152 | } 153 | 154 | fromgap = false; 155 | 156 | if (l != newpoints.length && withbottom) 157 | newpoints[l + 2] = bottom; 158 | } 159 | 160 | // maintain the line steps invariant 161 | if (withsteps && l != newpoints.length && l > 0 162 | && newpoints[l] != null 163 | && newpoints[l] != newpoints[l - ps] 164 | && newpoints[l + 1] != newpoints[l - ps + 1]) { 165 | for (m = 0; m < ps; ++m) 166 | newpoints[l + ps + m] = newpoints[l + m]; 167 | newpoints[l + 1] = newpoints[l - ps + 1]; 168 | } 169 | } 170 | 171 | datapoints.points = newpoints; 172 | } 173 | 174 | plot.hooks.processDatapoints.push(computeFillBottoms); 175 | } 176 | 177 | $.plot.plugins.push({ 178 | init: init, 179 | options: options, 180 | name: 'fillbetween', 181 | version: '1.0' 182 | }); 183 | })(jQuery); 184 | -------------------------------------------------------------------------------- /js/flot/jquery.flot.fillbetween.min.js: -------------------------------------------------------------------------------- 1 | (function(b){var a={series:{fillBetween:null}};function c(f){function d(j,h){var g;for(g=0;g=h.length){return null}return h[g]}return null}function e(B,u,g){if(u.fillBetween==null){return}var p=d(u,B.getData());if(!p){return}var y=g.pointsize,E=g.points,h=p.datapoints.pointsize,x=p.datapoints.points,r=[],w,v,k,G,F,q,t=u.lines.show,o=y>2&&g.format[2].y,n=t&&u.lines.steps,D=true,C=0,A=0,z;while(true){if(C>=E.length){break}z=r.length;if(E[C]==null){for(m=0;m=x.length){if(!t){for(m=0;mG){if(t&&C>0&&E[C-y]!=null){k=v+(E[C-y+1]-v)*(G-w)/(E[C-y]-w);r.push(G);r.push(k);for(m=2;m0&&x[A-h]!=null){q=F+(x[A-h+1]-F)*(w-G)/(x[A-h]-G)}C+=y}}D=false;if(z!=r.length&&o){r[z+2]=q}}}}if(n&&z!=r.length&&z>0&&r[z]!=null&&r[z]!=r[z-y]&&r[z+1]!=r[z-y+1]){for(m=0;m').load(handler).error(handler).attr('src', url); 112 | }); 113 | } 114 | 115 | function drawSeries(plot, ctx, series) { 116 | var plotOffset = plot.getPlotOffset(); 117 | 118 | if (!series.images || !series.images.show) 119 | return; 120 | 121 | var points = series.datapoints.points, 122 | ps = series.datapoints.pointsize; 123 | 124 | for (var i = 0; i < points.length; i += ps) { 125 | var img = points[i], 126 | x1 = points[i + 1], y1 = points[i + 2], 127 | x2 = points[i + 3], y2 = points[i + 4], 128 | xaxis = series.xaxis, yaxis = series.yaxis, 129 | tmp; 130 | 131 | // actually we should check img.complete, but it 132 | // appears to be a somewhat unreliable indicator in 133 | // IE6 (false even after load event) 134 | if (!img || img.width <= 0 || img.height <= 0) 135 | continue; 136 | 137 | if (x1 > x2) { 138 | tmp = x2; 139 | x2 = x1; 140 | x1 = tmp; 141 | } 142 | if (y1 > y2) { 143 | tmp = y2; 144 | y2 = y1; 145 | y1 = tmp; 146 | } 147 | 148 | // if the anchor is at the center of the pixel, expand the 149 | // image by 1/2 pixel in each direction 150 | if (series.images.anchor == "center") { 151 | tmp = 0.5 * (x2-x1) / (img.width - 1); 152 | x1 -= tmp; 153 | x2 += tmp; 154 | tmp = 0.5 * (y2-y1) / (img.height - 1); 155 | y1 -= tmp; 156 | y2 += tmp; 157 | } 158 | 159 | // clip 160 | if (x1 == x2 || y1 == y2 || 161 | x1 >= xaxis.max || x2 <= xaxis.min || 162 | y1 >= yaxis.max || y2 <= yaxis.min) 163 | continue; 164 | 165 | var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height; 166 | if (x1 < xaxis.min) { 167 | sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1); 168 | x1 = xaxis.min; 169 | } 170 | 171 | if (x2 > xaxis.max) { 172 | sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1); 173 | x2 = xaxis.max; 174 | } 175 | 176 | if (y1 < yaxis.min) { 177 | sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1); 178 | y1 = yaxis.min; 179 | } 180 | 181 | if (y2 > yaxis.max) { 182 | sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1); 183 | y2 = yaxis.max; 184 | } 185 | 186 | x1 = xaxis.p2c(x1); 187 | x2 = xaxis.p2c(x2); 188 | y1 = yaxis.p2c(y1); 189 | y2 = yaxis.p2c(y2); 190 | 191 | // the transformation may have swapped us 192 | if (x1 > x2) { 193 | tmp = x2; 194 | x2 = x1; 195 | x1 = tmp; 196 | } 197 | if (y1 > y2) { 198 | tmp = y2; 199 | y2 = y1; 200 | y1 = tmp; 201 | } 202 | 203 | tmp = ctx.globalAlpha; 204 | ctx.globalAlpha *= series.images.alpha; 205 | ctx.drawImage(img, 206 | sx1, sy1, sx2 - sx1, sy2 - sy1, 207 | x1 + plotOffset.left, y1 + plotOffset.top, 208 | x2 - x1, y2 - y1); 209 | ctx.globalAlpha = tmp; 210 | } 211 | } 212 | 213 | function processRawData(plot, series, data, datapoints) { 214 | if (!series.images.show) 215 | return; 216 | 217 | // format is Image, x1, y1, x2, y2 (opposite corners) 218 | datapoints.format = [ 219 | { required: true }, 220 | { x: true, number: true, required: true }, 221 | { y: true, number: true, required: true }, 222 | { x: true, number: true, required: true }, 223 | { y: true, number: true, required: true } 224 | ]; 225 | } 226 | 227 | function init(plot) { 228 | plot.hooks.processRawData.push(processRawData); 229 | plot.hooks.drawSeries.push(drawSeries); 230 | } 231 | 232 | $.plot.plugins.push({ 233 | init: init, 234 | options: options, 235 | name: 'image', 236 | version: '1.1' 237 | }); 238 | })(jQuery); 239 | -------------------------------------------------------------------------------- /js/flot/jquery.flot.image.min.js: -------------------------------------------------------------------------------- 1 | (function(c){var a={series:{images:{show:false,alpha:1,anchor:"corner"}}};c.plot.image={};c.plot.image.loadDataImages=function(g,f,k){var j=[],h=[];var i=f.series.images.show;c.each(g,function(l,m){if(!(i||m.images.show)){return}if(m.data){m=m.data}c.each(m,function(n,o){if(typeof o[0]=="string"){j.push(o[0]);h.push(o)}})});c.plot.image.load(j,function(l){c.each(h,function(n,o){var m=o[0];if(l[m]){o[0]=l[m]}});k()})};c.plot.image.load=function(h,i){var g=h.length,f={};if(g==0){i({})}c.each(h,function(k,j){var l=function(){--g;f[j]=this;if(g==0){i(f)}};c("").load(l).error(l).attr("src",j)})};function d(q,o,l){var m=q.getPlotOffset();if(!l.images||!l.images.show){return}var r=l.datapoints.points,n=l.datapoints.pointsize;for(var t=0;tv){x=v;v=w;w=x}if(g>f){x=f;f=g;g=x}if(l.images.anchor=="center"){x=0.5*(v-w)/(y.width-1);w-=x;v+=x;x=0.5*(f-g)/(y.height-1);g-=x;f+=x}if(w==v||g==f||w>=h.max||v<=h.min||g>=u.max||f<=u.min){continue}var k=0,s=0,j=y.width,p=y.height;if(wh.max){j+=(j-k)*(h.max-v)/(v-w);v=h.max}if(gu.max){s+=(s-p)*(u.max-f)/(f-g);f=u.max}w=h.p2c(w);v=h.p2c(v);g=u.p2c(g);f=u.p2c(f);if(w>v){x=v;v=w;w=x}if(g>f){x=f;f=g;g=x}x=o.globalAlpha;o.globalAlpha*=l.images.alpha;o.drawImage(y,k,s,j-k,p-s,w+m.left,g+m.top,v-w,f-g);o.globalAlpha=x}}function b(i,f,g,h){if(!f.images.show){return}h.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function e(f){f.hooks.processRawData.push(b);f.hooks.drawSeries.push(d)}c.plot.plugins.push({init:e,options:a,name:"image",version:"1.1"})})(jQuery); -------------------------------------------------------------------------------- /js/flot/jquery.flot.navigate.min.js: -------------------------------------------------------------------------------- 1 | (function(i){i.fn.drag=function(j,k,l){if(k){this.bind("dragstart",j)}if(l){this.bind("dragend",l)}return !j?this.trigger("drag"):this.bind("drag",k?k:j)};var d=i.event,c=d.special,h=c.drag={not:":input",distance:0,which:1,dragging:false,setup:function(j){j=i.extend({distance:h.distance,which:h.which,not:h.not},j||{});j.distance=e(j.distance);d.add(this,"mousedown",f,j);if(this.attachEvent){this.attachEvent("ondragstart",a)}},teardown:function(){d.remove(this,"mousedown",f);if(this===h.dragging){h.dragging=h.proxy=false}g(this,true);if(this.detachEvent){this.detachEvent("ondragstart",a)}}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}};function f(j){var k=this,l,m=j.data||{};if(m.elem){k=j.dragTarget=m.elem;j.dragProxy=h.proxy||k;j.cursorOffsetX=m.pageX-m.left;j.cursorOffsetY=m.pageY-m.top;j.offsetX=j.pageX-j.cursorOffsetX;j.offsetY=j.pageY-j.cursorOffsetY}else{if(h.dragging||(m.which>0&&j.which!=m.which)||i(j.target).is(m.not)){return}}switch(j.type){case"mousedown":i.extend(m,i(k).offset(),{elem:k,target:j.target,pageX:j.pageX,pageY:j.pageY});d.add(document,"mousemove mouseup",f,m);g(k,false);h.dragging=null;return false;case !h.dragging&&"mousemove":if(e(j.pageX-m.pageX)+e(j.pageY-m.pageY)w){var A=B;B=w;w=A}var y=w-B;if(E&&((E[0]!=null&&yE[1]))){return}D.min=B;D.max=w});o.setupGrid();o.draw();if(!q.preventEvent){o.getPlaceholder().trigger("plotzoom",[o])}};o.pan=function(p){var q={x:+p.left,y:+p.top};if(isNaN(q.x)){q.x=0}if(isNaN(q.y)){q.y=0}b.each(o.getAxes(),function(s,u){var v=u.options,t,r,w=q[u.direction];t=u.c2p(u.p2c(u.min)+w),r=u.c2p(u.p2c(u.max)+w);var x=v.panRange;if(x===false){return}if(x){if(x[0]!=null&&x[0]>t){w=x[0]-t;t+=w;r+=w}if(x[1]!=null&&x[1]1){N.series.pie.tilt=1}if(N.series.pie.tilt<0){N.series.pie.tilt=0}O.hooks.processDatapoints.push(E);O.hooks.drawOverlay.push(H);O.hooks.draw.push(r)}}function e(P,N){var O=P.getOptions();if(O.series.pie.show&&O.grid.hoverable){N.unbind("mousemove").mousemove(t)}if(O.series.pie.show&&O.grid.clickable){N.unbind("click").click(l)}}function G(O){var P="";function N(S,T){if(!T){T=0}for(var R=0;Rh.width-n){B=h.width-n}}}function v(O){for(var N=0;N0){R.push({data:[[1,P]],color:N,label:a.series.pie.combine.label,angle:(P*(Math.PI*2))/M,percent:(P/M*100)})}return R}function r(S,Q){if(!L){return}ctx=Q;I();var T=S.getData();var P=0;while(F&&P0){n*=w}P+=1;N();if(a.series.pie.tilt<=0.8){O()}R()}if(P>=o){N();L.prepend('
    Could not draw pie with labels contained inside canvas
    ')}if(S.setSeries&&S.insertLegend){S.setSeries(T);S.insertLegend()}function N(){ctx.clearRect(0,0,h.width,h.height);L.children().filter(".pieLabel, .pieLabelBackground").remove()}function O(){var Z=5;var Y=15;var W=10;var X=0.02;if(a.series.pie.radius>1){var U=a.series.pie.radius}else{var U=n*a.series.pie.radius}if(U>=(h.width/2)-Z||U*a.series.pie.tilt>=(h.height/2)-Y||U<=W){return}ctx.save();ctx.translate(Z,Y);ctx.globalAlpha=X;ctx.fillStyle="#000";ctx.translate(B,p);ctx.scale(1,a.series.pie.tilt);for(var V=1;V<=W;V++){ctx.beginPath();ctx.arc(0,0,U,0,Math.PI*2,false);ctx.fill();U-=V}ctx.restore()}function R(){startAngle=Math.PI*a.series.pie.startAngle;if(a.series.pie.radius>1){var U=a.series.pie.radius}else{var U=n*a.series.pie.radius}ctx.save();ctx.translate(B,p);ctx.scale(1,a.series.pie.tilt);ctx.save();var Y=startAngle;for(var W=0;W1e-9){ctx.moveTo(0,0)}else{if(b.browser.msie){ab-=0.0001}}ctx.arc(0,0,U,Y,Y+ab,false);ctx.closePath();Y+=ab;if(aa){ctx.fill()}else{ctx.stroke()}}function V(){var ac=startAngle;if(a.series.pie.label.radius>1){var Z=a.series.pie.label.radius}else{var Z=n*a.series.pie.label.radius}for(var ab=0;ab=a.series.pie.label.threshold*100){aa(T[ab],ac,ab)}ac+=T[ab].angle}function aa(ap,ai,ag){if(ap.data[0][1]==0){return}var ar=a.legend.labelFormatter,aq,ae=a.series.pie.label.formatter;if(ar){aq=ar(ap.label,ap)}else{aq=ap.label}if(ae){aq=ae(aq,ap)}var aj=((ai+ap.angle)+ai)/2;var ao=B+Math.round(Math.cos(aj)*Z);var am=p+Math.round(Math.sin(aj)*Z)*a.series.pie.tilt;var af=''+aq+"";L.append(af);var an=L.children("#pieLabel"+ag);var ad=(am-an.height()/2);var ah=(ao-an.width()/2);an.css("top",ad);an.css("left",ah);if(0-ad>0||0-ah>0||h.height-(ad+an.height())<0||h.width-(ah+an.width())<0){F=true}if(a.series.pie.label.background.opacity!=0){var ak=a.series.pie.label.background.color;if(ak==null){ak=ap.color}var al="top:"+ad+"px;left:"+ah+"px;";b('
    ').insertBefore(an).css("opacity",a.series.pie.label.background.opacity)}}}}}function J(N){if(a.series.pie.innerRadius>0){N.save();innerRadius=a.series.pie.innerRadius>1?a.series.pie.innerRadius:n*a.series.pie.innerRadius;N.globalCompositeOperation="destination-out";N.beginPath();N.fillStyle=a.series.pie.stroke.color;N.arc(0,0,innerRadius,0,Math.PI*2,false);N.fill();N.closePath();N.restore();N.save();N.beginPath();N.strokeStyle=a.series.pie.stroke.color;N.arc(0,0,innerRadius,0,Math.PI*2,false);N.stroke();N.closePath();N.restore()}}function s(Q,R){for(var S=false,P=-1,N=Q.length,O=N-1;++P1?O.series.pie.radius:n*O.series.pie.radius;for(var Q=0;Q1?P.series.pie.radius:n*P.series.pie.radius;R.save();R.translate(B,p);R.scale(1,P.series.pie.tilt);for(i=0;i1e-9){R.moveTo(0,0)}R.arc(0,0,N,S.startAngle,S.startAngle+S.angle,false);R.closePath();R.fill()}}}var a={series:{pie:{show:false,radius:"auto",innerRadius:0,startAngle:3/2,tilt:1,offset:{top:0,left:"auto"},stroke:{color:"#FFF",width:1},label:{show:"auto",formatter:function(d,e){return'
    '+d+"
    "+Math.round(e.percent)+"%
    "},radius:1,background:{color:null,opacity:0},threshold:0},combine:{threshold:-1,color:null,label:"Other"},highlight:{opacity:0.5}}}};b.plot.plugins.push({init:c,options:a,name:"pie",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /js/flot/jquery.flot.resize.js: -------------------------------------------------------------------------------- 1 | /* 2 | Flot plugin for automatically redrawing plots when the placeholder 3 | size changes, e.g. on window resizes. 4 | 5 | It works by listening for changes on the placeholder div (through the 6 | jQuery resize event plugin) - if the size changes, it will redraw the 7 | plot. 8 | 9 | There are no options. If you need to disable the plugin for some 10 | plots, you can just fix the size of their placeholders. 11 | */ 12 | 13 | 14 | /* Inline dependency: 15 | * jQuery resize event - v1.1 - 3/14/2010 16 | * http://benalman.com/projects/jquery-resize-plugin/ 17 | * 18 | * Copyright (c) 2010 "Cowboy" Ben Alman 19 | * Dual licensed under the MIT and GPL licenses. 20 | * http://benalman.com/about/license/ 21 | */ 22 | (function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); 23 | 24 | 25 | (function ($) { 26 | var options = { }; // no options 27 | 28 | function init(plot) { 29 | function onResize() { 30 | var placeholder = plot.getPlaceholder(); 31 | 32 | // somebody might have hidden us and we can't plot 33 | // when we don't have the dimensions 34 | if (placeholder.width() == 0 || placeholder.height() == 0) 35 | return; 36 | 37 | plot.resize(); 38 | plot.setupGrid(); 39 | plot.draw(); 40 | } 41 | 42 | function bindEvents(plot, eventHolder) { 43 | plot.getPlaceholder().resize(onResize); 44 | } 45 | 46 | function shutdown(plot, eventHolder) { 47 | plot.getPlaceholder().unbind("resize", onResize); 48 | } 49 | 50 | plot.hooks.bindEvents.push(bindEvents); 51 | plot.hooks.shutdown.push(shutdown); 52 | } 53 | 54 | $.plot.plugins.push({ 55 | init: init, 56 | options: options, 57 | name: 'resize', 58 | version: '1.0' 59 | }); 60 | })(jQuery); 61 | -------------------------------------------------------------------------------- /js/flot/jquery.flot.resize.min.js: -------------------------------------------------------------------------------- 1 | (function(n,p,u){var w=n([]),s=n.resize=n.extend(n.resize,{}),o,l="setTimeout",m="resize",t=m+"-special-event",v="delay",r="throttleWindow";s[v]=250;s[r]=true;n.event.special[m]={setup:function(){if(!s[r]&&this[l]){return false}var a=n(this);w=w.add(a);n.data(this,t,{w:a.width(),h:a.height()});if(w.length===1){q()}},teardown:function(){if(!s[r]&&this[l]){return false}var a=n(this);w=w.not(a);a.removeData(t);if(!w.length){clearTimeout(o)}},add:function(b){if(!s[r]&&this[l]){return false}var c;function a(d,h,g){var f=n(this),e=n.data(this,t);e.w=h!==u?h:f.width();e.h=g!==u?g:f.height();c.apply(this,arguments)}if(n.isFunction(b)){c=b;return a}else{c=b.handler;b.handler=a}}};function q(){o=p[l](function(){w.each(function(){var d=n(this),a=d.width(),b=d.height(),c=n.data(this,t);if(a!==c.w||b!==c.h){d.trigger(m,[c.w=a,c.h=b])}});q()},s[v])}})(jQuery,this);(function(b){var a={};function c(f){function e(){var h=f.getPlaceholder();if(h.width()==0||h.height()==0){return}f.resize();f.setupGrid();f.draw()}function g(i,h){i.getPlaceholder().resize(e)}function d(i,h){i.getPlaceholder().unbind("resize",e)}f.hooks.bindEvents.push(g);f.hooks.shutdown.push(d)}b.plot.plugins.push({init:c,options:a,name:"resize",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /js/flot/jquery.flot.selection.min.js: -------------------------------------------------------------------------------- 1 | (function(a){function b(k){var p={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false};var m={};var r=null;function e(s){if(p.active){l(s);k.getPlaceholder().trigger("plotselecting",[g()])}}function n(s){if(s.which!=1){return}document.body.focus();if(document.onselectstart!==undefined&&m.onselectstart==null){m.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&m.ondrag==null){m.ondrag=document.ondrag;document.ondrag=function(){return false}}d(p.first,s);p.active=true;r=function(t){j(t)};a(document).one("mouseup",r)}function j(s){r=null;if(document.onselectstart!==undefined){document.onselectstart=m.onselectstart}if(document.ondrag!==undefined){document.ondrag=m.ondrag}p.active=false;l(s);if(f()){i()}else{k.getPlaceholder().trigger("plotunselected",[]);k.getPlaceholder().trigger("plotselecting",[null])}return false}function g(){if(!f()){return null}var u={},t=p.first,s=p.second;a.each(k.getAxes(),function(v,w){if(w.used){var y=w.c2p(t[w.direction]),x=w.c2p(s[w.direction]);u[v]={from:Math.min(y,x),to:Math.max(y,x)}}});return u}function i(){var s=g();k.getPlaceholder().trigger("plotselected",[s]);if(s.xaxis&&s.yaxis){k.getPlaceholder().trigger("selected",[{x1:s.xaxis.from,y1:s.yaxis.from,x2:s.xaxis.to,y2:s.yaxis.to}])}}function h(t,u,s){return us?s:u)}function d(w,t){var v=k.getOptions();var u=k.getPlaceholder().offset();var s=k.getPlotOffset();w.x=h(0,t.pageX-u.left-s.left,k.width());w.y=h(0,t.pageY-u.top-s.top,k.height());if(v.selection.mode=="y"){w.x=w==p.first?0:k.width()}if(v.selection.mode=="x"){w.y=w==p.first?0:k.height()}}function l(s){if(s.pageX==null){return}d(p.second,s);if(f()){p.show=true;k.triggerRedrawOverlay()}else{q(true)}}function q(s){if(p.show){p.show=false;k.triggerRedrawOverlay();if(!s){k.getPlaceholder().trigger("plotunselected",[])}}}function c(s,w){var t,y,z,A,x=k.getAxes();for(var u in x){t=x[u];if(t.direction==w){A=w+t.n+"axis";if(!s[A]&&t.n==1){A=w+"axis"}if(s[A]){y=s[A].from;z=s[A].to;break}}}if(!s[A]){t=w=="x"?k.getXAxes()[0]:k.getYAxes()[0];y=s[w+"1"];z=s[w+"2"]}if(y!=null&&z!=null&&y>z){var v=y;y=z;z=v}return{from:y,to:z,axis:t}}function o(t,s){var v,u,w=k.getOptions();if(w.selection.mode=="y"){p.first.x=0;p.second.x=k.width()}else{u=c(t,"x");p.first.x=u.axis.p2c(u.from);p.second.x=u.axis.p2c(u.to)}if(w.selection.mode=="x"){p.first.y=0;p.second.y=k.height()}else{u=c(t,"y");p.first.y=u.axis.p2c(u.from);p.second.y=u.axis.p2c(u.to)}p.show=true;k.triggerRedrawOverlay();if(!s&&f()){i()}}function f(){var s=5;return Math.abs(p.second.x-p.first.x)>=s&&Math.abs(p.second.y-p.first.y)>=s}k.clearSelection=q;k.setSelection=o;k.getSelection=g;k.hooks.bindEvents.push(function(t,s){var u=t.getOptions();if(u.selection.mode!=null){s.mousemove(e);s.mousedown(n)}});k.hooks.drawOverlay.push(function(v,D){if(p.show&&f()){var t=v.getPlotOffset();var s=v.getOptions();D.save();D.translate(t.left,t.top);var z=a.color.parse(s.selection.color);D.strokeStyle=z.scale("a",0.8).toString();D.lineWidth=1;D.lineJoin="round";D.fillStyle=z.scale("a",0.4).toString();var B=Math.min(p.first.x,p.second.x),A=Math.min(p.first.y,p.second.y),C=Math.abs(p.second.x-p.first.x),u=Math.abs(p.second.y-p.first.y);D.fillRect(B,A,C,u);D.strokeRect(B,A,C,u);D.restore()}});k.hooks.shutdown.push(function(t,s){s.unbind("mousemove",e);s.unbind("mousedown",n);if(r){a(document).unbind("mouseup",r)}})}a.plot.plugins.push({init:b,options:{selection:{mode:null,color:"#e8cfac"}},name:"selection",version:"1.1"})})(jQuery); -------------------------------------------------------------------------------- /js/flot/jquery.flot.stack.js: -------------------------------------------------------------------------------- 1 | /* 2 | Flot plugin for stacking data sets, i.e. putting them on top of each 3 | other, for accumulative graphs. 4 | 5 | The plugin assumes the data is sorted on x (or y if stacking 6 | horizontally). For line charts, it is assumed that if a line has an 7 | undefined gap (from a null point), then the line above it should have 8 | the same gap - insert zeros instead of "null" if you want another 9 | behaviour. This also holds for the start and end of the chart. Note 10 | that stacking a mix of positive and negative values in most instances 11 | doesn't make sense (so it looks weird). 12 | 13 | Two or more series are stacked when their "stack" attribute is set to 14 | the same key (which can be any number or string or just "true"). To 15 | specify the default stack, you can set 16 | 17 | series: { 18 | stack: null or true or key (number/string) 19 | } 20 | 21 | or specify it for a specific series 22 | 23 | $.plot($("#placeholder"), [{ data: [ ... ], stack: true }]) 24 | 25 | The stacking order is determined by the order of the data series in 26 | the array (later series end up on top of the previous). 27 | 28 | Internally, the plugin modifies the datapoints in each series, adding 29 | an offset to the y value. For line series, extra data points are 30 | inserted through interpolation. If there's a second y value, it's also 31 | adjusted (e.g for bar charts or filled areas). 32 | */ 33 | 34 | (function ($) { 35 | var options = { 36 | series: { stack: null } // or number/string 37 | }; 38 | 39 | function init(plot) { 40 | function findMatchingSeries(s, allseries) { 41 | var res = null 42 | for (var i = 0; i < allseries.length; ++i) { 43 | if (s == allseries[i]) 44 | break; 45 | 46 | if (allseries[i].stack == s.stack) 47 | res = allseries[i]; 48 | } 49 | 50 | return res; 51 | } 52 | 53 | function stackData(plot, s, datapoints) { 54 | if (s.stack == null) 55 | return; 56 | 57 | var other = findMatchingSeries(s, plot.getData()); 58 | if (!other) 59 | return; 60 | 61 | var ps = datapoints.pointsize, 62 | points = datapoints.points, 63 | otherps = other.datapoints.pointsize, 64 | otherpoints = other.datapoints.points, 65 | newpoints = [], 66 | px, py, intery, qx, qy, bottom, 67 | withlines = s.lines.show, 68 | horizontal = s.bars.horizontal, 69 | withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), 70 | withsteps = withlines && s.lines.steps, 71 | fromgap = true, 72 | keyOffset = horizontal ? 1 : 0, 73 | accumulateOffset = horizontal ? 0 : 1, 74 | i = 0, j = 0, l; 75 | 76 | while (true) { 77 | if (i >= points.length) 78 | break; 79 | 80 | l = newpoints.length; 81 | 82 | if (points[i] == null) { 83 | // copy gaps 84 | for (m = 0; m < ps; ++m) 85 | newpoints.push(points[i + m]); 86 | i += ps; 87 | } 88 | else if (j >= otherpoints.length) { 89 | // for lines, we can't use the rest of the points 90 | if (!withlines) { 91 | for (m = 0; m < ps; ++m) 92 | newpoints.push(points[i + m]); 93 | } 94 | i += ps; 95 | } 96 | else if (otherpoints[j] == null) { 97 | // oops, got a gap 98 | for (m = 0; m < ps; ++m) 99 | newpoints.push(null); 100 | fromgap = true; 101 | j += otherps; 102 | } 103 | else { 104 | // cases where we actually got two points 105 | px = points[i + keyOffset]; 106 | py = points[i + accumulateOffset]; 107 | qx = otherpoints[j + keyOffset]; 108 | qy = otherpoints[j + accumulateOffset]; 109 | bottom = 0; 110 | 111 | if (px == qx) { 112 | for (m = 0; m < ps; ++m) 113 | newpoints.push(points[i + m]); 114 | 115 | newpoints[l + accumulateOffset] += qy; 116 | bottom = qy; 117 | 118 | i += ps; 119 | j += otherps; 120 | } 121 | else if (px > qx) { 122 | // we got past point below, might need to 123 | // insert interpolated extra point 124 | if (withlines && i > 0 && points[i - ps] != null) { 125 | intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); 126 | newpoints.push(qx); 127 | newpoints.push(intery + qy); 128 | for (m = 2; m < ps; ++m) 129 | newpoints.push(points[i + m]); 130 | bottom = qy; 131 | } 132 | 133 | j += otherps; 134 | } 135 | else { // px < qx 136 | if (fromgap && withlines) { 137 | // if we come from a gap, we just skip this point 138 | i += ps; 139 | continue; 140 | } 141 | 142 | for (m = 0; m < ps; ++m) 143 | newpoints.push(points[i + m]); 144 | 145 | // we might be able to interpolate a point below, 146 | // this can give us a better y 147 | if (withlines && j > 0 && otherpoints[j - otherps] != null) 148 | bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); 149 | 150 | newpoints[l + accumulateOffset] += bottom; 151 | 152 | i += ps; 153 | } 154 | 155 | fromgap = false; 156 | 157 | if (l != newpoints.length && withbottom) 158 | newpoints[l + 2] += bottom; 159 | } 160 | 161 | // maintain the line steps invariant 162 | if (withsteps && l != newpoints.length && l > 0 163 | && newpoints[l] != null 164 | && newpoints[l] != newpoints[l - ps] 165 | && newpoints[l + 1] != newpoints[l - ps + 1]) { 166 | for (m = 0; m < ps; ++m) 167 | newpoints[l + ps + m] = newpoints[l + m]; 168 | newpoints[l + 1] = newpoints[l - ps + 1]; 169 | } 170 | } 171 | 172 | datapoints.points = newpoints; 173 | } 174 | 175 | plot.hooks.processDatapoints.push(stackData); 176 | } 177 | 178 | $.plot.plugins.push({ 179 | init: init, 180 | options: options, 181 | name: 'stack', 182 | version: '1.2' 183 | }); 184 | })(jQuery); 185 | -------------------------------------------------------------------------------- /js/flot/jquery.flot.stack.min.js: -------------------------------------------------------------------------------- 1 | (function(b){var a={series:{stack:null}};function c(f){function d(k,j){var h=null;for(var g=0;g2&&(G?g.format[2].x:g.format[2].y),n=u&&v.lines.steps,E=true,q=G?1:0,H=G?0:1,D=0,B=0,A;while(true){if(D>=F.length){break}A=t.length;if(F[D]==null){for(m=0;m=y.length){if(!u){for(m=0;mJ){if(u&&D>0&&F[D-z]!=null){k=w+(F[D-z+H]-w)*(J-x)/(F[D-z+q]-x);t.push(J);t.push(k+I);for(m=2;m0&&y[B-h]!=null){r=I+(y[B-h+H]-I)*(x-J)/(y[B-h+q]-J)}t[A+H]+=r;D+=z}}E=false;if(A!=t.length&&o){t[A+2]+=r}}}}if(n&&A!=t.length&&A>0&&t[A]!=null&&t[A]!=t[A-z]&&t[A+1]!=t[A-z+1]){for(m=0;m s = r * sqrt(pi)/2 23 | var size = radius * Math.sqrt(Math.PI) / 2; 24 | ctx.rect(x - size, y - size, size + size, size + size); 25 | }, 26 | diamond: function (ctx, x, y, radius, shadow) { 27 | // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) 28 | var size = radius * Math.sqrt(Math.PI / 2); 29 | ctx.moveTo(x - size, y); 30 | ctx.lineTo(x, y - size); 31 | ctx.lineTo(x + size, y); 32 | ctx.lineTo(x, y + size); 33 | ctx.lineTo(x - size, y); 34 | }, 35 | triangle: function (ctx, x, y, radius, shadow) { 36 | // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) 37 | var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); 38 | var height = size * Math.sin(Math.PI / 3); 39 | ctx.moveTo(x - size/2, y + height/2); 40 | ctx.lineTo(x + size/2, y + height/2); 41 | if (!shadow) { 42 | ctx.lineTo(x, y - height/2); 43 | ctx.lineTo(x - size/2, y + height/2); 44 | } 45 | }, 46 | cross: function (ctx, x, y, radius, shadow) { 47 | // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 48 | var size = radius * Math.sqrt(Math.PI) / 2; 49 | ctx.moveTo(x - size, y - size); 50 | ctx.lineTo(x + size, y + size); 51 | ctx.moveTo(x - size, y + size); 52 | ctx.lineTo(x + size, y - size); 53 | } 54 | } 55 | 56 | var s = series.points.symbol; 57 | if (handlers[s]) 58 | series.points.symbol = handlers[s]; 59 | } 60 | 61 | function init(plot) { 62 | plot.hooks.processDatapoints.push(processRawData); 63 | } 64 | 65 | $.plot.plugins.push({ 66 | init: init, 67 | name: 'symbols', 68 | version: '1.0' 69 | }); 70 | })(jQuery); 71 | -------------------------------------------------------------------------------- /js/flot/jquery.flot.symbol.min.js: -------------------------------------------------------------------------------- 1 | (function(b){function a(h,e,g){var d={square:function(k,j,n,i,m){var l=i*Math.sqrt(Math.PI)/2;k.rect(j-l,n-l,l+l,l+l)},diamond:function(k,j,n,i,m){var l=i*Math.sqrt(Math.PI/2);k.moveTo(j-l,n);k.lineTo(j,n-l);k.lineTo(j+l,n);k.lineTo(j,n+l);k.lineTo(j-l,n)},triangle:function(l,k,o,j,n){var m=j*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3));var i=m*Math.sin(Math.PI/3);l.moveTo(k-m/2,o+i/2);l.lineTo(k+m/2,o+i/2);if(!n){l.lineTo(k,o-i/2);l.lineTo(k-m/2,o+i/2)}},cross:function(k,j,n,i,m){var l=i*Math.sqrt(Math.PI)/2;k.moveTo(j-l,n-l);k.lineTo(j+l,n+l);k.moveTo(j-l,n+l);k.lineTo(j+l,n-l)}};var f=e.points.symbol;if(d[f]){e.points.symbol=d[f]}}function c(d){d.hooks.processDatapoints.push(a)}b.plot.plugins.push({init:c,name:"symbols",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /js/flot/jquery.flot.threshold.js: -------------------------------------------------------------------------------- 1 | /* 2 | Flot plugin for thresholding data. Controlled through the option 3 | "threshold" in either the global series options 4 | 5 | series: { 6 | threshold: { 7 | below: number 8 | color: colorspec 9 | } 10 | } 11 | 12 | or in a specific series 13 | 14 | $.plot($("#placeholder"), [{ data: [ ... ], threshold: { ... }}]) 15 | 16 | The data points below "below" are drawn with the specified color. This 17 | makes it easy to mark points below 0, e.g. for budget data. 18 | 19 | Internally, the plugin works by splitting the data into two series, 20 | above and below the threshold. The extra series below the threshold 21 | will have its label cleared and the special "originSeries" attribute 22 | set to the original series. You may need to check for this in hover 23 | events. 24 | */ 25 | 26 | (function ($) { 27 | var options = { 28 | series: { threshold: null } // or { below: number, color: color spec} 29 | }; 30 | 31 | function init(plot) { 32 | function thresholdData(plot, s, datapoints) { 33 | if (!s.threshold) 34 | return; 35 | 36 | var ps = datapoints.pointsize, i, x, y, p, prevp, 37 | thresholded = $.extend({}, s); // note: shallow copy 38 | 39 | thresholded.datapoints = { points: [], pointsize: ps }; 40 | thresholded.label = null; 41 | thresholded.color = s.threshold.color; 42 | thresholded.threshold = null; 43 | thresholded.originSeries = s; 44 | thresholded.data = []; 45 | 46 | var below = s.threshold.below, 47 | origpoints = datapoints.points, 48 | addCrossingPoints = s.lines.show; 49 | 50 | threspoints = []; 51 | newpoints = []; 52 | 53 | for (i = 0; i < origpoints.length; i += ps) { 54 | x = origpoints[i] 55 | y = origpoints[i + 1]; 56 | 57 | prevp = p; 58 | if (y < below) 59 | p = threspoints; 60 | else 61 | p = newpoints; 62 | 63 | if (addCrossingPoints && prevp != p && x != null 64 | && i > 0 && origpoints[i - ps] != null) { 65 | var interx = (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]) * (below - y) + x; 66 | prevp.push(interx); 67 | prevp.push(below); 68 | for (m = 2; m < ps; ++m) 69 | prevp.push(origpoints[i + m]); 70 | 71 | p.push(null); // start new segment 72 | p.push(null); 73 | for (m = 2; m < ps; ++m) 74 | p.push(origpoints[i + m]); 75 | p.push(interx); 76 | p.push(below); 77 | for (m = 2; m < ps; ++m) 78 | p.push(origpoints[i + m]); 79 | } 80 | 81 | p.push(x); 82 | p.push(y); 83 | } 84 | 85 | datapoints.points = newpoints; 86 | thresholded.datapoints.points = threspoints; 87 | 88 | if (thresholded.datapoints.points.length > 0) 89 | plot.getData().push(thresholded); 90 | 91 | // FIXME: there are probably some edge cases left in bars 92 | } 93 | 94 | plot.hooks.processDatapoints.push(thresholdData); 95 | } 96 | 97 | $.plot.plugins.push({ 98 | init: init, 99 | options: options, 100 | name: 'threshold', 101 | version: '1.0' 102 | }); 103 | })(jQuery); 104 | -------------------------------------------------------------------------------- /js/flot/jquery.flot.threshold.min.js: -------------------------------------------------------------------------------- 1 | (function(B){var A={series:{threshold:null}};function C(D){function E(L,S,M){if(!S.threshold){return }var F=M.pointsize,I,O,N,G,K,H=B.extend({},S);H.datapoints={points:[],pointsize:F};H.label=null;H.color=S.threshold.color;H.threshold=null;H.originSeries=S;H.data=[];var P=S.threshold.below,Q=M.points,R=S.lines.show;threspoints=[];newpoints=[];for(I=0;I0&&Q[I-F]!=null){var J=(O-Q[I-F])/(N-Q[I-F+1])*(P-N)+O;K.push(J);K.push(P);for(m=2;m0){L.getData().push(H)}}D.hooks.processDatapoints.push(E)}B.plot.plugins.push({init:C,options:A,name:"threshold",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /js/jquery-ui-sliderAccess.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI Slider Access 3 | * By: Trent Richardson [http://trentrichardson.com] 4 | * Version 0.2 5 | * Last Modified: 12/02/2011 6 | * 7 | * Copyright 2011 Trent Richardson 8 | * Dual licensed under the MIT and GPL licenses. 9 | * http://trentrichardson.com/Impromptu/GPL-LICENSE.txt 10 | * http://trentrichardson.com/Impromptu/MIT-LICENSE.txt 11 | * 12 | */ 13 | (function($){ 14 | 15 | $.fn.extend({ 16 | sliderAccess: function(options){ 17 | options = options || {}; 18 | options.touchonly = options.touchonly !== undefined? options.touchonly : true; // by default only show it if touch device 19 | 20 | if(options.touchonly === true && !("ontouchend" in document)) 21 | return $(this); 22 | 23 | return $(this).each(function(i,obj){ 24 | var $t = $(this), 25 | o = $.extend({},{ 26 | where: 'after', 27 | step: $t.slider('option','step'), 28 | upIcon: 'ui-icon-plus', 29 | downIcon: 'ui-icon-minus', 30 | text: false, 31 | upText: '+', 32 | downText: '-', 33 | buttonset: true, 34 | buttonsetTag: 'span' 35 | }, options), 36 | $buttons = $('<'+ o.buttonsetTag +' class="ui-slider-access">'+ 37 | ''+ 38 | ''+ 39 | ''); 40 | 41 | $buttons.children('button').each(function(j, jobj){ 42 | var $jt = $(this); 43 | $jt.button({ 44 | text: o.text, 45 | icons: { primary: $jt.data('icon') } 46 | }) 47 | .click(function(e){ 48 | var step = $jt.data('step'), 49 | curr = $t.slider('value'), 50 | newval = curr += step*1, 51 | minval = $t.slider('option','min'), 52 | maxval = $t.slider('option','max'); 53 | 54 | e.preventDefault(); 55 | 56 | if(newval < minval || newval > maxval) 57 | return; 58 | 59 | $t.slider('value', newval); 60 | 61 | $t.slider("option", "slide").call($t, null, { value: newval }); 62 | }); 63 | }); 64 | 65 | // before or after 66 | $t[o.where]($buttons); 67 | 68 | if(o.buttonset){ 69 | $buttons.removeClass('ui-corner-right').removeClass('ui-corner-left').buttonset(); 70 | $buttons.eq(0).addClass('ui-corner-left'); 71 | $buttons.eq(1).addClass('ui-corner-right'); 72 | } 73 | 74 | // adjust the width so we don't break the original layout 75 | var bOuterWidth = $buttons.css({ 76 | marginLeft: (o.where == 'after'? 10:0), 77 | marginRight: (o.where == 'before'? 10:0) 78 | }).outerWidth(true) + 5; 79 | var tOuterWidth = $t.outerWidth(true); 80 | $t.css('display','inline-block').width(tOuterWidth-bOuterWidth); 81 | }); 82 | } 83 | }); 84 | 85 | })(jQuery); 86 | -------------------------------------------------------------------------------- /js/jquery.ui.core.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI 1.8.18 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI 9 | */ 10 | (function( $, undefined ) { 11 | 12 | // prevent duplicate loading 13 | // this is only a problem because we proxy existing functions 14 | // and we don't want to double proxy them 15 | $.ui = $.ui || {}; 16 | if ( $.ui.version ) { 17 | return; 18 | } 19 | 20 | $.extend( $.ui, { 21 | version: "1.8.18", 22 | 23 | keyCode: { 24 | ALT: 18, 25 | BACKSPACE: 8, 26 | CAPS_LOCK: 20, 27 | COMMA: 188, 28 | COMMAND: 91, 29 | COMMAND_LEFT: 91, // COMMAND 30 | COMMAND_RIGHT: 93, 31 | CONTROL: 17, 32 | DELETE: 46, 33 | DOWN: 40, 34 | END: 35, 35 | ENTER: 13, 36 | ESCAPE: 27, 37 | HOME: 36, 38 | INSERT: 45, 39 | LEFT: 37, 40 | MENU: 93, // COMMAND_RIGHT 41 | NUMPAD_ADD: 107, 42 | NUMPAD_DECIMAL: 110, 43 | NUMPAD_DIVIDE: 111, 44 | NUMPAD_ENTER: 108, 45 | NUMPAD_MULTIPLY: 106, 46 | NUMPAD_SUBTRACT: 109, 47 | PAGE_DOWN: 34, 48 | PAGE_UP: 33, 49 | PERIOD: 190, 50 | RIGHT: 39, 51 | SHIFT: 16, 52 | SPACE: 32, 53 | TAB: 9, 54 | UP: 38, 55 | WINDOWS: 91 // COMMAND 56 | } 57 | }); 58 | 59 | // plugins 60 | $.fn.extend({ 61 | propAttr: $.fn.prop || $.fn.attr, 62 | 63 | _focus: $.fn.focus, 64 | focus: function( delay, fn ) { 65 | return typeof delay === "number" ? 66 | this.each(function() { 67 | var elem = this; 68 | setTimeout(function() { 69 | $( elem ).focus(); 70 | if ( fn ) { 71 | fn.call( elem ); 72 | } 73 | }, delay ); 74 | }) : 75 | this._focus.apply( this, arguments ); 76 | }, 77 | 78 | scrollParent: function() { 79 | var scrollParent; 80 | if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) { 81 | scrollParent = this.parents().filter(function() { 82 | return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1)); 83 | }).eq(0); 84 | } else { 85 | scrollParent = this.parents().filter(function() { 86 | return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1)); 87 | }).eq(0); 88 | } 89 | 90 | return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent; 91 | }, 92 | 93 | zIndex: function( zIndex ) { 94 | if ( zIndex !== undefined ) { 95 | return this.css( "zIndex", zIndex ); 96 | } 97 | 98 | if ( this.length ) { 99 | var elem = $( this[ 0 ] ), position, value; 100 | while ( elem.length && elem[ 0 ] !== document ) { 101 | // Ignore z-index if position is set to a value where z-index is ignored by the browser 102 | // This makes behavior of this function consistent across browsers 103 | // WebKit always returns auto if the element is positioned 104 | position = elem.css( "position" ); 105 | if ( position === "absolute" || position === "relative" || position === "fixed" ) { 106 | // IE returns 0 when zIndex is not specified 107 | // other browsers return a string 108 | // we ignore the case of nested elements with an explicit value of 0 109 | //
    110 | value = parseInt( elem.css( "zIndex" ), 10 ); 111 | if ( !isNaN( value ) && value !== 0 ) { 112 | return value; 113 | } 114 | } 115 | elem = elem.parent(); 116 | } 117 | } 118 | 119 | return 0; 120 | }, 121 | 122 | disableSelection: function() { 123 | return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + 124 | ".ui-disableSelection", function( event ) { 125 | event.preventDefault(); 126 | }); 127 | }, 128 | 129 | enableSelection: function() { 130 | return this.unbind( ".ui-disableSelection" ); 131 | } 132 | }); 133 | 134 | $.each( [ "Width", "Height" ], function( i, name ) { 135 | var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], 136 | type = name.toLowerCase(), 137 | orig = { 138 | innerWidth: $.fn.innerWidth, 139 | innerHeight: $.fn.innerHeight, 140 | outerWidth: $.fn.outerWidth, 141 | outerHeight: $.fn.outerHeight 142 | }; 143 | 144 | function reduce( elem, size, border, margin ) { 145 | $.each( side, function() { 146 | size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0; 147 | if ( border ) { 148 | size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0; 149 | } 150 | if ( margin ) { 151 | size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0; 152 | } 153 | }); 154 | return size; 155 | } 156 | 157 | $.fn[ "inner" + name ] = function( size ) { 158 | if ( size === undefined ) { 159 | return orig[ "inner" + name ].call( this ); 160 | } 161 | 162 | return this.each(function() { 163 | $( this ).css( type, reduce( this, size ) + "px" ); 164 | }); 165 | }; 166 | 167 | $.fn[ "outer" + name] = function( size, margin ) { 168 | if ( typeof size !== "number" ) { 169 | return orig[ "outer" + name ].call( this, size ); 170 | } 171 | 172 | return this.each(function() { 173 | $( this).css( type, reduce( this, size, true, margin ) + "px" ); 174 | }); 175 | }; 176 | }); 177 | 178 | // selectors 179 | function focusable( element, isTabIndexNotNaN ) { 180 | var nodeName = element.nodeName.toLowerCase(); 181 | if ( "area" === nodeName ) { 182 | var map = element.parentNode, 183 | mapName = map.name, 184 | img; 185 | if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { 186 | return false; 187 | } 188 | img = $( "img[usemap=#" + mapName + "]" )[0]; 189 | return !!img && visible( img ); 190 | } 191 | return ( /input|select|textarea|button|object/.test( nodeName ) 192 | ? !element.disabled 193 | : "a" == nodeName 194 | ? element.href || isTabIndexNotNaN 195 | : isTabIndexNotNaN) 196 | // the element and all of its ancestors must be visible 197 | && visible( element ); 198 | } 199 | 200 | function visible( element ) { 201 | return !$( element ).parents().andSelf().filter(function() { 202 | return $.curCSS( this, "visibility" ) === "hidden" || 203 | $.expr.filters.hidden( this ); 204 | }).length; 205 | } 206 | 207 | $.extend( $.expr[ ":" ], { 208 | data: function( elem, i, match ) { 209 | return !!$.data( elem, match[ 3 ] ); 210 | }, 211 | 212 | focusable: function( element ) { 213 | return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); 214 | }, 215 | 216 | tabbable: function( element ) { 217 | var tabIndex = $.attr( element, "tabindex" ), 218 | isTabIndexNaN = isNaN( tabIndex ); 219 | return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); 220 | } 221 | }); 222 | 223 | // support 224 | $(function() { 225 | var body = document.body, 226 | div = body.appendChild( div = document.createElement( "div" ) ); 227 | 228 | // access offsetHeight before setting the style to prevent a layout bug 229 | // in IE 9 which causes the elemnt to continue to take up space even 230 | // after it is removed from the DOM (#8026) 231 | div.offsetHeight; 232 | 233 | $.extend( div.style, { 234 | minHeight: "100px", 235 | height: "auto", 236 | padding: 0, 237 | borderWidth: 0 238 | }); 239 | 240 | $.support.minHeight = div.offsetHeight === 100; 241 | $.support.selectstart = "onselectstart" in div; 242 | 243 | // set display to none to avoid a layout bug in IE 244 | // http://dev.jquery.com/ticket/4014 245 | body.removeChild( div ).style.display = "none"; 246 | }); 247 | 248 | 249 | 250 | 251 | 252 | // deprecated 253 | $.extend( $.ui, { 254 | // $.ui.plugin is deprecated. Use the proxy pattern instead. 255 | plugin: { 256 | add: function( module, option, set ) { 257 | var proto = $.ui[ module ].prototype; 258 | for ( var i in set ) { 259 | proto.plugins[ i ] = proto.plugins[ i ] || []; 260 | proto.plugins[ i ].push( [ option, set[ i ] ] ); 261 | } 262 | }, 263 | call: function( instance, name, args ) { 264 | var set = instance.plugins[ name ]; 265 | if ( !set || !instance.element[ 0 ].parentNode ) { 266 | return; 267 | } 268 | 269 | for ( var i = 0; i < set.length; i++ ) { 270 | if ( instance.options[ set[ i ][ 0 ] ] ) { 271 | set[ i ][ 1 ].apply( instance.element, args ); 272 | } 273 | } 274 | } 275 | }, 276 | 277 | // will be deprecated when we switch to jQuery 1.4 - use jQuery.contains() 278 | contains: function( a, b ) { 279 | return document.compareDocumentPosition ? 280 | a.compareDocumentPosition( b ) & 16 : 281 | a !== b && a.contains( b ); 282 | }, 283 | 284 | // only used by resizable 285 | hasScroll: function( el, a ) { 286 | 287 | //If overflow is hidden, the element might have extra content, but the user wants to hide it 288 | if ( $( el ).css( "overflow" ) === "hidden") { 289 | return false; 290 | } 291 | 292 | var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", 293 | has = false; 294 | 295 | if ( el[ scroll ] > 0 ) { 296 | return true; 297 | } 298 | 299 | // TODO: determine which cases actually cause this to happen 300 | // if the element doesn't have the scroll set, see if it's possible to 301 | // set the scroll 302 | el[ scroll ] = 1; 303 | has = ( el[ scroll ] > 0 ); 304 | el[ scroll ] = 0; 305 | return has; 306 | }, 307 | 308 | // these are odd functions, fix the API or move into individual plugins 309 | isOverAxis: function( x, reference, size ) { 310 | //Determines when x coordinate is over "b" element axis 311 | return ( x > reference ) && ( x < ( reference + size ) ); 312 | }, 313 | isOver: function( y, x, top, left, height, width ) { 314 | //Determines when x, y coordinates is over "b" element 315 | return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width ); 316 | } 317 | }); 318 | 319 | })( jQuery ); 320 | -------------------------------------------------------------------------------- /js/jquery.ui.widget.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Widget 1.8.18 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Widget 9 | */ 10 | (function( $, undefined ) { 11 | 12 | // jQuery 1.4+ 13 | if ( $.cleanData ) { 14 | var _cleanData = $.cleanData; 15 | $.cleanData = function( elems ) { 16 | for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 17 | try { 18 | $( elem ).triggerHandler( "remove" ); 19 | // http://bugs.jquery.com/ticket/8235 20 | } catch( e ) {} 21 | } 22 | _cleanData( elems ); 23 | }; 24 | } else { 25 | var _remove = $.fn.remove; 26 | $.fn.remove = function( selector, keepData ) { 27 | return this.each(function() { 28 | if ( !keepData ) { 29 | if ( !selector || $.filter( selector, [ this ] ).length ) { 30 | $( "*", this ).add( [ this ] ).each(function() { 31 | try { 32 | $( this ).triggerHandler( "remove" ); 33 | // http://bugs.jquery.com/ticket/8235 34 | } catch( e ) {} 35 | }); 36 | } 37 | } 38 | return _remove.call( $(this), selector, keepData ); 39 | }); 40 | }; 41 | } 42 | 43 | $.widget = function( name, base, prototype ) { 44 | var namespace = name.split( "." )[ 0 ], 45 | fullName; 46 | name = name.split( "." )[ 1 ]; 47 | fullName = namespace + "-" + name; 48 | 49 | if ( !prototype ) { 50 | prototype = base; 51 | base = $.Widget; 52 | } 53 | 54 | // create selector for plugin 55 | $.expr[ ":" ][ fullName ] = function( elem ) { 56 | return !!$.data( elem, name ); 57 | }; 58 | 59 | $[ namespace ] = $[ namespace ] || {}; 60 | $[ namespace ][ name ] = function( options, element ) { 61 | // allow instantiation without initializing for simple inheritance 62 | if ( arguments.length ) { 63 | this._createWidget( options, element ); 64 | } 65 | }; 66 | 67 | var basePrototype = new base(); 68 | // we need to make the options hash a property directly on the new instance 69 | // otherwise we'll modify the options hash on the prototype that we're 70 | // inheriting from 71 | // $.each( basePrototype, function( key, val ) { 72 | // if ( $.isPlainObject(val) ) { 73 | // basePrototype[ key ] = $.extend( {}, val ); 74 | // } 75 | // }); 76 | basePrototype.options = $.extend( true, {}, basePrototype.options ); 77 | $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { 78 | namespace: namespace, 79 | widgetName: name, 80 | widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, 81 | widgetBaseClass: fullName 82 | }, prototype ); 83 | 84 | $.widget.bridge( name, $[ namespace ][ name ] ); 85 | }; 86 | 87 | $.widget.bridge = function( name, object ) { 88 | $.fn[ name ] = function( options ) { 89 | var isMethodCall = typeof options === "string", 90 | args = Array.prototype.slice.call( arguments, 1 ), 91 | returnValue = this; 92 | 93 | // allow multiple hashes to be passed on init 94 | options = !isMethodCall && args.length ? 95 | $.extend.apply( null, [ true, options ].concat(args) ) : 96 | options; 97 | 98 | // prevent calls to internal methods 99 | if ( isMethodCall && options.charAt( 0 ) === "_" ) { 100 | return returnValue; 101 | } 102 | 103 | if ( isMethodCall ) { 104 | this.each(function() { 105 | var instance = $.data( this, name ), 106 | methodValue = instance && $.isFunction( instance[options] ) ? 107 | instance[ options ].apply( instance, args ) : 108 | instance; 109 | // TODO: add this back in 1.9 and use $.error() (see #5972) 110 | // if ( !instance ) { 111 | // throw "cannot call methods on " + name + " prior to initialization; " + 112 | // "attempted to call method '" + options + "'"; 113 | // } 114 | // if ( !$.isFunction( instance[options] ) ) { 115 | // throw "no such method '" + options + "' for " + name + " widget instance"; 116 | // } 117 | // var methodValue = instance[ options ].apply( instance, args ); 118 | if ( methodValue !== instance && methodValue !== undefined ) { 119 | returnValue = methodValue; 120 | return false; 121 | } 122 | }); 123 | } else { 124 | this.each(function() { 125 | var instance = $.data( this, name ); 126 | if ( instance ) { 127 | instance.option( options || {} )._init(); 128 | } else { 129 | $.data( this, name, new object( options, this ) ); 130 | } 131 | }); 132 | } 133 | 134 | return returnValue; 135 | }; 136 | }; 137 | 138 | $.Widget = function( options, element ) { 139 | // allow instantiation without initializing for simple inheritance 140 | if ( arguments.length ) { 141 | this._createWidget( options, element ); 142 | } 143 | }; 144 | 145 | $.Widget.prototype = { 146 | widgetName: "widget", 147 | widgetEventPrefix: "", 148 | options: { 149 | disabled: false 150 | }, 151 | _createWidget: function( options, element ) { 152 | // $.widget.bridge stores the plugin instance, but we do it anyway 153 | // so that it's stored even before the _create function runs 154 | $.data( element, this.widgetName, this ); 155 | this.element = $( element ); 156 | this.options = $.extend( true, {}, 157 | this.options, 158 | this._getCreateOptions(), 159 | options ); 160 | 161 | var self = this; 162 | this.element.bind( "remove." + this.widgetName, function() { 163 | self.destroy(); 164 | }); 165 | 166 | this._create(); 167 | this._trigger( "create" ); 168 | this._init(); 169 | }, 170 | _getCreateOptions: function() { 171 | return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; 172 | }, 173 | _create: function() {}, 174 | _init: function() {}, 175 | 176 | destroy: function() { 177 | this.element 178 | .unbind( "." + this.widgetName ) 179 | .removeData( this.widgetName ); 180 | this.widget() 181 | .unbind( "." + this.widgetName ) 182 | .removeAttr( "aria-disabled" ) 183 | .removeClass( 184 | this.widgetBaseClass + "-disabled " + 185 | "ui-state-disabled" ); 186 | }, 187 | 188 | widget: function() { 189 | return this.element; 190 | }, 191 | 192 | option: function( key, value ) { 193 | var options = key; 194 | 195 | if ( arguments.length === 0 ) { 196 | // don't return a reference to the internal hash 197 | return $.extend( {}, this.options ); 198 | } 199 | 200 | if (typeof key === "string" ) { 201 | if ( value === undefined ) { 202 | return this.options[ key ]; 203 | } 204 | options = {}; 205 | options[ key ] = value; 206 | } 207 | 208 | this._setOptions( options ); 209 | 210 | return this; 211 | }, 212 | _setOptions: function( options ) { 213 | var self = this; 214 | $.each( options, function( key, value ) { 215 | self._setOption( key, value ); 216 | }); 217 | 218 | return this; 219 | }, 220 | _setOption: function( key, value ) { 221 | this.options[ key ] = value; 222 | 223 | if ( key === "disabled" ) { 224 | this.widget() 225 | [ value ? "addClass" : "removeClass"]( 226 | this.widgetBaseClass + "-disabled" + " " + 227 | "ui-state-disabled" ) 228 | .attr( "aria-disabled", value ); 229 | } 230 | 231 | return this; 232 | }, 233 | 234 | enable: function() { 235 | return this._setOption( "disabled", false ); 236 | }, 237 | disable: function() { 238 | return this._setOption( "disabled", true ); 239 | }, 240 | 241 | _trigger: function( type, event, data ) { 242 | var prop, orig, 243 | callback = this.options[ type ]; 244 | 245 | data = data || {}; 246 | event = $.Event( event ); 247 | event.type = ( type === this.widgetEventPrefix ? 248 | type : 249 | this.widgetEventPrefix + type ).toLowerCase(); 250 | // the original event may come from any element 251 | // so we need to reset the target on the new event 252 | event.target = this.element[ 0 ]; 253 | 254 | // copy original event properties over to the new event 255 | orig = event.originalEvent; 256 | if ( orig ) { 257 | for ( prop in orig ) { 258 | if ( !( prop in event ) ) { 259 | event[ prop ] = orig[ prop ]; 260 | } 261 | } 262 | } 263 | 264 | this.element.trigger( event, data ); 265 | 266 | return !( $.isFunction(callback) && 267 | callback.call( this.element[0], event, data ) === false || 268 | event.isDefaultPrevented() ); 269 | } 270 | }; 271 | 272 | })( jQuery ); 273 | -------------------------------------------------------------------------------- /js/lang-sql.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2008 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | 17 | /** 18 | * @fileoverview 19 | * Registers a language handler for SQL. 20 | * 21 | * 22 | * To use, include prettify.js and this file in your HTML page. 23 | * Then put your code in an HTML tag like 24 | *
    (my SQL code)
    25 | * 26 | * 27 | * http://savage.net.au/SQL/sql-99.bnf.html is the basis for the grammar, and 28 | * http://msdn.microsoft.com/en-us/library/aa238507(SQL.80).aspx as the basis 29 | * for the keyword list. 30 | * 31 | * @author mikesamuel@gmail.com 32 | */ 33 | 34 | PR['registerLangHandler']( 35 | PR['createSimpleLexer']( 36 | [ 37 | // Whitespace 38 | [PR['PR_PLAIN'], /^[\t\n\r \xA0]+/, null, '\t\n\r \xA0'], 39 | // A double or single quoted, possibly multi-line, string. 40 | [PR['PR_STRING'], /^(?:"(?:[^\"\\]|\\.)*"|'(?:[^\'\\]|\\.)*')/, null, 41 | '"\''] 42 | ], 43 | [ 44 | // A comment is either a line comment that starts with two dashes, or 45 | // two dashes preceding a long bracketed block. 46 | [PR['PR_COMMENT'], /^(?:--[^\r\n]*|\/\*[\s\S]*?(?:\*\/|$))/], 47 | [PR['PR_KEYWORD'], /^(?:ADD|ALL|ALTER|AND|ANY|AS|ASC|AUTHORIZATION|BACKUP|BEGIN|BETWEEN|BREAK|BROWSE|BULK|BY|CASCADE|CASE|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COMMIT|COMPUTE|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DBCC|DEALLOCATE|DECLARE|DEFAULT|DELETE|DENY|DESC|DISK|DISTINCT|DISTRIBUTED|DOUBLE|DROP|DUMMY|DUMP|ELSE|END|ERRLVL|ESCAPE|EXCEPT|EXEC|EXECUTE|EXISTS|EXIT|FETCH|FILE|FILLFACTOR|FOR|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GOTO|GRANT|GROUP|HAVING|HOLDLOCK|IDENTITY|IDENTITYCOL|IDENTITY_INSERT|IF|IN|INDEX|INNER|INSERT|INTERSECT|INTO|IS|JOIN|KEY|KILL|LEFT|LIKE|LINENO|LOAD|MATCH|MERGE|NATIONAL|NOCHECK|NONCLUSTERED|NOT|NULL|NULLIF|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPENXML|OPTION|OR|ORDER|OUTER|OVER|PERCENT|PLAN|PRECISION|PRIMARY|PRINT|PROC|PROCEDURE|PUBLIC|RAISERROR|READ|READTEXT|RECONFIGURE|REFERENCES|REPLICATION|RESTORE|RESTRICT|RETURN|REVOKE|RIGHT|ROLLBACK|ROWCOUNT|ROWGUIDCOL|RULE|SAVE|SCHEMA|SELECT|SESSION_USER|SET|SETUSER|SHUTDOWN|SOME|STATISTICS|SYSTEM_USER|TABLE|TEXTSIZE|THEN|TO|TOP|TRAN|TRANSACTION|TRIGGER|TRUNCATE|TSEQUAL|UNION|UNIQUE|UPDATE|UPDATETEXT|USE|USER|USING|VALUES|VARYING|VIEW|WAITFOR|WHEN|WHERE|WHILE|WITH|WRITETEXT)(?=[^\w-]|$)/i, null], 48 | // A number is a hex integer literal, a decimal real literal, or in 49 | // scientific notation. 50 | [PR['PR_LITERAL'], 51 | /^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i], 52 | // An identifier 53 | [PR['PR_PLAIN'], /^[a-z_][\w-]*/i], 54 | // A run of punctuation 55 | [PR['PR_PUNCTUATION'], /^[^\w\t\n\r \xA0\"\'][^\w\t\n\r \xA0+\-\"\']*/] 56 | ]), 57 | ['sql']); 58 | -------------------------------------------------------------------------------- /lib/Helpers.php: -------------------------------------------------------------------------------- 1 | 6 | * @license Apache 2.0 license. See LICENSE document for more info 7 | * @created 2012-01-01 8 | */ 9 | 10 | // @todo validation? 11 | /** 12 | * search global request variables $_POST and $_GET in that order and return 13 | * the first defined value for the given key 14 | * 15 | * @param string $name 16 | * @return mixed the value of the variable name (if any) or null. 17 | */ 18 | function get_var($name) 19 | { 20 | $sources = array($_POST, $_GET); 21 | foreach ($sources as $s) 22 | { 23 | if (isset($s[$name])) 24 | { 25 | return $s[$name]; 26 | } 27 | } 28 | return null; 29 | } 30 | 31 | /** 32 | * return the full URL for the base page of the site. 33 | * 34 | * @return string 35 | */ 36 | function site_url() 37 | { 38 | if (isset($_SERVER['HTTPS'])) 39 | { 40 | $proto = 'https://'; 41 | } 42 | else 43 | { 44 | $proto = 'http://'; 45 | } 46 | return $proto . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME']; 47 | } 48 | 49 | 50 | /** 51 | * wrap html pre tags around the given string, with class="prettyprint" 52 | * 53 | * @param string $string 54 | */ 55 | function prettyprint($string) 56 | { 57 | print '
    ';
    58 |     print $string;
    59 |     print "
    "; 60 | } 61 | 62 | 63 | /** 64 | * die with the error message if the given result handle or mysqli object has an error 65 | * 66 | * @param MySQLi_Result $result 67 | * @param MySQLi $mysqli 68 | */ 69 | function check_mysql_error($result, $mysqli) 70 | { 71 | if (!$result) 72 | { 73 | // @TODO put markup in a view 74 | die("
    ".$mysqli->errno."
    ".$mysqli->error ."
    "); 75 | } 76 | } 77 | 78 | ?> 79 | -------------------------------------------------------------------------------- /lib/Loader.php: -------------------------------------------------------------------------------- 1 | 6 | * @created 2012-01-01 7 | * @license Apache 2.0 license. See LICENSE document for more info 8 | */ 9 | class Loader 10 | { 11 | 12 | /** 13 | * Finds and displays the given view, and makes the values in $data available to it. 14 | * The name of the view is passed in without the leading "views/" directory or the trailing 15 | * ".php" extension. So loading a view with $this->view("myview"); would look for a file 16 | * called "views/myview.php" 17 | * 18 | * the data is made available my taking the keys of $data, and assigning them to 19 | * a locally scoped variable of the same name. array( 'title' => 'The Title' ) 20 | * would be available in the view as $title. 21 | * 22 | * @param string $view_name The name of the view to load 23 | * @param type $data array of values to make available to the view 24 | */ 25 | public function view($view_name, $data = null) 26 | { 27 | // make local variables out of array keys 28 | if (is_array($data)) 29 | { 30 | foreach($data as $key => $value) 31 | { 32 | ${$key} = $value; 33 | } 34 | } 35 | 36 | // find and include the view 37 | $view_name = "views/{$view_name}.php"; 38 | if (file_exists($view_name)) 39 | { 40 | include $view_name; 41 | } 42 | else 43 | { 44 | print "
    Error {$view_name} not found
    "; 45 | } 46 | } 47 | 48 | } 49 | 50 | ?> -------------------------------------------------------------------------------- /scripts/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box/RainGauge/6ed95786bfa90b7f400145f54433220d621618f3/scripts/index.html -------------------------------------------------------------------------------- /scripts/pt-stalk_2.1.1.patch: -------------------------------------------------------------------------------- 1 | --- /home/gtowey/pt-stalk 2012-08-02 15:59:55.116406208 -0700 2 | +++ ./pt-stalk 2012-08-02 15:54:24.000000000 -0700 3 | @@ -238,21 +238,21 @@ 4 | [ "$val" = "size" ] && size=1 5 | ;; 6 | desc) 7 | ;; 8 | negatable) 9 | if [ "$val" = "yes" ]; then 10 | neg=1 11 | fi 12 | ;; 13 | *) 14 | - echo "Invalid attribute in $opt_spec: $line" >&2 15 | + echo "Invalid attribute in $opt_spec: $key $val" >&2 16 | exit 1 17 | esac 18 | done < "$opt_spec" 19 | 20 | if [ -z "$opt" ]; then 21 | echo "No long attribute in option spec $opt_spec" >&2 22 | exit 1 23 | fi 24 | 25 | if [ $neg -eq 1 ]; then 26 | @@ -769,21 +769,23 @@ 27 | (echo $ts; df -h) >> "$d/$p-df" & 28 | 29 | (echo $ts; netstat -antp) >> "$d/$p-netstat" & 30 | (echo $ts; netstat -s) >> "$d/$p-netstat_s" & 31 | 32 | (echo $ts; $CMD_MYSQL $EXT_ARGV -e "SHOW FULL PROCESSLIST\G") \ 33 | >> "$d/$p-processlist" & 34 | 35 | if [ "$have_lock_waits_table" ]; then 36 | (echo $ts; lock_waits) >>"$d/$p-lock-waits" & 37 | + (echo $ts; locks) >> "$d/$p-locks" & 38 | fi 39 | + 40 | done 41 | log "Loop end: $(date +'TS %s.%N %F %T')" 42 | 43 | if [ "$have_oprofile" ]; then 44 | $CMD_OPCONTROL --stop 45 | $CMD_OPCONTROL --dump 46 | 47 | local oprofiled_pid=$(_pidof oprofiled | awk '{print $1; exit;}') 48 | if [ "$oprofiled_pid" ]; then 49 | kill $oprofiled_pid 50 | @@ -869,20 +871,25 @@ 51 | b.trx_query AS blocking_query 52 | FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS AS w 53 | INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS b ON b.trx_id = w.blocking_trx_id 54 | INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS r ON r.trx_id = w.requesting_trx_id 55 | INNER JOIN INFORMATION_SCHEMA.INNODB_LOCKS AS l ON w.requested_lock_id = l.lock_id 56 | LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST AS p ON p.id = b.trx_mysql_thread_id 57 | ORDER BY wait_time DESC\G" 58 | $CMD_MYSQL $EXT_ARGV -e "$sql2" 59 | } 60 | 61 | +locks() { 62 | + local sql="SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS" 63 | + $CMD_MYSQL $EXT_ARGV -e "$sql" 64 | +} 65 | + 66 | # ########################################################################### 67 | # End collect package 68 | # ########################################################################### 69 | 70 | # ########################################################################### 71 | # Global variables 72 | # ########################################################################### 73 | TRIGGER_FUNCTION="" 74 | RAN_WITH="" 75 | EXIT_REASON="" 76 | @@ -982,21 +989,21 @@ 77 | EXIT_REASON="no more iterations" 78 | return 1 # stop running 79 | fi 80 | 81 | return 0 # continue running 82 | } 83 | 84 | sleep_ok() { 85 | local seconds="$1" 86 | local msg="${2:-""}" 87 | - if oktorun; then 88 | + if oktorun || [[ "$OPT_EXEC_AFTER_SLEEP" ]]; then 89 | if [ -n "$msg" ]; then 90 | log "$msg" 91 | fi 92 | sleep $seconds 93 | fi 94 | } 95 | 96 | purge_samples() { 97 | local dir="$1" 98 | local retention_time="$2" 99 | @@ -1095,31 +1102,41 @@ 100 | last_prefix="$prefix" 101 | 102 | # Fork and background the collect subroutine which will 103 | # run for --run-time seconds. We (the parent) sleep 104 | # while its collecting (hopefully --sleep is longer than 105 | # --run-time). 106 | ( 107 | collect "$OPT_DEST" "$prefix" 108 | ) >> "$OPT_DEST/$prefix-output" 2>&1 & 109 | log "Collector PID $!" 110 | + if [ "$OPT_EXEC_AFTER_COLLECT" ]; then 111 | + log "Executing script after collection: $OPT_EXEC_AFTER_COLLECT" 112 | + $OPT_EXEC_AFTER_COLLECT 113 | + fi 114 | else 115 | # There will not be enough disk space, so do not collect. 116 | warn "Collect canceled because there will not be enough disk space after collecting another $margin MB" 117 | fi 118 | fi 119 | 120 | # ################################################################## 121 | # Done collecting. 122 | # ################################################################## 123 | ITER=$((ITER + 1)) 124 | sleep_ok "$OPT_SLEEP" "Sleeping $OPT_SLEEP seconds after collect" 125 | + 126 | + if [ "$OPT_EXEC_AFTER_SLEEP" ]; then 127 | + log "Executing script after sleep: $OPT_EXEC_AFTER_SLEEP" 128 | + $OPT_EXEC_AFTER_SLEEP 129 | + fi 130 | + 131 | else 132 | # Trigger/check/value is ok, sleep until next check. 133 | sleep_ok "$OPT_INTERVAL" 134 | fi 135 | 136 | # Purge old collect file between checks. 137 | if [ -d "$OPT_DEST" ]; then 138 | purge_samples "$OPT_DEST" "$OPT_RETENTION_TIME" 139 | fi 140 | done 141 | @@ -1171,21 +1188,21 @@ 142 | 143 | # Verify and set TRIGGER_FUNCTION based on --function. 144 | if ! set_trg_func "$OPT_FUNCTION"; then 145 | option_error "Invalid --function value: $OPT_FUNCTION" 146 | fi 147 | 148 | if [ -z "$OPT_STALK" -a "$OPT_COLLECT" ]; then 149 | # Not stalking; do immediate collect once. 150 | OPT_ITERATIONS=1 151 | OPT_CYCLES=0 152 | - OPT_SLEEP=0 153 | + #OPT_SLEEP=0 154 | OPT_INTERVAL=0 155 | fi 156 | 157 | usage_or_errors "$0" 158 | po_status=$? 159 | rm_tmpdir 160 | if [ $po_status -ne 0 ]; then 161 | [ $OPT_ERRS -gt 0 ] && exit 1 162 | exit 0 163 | fi 164 | @@ -1362,20 +1379,21 @@ 165 | threshold=20 166 | 167 | If you're not running the tool as it's designed (as a root user, daemonized) 168 | then you'll need to set several options, such as L<"--dest">, to locations that 169 | are writable by non-root users. 170 | 171 | =head1 OPTIONS 172 | 173 | =over 174 | 175 | + 176 | =item --collect 177 | 178 | default: yes; negatable: yes 179 | 180 | Collect system information. You can negate this option to make the tool watch 181 | the system but not actually gather any diagnostic data. 182 | 183 | See also L<"--stalk">. 184 | 185 | =item --collect-gdb 186 | @@ -1462,20 +1480,33 @@ 187 | type: int; default: 5 188 | 189 | Don't collect data if the disk has less than this percent free space. 190 | This prevents the tool from filling up the disk with diagnostic data. 191 | 192 | This option works similarly to L<"--disk-bytes-free"> but specifies a 193 | percentage margin of safety instead of a bytes margin of safety. 194 | The tool honors both options, and will not collect any data unless both 195 | margins are satisfied. 196 | 197 | +=item --exec-after-collect 198 | + 199 | +type: string 200 | + 201 | +Run this script immediately after the collection has been started. 202 | + 203 | + 204 | +=item --exec-after-sleep 205 | + 206 | +type: string 207 | + 208 | +Run this script after collection has been completed, and pt-stalk has waited for L<"--sleep"> seconds. 209 | + 210 | =item --function 211 | 212 | type: string; default: status 213 | 214 | Specifies what to watch for a diagnostic trigger. The default value watches 215 | SHOW GLOBAL STATUS, but you can also watch SHOW PROCESSLIST or supply a plugin 216 | file with your own custom code. This function supplies the value of 217 | L<"--variable">, which is then compared against L<"--threshold"> to see if the 218 | trigger condition is met. Additional options may be required as well; see 219 | below. Possible values: 220 | -------------------------------------------------------------------------------- /scripts/pt-stalk_2.1.2.patch: -------------------------------------------------------------------------------- 1 | --- /usr/bin/pt-stalk 2012-06-12 09:04:22.000000000 -0700 2 | +++ ./pt-stalk-raingauge 2012-09-05 11:14:58.948416483 -0700 3 | @@ -783,20 +783,21 @@ 4 | (echo $ts; df -h) >> "$d/$p-df" & 5 | 6 | (echo $ts; netstat -antp) >> "$d/$p-netstat" & 7 | (echo $ts; netstat -s) >> "$d/$p-netstat_s" & 8 | 9 | (echo $ts; $CMD_MYSQL $EXT_ARGV -e "SHOW FULL PROCESSLIST\G") \ 10 | >> "$d/$p-processlist" & 11 | 12 | if [ "$have_lock_waits_table" ]; then 13 | (echo $ts; lock_waits) >>"$d/$p-lock-waits" & 14 | + (echo $ts; locks) >> "$d/$p-locks" & 15 | fi 16 | done 17 | log "Loop end: $(date +'TS %s.%N %F %T')" 18 | 19 | if [ "$have_oprofile" ]; then 20 | $CMD_OPCONTROL --stop 21 | $CMD_OPCONTROL --dump 22 | 23 | local oprofiled_pid=$(_pidof oprofiled | awk '{print $1; exit;}') 24 | if [ "$oprofiled_pid" ]; then 25 | @@ -881,33 +882,38 @@ 26 | SUBSTRING(p.host, INSTR(p.host, ':') +1) AS blocking_port, 27 | IF(p.command = \"Sleep\", p.time, 0) AS idle_in_trx, 28 | b.trx_query AS blocking_query 29 | FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS AS w 30 | INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS b ON b.trx_id = w.blocking_trx_id 31 | INNER JOIN INFORMATION_SCHEMA.INNODB_TRX AS r ON r.trx_id = w.requesting_trx_id 32 | INNER JOIN INFORMATION_SCHEMA.INNODB_LOCKS AS l ON w.requested_lock_id = l.lock_id 33 | LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST AS p ON p.id = b.trx_mysql_thread_id 34 | ORDER BY wait_time DESC\G" 35 | $CMD_MYSQL $EXT_ARGV -e "$sql2" 36 | -} 37 | +} 38 | + 39 | +locks() { 40 | + local sql="SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS" 41 | + $CMD_MYSQL $EXT_ARGV -e "$sql" 42 | +} 43 | 44 | # ########################################################################### 45 | # End collect package 46 | # ########################################################################### 47 | 48 | # ########################################################################### 49 | # Global variables 50 | # ########################################################################### 51 | TRIGGER_FUNCTION="" 52 | RAN_WITH="" 53 | EXIT_REASON="" 54 | TOOL="pt-stalk" 55 | OKTORUN=1 56 | ITER=1 57 | 58 | # ########################################################################### 59 | # Subroutines 60 | # ########################################################################### 61 | 62 | grep_processlist() { 63 | local file="$1" 64 | local col="$2" 65 | @@ -996,21 +1002,21 @@ 66 | EXIT_REASON="no more iterations" 67 | return 1 # stop running 68 | fi 69 | 70 | return 0 # continue running 71 | } 72 | 73 | sleep_ok() { 74 | local seconds="$1" 75 | local msg="${2:-""}" 76 | - if oktorun; then 77 | + if oktorun || [[ "$OPT_EXEC_AFTER_SLEEP" ]]; then 78 | if [ -n "$msg" ]; then 79 | log "$msg" 80 | fi 81 | sleep $seconds 82 | fi 83 | } 84 | 85 | purge_samples() { 86 | local dir="$1" 87 | local retention_time="$2" 88 | @@ -1121,29 +1127,36 @@ 89 | warn "Collect canceled because there will not be enough disk space after collecting another $margin MB" 90 | fi 91 | fi 92 | 93 | # ################################################################## 94 | # Done collecting. 95 | # ################################################################## 96 | ITER=$((ITER + 1)) 97 | cycles_true=0 98 | sleep_ok "$OPT_SLEEP" "Sleeping $OPT_SLEEP seconds after collect" 99 | + 100 | + if [ "$OPT_EXEC_AFTER_SLEEP" ]; then 101 | + log "Executing script after sleep: $OPT_EXEC_AFTER_SLEEP" 102 | + $OPT_EXEC_AFTER_SLEEP 103 | + fi 104 | + 105 | + # Purge old collect file between checks. 106 | + if [ -d "$OPT_DEST" ]; then 107 | + purge_samples "$OPT_DEST" "$OPT_RETENTION_TIME" 108 | + fi 109 | + 110 | else 111 | # Trigger/check/value is ok, sleep until next check. 112 | sleep_ok "$OPT_INTERVAL" 113 | fi 114 | 115 | - # Purge old collect file between checks. 116 | - if [ -d "$OPT_DEST" ]; then 117 | - purge_samples "$OPT_DEST" "$OPT_RETENTION_TIME" 118 | - fi 119 | done 120 | } 121 | 122 | # ########################################################################### 123 | # Main program loop, called below if tool is ran from the command line. 124 | # ########################################################################### 125 | 126 | main() { 127 | trap sigtrap SIGHUP SIGINT SIGTERM 128 | 129 | @@ -1186,21 +1199,21 @@ 130 | 131 | # Verify and set TRIGGER_FUNCTION based on --function. 132 | if ! set_trg_func "$OPT_FUNCTION"; then 133 | option_error "Invalid --function value: $OPT_FUNCTION" 134 | fi 135 | 136 | if [ -z "$OPT_STALK" -a "$OPT_COLLECT" ]; then 137 | # Not stalking; do immediate collect once. 138 | OPT_ITERATIONS=1 139 | OPT_CYCLES=0 140 | - OPT_SLEEP=0 141 | + #OPT_SLEEP=0 142 | OPT_INTERVAL=0 143 | fi 144 | 145 | usage_or_errors "$0" 146 | po_status=$? 147 | rm_tmpdir 148 | if [ $po_status -ne 0 ]; then 149 | [ $OPT_ERRS -gt 0 ] && exit 1 150 | exit 0 151 | fi 152 | @@ -1477,20 +1490,33 @@ 153 | type: int; default: 5 154 | 155 | Don't collect data if the disk has less than this percent free space. 156 | This prevents the tool from filling up the disk with diagnostic data. 157 | 158 | This option works similarly to L<"--disk-bytes-free"> but specifies a 159 | percentage margin of safety instead of a bytes margin of safety. 160 | The tool honors both options, and will not collect any data unless both 161 | margins are satisfied. 162 | 163 | +=item --exec-after-collect 164 | + 165 | +type: string 166 | + 167 | +Run this script immediately after the collection has been started. 168 | + 169 | + 170 | +=item --exec-after-sleep 171 | + 172 | +type: string 173 | + 174 | +Run this script after collection has been completed, and pt-stalk has waited for L<"--sleep"> seconds. 175 | + 176 | =item --function 177 | 178 | type: string; default: status 179 | 180 | Specifies what to watch for a diagnostic trigger. The default value watches 181 | SHOW GLOBAL STATUS, but you can also watch SHOW PROCESSLIST or supply a plugin 182 | file with your own custom code. This function supplies the value of 183 | L<"--variable">, which is then compared against L<"--threshold"> to see if the 184 | trigger condition is met. Additional options may be required as well; see 185 | below. Possible values: 186 | -------------------------------------------------------------------------------- /scripts/raingauge_package_and_send.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Bring in config options for raingauge (to get user/password info) 4 | source /etc/raingauge_rc 5 | 6 | if [[ -n "$PT_MYSQL_USER" ]] && [[ -n "$PT_MYSQL_PASS" ]] 7 | then 8 | userPassArgs="-u${PT_MYSQL_USER} -p${PT_MYSQL_PASS}" 9 | elif [[ -n "$PT_MYSQL_USER" ]] 10 | then 11 | userPassArgs="-u${PT_MYSQL_USER}" 12 | else 13 | userPassArgs= 14 | fi 15 | 16 | PORT=$(mysql -h localhost -e "select @@port" -ss $userPassArgs) 17 | DATE=$(ls -r "$PT_STALK_COLLECT_DIR" | tail -n1 | cut -d'-' -f1) 18 | FILE="/tmp/$HOSTNAME-$DATE.tar.gz" 19 | URL="${RG_WEB_SERVER}/index.php?action=upload&hostname=$HOSTNAME&port=$PORT" 20 | 21 | #echo "$FILE" 22 | if [[ ! "$DATE" =~ "^[0-9][0-9][0-9][0-9]" ]]; 23 | then 24 | echo "bad/no file" 25 | fi 26 | 27 | tar -zcf "$FILE" -C "$PT_STALK_COLLECT_DIR" . 28 | if [[ -r "$FILE" ]]; then 29 | rm -f "$PT_STALK_COLLECT_DIR"/* 30 | curl -F "file=@$FILE" $RG_CURL_PARAMS "$URL" > /dev/null 2>&1 31 | rm -f "$FILE" 32 | fi 33 | -------------------------------------------------------------------------------- /scripts/raingauge_rc: -------------------------------------------------------------------------------- 1 | #@IgnoreInspection BashAddShebang 2 | # rc file for the raingauge service. 3 | # define all options here to pass to the init script 4 | 5 | # use a modified version of pt stalk instead of the default pt-stalk. See the github project for the script 6 | # modified version includes --exec-after-sleep option needed for collection package and send 7 | PT_STALK_PROG="/usr/bin/pt-stalk-raingauge" 8 | 9 | # most common program options 10 | PT_STALK_VARIABLE="Threads_running" 11 | PT_STALK_THRESHOLD="0" 12 | 13 | # leave empty when using pt-stalk's default trigger variable and threshold 14 | # customize your own triggers in raingauge_triggers.sh 15 | PT_STALK_FUNCTION="/usr/bin/raingauge_triggers.sh" 16 | 17 | PT_STALK_CYCLES="2" 18 | PT_STALK_ITERATIONS="1" 19 | PT_STALK_SLEEP="120" 20 | PT_STALK_EXECUTE_AFTER="/usr/bin/raingauge_package_and_send.sh" 21 | PT_STALK_COLLECT_DIR="/tmp/raingauge" 22 | 23 | # and other options can be set here 24 | # such as --collect-gdb (USE WIITH EXTREME CAUTION, gdb collection can stall a busy server for long enough to cause severe issues) 25 | PT_STALK_EXTRA_OPTIONS="" 26 | 27 | # Location of the pt stalk PID file 28 | PT_PID_FILE="/var/run/ptstalk.pid" 29 | 30 | # Location of the mysql socket pt stalk should connect to 31 | PT_MYSQL_SOCKET="/var/lib/mysql/mysql.sock" 32 | 33 | # MySQL username and password for a user to watch 34 | PT_MYSQL_USER="raingauge" 35 | PT_MYSQL_PASS="SuperSecurePass" 36 | 37 | # RainGauge Web Server 38 | RG_WEB_SERVER="http://localhost/RainGauge" 39 | 40 | # Extra curl parameters when posting to RainGauge Web Server 41 | RG_CURL_PARAMS="" 42 | -------------------------------------------------------------------------------- /scripts/raingauge_service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # /etc/init.d/raingauge 4 | # init script for pt-stalk/raingauge service 5 | # 6 | # chkconfig: 5 90 60 7 | # description: Runs pt-stalk to collect data from anomolous db events 8 | # 9 | RETVAL=0 10 | 11 | # Bring in config variables needed to start up the pt-stalk program 12 | source /etc/raingauge_rc 13 | 14 | validate_config() { 15 | # If all the variables are set, 0 is returned. Otherwise, non-zero status will be returned 16 | [[ ! -z "${PT_STALK_PROG}" ]] && [[ ! -z "${PT_STALK_EXECUTE_AFTER}" ]] && \ 17 | [[ ! -z "${PT_MYSQL_USER}" ]] && [[ ! -z "${PT_MYSQL_SOCKET}" ]] && [[ ! -z "${PT_PID_FILE}" ]] && \ 18 | [[ ! -z "${PT_STALK_THRESHOLD}" ]] && [[ ! -z "${PT_STALK_CYCLES}" ]] && [[ ! -z "${PT_STALK_ITERATIONS}" ]] && \ 19 | [[ ! -z "${PT_STALK_SLEEP}" ]] && [[ ! -z "${PT_STALK_COLLECT_DIR}" ]] && [[ ! -z "${PT_STALK_VARIABLE}" ]] 20 | } 21 | 22 | ptstalk_start() { 23 | validate_config 24 | RETVAL=$? 25 | if (( $RETVAL == 0 )) 26 | then 27 | echo "Starting pt-stalk..." 28 | 29 | if [[ -z "${PT_MYSQL_PASS}" ]] 30 | then 31 | PT_MYSQL_PASS_ARG="" 32 | else 33 | if test -z "${PT_MYSQL_PASS}" 34 | then 35 | PT_MYSQL_PASS_ARG="-p\"\"" 36 | else 37 | PT_MYSQL_PASS_ARG="-p${PT_MYSQL_PASS}" 38 | fi 39 | fi 40 | 41 | if [[ -z "${PT_STALK_FUNCTION}" ]] 42 | then 43 | HOME="/root" "${PT_STALK_PROG}" --daemonize --variable="${PT_STALK_VARIABLE}" \ 44 | --threshold="${PT_STALK_THRESHOLD}" --pid="${PT_PID_FILE}" --cycles="${PT_STALK_CYCLES}" \ 45 | --sleep="${PT_STALK_SLEEP}" --exec-after-sleep="${PT_STALK_EXECUTE_AFTER}" \ 46 | --dest="${PT_STALK_COLLECT_DIR}" ${PT_STALK_EXTRA_OPTIONS} -- \ 47 | -S "${PT_MYSQL_SOCKET}" -u "${PT_MYSQL_USER}" "${PT_MYSQL_PASS_ARG}" 48 | else 49 | HOME="/root" "${PT_STALK_PROG}" --daemonize --function="${PT_STALK_FUNCTION}" \ 50 | --variable=test_triggered --threshold=0 --pid="${PT_PID_FILE}" --cycles="${PT_STALK_CYCLES}" \ 51 | --sleep="${PT_STALK_SLEEP}" --exec-after-sleep="${PT_STALK_EXECUTE_AFTER}" \ 52 | --dest="${PT_STALK_COLLECT_DIR}" ${PT_STALK_EXTRA_OPTIONS} -- \ 53 | -S "${PT_MYSQL_SOCKET}" -u "${PT_MYSQL_USER}" "${PT_MYSQL_PASS_ARG}" 54 | fi 55 | RETVAL=$? 56 | fi 57 | (( $RETVAL == 0 )) && touch /var/lock/ptstalk 58 | return $RETVAL 59 | } 60 | 61 | ptstalk_stop() { 62 | echo "Stopping pt-stalk..." 63 | kill $( cat "${PT_PID_FILE}" ) 64 | RETVAL=$? 65 | [ $RETVAL -eq 0 ] && rm -f /var/lock/ptstalk 66 | return $RETVAL 67 | } 68 | 69 | ptstalk_restart() { 70 | ptstalk_stop && ptstalk_start 71 | } 72 | 73 | case "$1" in 74 | start) 75 | ptstalk_start 76 | ;; 77 | stop) 78 | ptstalk_stop 79 | ;; 80 | restart) 81 | ptstalk_restart 82 | ;; 83 | status) 84 | echo -n "RainGauge status: " 85 | if test -f "${PT_PID_FILE}" 86 | then 87 | set -e 88 | kill -0 $(cat ${PT_PID_FILE}) 89 | echo "running" 90 | set +e 91 | else 92 | echo "stopped" 93 | exit -1 94 | fi 95 | ;; 96 | *) 97 | echo "Usage: $0 {start|stop|restart|status}" 98 | exit 1 99 | ;; 100 | esac 101 | 102 | exit $RETVAL 103 | -------------------------------------------------------------------------------- /scripts/raingauge_triggers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script builds the custom trg_plugin function used by pt-stalk-rainguage 4 | # 5 | 6 | # Bring in config options for raingauge 7 | source /etc/raingauge_rc 8 | 9 | # Calculates rate (change in value over 1 second) for a given trigger command 10 | # Gets previous value from file and calculates the change in values 11 | get_delta() { 12 | local counter_name="$1" 13 | local new_value="$2" 14 | file="${PT_STALK_COLLECT_DIR}/saved_trigger_values" 15 | 16 | if [[ ! -e "$file" ]]; then 17 | touch "$file" 18 | fi 19 | 20 | local last_value="$(cat "$file" | grep "$counter_name" | awk '{print $2}')" 21 | 22 | if [[ -z "$last_value" ]]; then 23 | echo 0 24 | else 25 | echo "$(( new_value - last_value ))" 26 | sed -i "/${counter_name}/d" "$file" 27 | fi 28 | echo -e "${counter_name}\t${new_value}" >> "$file" 29 | } 30 | 31 | # Function called by pt-stalk-raingauge 32 | trg_plugin() { 33 | # Evaluate all trigger commands, calling get_delta if necessary 34 | # e.g trigger_name="$(bash_command_to_execute)" 35 | seconds_behind_master="$(mysql $EXT_ARGV -e "SHOW SLAVE STATUS\G" -ss | grep -i seconds_behind | awk '{print $2}')" 36 | threads_running="$(mysql $EXT_ARGV -e "SHOW GLOBAL STATUS LIKE 'Threads_running'" -ss | awk '{print $2}')" 37 | cpu_percentage="$(top -d 0.1 -bn2 | grep "Cpu(s)" | tail -n 1 | awk -F',' '{print 100 - $4}')" 38 | threads_created="$(get_delta "threads_created" "$(mysql $EXT_ARGV -e "SHOW GLOBAL STATUS LIKE 'Threads_created'" -ss | awk '{print $2}')")" 39 | 40 | # Check triggers against their threshold 41 | # 42 | # Nonzero value will cause pt-stalk-raingauge to collect and is used to reference which trigger was set off 43 | # See which trigger set off collector by peeking in /var/log/pt-stalk.log 44 | # Checks are formatted this way to handle comparisons involving floating point numbers 45 | # 46 | # The format is: triggered|threshold|name 47 | # The threshold is how many times the trigger needs to fire in a row before a collection is processed 48 | # 49 | # To add new trigger, copy the format below and change variables 50 | 51 | val="$(echo "$seconds_behind_master" | awk '{print ($1 > 10)}')" 52 | echo "$val|10|seconds_behind_master" 53 | 54 | val="$(echo "$threads_running" | awk '{print ($1 > 150)}')" 55 | echo "$val|5|threads_running" 56 | 57 | val="$(echo "$cpu_percentage" | awk '{print ($1 > 50)}')" 58 | echo "$val|5|cpu_percentage" 59 | 60 | val="$(echo "$threads_created" | awk '{print ($1 > 100)}')" 61 | echo "$val|5|threads_created" 62 | 63 | # val="$(echo "$variable" | awk '{print ($1 > 0)}')" 64 | # echo "$val|threshold|variable" 65 | 66 | } 67 | -------------------------------------------------------------------------------- /vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | #-*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = '2' 6 | 7 | $script = < 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 61 | 62 | 63 | 64 |
    65 | -------------------------------------------------------------------------------- /views/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 |
    5 |
    6 | 11 |
    12 | 13 | 14 | 15 | 16 |
    ServerTotal SamplesLast Sample
    17 |
    18 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /views/navbar.php: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /views/sample.php: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    ">

    4 |
    5 |
    6 |

    7 |
    8 |
    9 | 10 |
    11 |
    12 | 20 |
    21 | 29 |
    30 | 31 |
    32 |
    33 | $b['name']; } ); ?> 34 | 35 | 36 | 42 |
    43 | 47 |
    48 | 49 | 50 | 51 |
    52 | 53 |
    54 |
    55 |
    56 |
    57 |
    58 | 59 | 60 | 61 | Search in files: 62 | 70 | 75 | lines of context 76 | 77 | regex pattern 78 | 79 |
    80 |
    81 |
    82 | 83 | Sift | 84 | Netstat Summary 85 | 86 | 1) { ?> 87 | 94 | 95 | 96 | 97 |
    98 | 99 |
    100 |
    101 | $values) { ?> 102 | 103 | 104 |
    105 |

    106 |
    107 | 108 | 109 | 110 | 111 | 112 | 235 | 236 | 237 | 238 | 239 |
    240 | 		×
    241 | 
    242 | 
    243 | 	
    244 | 245 | 246 | 247 | 248 | $row) { ?> 249 | 250 | 251 |
    ", array_map(function ($x,$y) { return $x-$y; }, $row, array_slice(array_merge(array(0), $row),0,count($row)))); ?>
    252 | 253 | 254 | 255 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /views/server.php: -------------------------------------------------------------------------------- 1 |
    2 |

    Samples Collected for

    3 | 4 |
    5 |
    6 | 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 161 | -------------------------------------------------------------------------------- /views/server_list_samples.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 |
    6 | 7 | Date 8 | 9 | ">Date 10 | 11 | 13 | 14 | Size 15 | 16 | ">Size 17 | 18 | Trigger
    26 | "> 27 | " class="btn">Download
    34 |
    35 | -------------------------------------------------------------------------------- /views/server_list_samples_json.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /views/upload_test.php: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 |
    5 | 6 | 7 |
    8 | 9 | 10 |
    11 | 12 |
    13 | --------------------------------------------------------------------------------