├── .gitignore ├── LICENSE ├── README.md ├── app ├── __init__.py ├── controllers │ ├── __init__.py │ ├── dialog.py │ ├── generic.py │ ├── process_programs.py │ ├── tool_ec.py │ ├── tool_remotecommand.py │ ├── tool_settings.py │ ├── tool_sv.py │ ├── tool_terminal.py │ └── tools.py ├── helpers │ ├── __init__.py │ ├── contextprocessor.py │ ├── json_encoder.py │ ├── plugins.py │ ├── processprograms.py │ └── secs.py ├── models │ ├── G85_map.py │ ├── __init__.py │ ├── collection_event.py │ ├── data_value.py │ ├── log_entry.py │ └── tool.py ├── static │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── style.css │ │ ├── tool_detail.css │ │ └── tools_list.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── handlebars.js │ │ ├── jquery-2.1.3.min.js │ │ ├── npm.js │ │ └── typeahead.js ├── templates │ ├── dialog_settings.html │ ├── layout.html │ ├── process_programs.html │ ├── tool_detail.html │ └── tools_list.html └── views │ ├── __init__.py │ └── settings.py ├── config.py ├── data ├── .keep ├── map │ └── .keep └── processprogram │ └── .keep ├── plugins ├── .keep └── toolhandlers │ └── gemma_default_handler.py ├── requirements.txt ├── run.py └── shell.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # Toolhandlers 57 | plugins/toolhandlers/Data*.py 58 | 59 | # Documentation 60 | docu 61 | 62 | # Database 63 | gemma.db 64 | 65 | # Data 66 | data/processprogram/*/* 67 | data/map/* 68 | 69 | # IDE 70 | .idea 71 | 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | (This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.) 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | {description} 474 | Copyright (C) {year} {fullname} 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | {signature of Ty Coon}, 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | 506 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gemma 2 | Simple SECS/GEM host 3 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/__init__.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from flask import Flask 18 | from flask.ext.sqlalchemy import SQLAlchemy 19 | 20 | import logging 21 | 22 | logging.basicConfig(format='%(asctime)s %(name)s.%(funcName)s: %(message)s', level=logging.DEBUG) 23 | 24 | app = Flask(__name__) 25 | app.config.from_object('config') 26 | 27 | db = SQLAlchemy(app) 28 | 29 | import helpers 30 | 31 | toolHandlers = helpers.loadModules("plugins/toolhandlers") 32 | 33 | import models 34 | import controllers 35 | import views 36 | # from models import * 37 | # from controllers import * 38 | 39 | db.create_all() 40 | 41 | # instanciate all configured tools 42 | for tool in models.Tool.query.all(): 43 | helpers.addTool(tool) 44 | 45 | 46 | def stop(): 47 | helpers.stop() 48 | helpers._onEvent("terminate", {}) 49 | -------------------------------------------------------------------------------- /app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/__init__.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | import generic 18 | import dialog 19 | import tools 20 | import tool_settings 21 | import tool_terminal 22 | import tool_remotecommand 23 | import tool_sv 24 | import tool_ec 25 | import process_programs 26 | -------------------------------------------------------------------------------- /app/controllers/dialog.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/dialog.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import app, views 18 | 19 | from flask import render_template_string, request, json 20 | 21 | 22 | @app.route("/dialog/", methods=['POST', 'GET']) 23 | def renderDialog(dialog): 24 | if hasattr(views, dialog): 25 | dialogClass = getattr(views, dialog) 26 | 27 | if request.method == 'POST': 28 | dialogObject = dialogClass(postURL=request.url, **(request.args.to_dict())) 29 | if dialogObject.validate_on_submit(): 30 | dialogObject.save() 31 | return json.dumps("OK") 32 | 33 | return json.dumps(dialogObject.errors) 34 | else: 35 | dialogObject = dialogClass(postURL=request.url, **(request.args.to_dict())) 36 | dialogObject.load() 37 | return render_template_string(dialogObject.render()) 38 | else: 39 | return "Invalid dialog {}".format(dialog) 40 | -------------------------------------------------------------------------------- /app/controllers/generic.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/generic.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import app 18 | 19 | 20 | @app.route("/") 21 | def hello(): 22 | return "Hello World!" 23 | -------------------------------------------------------------------------------- /app/controllers/process_programs.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/process_programs.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import app, helpers 18 | from flask import render_template, json, request 19 | 20 | 21 | @app.route("/pp") 22 | def process_programs(): 23 | return render_template("process_programs.html") 24 | 25 | 26 | @app.route("/pp/scopes") 27 | def process_programs_scopes(): 28 | return json.dumps(helpers.getProcessProgramScopes()) 29 | 30 | 31 | @app.route("/pp/") 32 | def process_programs_scope(scope): 33 | return json.dumps(helpers.getProcessPrograms(scope)) 34 | 35 | 36 | @app.route("/pp//upload", methods=["POST"]) 37 | def process_program_upload(scope): 38 | helpers.processProgramUpload(scope, request.files['file']) 39 | return "OK" 40 | 41 | 42 | @app.route("/pp///copy/") 43 | def process_program_copy(scope, program, targetScope): 44 | helpers.processProgramCopy(scope, program, targetScope) 45 | return "OK" 46 | 47 | 48 | @app.route("/pp///move/") 49 | def process_program_move(scope, program, targetScope): 50 | helpers.processProgramMove(scope, program, targetScope) 51 | return "OK" 52 | 53 | @app.route("/pp///duplicate/") 54 | def process_program_duplicate(scope, program, newProgram): 55 | helpers.processProgramDuplicate(scope, program, newProgram) 56 | return "OK" 57 | 58 | 59 | @app.route("/pp///rename/") 60 | def process_program_rename(scope, program, newProgram): 61 | helpers.processProgramRename(scope, program, newProgram) 62 | return "OK" 63 | 64 | 65 | @app.route("/pp///remove") 66 | def process_program_remove(scope, program): 67 | helpers.processProgramRemove(scope, program) 68 | return "OK" 69 | -------------------------------------------------------------------------------- /app/controllers/tool_ec.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/tool_ec.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import app, helpers 18 | from flask import request 19 | from secsgem.secs.variables import SecsVarU4, SecsVarString 20 | 21 | 22 | @app.route("/tools//ec/", methods=['POST', 'GET']) 23 | def tool_ec(toolname, ecid): 24 | handler = helpers.connectionManager[toolname] 25 | if request.method == 'POST': 26 | if helpers.is_int(ecid): 27 | result = handler.request_ec(SecsVarU4(int(ecid))).data[0].value 28 | else: 29 | result = handler.request_ec(SecsVarString(ecid)).data[0].value 30 | result.set(request.form["value"]) 31 | 32 | if helpers.is_int(ecid): 33 | result = handler.set_ec(SecsVarU4(int(ecid)), result) 34 | else: 35 | result = handler.set_ec(SecsVarString(ecid), result) 36 | 37 | return str(result) 38 | else: 39 | if helpers.is_int(ecid): 40 | result = handler.request_ec(SecsVarU4(int(ecid)))[0].get() 41 | else: 42 | result = handler.request_ec(SecsVarString(ecid))[0].get() 43 | 44 | return str(result) 45 | -------------------------------------------------------------------------------- /app/controllers/tool_remotecommand.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/tool_remotecommand.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import app, models, helpers 18 | from flask import request, json 19 | 20 | 21 | @app.route("/tools//remotecommands") 22 | def tool_remotecommands(toolname): 23 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 24 | tool_connection = helpers.connectionManager[tool.name] 25 | 26 | return json.dumps(tool_connection.remote_commands.keys()) 27 | 28 | 29 | @app.route("/tools//remotecommand/") 30 | def tool_remotecommand_details(toolname, rcmd): 31 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 32 | tool_connection = helpers.connectionManager[tool.name] 33 | 34 | return json.dumps(tool_connection.remote_commands[rcmd]) 35 | 36 | 37 | @app.route("/tools//remotecommand//run") 38 | def tool_remotecommand_run(toolname, rcmd): 39 | handler = helpers.connectionManager[toolname] 40 | 41 | params = [] 42 | 43 | for param in request.args: 44 | params.append((param, request.args[param])) 45 | 46 | result = handler.send_remote_command(rcmd, params) 47 | 48 | HCACK = result.HCACK 49 | 50 | if HCACK == 0: 51 | return "OK" 52 | elif HCACK == 1: 53 | return "Invalid command (%d)" % (HCACK) 54 | elif HCACK == 2: 55 | return "Cannot do now (%d)" % (HCACK) 56 | elif HCACK == 3: 57 | return "Parameter error (%d)" % (HCACK) 58 | elif HCACK == 4: 59 | return "OK" 60 | elif HCACK == 5: 61 | return "Rejected, already in desired condition (%d)" % (HCACK) 62 | elif HCACK == 6: 63 | return "Invalid object (%d)" % (HCACK) 64 | else: 65 | return "Errorcode %d" % (HCACK) 66 | -------------------------------------------------------------------------------- /app/controllers/tool_settings.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/tool_settings.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | import logging 18 | 19 | from app import app, models, helpers 20 | from flask import request, json 21 | 22 | 23 | @app.route("/tools//settings", methods=['GET', 'POST']) 24 | def tool_settings(toolname): 25 | if request.method == 'GET': 26 | settings = {} 27 | 28 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 29 | 30 | settings["enabled"] = tool.enabled 31 | settings["type"] = tool.type 32 | settings["address"] = tool.address 33 | settings["port"] = tool.port 34 | settings["deviceID"] = tool.device_id 35 | settings["passive"] = tool.passive 36 | settings["processProgramScope"] = tool.process_program_scope 37 | 38 | return json.dumps(settings) 39 | elif request.method == 'POST': 40 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 41 | 42 | restartRequired = False 43 | 44 | formEnabled = (request.form["enabled"] == "true") 45 | formType = request.form["type"] 46 | formAddress = request.form["address"] 47 | formPort = request.form["port"] 48 | formDeviceID = request.form["deviceID"] 49 | formPassive = (request.form["passive"] == "true") 50 | formProcessProgramScope = request.form["processProgramScope"] 51 | 52 | if not formEnabled == tool.enabled: 53 | logging.info("tool_settings_update(" + toolname + "): Enabled changed, reconnect required") 54 | restartRequired = True 55 | if not formType == tool.type: 56 | logging.info("tool_settings_update(" + toolname + "): Type changed, reconnect required") 57 | restartRequired = True 58 | if not formAddress == tool.address: 59 | logging.info("tool_settings_update(" + toolname + "): Address changed, reconnect required") 60 | restartRequired = True 61 | if not int(formPort) == tool.port: 62 | logging.info("tool_settings_update(" + toolname + "): Port changed, reconnect required") 63 | restartRequired = True 64 | if not int(formDeviceID) == tool.device_id: 65 | logging.info("tool_settings_update(" + toolname + "): DeviceID changed, reconnect required") 66 | restartRequired = True 67 | if not formPassive == tool.passive: 68 | logging.info("tool_settings_update(" + toolname + "): Passive changed, reconnect required") 69 | restartRequired = True 70 | 71 | if restartRequired: 72 | if helpers.connectionManager.has_connection_to(tool.name): 73 | helpers.connectionManager.remove_peer(tool.name, tool.address, tool.port) 74 | 75 | tool.enabled = formEnabled 76 | tool.type = formType 77 | tool.address = formAddress 78 | tool.port = formPort 79 | tool.device_id = formDeviceID 80 | tool.passive = formPassive 81 | tool.process_program_scope = formProcessProgramScope 82 | 83 | tool.store() 84 | 85 | if restartRequired: 86 | helpers.addTool(tool) 87 | 88 | return "OK" 89 | else: 90 | return "NOGET" 91 | 92 | 93 | @app.route("/tools//settings/collectionevents/") 94 | def tool_settings_collectionevents(toolname): 95 | collectionEvents = [] 96 | 97 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 98 | tool_connection = helpers.connectionManager[tool.name] 99 | 100 | for collection_event in tool.collection_events: 101 | collectionEvent = {} 102 | collectionEvent["ID"] = collection_event.ceid 103 | if collection_event.ceid in tool_connection.collection_events: 104 | collectionEvent["name"] = tool_connection.collection_events[collection_event.ceid]["name"] 105 | else: 106 | collectionEvent["name"] = str(collection_event.ceid) 107 | 108 | dataValues = [] 109 | for dv in collection_event.dvs: 110 | dataValue = {} 111 | dataValue["ID"] = dv.dvid 112 | 113 | if dv.dvid in tool_connection.data_values: 114 | dataValue["name"] = tool_connection.data_values[dv.dvid]["name"] 115 | else: 116 | dataValue["name"] = str(dv.dvid) 117 | 118 | dataValues.append(dataValue) 119 | 120 | collectionEvent["DVIDs"] = dataValues 121 | 122 | collectionEvents.append(collectionEvent) 123 | 124 | return json.dumps(collectionEvents) 125 | 126 | 127 | @app.route("/tools//settings/collectionevents/available") 128 | def tool_settings_collectionevent_available(toolname): 129 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 130 | tool_connection = helpers.connectionManager[tool.name] 131 | 132 | ceids = [] 133 | for ceid in tool_connection.collection_events: 134 | ce = {"id": str(ceid)} 135 | ce.update(tool_connection.collection_events[ceid]) 136 | ceids.append(ce) 137 | 138 | return json.dumps(ceids) 139 | 140 | 141 | @app.route("/tools//settings/collectionevents/create", methods=["POST"]) 142 | def tool_settings_collectionevent_create(toolname): 143 | if request.method == 'POST': 144 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 145 | 146 | ceid = request.form["ceid"] 147 | 148 | for collection_event in tool.collection_events: 149 | if int(collection_event.ceid) == int(ceid): 150 | return "EXISTING" 151 | 152 | ce = models.CollectionEvent(ceid=ceid) 153 | ce.tool = tool 154 | 155 | ce.create() 156 | 157 | return "OK" 158 | else: 159 | return "ILLEGAL_REQUEST " + request.method 160 | 161 | 162 | @app.route("/tools//settings/collectionevents//delete") 163 | def tool_settings_collectionevent_delete(toolname, ceid): 164 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 165 | 166 | for collection_event in tool.collection_events: 167 | if int(collection_event.ceid) == int(ceid): 168 | collection_event.delete() 169 | return "OK" 170 | 171 | return "NOTFOUND" 172 | 173 | 174 | @app.route("/tools//settings/collectionevents//dataValue/available") 175 | def tool_settings_datavalue_available(toolname, ceid): 176 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 177 | tool_connection = helpers.connectionManager[tool.name] 178 | 179 | dvids = [] 180 | for ceidIterator in tool_connection.collection_events: 181 | if int(ceidIterator) == int(ceid): 182 | for dvid in tool_connection.collection_events[ceidIterator]["dvids"]: 183 | dv = {"id": str(dvid)} 184 | dv.update(tool_connection.data_values[dvid]) 185 | dvids.append(dv) 186 | 187 | return json.dumps(dvids) 188 | 189 | 190 | @app.route("/tools//settings/collectionevents//dataValue/create", methods=["POST"]) 191 | def tool_settings_collectionevent_datavalue_create(toolname, ceid): 192 | if request.method == 'POST': 193 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 194 | 195 | dvid = request.form["dvid"] 196 | 197 | for collection_event in tool.collection_events: 198 | if int(collection_event.ceid) == int(ceid): 199 | for dv in collection_event.dvs: 200 | if int(dv.dvid) == int(dvid): 201 | return "EXISTING" 202 | 203 | dv = models.DataValue(dvid=dvid, ce_id=collection_event.id) 204 | 205 | dv.create() 206 | 207 | return "OK" 208 | 209 | return "NOCE" 210 | else: 211 | return "ILLEGAL_REQUEST " + request.method 212 | 213 | 214 | @app.route("/tools//settings/collectionevents//dataValue//delete") 215 | def tool_settings_collectionevent_datavalue_delete(toolname, ceid, dvid): 216 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 217 | 218 | for collection_event in tool.collection_events: 219 | if int(collection_event.ceid) == int(ceid): 220 | for dv in collection_event.dvs: 221 | if int(dv.dvid) == int(dvid): 222 | dv.delete() 223 | return "OK" 224 | 225 | return "NOTFOUND" 226 | -------------------------------------------------------------------------------- /app/controllers/tool_sv.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/tool_sv.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import app, helpers 18 | from secsgem.secs.variables import SecsVarU4, SecsVarString 19 | 20 | 21 | @app.route("/tools//sv/") 22 | def tool_sv(toolname, svid): 23 | handler = helpers.connectionManager[toolname] 24 | if helpers.is_int(svid): 25 | result = handler.request_sv(SecsVarU4(int(svid))).get() 26 | else: 27 | result = handler.request_sv(SecsVarString(svid)).get() 28 | 29 | if isinstance(result, list): 30 | return str(result) 31 | 32 | return str(result) 33 | -------------------------------------------------------------------------------- /app/controllers/tool_terminal.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/tool_terminal.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import app, helpers 18 | from flask import request, abort 19 | 20 | 21 | @app.route("/tools//terminal/", methods=['POST', 'GET']) 22 | def tool_terminal(toolname, TID): 23 | handler = helpers.connectionManager[toolname] 24 | 25 | if handler is None: 26 | abort(404) 27 | 28 | if request.method == 'POST': 29 | handler.send_equipment_terminal(int(TID), request.form["text"].encode('ascii','replace')) 30 | 31 | return "OK" 32 | -------------------------------------------------------------------------------- /app/controllers/tools.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/controllers/tools.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | import time 18 | 19 | from app import app, models, helpers, toolHandlers 20 | from flask import render_template, json 21 | 22 | 23 | @app.route("/tools/") 24 | def tools_list(): 25 | return render_template("tools_list.html", tools=models.Tool.query.all(), connectionManager = helpers.connectionManager) 26 | 27 | 28 | @app.route("/tools/") 29 | def tool_detail(toolname): 30 | handler = helpers.connectionManager[toolname] 31 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 32 | 33 | if handler and handler.connection.connected: 34 | SVs = sorted(handler.list_svs(), key=lambda SV: SV.SVID.get()) 35 | ECs = sorted(handler.list_ecs(), key=lambda EC: EC.ECID.get()) 36 | else: 37 | SVs = {} 38 | ECs = {} 39 | 40 | return render_template("tool_detail.html", handler=handler, tool=tool, modules=toolHandlers, svids=SVs, ecids=ECs) 41 | 42 | 43 | @app.route("/tools//restart") 44 | def tool_restart(toolname): 45 | tool = models.Tool.query.filter(models.Tool.name == toolname).first() 46 | 47 | if helpers.connectionManager.has_connection_to(tool.name): 48 | helpers.connectionManager.remove_peer(tool.name, tool.address, tool.port) 49 | 50 | helpers.addTool(tool) 51 | 52 | return "OK" 53 | 54 | 55 | @app.route("/tools//comet/") 56 | def tool_comet(toolname, queue): 57 | handler = helpers.connectionManager[toolname] 58 | 59 | if not helpers.queueExists(queue): 60 | return json.dumps({}, default=helpers.jsonEncoder, encoding='latin1') 61 | 62 | if handler is None: 63 | time.sleep(2) 64 | return json.dumps([]) 65 | 66 | events = helpers.waitForEvents(queue) 67 | return json.dumps(events, default=helpers.jsonEncoder, encoding='latin1') 68 | -------------------------------------------------------------------------------- /app/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/helpers/__init__.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from json_encoder import jsonEncoder 18 | from plugins import getToolType, loadModules 19 | from secs import connectionManager, addTool, waitForEvents, stop, queueExists, _onEvent, is_int 20 | from processprograms import getProcessProgram, storeProcessProgram, getProcessProgramScopes, getProcessPrograms, processProgramCopy, processProgramMove, processProgramDuplicate, processProgramRename, processProgramRemove, processProgramUpload 21 | import contextprocessor 22 | 23 | def stop(): 24 | secs.stop() 25 | -------------------------------------------------------------------------------- /app/helpers/contextprocessor.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/helpers/contextprocessor.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import app 18 | 19 | 20 | @app.context_processor 21 | def utility_processor(): 22 | def handlebar_tag(tag): 23 | return '{{' + tag + '}}' 24 | 25 | def handlebar_tag_raw(tag): 26 | return '{{{' + tag + '}}}' 27 | return dict(handlebar_tag=handlebar_tag, handlebar_tag_raw=handlebar_tag_raw) 28 | -------------------------------------------------------------------------------- /app/helpers/json_encoder.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/helpers/json_encoder.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | 18 | def jsonEncoder(o): 19 | if isinstance(o, set): 20 | return list(o) 21 | 22 | if hasattr(o, "_serialize_data") and callable(getattr(o, "_serialize_data")): 23 | return getattr(o, "_serialize_data")() 24 | else: 25 | return o.__dict__ 26 | -------------------------------------------------------------------------------- /app/helpers/plugins.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/helpers/plugins.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | import sys 18 | import os 19 | 20 | import secsgem 21 | 22 | 23 | def loadModules(path): 24 | modules = [] 25 | sys.path.append(path) 26 | for name in os.listdir(path): 27 | if name.endswith(".py"): 28 | # strip the extension 29 | module = name[:-3] 30 | modules.append((module, module)) 31 | # set the module name in the current global name space: 32 | globals()[module] = __import__(module) 33 | 34 | return modules 35 | 36 | 37 | def getToolType(tool): 38 | # set default handler 39 | toolType = secsgem.SecsHandler 40 | 41 | # check if module loaded for tooltype 42 | if tool.type in globals(): 43 | # get loaded module 44 | module = globals()[tool.type] 45 | # check if module has class for tooltype 46 | if tool.type in module.__dict__: 47 | # get loaded class 48 | classType = module.__dict__[tool.type] 49 | # check if class is correct type 50 | if issubclass(classType, secsgem.SecsHandler): 51 | toolType = classType 52 | 53 | return toolType 54 | -------------------------------------------------------------------------------- /app/helpers/processprograms.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/helpers/processprograms.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | import os 18 | import shutil 19 | 20 | from werkzeug import secure_filename 21 | 22 | def getProcessProgramScopes(): 23 | return next(os.walk("data/processprogram/"))[1] 24 | 25 | 26 | def getProcessPrograms(processProgramScope): 27 | return next(os.walk("data/processprogram/" + processProgramScope))[2] 28 | 29 | 30 | def processProgramFilename(processProgramScope, processProgramID): 31 | return os.path.abspath("data/processprogram/{}/{}".format(processProgramScope, processProgramID)) 32 | 33 | 34 | def getProcessProgram(processProgramScope, processProgramID): 35 | filename = processProgramFilename(processProgramScope, processProgramID) 36 | 37 | if not os.path.exists(filename): 38 | return None 39 | 40 | with open(filename, "r") as file: 41 | data = file.read() 42 | 43 | return data 44 | 45 | 46 | def storeProcessProgram(processProgramScope, processProgramID, processProgramData): 47 | filename = processProgramFilename(processProgramScope, processProgramID) 48 | filepath = os.path.dirname(filename) 49 | 50 | if not os.path.exists(filepath): 51 | os.makedirs(filepath) 52 | 53 | with open(filename, "w") as file: 54 | file.write(processProgramData) 55 | 56 | 57 | def processProgramCopy(processProgramScopeSource, processProgramID, processProgramScopeTarget): 58 | return shutil.copy2(processProgramFilename(processProgramScopeSource, processProgramID), processProgramFilename(processProgramScopeTarget, processProgramID)) 59 | 60 | 61 | def processProgramMove(processProgramScopeSource, processProgramID, processProgramScopeTarget): 62 | return shutil.move(processProgramFilename(processProgramScopeSource, processProgramID), processProgramFilename(processProgramScopeTarget, processProgramID)) 63 | 64 | 65 | def processProgramRename(processProgramScopeSource, processProgramID, processProgramIDNew): 66 | return shutil.move(processProgramFilename(processProgramScopeSource, processProgramID), processProgramFilename(processProgramScopeSource, processProgramIDNew)) 67 | 68 | 69 | def processProgramDuplicate(processProgramScopeSource, processProgramID, processProgramIDNew): 70 | return shutil.copy2(processProgramFilename(processProgramScopeSource, processProgramID), processProgramFilename(processProgramScopeSource, processProgramIDNew)) 71 | 72 | 73 | def processProgramRemove(processProgramScopeSource, processProgramID): 74 | return os.remove(processProgramFilename(processProgramScopeSource, processProgramID)) 75 | 76 | 77 | def processProgramUpload(processProgramScope, file): 78 | filename = processProgramFilename(processProgramScope, secure_filename(file.filename)) 79 | file.save(filename) 80 | return True 81 | -------------------------------------------------------------------------------- /app/helpers/secs.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/helpers/secs.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | import threading 18 | 19 | import secsgem 20 | 21 | from app.helpers import getToolType 22 | 23 | events = {} 24 | eventsLock = threading.Lock() 25 | eventNotify = {} 26 | 27 | 28 | def ceSetup(event, data): 29 | handler = data['handler'] 30 | 31 | handler.clear_collection_events() 32 | 33 | for collectionEvent in handler.registeredCollectionEvents: 34 | ceid = collectionEvent[0] 35 | dvids = collectionEvent[1] 36 | handler.subscribe_collection_event(ceid, dvids) 37 | if ceid not in handler.collection_events: 38 | print "configured ceid %d not found" % (ceid) 39 | 40 | 41 | def _onEvent(eventName, params): 42 | eventsLock.acquire() 43 | 44 | params["event"] = eventName 45 | for queue in events: 46 | events[queue].append(params) 47 | 48 | eventNotify[queue].set() 49 | 50 | eventsLock.release() 51 | 52 | print "_onEvent:", eventName, "params:", params 53 | 54 | 55 | def queueExists(queue): 56 | queueAvailable = queue in events 57 | 58 | if not queueAvailable: 59 | events[queue] = [] 60 | eventNotify[queue] = threading.Event() 61 | 62 | return queueAvailable 63 | 64 | 65 | def waitForEvents(queue): 66 | """Wait for events in the event list and return 67 | 68 | :returns: currently available events 69 | :rtype: list 70 | """ 71 | eventsLock.acquire() 72 | 73 | if queue not in events: 74 | events[queue] = [] 75 | eventNotify[queue] = threading.Event() 76 | 77 | if not events[queue]: 78 | eventsLock.release() 79 | 80 | while not eventNotify[queue].wait(1): 81 | pass 82 | 83 | eventsLock.acquire() 84 | eventNotify[queue].clear() 85 | 86 | result = list(events[queue]) 87 | events[queue] = [] 88 | 89 | eventsLock.release() 90 | 91 | return result 92 | 93 | # setup event handler 94 | eventHandler = secsgem.EventHandler(events={'handler_communicating': ceSetup}, generic_handler=_onEvent) 95 | 96 | # setup connection manager 97 | connectionManager = secsgem.HsmsConnectionManager(event_handler=eventHandler) 98 | 99 | 100 | def addTool(tool): 101 | # skip disabled tools 102 | if not tool.enabled: 103 | return 104 | 105 | toolType = getToolType(tool) 106 | 107 | # add configured tool to the connectionmanager 108 | handler = connectionManager.add_peer(tool.name, tool.address, tool.port, tool.passive, tool.device_id, toolType) 109 | 110 | ceids = [] 111 | for collection_event in tool.collection_events: 112 | dvids = [] 113 | for data_value in collection_event.dvs: 114 | dvids.append(data_value.dvid) 115 | 116 | ceids.append((collection_event.ceid, dvids)) 117 | 118 | handler.registeredCollectionEvents = ceids 119 | 120 | 121 | def stop(): 122 | connectionManager.stop() 123 | 124 | def is_int(value): 125 | try: 126 | int(value) 127 | return True 128 | except ValueError: 129 | return False 130 | -------------------------------------------------------------------------------- /app/models/G85_map.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # G85_map.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | import logging 18 | 19 | from xml.etree import ElementTree 20 | 21 | 22 | def int16(value): 23 | return int(value, 16) 24 | 25 | 26 | class G85Object(object): 27 | def __repr__(self): 28 | return self.__class__.__name__ + ": " + str(self.__dict__) 29 | 30 | def propertyFromMap(self, map, propertyName, converter, mandatory=True, default=None): 31 | if propertyName in map.attrib: 32 | setattr(self, propertyName, converter(map.attrib[propertyName])) 33 | else: 34 | if mandatory: 35 | logging.warning("{} is missing mandatory field {}".format(self.__class__.__name__, propertyName)) 36 | 37 | setattr(self, propertyName, default) 38 | 39 | @staticmethod 40 | def elementName(name): 41 | return "{http://www.semi.org}" + name 42 | 43 | 44 | class G85Row(G85Object): 45 | def __repr__(self): 46 | return self.__class__.__name__ + ": " + str(len(self.cols)) + "cols" 47 | 48 | @classmethod 49 | def fromXML(cls, row): 50 | newRow = cls() 51 | 52 | newRow.rowData = row.text 53 | 54 | return newRow 55 | 56 | def unpack(self, binType): 57 | if binType == "ASCII": 58 | self.cols = self.rowData 59 | elif binType == "Decimal": 60 | self.cols = [int(c) for c in self.rowData.split(" ")] 61 | elif binType == "HexaDecimal": 62 | self.cols = [int(c, 16) for c in [self.rowData[i:i+2] for i in range(0, len(self.rowData), 2)]] 63 | elif binType == "Integer2": 64 | self.cols = [int(c, 16) for c in [self.rowData[i:i+4] for i in range(0, len(self.rowData), 4)]] 65 | 66 | 67 | class G85Data(G85Object): 68 | @classmethod 69 | def fromXML(cls, data): 70 | newData = cls() 71 | 72 | newData.propertyFromMap(data, "MapName", str, False, "") 73 | newData.propertyFromMap(data, "MapVersion", str, False, "") 74 | 75 | newData.rows = [] 76 | 77 | for row in data.findall(newData.elementName("Row")): 78 | newData.rows.append(G85Row.fromXML(row)) 79 | 80 | return newData 81 | 82 | 83 | class G85ReferenceDevice(G85Object): 84 | @classmethod 85 | def fromXML(cls, referenceDevice): 86 | newReferenceDevice = cls() 87 | 88 | newReferenceDevice.propertyFromMap(referenceDevice, "ReferenceDeviceX", int, True) 89 | newReferenceDevice.propertyFromMap(referenceDevice, "ReferenceDeviceY", int, True) 90 | newReferenceDevice.propertyFromMap(referenceDevice, "RefDevicePosX", float, False, 0.0) 91 | newReferenceDevice.propertyFromMap(referenceDevice, "RefDevicePosY", float, False, 0.0) 92 | 93 | return newReferenceDevice 94 | 95 | 96 | class G85Bin(G85Object): 97 | @classmethod 98 | def fromXML(cls, bin): 99 | newBin = cls() 100 | 101 | newBin.propertyFromMap(bin, "BinCode", int16, False, 0) 102 | newBin.propertyFromMap(bin, "BinCount", int, False, 0) 103 | newBin.propertyFromMap(bin, "BinQuality", str, False, "") 104 | newBin.propertyFromMap(bin, "BinDescription", str, False, "") 105 | 106 | return newBin 107 | 108 | 109 | class G85Device(G85Object): 110 | @classmethod 111 | def fromXML(cls, device): 112 | newDevice = cls() 113 | 114 | newDevice.data = G85Data.fromXML(device.find(newDevice.elementName("Data"))) 115 | 116 | newDevice.propertyFromMap(device, "BinType", str, True, "ASCII") 117 | newDevice.propertyFromMap(device, "OriginLocation", int, True, 0) 118 | 119 | for row in newDevice.data.rows: 120 | row.unpack(newDevice.BinType) 121 | 122 | newDevice.propertyFromMap(device, "ProductId", str, False, "") 123 | newDevice.propertyFromMap(device, "LotId", str, False, "") 124 | newDevice.propertyFromMap(device, "Orientation", int) 125 | newDevice.propertyFromMap(device, "WaferSize", int, False, 0) 126 | newDevice.propertyFromMap(device, "DeviceSizeX", float, False, 0.0) 127 | newDevice.propertyFromMap(device, "DeviceSizeY", float, False, 0.0) 128 | newDevice.propertyFromMap(device, "StepSizeX", float, False, 0.0) 129 | newDevice.propertyFromMap(device, "StepSizeY", float, False, 0.0) 130 | newDevice.propertyFromMap(device, "MagazineId", str, False, "") 131 | newDevice.propertyFromMap(device, "Rows", int, False, len(newDevice.data.rows)) 132 | newDevice.propertyFromMap(device, "Columns", int, False, len(newDevice.data.rows[0].cols)) 133 | newDevice.propertyFromMap(device, "FrameId", str, False, "") 134 | newDevice.propertyFromMap(device, "NullBin", int16) 135 | newDevice.propertyFromMap(device, "SupplierName", str, False, "") 136 | newDevice.propertyFromMap(device, "CreateData", str, False, "") 137 | newDevice.propertyFromMap(device, "LastModified", str, False, "") 138 | newDevice.propertyFromMap(device, "Status", str, False, "") 139 | newDevice.propertyFromMap(device, "SlotNumber", int, False, 0) 140 | newDevice.propertyFromMap(device, "SubstrateNumber", int, False, 0) 141 | newDevice.propertyFromMap(device, "GoodDevices", int, False, 0) 142 | 143 | newDevice.referenceDevices = [] 144 | for referenceDevice in device.findall(newDevice.elementName("ReferenceDevice")): 145 | newDevice.referenceDevices.append(G85ReferenceDevice.fromXML(referenceDevice)) 146 | 147 | newDevice.bins = [] 148 | for bin in device.findall(newDevice.elementName("Bin")): 149 | newDevice.bins.append(G85Bin.fromXML(bin)) 150 | 151 | return newDevice 152 | 153 | 154 | class G85Map(G85Object): 155 | @classmethod 156 | def fromXML(cls, map): 157 | newMap = cls() 158 | 159 | newMap.device = G85Device.fromXML(map.find(newMap.elementName("Device"))) 160 | 161 | newMap.propertyFromMap(map, "SubstrateType", str) 162 | newMap.propertyFromMap(map, "SubstrateId", str) 163 | newMap.propertyFromMap(map, "FormatRevision", str, False, "G85-0703") 164 | 165 | return newMap 166 | 167 | @classmethod 168 | def load(cls, filename, waferId): 169 | document = ElementTree.parse(filename) 170 | root = document.getroot() 171 | 172 | if root.tag == G85Object.elementName("Map"): 173 | if (root.attrib["SubstrateId"] == waferId): 174 | return cls.fromXML(root) 175 | elif root.tag == "Maps": 176 | for map in root.findall('Map'): 177 | if (map.attrib["SubstrateId"] == waferId): 178 | return cls.fromXML(map) 179 | 180 | logging.warning("Map {} not found in {}".format(waferId, filename)) 181 | return None 182 | -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/models/__init__.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import db 18 | 19 | from tool import Tool 20 | from collection_event import CollectionEvent 21 | from data_value import DataValue 22 | 23 | from G85_map import G85Map 24 | 25 | #from log_entry import LogEntry -------------------------------------------------------------------------------- /app/models/collection_event.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/models/collection_event.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import db 18 | 19 | 20 | class CollectionEvent(db.Model): 21 | id = db.Column(db.Integer, primary_key=True) 22 | tool_id = db.Column(db.Integer, db.ForeignKey('tool.id'), nullable=False) 23 | ceid = db.Column(db.Integer) 24 | dvs = db.relationship('DataValue', backref='collection_event', lazy='dynamic', cascade="all, delete, delete-orphan") 25 | 26 | def __repr__(self): 27 | return str(self.ceid) 28 | 29 | def create(self): 30 | db.session.add(self) 31 | db.session.commit() 32 | 33 | def delete(self): 34 | db.session.delete(self) 35 | db.session.commit() 36 | -------------------------------------------------------------------------------- /app/models/data_value.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/models/data_value.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import db 18 | 19 | 20 | class DataValue(db.Model): 21 | id = db.Column(db.Integer, primary_key=True) 22 | ce_id = db.Column(db.Integer, db.ForeignKey('collection_event.id'), nullable=False) 23 | dvid = db.Column(db.Integer) 24 | 25 | def __repr__(self): 26 | return str(self.dvid) 27 | 28 | def create(self): 29 | db.session.add(self) 30 | db.session.commit() 31 | 32 | def delete(self): 33 | db.session.delete(self) 34 | db.session.commit() 35 | -------------------------------------------------------------------------------- /app/models/log_entry.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/models/log_entry.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import db 18 | 19 | #class LogEntry(db.Model): 20 | # id = db.Column(db.Integer, primary_key=True) 21 | # tool_id = db.Column(db.Integer, db.ForeignKey('tool.id'), nullable=False) 22 | # timestamp = db.Column(db.DateTime()) 23 | # severity = db.Column(db.Integer) 24 | # group = db.Column(db.String(20)) 25 | # data = db.Column(db.Text()) 26 | # 27 | # def __repr__(self): 28 | # return str(self.id) 29 | -------------------------------------------------------------------------------- /app/models/tool.py: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # app/models/tool.py 3 | # 4 | # (c) Copyright 2015, Benjamin Parzella. All rights reserved. 5 | # 6 | # This library is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation; either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | ##################################################################### 16 | 17 | from app import db 18 | 19 | 20 | class Tool(db.Model): 21 | id = db.Column(db.Integer, primary_key=True) 22 | name = db.Column(db.String(80), unique=True) 23 | type = db.Column(db.String(80)) 24 | passive = db.Column(db.Boolean) 25 | address = db.Column(db.String(80)) 26 | port = db.Column(db.Integer) 27 | device_id = db.Column(db.Integer) 28 | enabled = db.Column(db.Boolean) 29 | process_program_scope = db.Column(db.String(80)) 30 | collection_events = db.relationship('CollectionEvent', backref='tool', lazy='dynamic', cascade="all, delete, delete-orphan") 31 | # log_entries = db.relationship('LogEntry', backref='tool', lazy='dynamic', cascade="all, delete, delete-orphan") 32 | 33 | def __repr__(self): 34 | return self.name 35 | 36 | def store(self): 37 | db.session.commit() 38 | -------------------------------------------------------------------------------- /app/static/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.2 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default, 8 | .btn-primary, 9 | .btn-success, 10 | .btn-info, 11 | .btn-warning, 12 | .btn-danger { 13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 16 | } 17 | .btn-default:active, 18 | .btn-primary:active, 19 | .btn-success:active, 20 | .btn-info:active, 21 | .btn-warning:active, 22 | .btn-danger:active, 23 | .btn-default.active, 24 | .btn-primary.active, 25 | .btn-success.active, 26 | .btn-info.active, 27 | .btn-warning.active, 28 | .btn-danger.active { 29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 31 | } 32 | .btn-default .badge, 33 | .btn-primary .badge, 34 | .btn-success .badge, 35 | .btn-info .badge, 36 | .btn-warning .badge, 37 | .btn-danger .badge { 38 | text-shadow: none; 39 | } 40 | .btn:active, 41 | .btn.active { 42 | background-image: none; 43 | } 44 | .btn-default { 45 | text-shadow: 0 1px 0 #fff; 46 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 47 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 48 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 49 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 50 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 51 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 52 | background-repeat: repeat-x; 53 | border-color: #dbdbdb; 54 | border-color: #ccc; 55 | } 56 | .btn-default:hover, 57 | .btn-default:focus { 58 | background-color: #e0e0e0; 59 | background-position: 0 -15px; 60 | } 61 | .btn-default:active, 62 | .btn-default.active { 63 | background-color: #e0e0e0; 64 | border-color: #dbdbdb; 65 | } 66 | .btn-default.disabled, 67 | .btn-default:disabled, 68 | .btn-default[disabled] { 69 | background-color: #e0e0e0; 70 | background-image: none; 71 | } 72 | .btn-primary { 73 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 74 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 75 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 76 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 77 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 78 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 79 | background-repeat: repeat-x; 80 | border-color: #245580; 81 | } 82 | .btn-primary:hover, 83 | .btn-primary:focus { 84 | background-color: #265a88; 85 | background-position: 0 -15px; 86 | } 87 | .btn-primary:active, 88 | .btn-primary.active { 89 | background-color: #265a88; 90 | border-color: #245580; 91 | } 92 | .btn-primary.disabled, 93 | .btn-primary:disabled, 94 | .btn-primary[disabled] { 95 | background-color: #265a88; 96 | background-image: none; 97 | } 98 | .btn-success { 99 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 100 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 101 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 102 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 103 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 104 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 105 | background-repeat: repeat-x; 106 | border-color: #3e8f3e; 107 | } 108 | .btn-success:hover, 109 | .btn-success:focus { 110 | background-color: #419641; 111 | background-position: 0 -15px; 112 | } 113 | .btn-success:active, 114 | .btn-success.active { 115 | background-color: #419641; 116 | border-color: #3e8f3e; 117 | } 118 | .btn-success.disabled, 119 | .btn-success:disabled, 120 | .btn-success[disabled] { 121 | background-color: #419641; 122 | background-image: none; 123 | } 124 | .btn-info { 125 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 126 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 127 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 128 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 129 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 130 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 131 | background-repeat: repeat-x; 132 | border-color: #28a4c9; 133 | } 134 | .btn-info:hover, 135 | .btn-info:focus { 136 | background-color: #2aabd2; 137 | background-position: 0 -15px; 138 | } 139 | .btn-info:active, 140 | .btn-info.active { 141 | background-color: #2aabd2; 142 | border-color: #28a4c9; 143 | } 144 | .btn-info.disabled, 145 | .btn-info:disabled, 146 | .btn-info[disabled] { 147 | background-color: #2aabd2; 148 | background-image: none; 149 | } 150 | .btn-warning { 151 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 152 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 153 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 154 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 155 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 156 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 157 | background-repeat: repeat-x; 158 | border-color: #e38d13; 159 | } 160 | .btn-warning:hover, 161 | .btn-warning:focus { 162 | background-color: #eb9316; 163 | background-position: 0 -15px; 164 | } 165 | .btn-warning:active, 166 | .btn-warning.active { 167 | background-color: #eb9316; 168 | border-color: #e38d13; 169 | } 170 | .btn-warning.disabled, 171 | .btn-warning:disabled, 172 | .btn-warning[disabled] { 173 | background-color: #eb9316; 174 | background-image: none; 175 | } 176 | .btn-danger { 177 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 178 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 179 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 180 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 181 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 182 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 183 | background-repeat: repeat-x; 184 | border-color: #b92c28; 185 | } 186 | .btn-danger:hover, 187 | .btn-danger:focus { 188 | background-color: #c12e2a; 189 | background-position: 0 -15px; 190 | } 191 | .btn-danger:active, 192 | .btn-danger.active { 193 | background-color: #c12e2a; 194 | border-color: #b92c28; 195 | } 196 | .btn-danger.disabled, 197 | .btn-danger:disabled, 198 | .btn-danger[disabled] { 199 | background-color: #c12e2a; 200 | background-image: none; 201 | } 202 | .thumbnail, 203 | .img-thumbnail { 204 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 205 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 206 | } 207 | .dropdown-menu > li > a:hover, 208 | .dropdown-menu > li > a:focus { 209 | background-color: #e8e8e8; 210 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 211 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 212 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 213 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 214 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 215 | background-repeat: repeat-x; 216 | } 217 | .dropdown-menu > .active > a, 218 | .dropdown-menu > .active > a:hover, 219 | .dropdown-menu > .active > a:focus { 220 | background-color: #2e6da4; 221 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 222 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 223 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 224 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 225 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 226 | background-repeat: repeat-x; 227 | } 228 | .navbar-default { 229 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 230 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 231 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 232 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 233 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 234 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 235 | background-repeat: repeat-x; 236 | border-radius: 4px; 237 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 238 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 239 | } 240 | .navbar-default .navbar-nav > .open > a, 241 | .navbar-default .navbar-nav > .active > a { 242 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 243 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 244 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 245 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 246 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 247 | background-repeat: repeat-x; 248 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 249 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 250 | } 251 | .navbar-brand, 252 | .navbar-nav > li > a { 253 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 254 | } 255 | .navbar-inverse { 256 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 257 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 258 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 259 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 260 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 261 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 262 | background-repeat: repeat-x; 263 | } 264 | .navbar-inverse .navbar-nav > .open > a, 265 | .navbar-inverse .navbar-nav > .active > a { 266 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 267 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 268 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 269 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 270 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 271 | background-repeat: repeat-x; 272 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 273 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 274 | } 275 | .navbar-inverse .navbar-brand, 276 | .navbar-inverse .navbar-nav > li > a { 277 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 278 | } 279 | .navbar-static-top, 280 | .navbar-fixed-top, 281 | .navbar-fixed-bottom { 282 | border-radius: 0; 283 | } 284 | @media (max-width: 767px) { 285 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 286 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 287 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 288 | color: #fff; 289 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 290 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 291 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 292 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 293 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 294 | background-repeat: repeat-x; 295 | } 296 | } 297 | .alert { 298 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 299 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 300 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 301 | } 302 | .alert-success { 303 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 304 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 305 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 306 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 307 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 308 | background-repeat: repeat-x; 309 | border-color: #b2dba1; 310 | } 311 | .alert-info { 312 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 313 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 314 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 315 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 316 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 317 | background-repeat: repeat-x; 318 | border-color: #9acfea; 319 | } 320 | .alert-warning { 321 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 322 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 323 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 324 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 325 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 326 | background-repeat: repeat-x; 327 | border-color: #f5e79e; 328 | } 329 | .alert-danger { 330 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 331 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 332 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 333 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 334 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 335 | background-repeat: repeat-x; 336 | border-color: #dca7a7; 337 | } 338 | .progress { 339 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 340 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 342 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 344 | background-repeat: repeat-x; 345 | } 346 | .progress-bar { 347 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 348 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 349 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 350 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 351 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 352 | background-repeat: repeat-x; 353 | } 354 | .progress-bar-success { 355 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 356 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 357 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 358 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 359 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 360 | background-repeat: repeat-x; 361 | } 362 | .progress-bar-info { 363 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 364 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 365 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 366 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 367 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 368 | background-repeat: repeat-x; 369 | } 370 | .progress-bar-warning { 371 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 372 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 373 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 374 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 375 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 376 | background-repeat: repeat-x; 377 | } 378 | .progress-bar-danger { 379 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 380 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 381 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 382 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 383 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 384 | background-repeat: repeat-x; 385 | } 386 | .progress-bar-striped { 387 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 388 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 389 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 390 | } 391 | .list-group { 392 | border-radius: 4px; 393 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 394 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 395 | } 396 | .list-group-item.active, 397 | .list-group-item.active:hover, 398 | .list-group-item.active:focus { 399 | text-shadow: 0 -1px 0 #286090; 400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 405 | background-repeat: repeat-x; 406 | border-color: #2b669a; 407 | } 408 | .list-group-item.active .badge, 409 | .list-group-item.active:hover .badge, 410 | .list-group-item.active:focus .badge { 411 | text-shadow: none; 412 | } 413 | .panel { 414 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 415 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 416 | } 417 | .panel-default > .panel-heading { 418 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 419 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 420 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 421 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 422 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 423 | background-repeat: repeat-x; 424 | } 425 | .panel-primary > .panel-heading { 426 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 427 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 428 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 429 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 430 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 431 | background-repeat: repeat-x; 432 | } 433 | .panel-success > .panel-heading { 434 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 435 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 436 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 437 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 438 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 439 | background-repeat: repeat-x; 440 | } 441 | .panel-info > .panel-heading { 442 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 443 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 444 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 445 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 446 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 447 | background-repeat: repeat-x; 448 | } 449 | .panel-warning > .panel-heading { 450 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 451 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 453 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 455 | background-repeat: repeat-x; 456 | } 457 | .panel-danger > .panel-heading { 458 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 459 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 461 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 463 | background-repeat: repeat-x; 464 | } 465 | .well { 466 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 467 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 469 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 471 | background-repeat: repeat-x; 472 | border-color: #dcdcdc; 473 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 474 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 475 | } 476 | /*# sourceMappingURL=bootstrap-theme.css.map */ 477 | -------------------------------------------------------------------------------- /app/static/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.2 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /app/static/css/style.css: -------------------------------------------------------------------------------- 1 | #overlay { 2 | visibility: hidden; 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | z-index: 10; 9 | background-color: rgba(0,0,0,0.5); 10 | } 11 | 12 | #overlay_text { 13 | font-size: 32px; 14 | position: relative; 15 | top: 50%; 16 | text-align: center; 17 | transform: translateY(-50%); 18 | color: #FFFFFF; 19 | } 20 | 21 | .twitter-typeahead{ 22 | width:100%; 23 | } 24 | 25 | .twitter-typeahead .tt-query, 26 | .twitter-typeahead .tt-hint { 27 | margin-bottom: 0; 28 | } 29 | 30 | .tt-dropdown-menu { 31 | min-width: 160px; 32 | margin-top: 2px; 33 | padding: 5px 0; 34 | background-color: #fff; 35 | border: 1px solid #ccc; 36 | border: 1px solid rgba(0,0,0,.2); 37 | *border-right-width: 2px; 38 | *border-bottom-width: 2px; 39 | -webkit-border-radius: 6px; 40 | -moz-border-radius: 6px; 41 | border-radius: 6px; 42 | -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); 43 | -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); 44 | box-shadow: 0 5px 10px rgba(0,0,0,.2); 45 | -webkit-background-clip: padding-box; 46 | -moz-background-clip: padding; 47 | background-clip: padding-box; 48 | width:100%; 49 | } 50 | 51 | .tt-suggestion { 52 | display: block; 53 | padding: 3px 20px; 54 | } 55 | 56 | .tt-suggestion.tt-is-under-cursor { 57 | color: #fff; 58 | background-color: #0081c2; 59 | background-image: -moz-linear-gradient(top, #0088cc, #0077b3); 60 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); 61 | background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); 62 | background-image: -o-linear-gradient(top, #0088cc, #0077b3); 63 | background-image: linear-gradient(to bottom, #0088cc, #0077b3); 64 | background-repeat: repeat-x; 65 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0) 66 | } 67 | 68 | .tt-suggestion.tt-is-under-cursor a { 69 | color: #fff; 70 | } 71 | 72 | .tt-suggestion p { 73 | margin: 0; 74 | } 75 | 76 | #formNewDataValue .tt-dropdown-menu { 77 | max-height: 150px; 78 | overflow-y: auto; 79 | } 80 | -------------------------------------------------------------------------------- /app/static/css/tool_detail.css: -------------------------------------------------------------------------------- 1 | #toolCollectionEventsTable { 2 | margin-bottom: 0px; 3 | } 4 | 5 | #toolCollectionEventsTable table { 6 | margin-bottom: 0px; 7 | } 8 | 9 | #terminal { 10 | background-color: black; 11 | color: white; 12 | display:block; 13 | height: 400px; 14 | overflow:scroll; 15 | } 16 | 17 | #overlay_disable_icon { 18 | font-size: 5em; 19 | } 20 | -------------------------------------------------------------------------------- /app/static/css/tools_list.css: -------------------------------------------------------------------------------- 1 | .tool { 2 | padding: 10px; 3 | margin: 5px; 4 | border-radius: 5px; 5 | } 6 | 7 | .toolOnline { 8 | background: #30FF7F; 9 | } 10 | 11 | .toolConnecting { 12 | background: #3094FF; 13 | } 14 | 15 | .toolOffline { 16 | background: #CCCCCC; 17 | } 18 | 19 | .toolAdd { 20 | background: #CCCCCC; 21 | text-align: center; 22 | } 23 | 24 | a:link .tool { 25 | color: #000000; 26 | } 27 | 28 | a:visited .tool { 29 | color: #000000; 30 | } 31 | 32 | a:hover .tool { 33 | color: #000000; 34 | } 35 | 36 | a:active .tool { 37 | color: #000000; 38 | } 39 | 40 | .nopadding { 41 | padding: 0 !important; 42 | margin: 0 !important; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bparzella/gemma/fe8b4b7fe24b0665aacf4face23ab558c871d048/app/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bparzella/gemma/fe8b4b7fe24b0665aacf4face23ab558c871d048/app/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bparzella/gemma/fe8b4b7fe24b0665aacf4face23ab558c871d048/app/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bparzella/gemma/fe8b4b7fe24b0665aacf4face23ab558c871d048/app/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /app/static/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /app/static/js/typeahead.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * typeahead.js 0.10.5 3 | * https://github.com/twitter/typeahead.js 4 | * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT 5 | */ 6 | 7 | !function(a){var b=function(){"use strict";return{isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},toStr:function(a){return b.isUndefined(a)||null===a?"":a+""},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},noop:function(){}}}(),c="0.10.5",d=function(){"use strict";function a(a){return a=b.toStr(a),a?a.split(/\s+/):[]}function c(a){return a=b.toStr(a),a?a.split(/\W+/):[]}function d(a){return function(){var c=[].slice.call(arguments,0);return function(d){var e=[];return b.each(c,function(c){e=e.concat(a(b.toStr(d[c])))}),e}}}return{nonword:c,whitespace:a,obj:{nonword:d(c),whitespace:d(a)}}}(),e=function(){"use strict";function c(c){this.maxSize=b.isNumber(c)?c:100,this.reset(),this.maxSize<=0&&(this.set=this.get=a.noop)}function d(){this.head=this.tail=null}function e(a,b){this.key=a,this.val=b,this.prev=this.next=null}return b.mixin(c.prototype,{set:function(a,b){var c,d=this.list.tail;this.size>=this.maxSize&&(this.list.remove(d),delete this.hash[d.key]),(c=this.hash[a])?(c.val=b,this.list.moveToFront(c)):(c=new e(a,b),this.list.add(c),this.hash[a]=c,this.size++)},get:function(a){var b=this.hash[a];return b?(this.list.moveToFront(b),b.val):void 0},reset:function(){this.size=0,this.hash={},this.list=new d}}),b.mixin(d.prototype,{add:function(a){this.head&&(a.next=this.head,this.head.prev=a),this.head=a,this.tail=this.tail||a},remove:function(a){a.prev?a.prev.next=a.next:this.head=a.next,a.next?a.next.prev=a.prev:this.tail=a.prev},moveToFront:function(a){this.remove(a),this.add(a)}}),c}(),f=function(){"use strict";function a(a){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+b.escapeRegExChars(this.prefix))}function c(){return(new Date).getTime()}function d(a){return JSON.stringify(b.isUndefined(a)?null:a)}function e(a){return JSON.parse(a)}var f,g;try{f=window.localStorage,f.setItem("~~~","!"),f.removeItem("~~~")}catch(h){f=null}return g=f&&window.JSON?{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},get:function(a){return this.isExpired(a)&&this.remove(a),e(f.getItem(this._prefix(a)))},set:function(a,e,g){return b.isNumber(g)?f.setItem(this._ttlKey(a),d(c()+g)):f.removeItem(this._ttlKey(a)),f.setItem(this._prefix(a),d(e))},remove:function(a){return f.removeItem(this._ttlKey(a)),f.removeItem(this._prefix(a)),this},clear:function(){var a,b,c=[],d=f.length;for(a=0;d>a;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return b.isNumber(d)&&c()>d?!0:!1}}:{get:b.noop,set:b.noop,remove:b.noop,clear:b.noop,isExpired:b.noop},b.mixin(a.prototype,g),a}(),g=function(){"use strict";function c(b){b=b||{},this.cancelled=!1,this.lastUrl=null,this._send=b.transport?d(b.transport):a.ajax,this._get=b.rateLimiter?b.rateLimiter(this._get):this._get,this._cache=b.cache===!1?new e(0):i}function d(c){return function(d,e){function f(a){b.defer(function(){h.resolve(a)})}function g(a){b.defer(function(){h.reject(a)})}var h=a.Deferred();return c(d,e,f,g),h}}var f=0,g={},h=6,i=new e(10);return c.setMaxPendingRequests=function(a){h=a},c.resetCache=function(){i.reset()},b.mixin(c.prototype,{_get:function(a,b,c){function d(b){c&&c(null,b),k._cache.set(a,b)}function e(){c&&c(!0)}function i(){f--,delete g[a],k.onDeckRequestArgs&&(k._get.apply(k,k.onDeckRequestArgs),k.onDeckRequestArgs=null)}var j,k=this;this.cancelled||a!==this.lastUrl||((j=g[a])?j.done(d).fail(e):h>f?(f++,g[a]=this._send(a,b).done(d).fail(e).always(i)):this.onDeckRequestArgs=[].slice.call(arguments,0))},get:function(a,c,d){var e;return b.isFunction(c)&&(d=c,c={}),this.cancelled=!1,this.lastUrl=a,(e=this._cache.get(a))?b.defer(function(){d&&d(null,e)}):this._get(a,c,d),!!e},cancel:function(){this.cancelled=!0}}),c}(),h=function(){"use strict";function c(b){b=b||{},b.datumTokenizer&&b.queryTokenizer||a.error("datumTokenizer and queryTokenizer are both required"),this.datumTokenizer=b.datumTokenizer,this.queryTokenizer=b.queryTokenizer,this.reset()}function d(a){return a=b.filter(a,function(a){return!!a}),a=b.map(a,function(a){return a.toLowerCase()})}function e(){return{ids:[],children:{}}}function f(a){for(var b={},c=[],d=0,e=a.length;e>d;d++)b[a[d]]||(b[a[d]]=!0,c.push(a[d]));return c}function g(a,b){function c(a,b){return a-b}var d=0,e=0,f=[];a=a.sort(c),b=b.sort(c);for(var g=a.length,h=b.length;g>d&&h>e;)a[d]b[e]?e++:(f.push(a[d]),d++,e++);return f}return b.mixin(c.prototype,{bootstrap:function(a){this.datums=a.datums,this.trie=a.trie},add:function(a){var c=this;a=b.isArray(a)?a:[a],b.each(a,function(a){var f,g;f=c.datums.push(a)-1,g=d(c.datumTokenizer(a)),b.each(g,function(a){var b,d,g;for(b=c.trie,d=a.split("");g=d.shift();)b=b.children[g]||(b.children[g]=e()),b.ids.push(f)})})},get:function(a){var c,e,h=this;return c=d(this.queryTokenizer(a)),b.each(c,function(a){var b,c,d,f;if(e&&0===e.length)return!1;for(b=h.trie,c=a.split("");b&&(d=c.shift());)b=b.children[d];return b&&0===c.length?(f=b.ids.slice(0),void(e=e?g(e,f):f)):(e=[],!1)}),e?b.map(f(e),function(a){return h.datums[a]}):[]},reset:function(){this.datums=[],this.trie=e()},serialize:function(){return{datums:this.datums,trie:this.trie}}}),c}(),i=function(){"use strict";function d(a){return a.local||null}function e(d){var e,f;return f={url:null,thumbprint:"",ttl:864e5,filter:null,ajax:{}},(e=d.prefetch||null)&&(e=b.isString(e)?{url:e}:e,e=b.mixin(f,e),e.thumbprint=c+e.thumbprint,e.ajax.type=e.ajax.type||"GET",e.ajax.dataType=e.ajax.dataType||"json",!e.url&&a.error("prefetch requires url to be set")),e}function f(c){function d(a){return function(c){return b.debounce(c,a)}}function e(a){return function(c){return b.throttle(c,a)}}var f,g;return g={url:null,cache:!0,wildcard:"%QUERY",replace:null,rateLimitBy:"debounce",rateLimitWait:300,send:null,filter:null,ajax:{}},(f=c.remote||null)&&(f=b.isString(f)?{url:f}:f,f=b.mixin(g,f),f.rateLimiter=/^throttle$/i.test(f.rateLimitBy)?e(f.rateLimitWait):d(f.rateLimitWait),f.ajax.type=f.ajax.type||"GET",f.ajax.dataType=f.ajax.dataType||"json",delete f.rateLimitBy,delete f.rateLimitWait,!f.url&&a.error("remote requires url to be set")),f}return{local:d,prefetch:e,remote:f}}();!function(c){"use strict";function e(b){b&&(b.local||b.prefetch||b.remote)||a.error("one of local, prefetch, or remote is required"),this.limit=b.limit||5,this.sorter=j(b.sorter),this.dupDetector=b.dupDetector||k,this.local=i.local(b),this.prefetch=i.prefetch(b),this.remote=i.remote(b),this.cacheKey=this.prefetch?this.prefetch.cacheKey||this.prefetch.url:null,this.index=new h({datumTokenizer:b.datumTokenizer,queryTokenizer:b.queryTokenizer}),this.storage=this.cacheKey?new f(this.cacheKey):null}function j(a){function c(b){return b.sort(a)}function d(a){return a}return b.isFunction(a)?c:d}function k(){return!1}var l,m;return l=c.Bloodhound,m={data:"data",protocol:"protocol",thumbprint:"thumbprint"},c.Bloodhound=e,e.noConflict=function(){return c.Bloodhound=l,e},e.tokenizers=d,b.mixin(e.prototype,{_loadPrefetch:function(b){function c(a){f.clear(),f.add(b.filter?b.filter(a):a),f._saveToStorage(f.index.serialize(),b.thumbprint,b.ttl)}var d,e,f=this;return(d=this._readFromStorage(b.thumbprint))?(this.index.bootstrap(d),e=a.Deferred().resolve()):e=a.ajax(b.url,b.ajax).done(c),e},_getFromRemote:function(a,b){function c(a,c){b(a?[]:f.remote.filter?f.remote.filter(c):c)}var d,e,f=this;if(this.transport)return a=a||"",e=encodeURIComponent(a),d=this.remote.replace?this.remote.replace(this.remote.url,a):this.remote.url.replace(this.remote.wildcard,e),this.transport.get(d,this.remote.ajax,c)},_cancelLastRemoteRequest:function(){this.transport&&this.transport.cancel()},_saveToStorage:function(a,b,c){this.storage&&(this.storage.set(m.data,a,c),this.storage.set(m.protocol,location.protocol,c),this.storage.set(m.thumbprint,b,c))},_readFromStorage:function(a){var b,c={};return this.storage&&(c.data=this.storage.get(m.data),c.protocol=this.storage.get(m.protocol),c.thumbprint=this.storage.get(m.thumbprint)),b=c.thumbprint!==a||c.protocol!==location.protocol,c.data&&!b?c.data:null},_initialize:function(){function c(){e.add(b.isFunction(f)?f():f)}var d,e=this,f=this.local;return d=this.prefetch?this._loadPrefetch(this.prefetch):a.Deferred().resolve(),f&&d.done(c),this.transport=this.remote?new g(this.remote):null,this.initPromise=d.promise()},initialize:function(a){return!this.initPromise||a?this._initialize():this.initPromise},add:function(a){this.index.add(a)},get:function(a,c){function d(a){var d=f.slice(0);b.each(a,function(a){var c;return c=b.some(d,function(b){return e.dupDetector(a,b)}),!c&&d.push(a),d.length0||!this.transport)&&c&&c(f)},clear:function(){this.index.reset()},clearPrefetchCache:function(){this.storage&&this.storage.clear()},clearRemoteCache:function(){this.transport&&g.resetCache()},ttAdapter:function(){return b.bind(this.get,this)}}),e}(this);var j=function(){return{wrapper:'',dropdown:'',dataset:'
',suggestions:'',suggestion:'
'}}(),k=function(){"use strict";var a={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none",opacity:"1"},input:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},inputWithNoHint:{position:"relative",verticalAlign:"top"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"},suggestions:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"},ltr:{left:"0",right:"auto"},rtl:{left:"auto",right:" 0"}};return b.isMsie()&&b.mixin(a.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),b.isMsie()&&b.isMsie()<=7&&b.mixin(a.input,{marginTop:"-1px"}),a}(),l=function(){"use strict";function c(b){b&&b.el||a.error("EventBus initialized without el"),this.$el=a(b.el)}var d="typeahead:";return b.mixin(c.prototype,{trigger:function(a){var b=[].slice.call(arguments,1);this.$el.trigger(d+a,b)}}),c}(),m=function(){"use strict";function a(a,b,c,d){var e;if(!c)return this;for(b=b.split(i),c=d?h(c,d):c,this._callbacks=this._callbacks||{};e=b.shift();)this._callbacks[e]=this._callbacks[e]||{sync:[],async:[]},this._callbacks[e][a].push(c);return this}function b(b,c,d){return a.call(this,"async",b,c,d)}function c(b,c,d){return a.call(this,"sync",b,c,d)}function d(a){var b;if(!this._callbacks)return this;for(a=a.split(i);b=a.shift();)delete this._callbacks[b];return this}function e(a){var b,c,d,e,g;if(!this._callbacks)return this;for(a=a.split(i),d=[].slice.call(arguments,1);(b=a.shift())&&(c=this._callbacks[b]);)e=f(c.sync,this,[b].concat(d)),g=f(c.async,this,[b].concat(d)),e()&&j(g);return this}function f(a,b,c){function d(){for(var d,e=0,f=a.length;!d&&f>e;e+=1)d=a[e].apply(b,c)===!1;return!d}return d}function g(){var a;return a=window.setImmediate?function(a){setImmediate(function(){a()})}:function(a){setTimeout(function(){a()},0)}}function h(a,b){return a.bind?a.bind(b):function(){a.apply(b,[].slice.call(arguments,0))}}var i=/\s+/,j=g();return{onSync:c,onAsync:b,off:d,trigger:e}}(),n=function(a){"use strict";function c(a,c,d){for(var e,f=[],g=0,h=a.length;h>g;g++)f.push(b.escapeRegExChars(a[g]));return e=d?"\\b("+f.join("|")+")\\b":"("+f.join("|")+")",c?new RegExp(e):new RegExp(e,"i")}var d={node:null,pattern:null,tagName:"strong",className:null,wordsOnly:!1,caseSensitive:!1};return function(e){function f(b){var c,d,f;return(c=h.exec(b.data))&&(f=a.createElement(e.tagName),e.className&&(f.className=e.className),d=b.splitText(c.index),d.splitText(c[0].length),f.appendChild(d.cloneNode(!0)),b.parentNode.replaceChild(f,d)),!!c}function g(a,b){for(var c,d=3,e=0;e